RecyclerView中实现单选和长按复选

个人原创,转载请注明出处:https://www.jianshu.com/p/36740a1ac404

概述

最近写app时需要实现一个recyclerview的单选和复选功能,查了下资料大概整理出两种思路。一种是给列表的javabean增加一个boolean型的变量isSelected,然后利用databinding将该变量与itemview中的选中标志绑定,再在item点击事件中去操作该变量。另一种是维护一个hashset,通过将item的position加入或移除这个set来控制item的选中状态。由于第一种方法需要修改javabean,对项目整体可能造成更多影响,于是权衡了一下决定采用第二种方法,下面是具体实现。

Item

首先是JavaBean:

public class Fruit {

    private String name;
    private int imageId;

    public Fruit(String name, int imageId){
        this.name = name;
        this.imageId = imageId;
    }
    public String getName(){
        return name;
    }
    public int getImageId(){
        return imageId;
    }
}

很简单,只包含两个变量。然后是xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="5dp">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="3dp"  /><!--留出3dp的空间用于显示选中效果-->

        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="10dp"/>

    </LinearLayout>
    
</FrameLayout>

布局也很简单。注意这里用一个linearlayout外套一个framelayout并且中间留3dp的空间,是为了通过改变item_layout的底色,从而使item被选中时显示一个有色的框,进而表示选中效果。当然用其他控件表示选中效果也行,这里只是用了一种简单的方法。

Activity

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="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

就是一个recyclerview。下面重点来了,mainactivity:

public class MainActivity extends AppCompatActivity {

    public static Set<Integer> positionSet = new HashSet<>();
    private FruitAdapter adapter;
    private List<Fruit> fruitList = new ArrayList<>();
    private boolean selectMode;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
        adapter.setOnItemClickListener(new FruitAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                if (selectMode) {
                    // 如果当前处于多选状态,则进入多选状态的逻辑
                    // 维护当前已选的position
                    addOrRemove(position);
                } else {
                    // 如果不是多选状态,则进入单选事件的业务逻辑
                    if (!positionSet.contains(position)){
                        // 选择不同的单位时取消之前选中的单位
                        positionSet.clear();
                    }
                    addOrRemove(position);
                }
            }

            @Override
            public void onItemLongClick(View view, int position) {
                if (!selectMode) {
                    selectMode = true;
                    positionSet.clear();
                }
            }
        });
    }

    private void addOrRemove(int position) {
        if (positionSet.contains(position)) {
            // 如果包含,则撤销选择
            positionSet.remove(position);
        } else {
            // 如果不包含,则添加
            positionSet.add(position);
        }
        if (positionSet.size() == 0) {
            // 如果没有选中任何的item,则退出多选模式
            adapter.notifyDataSetChanged();
            selectMode = false;
        } else {
            // 更新列表界面,否则无法显示已选的item
            adapter.notifyDataSetChanged();
        }
    }

    private void initFruits(){
        Fruit apple = new Fruit("apple",R.drawable.apple);
        fruitList.add(apple);
        Fruit banana = new Fruit("banana",R.drawable.banana);
        fruitList.add(banana);
        Fruit cherry = new Fruit("cherry",R.drawable.cherry);
        fruitList.add(cherry);
        Fruit grape = new Fruit("grape",R.drawable.grape);
        fruitList.add(grape);
        Fruit mango = new Fruit("mango",R.drawable.mango);
        fruitList.add(mango);
        Fruit orange = new Fruit("orange",R.drawable.orange);
        fruitList.add(orange);
        Fruit pear = new Fruit("pear",R.drawable.pear);
        fruitList.add(pear);
        Fruit pineapple = new Fruit("pineapple",R.drawable.pineapple);
        fruitList.add(pineapple);
        Fruit strawberry = new Fruit("strawberry",R.drawable.strawberry);
        fruitList.add(strawberry);
        Fruit watermelon = new Fruit("watermelon",R.drawable.watermelon);
        fruitList.add(watermelon);
    }

}

这里用一个boolean变量selectMode来判断是否进入了复选模式,默认false(单选),通过longclick进入复选。当然用android自带的actionMode也行,但稍微麻烦点,本例选用了一种轻量级的实现方法。
这段代码的核心在于用addOrRemove()方法来维护用于一个记录item是否选中的positionSet(用ArrayList也行,不过由于对插入删除的顺序不敏感,显然HashSet效率更高)。
另外click和longclick下逻辑判断的不同也需要注意。

Adapter

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private List<Fruit> mFruitList;
    private OnItemClickListener onItemClickListener;
    private Context mContext;

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public interface OnItemClickListener{
        void onItemClick(View view, int position);
        void onItemLongClick(View view,int position);
    }

    static class ViewHolder extends RecyclerView.ViewHolder{
        ImageView fruitImage;
        TextView fruitName;
        FrameLayout itemLayout;

        public ViewHolder(View view){
            super(view);
            fruitImage = view.findViewById(R.id.fruit_image);
            fruitName = view.findViewById(R.id.fruit_name);
            itemLayout = view.findViewById(R.id.item_layout);
        }
    }

    public FruitAdapter(List<Fruit> fruitList){
        mFruitList = fruitList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (mContext == null){
            mContext = parent.getContext();
        }
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        Set<Integer> positionSet = MainActivity.positionSet;
        //检查set里是否包含position,包含则显示选中的背景色,不包含则反之
        if (positionSet.contains(position)) {
            holder.itemLayout.setBackgroundColor(ContextCompat.getColor(mContext,R.color.selected));
        } else {
            holder.itemLayout.setBackgroundColor(ContextCompat.getColor(mContext,R.color.unSelected));
        }
        Fruit fruit = mFruitList.get(position);
        Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage);
        holder.fruitName.setText(fruit.getName());
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onItemClick(v,position);
            }
        });
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onItemLongClick(v,position);
                return false;
            }
        });
    }

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

在adapter里需要导入FruitActivity里的positionSet,并通过检查set里是否包含当前item的position来设置itemlayout的背景色。这里R.color.selected是选中的颜色R.color.unSelected是未选中的颜色,都定义在color.xml文件中。

效果图

Animation.gif

总结

本例的核心在于三点:
1.boolean变量selectMode控制单选/复选
2.positionSet记录选中item,addOrRemove()方法维护positionSet
3.click/longclick下不同的逻辑判断

本例稍加拓展,也很容易实现全选、反选和批量删除等功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容