资料 | 汇总Android开发Android知识

Android RecyclerView设计通用Adapter

2017-02-25  本文已影响2642人  业志陈

RecylerView 的使用频率现在也算做是很高了吧?使用起来的确是挺方便的,也容易实现一些比较好看的效果

一、一般步骤

一般的设计流程都是如下所示
首先是需要一个 JavaBean 来承载数据,包含的内容分别是标题还有内容

public class Data {

    private String title;

    private String content;

    public Data(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
        
}

然后继承 RecyclerView.Adapter ,并实现几个固定方法

public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> {

    private List<Data> dataList;

    private LayoutInflater layoutInflater;

    public MyRecyclerAdapter(Context context, List<Data> dataList) {
        this.dataList = dataList;
        layoutInflater = LayoutInflater.from(context);
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = layoutInflater.inflate(R.layout.item_left, parent, false);
        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.tv_title.setText(dataList.get(position).getTitle());
        holder.tv_content.setText(dataList.get(position).getContent());
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        private TextView tv_title;

        private TextView tv_content;

        MyViewHolder(View itemView) {
            super(itemView);
            tv_title = (TextView) itemView.findViewById(R.id.tv_title);
            tv_content = (TextView) itemView.findViewById(R.id.tv_content);
        }
    }

}

使用到的布局文件

<?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="vertical">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:textSize="15sp" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="15sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#abc" />

</LinearLayout>

然后再来为 RecycleView 填充 Adapter

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycleView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        List<Data> dataList = new ArrayList<>();
        for (int i = 0; i < 40; i++) {
            Data data = new Data("叶应是叶", "我也不知道说什么好,我也不知道说什么好,我也不知道说什么好,我也不知道说什么好");
            dataList.add(data);
        }
        MyRecyclerAdapter adapter = new MyRecyclerAdapter(this, dataList);
        recyclerView.setAdapter(adapter);
    }
}

运行效果如下


这里写图片描述

使用多了会发现,其实 MyRecyclerAdapter 中几个需要实现的方法其实都是步骤都是类似的,如果每次都要来重复这样的步骤,那真的是在做无用的工作了

这里,来为 RecycleView 设计一个通用 Adapter,取代掉那些重复的步骤,让代码更加简洁

二、通用ViewHolder

首先,继承 RecyclerView.ViewHolder 实现一个通用的 ViewHolder
当中,使用 SparseArray 来存放 View 以减少 findViewById 的次数,SparseArray 比 HashMap 更省内存,在某些条件下性能会更好,不过只能存储 key 为 int 类型的数据,正好用来存放资源ID

因为列表项中一般都是使用 TextView 和 ImageView 两个控件,所以这里提供两个控件的操作方法。此外,为了监听列表项单击和双击事件,这里再来自定义一个接口 onItemCommonClickListener ,用于点击事件回调

public class CommonViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener, View.OnClickListener {

    // SparseArray 比 HashMap 更省内存,在某些条件下性能更好,只能存储 key 为 int 类型的数据,
    // 用来存放 View 以减少 findViewById 的次数
    private SparseArray<View> viewSparseArray;

    private onItemCommonClickListener commonClickListener;

    public CommonViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
        itemView.setOnLongClickListener(this);
        viewSparseArray = new SparseArray<>();
    }

    /**
     * 根据 ID 来获取 View
     *
     * @param viewId viewID
     * @param <T>    泛型
     * @return 将结果强转为 View 或 View 的子类型
     */
    public <T extends View> T getView(int viewId) {
        // 先从缓存中找,找打的话则直接返回
        // 如果找不到则 findViewById ,再把结果存入缓存中
        View view = viewSparseArray.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            viewSparseArray.put(viewId, view);
        }
        return (T) view;
    }

    public CommonViewHolder setText(int viewId, CharSequence text) {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }

    public CommonViewHolder setViewVisibility(int viewId, int visibility) {
        getView(viewId).setVisibility(visibility);
        return this;
    }

    public CommonViewHolder setImageResource(int viewId, int resourceId) {
        ImageView imageView = getView(viewId);
        imageView.setImageResource(resourceId);
        return this;
    }

    protected interface onItemCommonClickListener {

        void onItemClickListener(int position);

        void onItemLongClickListener(int position);

    }

    public void setCommonClickListener(onItemCommonClickListener commonClickListener) {
        this.commonClickListener = commonClickListener;
    }

    @Override
    public void onClick(View v) {
        if (commonClickListener != null) {
            commonClickListener.onItemLongClickListener(getAdapterPosition());
        }
    }

    @Override
    public boolean onLongClick(View v) {
        if (commonClickListener != null) {
            commonClickListener.onItemClickListener(getAdapterPosition());
        }
        return false;
    }
}

三、通用RecyclerAdapter

再来实现一个通用的 RecyclerView.Adapter
因为不知道要使用到的数据类型是哪一种,也为了更好的适配各种数据类型,所以这里需要用到泛型

当中,onBindViewHolder(CommonViewHolder holder, int position) 需要我们自己来操作,所以这里再来声明一个抽象方法 bindData(CommonViewHolder holder, T data) ,由子类来负责实现绑定操作

public abstract class CommonRecycleAdapter<T> extends RecyclerView.Adapter<CommonViewHolder> {

    protected LayoutInflater layoutInflater;

    protected List<T> dataList;

    protected int layoutId;

    public CommonRecycleAdapter(Context context, List<T> dataList, int layoutId) {
        this.layoutInflater = LayoutInflater.from(context);
        this.dataList = dataList;
        this.layoutId = layoutId;
    }

    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

    @Override
    public CommonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = layoutInflater.inflate(layoutId, parent, false);
        return new CommonViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(CommonViewHolder holder, int position) {
        bindData(holder, dataList.get(position));
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    abstract void bindData(CommonViewHolder holder, T data);

}

这样,就有了一个实现了基本操作的通用 Adapter
这里再来看看如何使用它

四、使用通用 Adapter

需要先来继承 CommonRecycleAdapter ,只需要实现一个方法即可,看起来简洁多了吧
代码中声明了两个构造函数,根据是否需要用到点击事件监听来选择

public class MyAdapter extends CommonRecycleAdapter<Data> {

    private CommonViewHolder.onItemCommonClickListener commonClickListener;

    public MyAdapter(Context context, List<Data> dataList) {
        super(context, dataList, R.layout.item_left);
    }

    public MyAdapter(Context context, List<Data> dataList, CommonViewHolder.onItemCommonClickListener commonClickListener) {
        super(context, dataList, R.layout.item_left);
        this.commonClickListener = commonClickListener;
    }
    
    @Override
    void bindData(CommonViewHolder holder, Data data) {
        holder.setText(R.id.tv_title, data.getTitle())
                .setText(R.id.tv_content, data.getContent())
                .setCommonClickListener(commonClickListener);
    }
    
}

这里选择要监听点击事件

public class MainActivity extends AppCompatActivity implements CommonViewHolder.onItemCommonClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycleView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        List<Data> dataList = new ArrayList<>();
        for (int i = 0; i < 40; i++) {
            Data data = new Data("叶应是叶", "我也不知道说什么好,我也不知道说什么好,我也不知道说什么好,我也不知道说什么好");
            dataList.add(data);
        }
        //MyRecyclerAdapter adapter = new MyRecyclerAdapter(this, dataList);
        MyAdapter adapter = new MyAdapter(this, dataList, this);
        recyclerView.setAdapter(adapter);
    }

    @Override
    public void onItemClickListener(int position) {
        Toast.makeText(this, "position:" + position, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClickListener(int position) {
        Toast.makeText(this, "position:" + position, Toast.LENGTH_SHORT).show();
    }
    
}

运行效果


这里写图片描述

五、布局多样化

前面,CommonRecycleAdapter 已经可以为我们节省很多代码了,免去了一些重复性操作
这里,可以再来更进一步使 CommonRecycleAdapter 得以更加通用

举个例子,类似微信App的聊天界面那样,自己发送的信息和对方发送来的信息位置是一左一右的,也就是说,这个聊天列表使用了两个不同的布局文件
那么,可以也为 CommonRecycleAdapter 加入支持多布局的功能

为了标识数据的不同,在 Data 类中加入一个新的变量 location

/**
 * Created by 叶应是叶 on 2017/2/25.
 */

public class Data {

    private String title;

    private String content;

    private String location;

    public Data(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

}

之前使用的布局中标题是靠左的,这里再定义一个布局,使标题靠右

<?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="vertical">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="right"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#abc" />

</LinearLayout>

然后,需要有一个方法来判断哪种数据类型需要使用哪种布局,所以再来定义一个接口,getLayoutId() 用于返会布局文件ID

public interface MultiTypeSupport<T> {

    int getLayoutId(T item, int position);

}

修改 CommonRecycleAdapter
如果 multiTypeSupport 不为 null,意思就是要使用到不同的布局文件了,则调用 getLayoutId() 方法,将其返回值作为 ItemViewType

public abstract class CommonRecycleAdapter<T> extends RecyclerView.Adapter<CommonViewHolder> {

    protected LayoutInflater layoutInflater;

    protected List<T> dataList;

    protected int layoutId;

    protected MultiTypeSupport<T> multiTypeSupport;

    public CommonRecycleAdapter(Context context, List<T> dataList, int layoutId) {
        this.layoutInflater = LayoutInflater.from(context);
        this.dataList = dataList;
        this.layoutId = layoutId;
    }

    @Override
    public int getItemViewType(int position) {
        if (multiTypeSupport != null) {
            return multiTypeSupport.getLayoutId(dataList.get(position), position);
        }
        return super.getItemViewType(position);
    }

    @Override
    public CommonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (multiTypeSupport != null) {
            layoutId = viewType;
        }
        View itemView = layoutInflater.inflate(layoutId, parent, false);
        return new CommonViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(CommonViewHolder holder, int position) {
        bindData(holder, dataList.get(position));
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    abstract void bindData(CommonViewHolder holder, T data);

}

修改 MyAdapter 类,实现 MultiTypeSupport 接口,根据 Data 对象的 location 字段的值,来决定返回哪个布局文件的ID

public class MyAdapter extends CommonRecycleAdapter<Data> implements MultiTypeSupport<Data> {

    private CommonViewHolder.onItemCommonClickListener commonClickListener;

    public MyAdapter(Context context, List<Data> dataList) {
        super(context, dataList, R.layout.item_left);
    }

    public MyAdapter(Context context, List<Data> dataList, CommonViewHolder.onItemCommonClickListener commonClickListener) {
        super(context, dataList, R.layout.item_left);
        this.commonClickListener = commonClickListener;
        this.multiTypeSupport = this;
    }


    @Override
    void bindData(CommonViewHolder holder, Data data) {
        holder.setText(R.id.tv_title, data.getTitle())
                .setText(R.id.tv_content, data.getContent())
                .setCommonClickListener(commonClickListener);
    }

    @Override
    public int getLayoutId(Data item, int position) {
        if (item.getLocation().equals("left")) {
            return R.layout.item_left;
        }
        return R.layout.item_right;
    }

}

再来修改 Activity 中的代码

public class MainActivity extends AppCompatActivity implements CommonViewHolder.onItemCommonClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycleView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        List<Data> dataList = new ArrayList<>();
        boolean bool = false;
        for (int i = 0; i < 40; i++) {
            Data data = new Data("叶应是叶", "我也不知道说什么好,我也不知道说什么好,我也不知道说什么好,我也不知道说什么好");
            if (!bool) {
                data.setLocation("left");
            } else {
                data.setLocation("right");
            }
            bool = !bool;
            dataList.add(data);
        }
        //MyRecyclerAdapter adapter = new MyRecyclerAdapter(this, dataList);
        MyAdapter adapter = new MyAdapter(this, dataList, this);
        recyclerView.setAdapter(adapter);
    }

    @Override
    public void onItemClickListener(int position) {
        Toast.makeText(this, "position:" + position, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClickListener(int position) {
        Toast.makeText(this, "position:" + position, Toast.LENGTH_SHORT).show();
    }

}

运行效果如下


这里写图片描述

可以看到,每隔一行使用到的布局都不一样

这样一来,通用的 Adapter 也设计完成了,以后即使需要使用到很多种不同的数据类型,只要继承 CommonRecycleAdapter ,实现一个或两个方法,就可以搞定 Adapter 了

这里提供示例代码下载:Android RecyclerView设计通用Adapter

上一篇下一篇

猜你喜欢

热点阅读