我用Android的RecyclerView做过几次物品列表,但这是一个相当复杂的过程。浏览众多在线教程中的一个作品(这个,这个,这个都很好),但我正在寻找一个简单的示例,我可以复制和粘贴来快速启动和运行。只需要以下特性:
垂直布局 每行都有一个单一的TextView 响应点击事件
因为这个愿望我已经许过好几次了,所以我最终决定把答案写在下面,供我和你们将来参考。
我用Android的RecyclerView做过几次物品列表,但这是一个相当复杂的过程。浏览众多在线教程中的一个作品(这个,这个,这个都很好),但我正在寻找一个简单的示例,我可以复制和粘贴来快速启动和运行。只需要以下特性:
垂直布局 每行都有一个单一的TextView 响应点击事件
因为这个愿望我已经许过好几次了,所以我最终决定把答案写在下面,供我和你们将来参考。
当前回答
现在,您需要一个适配器来支持所有的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。
如果您在设置数据绑定时遇到问题,请参阅此回答。
其他回答
下面是一个类似下图的最小示例。
从一个空活动开始。您将执行以下任务来添加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:教程
这里有一个更新的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作为一个内部类,但不太清楚如何在外部类参数的类型中引用它。
实现androidx.recyclerview: recyclerview:… 建议更新到下面的androidx库:
https://developer.android.com/jetpack/androidx/releases/recyclerview
布局文件Widget XML标签必须更新为: androidx.recyclerview.widget.RecyclerView
依赖关系
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:multidex:1.0.1'
compile 'com.android.support:cardview-v7:25.3.1'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.lguipeng.bubbleview:library:1.0.0'
compile 'com.larswerkman:HoloColorPicker:1.5'
compile 'com.mcxiaoke.volley:library-aar:1.0.0'
一个类为点击项目
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
private OnItemClickListener mListener;
public interface OnItemClickListener {
public void onItemClick(View view, int position);
}
GestureDetector mGestureDetector;
public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
mListener = listener;
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
View childView = view.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
mListener.onItemClick(childView, view.getChildPosition(childView));
return true;
}
return false;
}
@Override
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
第二个类RecyclerView
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class SLByTopics extends Fragment {
public static ArrayList<MByTopics> byTopicsMainArrayList=new ArrayList<>();
TabRefreshReceiver tabRefreshReceiver;
RecyclerView recyclerView;
SAdpByTopics sAdpByTopics;
public ArrayList<MByTopics> mByTopicsArrayList=new ArrayList<>();
ProgressDialog progressDialog;
public SLByTopics(){
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.sl_fragment_by_topics, container, false);
progressDialog = new ProgressDialog(getActivity());
if (IsOnline.isNetworkAvailable(getActivity())) {
getCategoryTree();
} else{
IsOnline.showNoInterNetMessage(getActivity());
}
tabRefreshReceiver = new TabRefreshReceiver();
LocalBroadcastManager.getInstance(getContext()).registerReceiver(tabRefreshReceiver, new IntentFilter("BY_TOPICS"));
setUpView(view);
return view;
}
private void setUpView(View view) {
recyclerView=(RecyclerView)view.findViewById(R.id.by_topics_list_recyclerView);
LinearLayoutManager linearLayoutManager=new LinearLayoutManager(getActivity());
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
}
@Override
public void onResume() {
super.onResume();
recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), new RecyclerItemClickListener.OnItemClickListener() {
@Override
public void onItemClick(View view, final int position) {
if (mByTopicsArrayList.get(position).getChild().size()>0){
Intent intent = new Intent(getActivity(), SByTopicCategory.class);
intent.putExtra("selectedCategoryName",mByTopicsArrayList.get(position).getCatname());
intent.putExtra("jsonData",mByTopicsArrayList.get(position).getMainTopicJson());
startActivity(intent);
getActivity().overridePendingTransition(R.anim.activity_in, R.anim.activity_out);
}else {
Intent intent = new Intent(getActivity(), SByCategoryQuestionList.class);
intent.putExtra("selectedSubCategoryName",mByTopicsArrayList.get(position).getCatname());
intent.putExtra("catID",mByTopicsArrayList.get(position).getId());
startActivity(intent);
getActivity().overridePendingTransition(R.anim.activity_in, R.anim.activity_out);
}
}
}));
}
private class TabRefreshReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.detach(SLByTopics.this).attach(SLByTopics.this).commit();
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(tabRefreshReceiver);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void getCategoryTree() {
progressDialog.setMessage("Please Wait...");
progressDialog.setCancelable(false);
progressDialog.show();
StringRequest stringRequest = new StringRequest(Request.Method.POST, Const.HOSTNAME + Const.STUDENT_GET_CATEGORY_TREE,
new Response.Listener<String>() {
@SuppressLint("LongLogTag")
@Override
public void onResponse(String response) {
try {
JSONObject object = new JSONObject(response);
String status = object.getString("status");
int i = Integer.parseInt(status);
switch (i) {
case 0:
progressDialog.dismiss();
// Toast.makeText(getActivity(), "getCategorySuccess", Toast.LENGTH_SHORT).show();
Log.e("getCategoryTree Response", "getCategoryTree Response : " + response);
try {
byTopicsMainArrayList.clear();
JSONArray info = object.getJSONArray("info");
if (info.length() > 0) {
for (i = 0; i < info.length(); i++) {
JSONObject data = info.getJSONObject(i);
MByTopics mByTopics = new MByTopics();
mByTopics.setId(data.getString("id"));
mByTopics.setCatname(data.getString("catname"));
mByTopics.setMainTopicJson(data.toString());
JSONArray topicChildren = data.getJSONArray("children");
ArrayList<SMByTopicCategory> byChildrenArrayList = new ArrayList<>();
for (int j = 0; j < topicChildren.length(); j++) {
JSONObject topicChildrenData = topicChildren.getJSONObject(j);
SMByTopicCategory smByTopicCategory = new SMByTopicCategory();
smByTopicCategory.setId(topicChildrenData.getString("id"));
smByTopicCategory.setCatname(topicChildrenData.getString("catname"));
smByTopicCategory.setChildTopicJson(topicChildrenData.toString());
JSONArray topicChildrenQuestion = topicChildrenData.getJSONArray("children");
ArrayList<SMByTopicSubCategory> byChildrenSubArrayList = new ArrayList<>();
for (int k = 0; k < topicChildrenQuestion.length(); k++) {
JSONObject topicChildrenSubData = topicChildrenQuestion.getJSONObject(k);
SMByTopicSubCategory smByTopicSubCategory = new SMByTopicSubCategory();
smByTopicSubCategory.setId(topicChildrenSubData.getString("id"));
smByTopicSubCategory.setCatname(topicChildrenSubData.getString("catname"));
smByTopicSubCategory.setChildSubTopicJson(topicChildrenSubData.toString());
byChildrenSubArrayList.add(smByTopicSubCategory);
}
smByTopicCategory.setQuestions(byChildrenSubArrayList);
byChildrenArrayList.add(smByTopicCategory);
}
mByTopics.setChild(byChildrenArrayList);
byTopicsMainArrayList.add(mByTopics);
}
mByTopicsArrayList.clear();
mByTopicsArrayList=byTopicsMainArrayList;
sAdpByTopics=new SAdpByTopics(mByTopicsArrayList,getActivity());
recyclerView.setAdapter(sAdpByTopics);
sAdpByTopics.notifyDataSetChanged();
}
}catch (Exception e){
e.printStackTrace();
}
break;
default:
progressDialog.dismiss();
// Toast.makeText(getActivity(), "getCategoryError : " + response, Toast.LENGTH_SHORT).show();
Log.e("getCategoryTree Not Response", "getCategoryTree Uploading Not Response : " + response);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
progressDialog.dismiss();
Log.e("getCategoryTree Error :","getCategoryTree Error :"+error.getMessage());
// Toast.makeText(getActivity(), error.getMessage(), Toast.LENGTH_LONG).show();
}
}){
};/* {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> map = new HashMap<String, String>();
// map.put("uid", String.valueOf(ConfigManager.getUserId()));
return map;
}
};*/
stringRequest.setRetryPolicy(new DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
RequestQueue requestQueue = Volley.newRequestQueue(getActivity());
requestQueue.add(stringRequest);
}
}
适配器类回收项目
import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
public class SAdpByTopics extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ArrayList<MByTopics> topicsArrayList=new ArrayList<>();
Activity activity;
public SAdpByTopics(ArrayList<MByTopics> topicsArrayList,Activity activity){
this.topicsArrayList=topicsArrayList;
this.activity=activity;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemeView= LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_by_topic_list,parent,false);
RecyclerView.ViewHolder holder=new Holder(itemeView);
holder.setIsRecyclable(false);
return holder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Holder classHolder = (Holder) holder;
try{
classHolder.txt_topic_name.setText(topicsArrayList.get(position).getCatname());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public int getItemCount() {
return topicsArrayList.size();
}
class Holder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView txt_topic_name;
public Holder(View itemView) {
super(itemView);
txt_topic_name = (TextView) itemView.findViewById(R.id.txt_topic_name);
}
@Override
public void onClick(View v) {
}
}
}
模块类
public class MByTopics {
String id;
String topicName;
String catname;
String MainTopicJson;
ArrayList<SMByTopicCategory> child;
ArrayList<SMByTopicSubCategory> questions;
public void setId(String id){
this.id=id;
}
public String getId(){
return id;
}
public void setCatname(String catname) {
this.catname = catname;
}
public String getCatname() {
return catname;
}
public void setTopicName(String topicName) {
this.topicName = topicName;
}
public String getTopicName() {
return topicName;
}
public void setChild(ArrayList<SMByTopicCategory> child) {
this.child = child;
}
public String getMainTopicJson() {
return MainTopicJson;
}
public void setMainTopicJson(String mainTopicJson) {
MainTopicJson = mainTopicJson;
}
public ArrayList<SMByTopicCategory> getChild() {
return child;
}
public void setQuestions(ArrayList<SMByTopicSubCategory> questions) {
this.questions = questions;
}
public ArrayList<SMByTopicSubCategory> getQuestions() {
return questions;
}
public ArrayList<MByTopics> getByTopicList() {
ArrayList<MByTopics> mByTopicsArrayList = new ArrayList<>();
for (int i=0;i<11;i++){
MByTopics mQuestionBankCategory=new MByTopics();
if (i==1 || i== 5|| i==9){
mQuestionBankCategory.setTopicName("Microeconomics");
}else if (i==2 || i== 10|| i==6) {
mQuestionBankCategory.setTopicName("Macroeconomics");
}else {
mQuestionBankCategory.setTopicName("Current Isssues");
}
mByTopicsArrayList.add(mQuestionBankCategory);
}
return mByTopicsArrayList;
}
}
您可以使用带有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")