我用Android的RecyclerView做过几次物品列表,但这是一个相当复杂的过程。浏览众多在线教程中的一个作品(这个,这个,这个都很好),但我正在寻找一个简单的示例,我可以复制和粘贴来快速启动和运行。只需要以下特性:
垂直布局 每行都有一个单一的TextView 响应点击事件
因为这个愿望我已经许过好几次了,所以我最终决定把答案写在下面,供我和你们将来参考。
我用Android的RecyclerView做过几次物品列表,但这是一个相当复杂的过程。浏览众多在线教程中的一个作品(这个,这个,这个都很好),但我正在寻找一个简单的示例,我可以复制和粘贴来快速启动和运行。只需要以下特性:
垂直布局 每行都有一个单一的TextView 响应点击事件
因为这个愿望我已经许过好几次了,所以我最终决定把答案写在下面,供我和你们将来参考。
当前回答
下面是一个类似下图的最小示例。
从一个空活动开始。您将执行以下任务来添加RecyclerView。您所需要做的就是复制并粘贴每个部分中的代码。稍后,您可以自定义它以满足您的需求。
向gradle添加依赖项 为活动和RecyclerView行添加xml布局文件 制作RecyclerView适配器 初始化活动中的RecyclerView
更新Gradle依赖项
确保以下依赖项在你的应用程序gradle中。构建文件:
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
您可以将版本号更新为最新的版本号。如果你还在使用Android Studio 2.x,请使用编译而不是实现。
创建活动布局
将RecyclerView添加到xml布局中。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvAnimals"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
创建行布局
我们的RecyclerView中的每一行都只会有一个单一的TextView。创建一个新的布局资源文件。
recyclerview_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:id="@+id/tvAnimalName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"/>
</LinearLayout>
创建适配器
RecyclerView需要一个适配器来用您的数据填充每行中的视图。创建一个新的java文件。
MyRecyclerViewAdapter.java
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private List<String> mData;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
// data is passed into the constructor
MyRecyclerViewAdapter(Context context, List<String> data) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
}
// inflates the row layout from xml when needed
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recyclerview_row, parent, false);
return new ViewHolder(view);
}
// binds the data to the TextView in each row
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
String animal = mData.get(position);
holder.myTextView.setText(animal);
}
// total number of rows
@Override
public int getItemCount() {
return mData.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView myTextView;
ViewHolder(View itemView) {
super(itemView);
myTextView = itemView.findViewById(R.id.tvAnimalName);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
String getItem(int id) {
return mData.get(id);
}
// allows clicks events to be caught
void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}
笔记
虽然不是严格必要的,但我包含了监听行上的单击事件的功能。这在旧的listview中是可用的,是一种常见的需求。如果不需要,可以删除此代码。
在Activity中初始化RecyclerView
将以下代码添加到您的主活动中。
MainActivity.java
public class MainActivity extends AppCompatActivity implements MyRecyclerViewAdapter.ItemClickListener {
MyRecyclerViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// data to populate the RecyclerView with
ArrayList<String> animalNames = new ArrayList<>();
animalNames.add("Horse");
animalNames.add("Cow");
animalNames.add("Camel");
animalNames.add("Sheep");
animalNames.add("Goat");
// set up the RecyclerView
RecyclerView recyclerView = findViewById(R.id.rvAnimals);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new MyRecyclerViewAdapter(this, animalNames);
adapter.setClickListener(this);
recyclerView.setAdapter(adapter);
}
@Override
public void onItemClick(View view, int position) {
Toast.makeText(this, "You clicked " + adapter.getItem(position) + " on row number " + position, Toast.LENGTH_SHORT).show();
}
}
笔记
注意,该活动实现了我们在适配器中定义的ItemClickListener。这允许我们在onItemClick中处理行单击事件。
完成了
就是这样。您现在应该能够运行您的项目,并获得与顶部图像类似的内容。
发生了
在行之间添加分隔符
你可以像这样加一个简单的分隔符
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration);
如果你想要一些更复杂的东西,请参阅以下答案:
如何在RecyclerView中添加项目之间的分隔符和空格? 如何在线性布局中缩进分隔符(即,仅在ItemDecoration中添加填充,边距或插入)
在单击时更改行颜色
查看这个答案,了解如何更改背景颜色,并在单击一行时添加涟漪效应。
更新的行
有关如何添加、删除和更新行,请参阅此回答。
进一步的阅读
CodePath 视频教程 Android RecyclerView示例(stacktips教程) RecyclerView在Android:教程
其他回答
基于不同的资源,我使用简单库创建了RecyclerView的简单实现。
在build.gradle中添加这一行
implementation 'com.hereshem.lib:awesomelib:2.0.1'
通过在activity_main.xml中添加MyRecyclerView来创建一个RecyclerView
<com.hereshem.lib.recycler.MyRecyclerView
android:id="@+id/recycler"
app:layoutManager="LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
现在在MainActivity中,通过传递需要绑定的类的名称来创建一个ViewHolder
public static class EVHolder extends MyViewHolder<Events> {
TextView date, title, summary;
public EVHolder(View v) {
super(v);
date = v.findViewById(R.id.date);
title = v.findViewById(R.id.title);
summary = v.findViewById(R.id.summary);
}
@Override
public void bindView(Events c) {
date.setText(c.date);
title.setText(c.title);
summary.setText(c.summary);
}
}
通过在适配器中传递项目、类和布局来创建Items列表变量和适配器,行数很少
List<Events> items = new ArrayList<>();
MyRecyclerView recycler = findViewById(R.id.recycler);
RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, items, EVHolder.class, R.layout.row_event);
recycler.setAdapter(adapter);
ClickListener可以用以下几行添加
recycler.setOnItemClickListener(new MyRecyclerView.OnItemClickListener() {
@Override
public void onItemClick(int position) {
Toast.makeText(MainActivity.this, "Recycler Item Clicked " + position, Toast.LENGTH_SHORT).show();
}
});
都搞定了。
更多示例和实现可以在这里找到。 希望这能有所帮助!!
由于我还不能评论,我将发布一个链接的答案..我在recyclerview上找到了一个简单、组织良好的教程 http://www.androiddeft.com/2017/10/01/recyclerview-android/
除此之外,当你要在你的活动中添加一个回收器视图时,你想做的事情如下所示,你应该如何做,已经在链接中描述了
将RecyclerView组件添加到布局文件中 创建一个要显示为列表行的类 制作一个布局文件,这是你列表中的一行的布局 现在我们需要一个自定义适配器,因此通过扩展来创建一个自定义适配器 从父类RecyclerView。适配器 在mainActivity oncreate中添加recyclerview 添加分隔符 添加Touch监听器
您可以使用带有diff utils和过滤器的抽象适配器
SimpleAbstractAdapter.kt
abstract class SimpleAbstractAdapter<T>(private var items: ArrayList<T> = arrayListOf()) : RecyclerView.Adapter<SimpleAbstractAdapter.VH>() {
protected var listener: OnViewHolderListener<T>? = null
private val filter = ArrayFilter()
private val lock = Any()
protected abstract fun getLayout(): Int
protected abstract fun bindView(item: T, viewHolder: VH)
protected abstract fun getDiffCallback(): DiffCallback<T>?
private var onFilterObjectCallback: OnFilterObjectCallback? = null
private var constraint: CharSequence? = ""
override fun onBindViewHolder(vh: VH, position: Int) {
getItem(position)?.let { bindView(it, vh) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
return VH(parent, getLayout())
}
override fun getItemCount(): Int = items.size
protected abstract class DiffCallback<T> : DiffUtil.Callback() {
private val mOldItems = ArrayList<T>()
private val mNewItems = ArrayList<T>()
fun setItems(oldItems: List<T>, newItems: List<T>) {
mOldItems.clear()
mOldItems.addAll(oldItems)
mNewItems.clear()
mNewItems.addAll(newItems)
}
override fun getOldListSize(): Int {
return mOldItems.size
}
override fun getNewListSize(): Int {
return mNewItems.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return areItemsTheSame(
mOldItems[oldItemPosition],
mNewItems[newItemPosition]
)
}
abstract fun areItemsTheSame(oldItem: T, newItem: T): Boolean
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return areContentsTheSame(
mOldItems[oldItemPosition],
mNewItems[newItemPosition]
)
}
abstract fun areContentsTheSame(oldItem: T, newItem: T): Boolean
}
class VH(parent: ViewGroup, @LayoutRes layout: Int) : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(layout, parent, false))
interface OnViewHolderListener<T> {
fun onItemClick(position: Int, item: T)
}
fun getItem(position: Int): T? {
return items.getOrNull(position)
}
fun getItems(): ArrayList<T> {
return items
}
fun setViewHolderListener(listener: OnViewHolderListener<T>) {
this.listener = listener
}
fun addAll(list: List<T>) {
val diffCallback = getDiffCallback()
when {
diffCallback != null && !items.isEmpty() -> {
diffCallback.setItems(items, list)
val diffResult = DiffUtil.calculateDiff(diffCallback)
items.clear()
items.addAll(list)
diffResult.dispatchUpdatesTo(this)
}
diffCallback == null && !items.isEmpty() -> {
items.clear()
items.addAll(list)
notifyDataSetChanged()
}
else -> {
items.addAll(list)
notifyDataSetChanged()
}
}
}
fun add(item: T) {
items.add(item)
notifyDataSetChanged()
}
fun add(position:Int, item: T) {
items.add(position,item)
notifyItemInserted(position)
}
fun remove(position: Int) {
items.removeAt(position)
notifyItemRemoved(position)
}
fun remove(item: T) {
items.remove(item)
notifyDataSetChanged()
}
fun clear(notify: Boolean=false) {
items.clear()
if (notify) {
notifyDataSetChanged()
}
}
fun setFilter(filter: SimpleAdapterFilter<T>): ArrayFilter {
return this.filter.setFilter(filter)
}
interface SimpleAdapterFilter<T> {
fun onFilterItem(contains: CharSequence, item: T): Boolean
}
fun convertResultToString(resultValue: Any): CharSequence {
return filter.convertResultToString(resultValue)
}
fun filter(constraint: CharSequence) {
this.constraint = constraint
filter.filter(constraint)
}
fun filter(constraint: CharSequence, listener: Filter.FilterListener) {
this.constraint = constraint
filter.filter(constraint, listener)
}
fun getFilter(): Filter {
return filter
}
interface OnFilterObjectCallback {
fun handle(countFilterObject: Int)
}
fun setOnFilterObjectCallback(objectCallback: OnFilterObjectCallback) {
onFilterObjectCallback = objectCallback
}
inner class ArrayFilter : Filter() {
private var original: ArrayList<T> = arrayListOf()
private var filter: SimpleAdapterFilter<T> = DefaultFilter()
private var list: ArrayList<T> = arrayListOf()
private var values: ArrayList<T> = arrayListOf()
fun setFilter(filter: SimpleAdapterFilter<T>): ArrayFilter {
original = items
this.filter = filter
return this
}
override fun performFiltering(constraint: CharSequence?): Filter.FilterResults {
val results = Filter.FilterResults()
if (constraint == null || constraint.isBlank()) {
synchronized(lock) {
list = original
}
results.values = list
results.count = list.size
} else {
synchronized(lock) {
values = original
}
val result = ArrayList<T>()
for (value in values) {
if (constraint!=null && constraint.trim().isNotEmpty() && value != null) {
if (filter.onFilterItem(constraint, value)) {
result.add(value)
}
} else {
value?.let { result.add(it) }
}
}
results.values = result
results.count = result.size
}
return results
}
override fun publishResults(constraint: CharSequence, results: Filter.FilterResults) {
items = results.values as? ArrayList<T> ?: arrayListOf()
notifyDataSetChanged()
onFilterObjectCallback?.handle(results.count)
}
}
class DefaultFilter<T> : SimpleAdapterFilter<T> {
override fun onFilterItem(contains: CharSequence, item: T): Boolean {
val valueText = item.toString().toLowerCase()
if (valueText.startsWith(contains.toString())) {
return true
} else {
val words = valueText.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
for (word in words) {
if (word.contains(contains)) {
return true
}
}
}
return false
}
}
}
并利用实现方法对抽象适配器进行扩展
TasksAdapter.kt
import android.annotation.SuppressLint
import kotlinx.android.synthetic.main.task_item_layout.view.*
class TasksAdapter(private val listener:TasksListener? = null) : SimpleAbstractAdapter<Task>() {
override fun getLayout(): Int {
return R.layout.task_item_layout
}
override fun getDiffCallback(): DiffCallback<Task>? {
return object : DiffCallback<Task>() {
override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem.items == newItem.items
}
}
}
@SuppressLint("SetTextI18n")
override fun bindView(item: Task, viewHolder: VH) {
viewHolder.itemView.apply {
val position = viewHolder.adapterPosition
val customer = item.customer
val customerName = if (customer != null) customer.name else ""
tvTaskCommentTitle.text = customerName + ", #" + item.id
tvCommentContent.text = item.taskAddress
ivCall.setOnClickListener {
listener?.onCallClick(position, item)
}
setOnClickListener {
listener?.onItemClick(position, item)
}
}
}
interface TasksListener : SimpleAbstractAdapter.OnViewHolderListener<Task> {
fun onCallClick(position: Int, item: Task)
}
}
初始化适配器
mAdapter = TasksAdapter(object : TasksAdapter.TasksListener {
override fun onCallClick(position: Int, item:Task) {
}
override fun onItemClick(position: Int, item:Task) {
}
})
rvTasks.adapter = mAdapter
并填写
mAdapter?.addAll(tasks)
添加自定义过滤器
mAdapter?.setFilter(object : SimpleAbstractAdapter.SimpleAdapterFilter<MoveTask> {
override fun onFilterItem(contains: CharSequence, item:Task): Boolean {
return contains.toString().toLowerCase().contains(item.id?.toLowerCase().toString())
}
})
过滤数据
mAdapter?.filter("test")
现在,您需要一个适配器来支持所有的RecyclerView
一个适配器可以用于所有的RecyclerView。NO onBindViewHolder, NO onCreateViewHolder处理。 没有从Java/Kotlin类设置适配器的代码。检查样本类。 您可以使用Binding Adapters为每个列表设置事件和自定义数据。
我在这里展示了通过1适配器设置两个不同的RecyclerView
activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="listOne"
type="java.util.List"/>
<variable
name="listTwo"
type="java.util.List"/>
<variable
name="onItemClickListenerOne"
type="com.ks.nestedrecyclerbindingexample.callbacks.OnItemClickListener"/>
<variable
name="onItemClickListenerTwo"
type="com.ks.nestedrecyclerbindingexample.callbacks.OnItemClickListener"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
rvItemLayout="@{@layout/row_one}"
rvList="@{listOne}"
rvOnItemClick="@{onItemClickListenerOne}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
/>
<android.support.v7.widget.RecyclerView
rvItemLayout="@{@layout/row_two}"
rvList="@{listTwo}"
rvOnItemClick="@{onItemClickListenerTwo}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
/>
</LinearLayout>
</layout>
你可以看到我传递列表,项目布局id和点击listener从布局。
rvItemLayout="@{@layout/row_one}"
rvList="@{listOne}"
rvOnItemClick="@{onItemClickListenerOne}"
该自定义属性由BindingAdapter创建。
public class BindingAdapters {
@BindingAdapter(value = {"rvItemLayout", "rvList", "rvOnItemClick"}, requireAll = false)
public static void setRvAdapter(RecyclerView recyclerView, int rvItemLayout, List rvList, @Nullable OnItemClickListener onItemClickListener) {
if (rvItemLayout != 0 && rvList != null && rvList.size() > 0)
recyclerView.setAdapter(new GeneralAdapter(rvItemLayout, rvList, onItemClickListener));
}
}
从Activity中,你传递list,点击listener like
HomeActivity.java
public class HomeActivity extends AppCompatActivity {
ActivityHomeBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_home);
binding.setListOne(new ArrayList()); // pass your list or set list from response of API
binding.setListTwo(new ArrayList());
binding.setOnItemClickListenerOne(new OnItemClickListener() {
@Override
public void onItemClick(View view, Object object) {
if (object instanceof ModelParent) {
// TODO: your action here
}
}
});
binding.setOnItemClickListenerTwo(new OnItemClickListener() {
@Override
public void onItemClick(View view, Object object) {
if (object instanceof ModelChild) {
// TODO: your action here
}
}
});
}
}
你不想读太多,直接克隆/下载完整的例子从我的github回购。你自己试试。
你可以在上面的repo中看到GeneralAdapter.java。
如果您在设置数据绑定时遇到问题,请参阅此回答。
这里有一个更新的Kotlin解决方案,它比这里写的许多答案要简单得多,它使用匿名类。
val items = mutableListOf<String>()
inner class ItemHolder(view: View): RecyclerView.ViewHolder(view) {
var textField: TextView = view.findViewById(android.R.id.text1) as TextView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
rvitems.layoutManager = LinearLayoutManager(context)
rvitems.adapter = object : RecyclerView.Adapter<ItemHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
return ItemHolder(LayoutInflater.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false))
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
holder.textField.text = items[position]
holder.textField.setOnClickListener {
Toast.makeText(context, "Clicked $position", Toast.LENGTH_SHORT).show()
}
}
}
}
我擅自使用了android.R.layout。Simple_list_item_1因为它更简单。我想进一步简化它,并将ItemHolder作为一个内部类,但不太清楚如何在外部类参数的类型中引用它。