[安卓/Android] RecyclerView 列表实现长按单选多选功能

        我又来划水了,这一次玩的是如何完成标题的功能,当然,代码全部贴出来没啥意义,因为百度一大堆,这里主要阐述如何实现这个功能,封装成一个可以用来多选的适配器,进行二次开发。等等,我为什么要说又...没错!要假装成很多人看的样子,这样你才会看完这段话,虽然这段话很长。

 大概的原理就是通过KV存储一些数据,K用来存View的id,V存boolean(选择的状态),所有的操作基本上都基于这个集合。

 

首先随便继承一个 XRecyclerView.Adapter,在随便敲两下键盘,大概敲的结果是这样的:

//存放多选操作的KV值,类似于Map
private SparseBooleanArray multiselectList = new SparseBooleanArray();

 

然后在对它做一些小操作,比如我又随便敲了两下键盘:

    /**
     * 设置指定Item的多选状态
     * @param id        Item的id
     * @param status    状态
     */
    public void setItemMultiselectStatus(@IdRes int id, boolean status) {
        multiselectList.put(id, status);
    }
    public void setItemMultiselectStatus(View v, boolean status) {
        setItemMultiselectStatus(v.getId(), status);
    }

    /**
     * 获取指定多选View的位置
     * @param v     查找的View
     * @return      返回下标。找不到返回0
     */
    public int getMultiselectPosition(@NonNull View v) {
        return multiselectList.indexOfKey( v.getId() ) + 1;
    }

    /**
     * 获取指定View id的多选状态
     * @param id    查询的id
     * @return      状态
     */
    public boolean getItemMultiselectStatus(int id) {
        return multiselectList.get(id, false);
    }

    /**
     * 获取指定View的多选状态
     * @param v     查询的View
     * @return      状态
     */
    public boolean getItemMultiselectStatus(@NonNull View v) {
        return getItemMultiselectStatus( v.getId() );
    }

    /**
     * 获取所有Item的多选状态
     * @return      状态列表
     */
    public List getItemMultiselectStatusAll() {
        List list = new ArrayList<>();
        for (int i = 0; i < multiselectList.size(); i++) {
            list.add( multiselectList.valueAt( i ) );
        }
        return list;
    }

这样一来我们就稍微的封了一些方法用于接下来的操作,那接来下该做什么呢?那就是初始化列表中所有Item对它的一个状态。因为我们做的的是BaseAdapter,所以要尽可能的保证它被继承后不会影响原先的操作。我是这么想的,就是当执行onCreateViewHolder的时候不是要返回一个Holder吗?然后Holder需要传入一个View是吧?所以我们写一个方法用来返回这个View,那这个方法必须要有几个参数(ViewGroup、主要显示的View、多选的View),如果需要拓展的话可以增加一个枚举或者常量类决定多选View的显示位置(比如左边或者右边出现)。

 

说了这么多,随手把笔记本一丢,随便用脚踩两下,拿起来后你会看到以下结果:

    /**
     * 创建带多选的ItemView
     * 代码有点长,最好的话独立出来可能会更好,但是我偷懒了
     *
     * @see #onCreateViewHolder(ViewGroup, int)
     * 创建Holder的时候需要传入一个View,可以通过它进行创建。
     *
     * @param viewGroup             {@link ViewGroup}
     * @param itemView              主要展示的View
     * @param multiselectView       自定义的多选View
     * @param gravity               多选View显示的位置
     * @return                      返回创建好的View
     */
    @SuppressLint("ClickableViewAccessibility")
    public View createMultiselectItemView(@NonNull ViewGroup viewGroup,
                                          @NonNull View itemView,
                                          @NonNull View multiselectView,
                                          @NonNull MultiselectGravity gravity) {
        //多选View的Id,前边的数字可以无视掉,除非你也喜欢这么玩的话
        int multiselectId = 1000000 + atoId.getAndIncrement();

        int match = ViewGroup.LayoutParams.MATCH_PARENT;

        //父布局,用来包裹 多选View 和 主要展示的View
        LinearLayout superLayout = new LinearLayout(viewGroup.getContext());
        LinearLayout.LayoutParams superLP, itemViewLP, multiselectLP;

        mViewGroup = viewGroup;
        superLP = new LinearLayout.LayoutParams( match, ViewGroup.LayoutParams.WRAP_CONTENT );

        /* 这里可以按照需求来做,我是通过权重来决定占用的比例 */
        multiselectLP = new LinearLayout.LayoutParams( 0, match );
        itemViewLP = new LinearLayout.LayoutParams( 0, match );

        //设置父布局参数
        superLayout.setLayoutParams( superLP );
        superLayout.setOrientation(LinearLayout.HORIZONTAL);

        //添加子布局,这个是之前所说的拓展,根据传入的gravity决定多选View展示的位置
        switch ( gravity ) {
            case RIGHT:
                superLayout.addView( itemView );            //itemView在左边
                superLayout.addView( multiselectView );     //多选View在右边
                break;
                default://默认为LEFT
                    superLayout.addView( multiselectView ); //多选View在左边
                    superLayout.addView( itemView );        //itemView在右边
                    break;
        }

        /* 设置权重 */
        multiselectLP.weight = 2;
        itemViewLP.weight = 8;

        /* 设置布局参数 */
        multiselectView.setLayoutParams( multiselectLP );
        itemView.setLayoutParams( itemViewLP );

        //设置id,方便我们查询到它
        multiselectView.setId( multiselectId );
        //默认是隐藏,有需要可以设为默认显示
        multiselectView.setVisibility( View.GONE );

        /* 多选View的点击事件监听器,这里监听的是Touch,我不是很喜欢在Base里边设置OnClick,那样
           会出现很多问题,如果通过GestureDetector那样的话可以实现很多功能 */
        multiselectView.setOnTouchListener((v, event) -> {
            int id = v.getId();
            //这就是之前写的,用来查询当前View的位置
            int position = getMultiselectPosition( v );

            //取反设置当前选中的多选VIew
            setItemMultiselectStatus( v, !getItemMultiselectStatus(v) );

            //回调点击事件监听器
            if( mOnClickMultiselectListener != null ) {
                //需要说明的是 mThis 是当前适配器的 this
                mOnClickMultiselectListener.onItemClick(mThis, v, position, id);
            }
            return false;
        });

        //添加多选View的id到列表中,默认隐藏
        setItemMultiselectStatus( multiselectId, false );
        return superLayout;
    }


    //多选View的点击事件监听器
    private OnItemClickMultiselectListener mOnClickMultiselectListener;

    /**
     * 设置Item的多选操作点击事件监听器
     * @param listener      监听器
     */
    public void setOnItemClickMultiselectListener(OnItemClickMultiselectListener listener) {
        mOnClickMultiselectListener = listener;
    }

这样我们只需要传入ItemView和多选View就行了,不必再对多选View进行事件处理,只要监听就行了。做到这里我们就完成了对多选View的添加和点击操作。

 

最后,我们只需要写一些方法用来显示和隐藏多选View就行了,比如这样:

    //是否启用/禁用多选模式
    private boolean isEnableMultiselectMode;

    /**
     * 设置是否启用多选模式
     * @param enable    是否启用
     */
    public void setEnableMultiselectMode(boolean enable) {
        isEnableMultiselectMode = enable;
        //通过遍历的形式隐藏和显示多选View
        for (int i = 0; i < multiselectList.size(); i++) {
            View v = mViewGroup.findViewById( multiselectList.keyAt( i ) );
            v.setVisibility( enable ? View.VISIBLE : View.GONE );
        }
    }

    /**
     * 开启/关闭多选模式
     * 为了方便,写了这个方法用来自动切换,就是单纯的取反
     */
    public void switchMultiselectMode() {
        setEnableMultiselectMode( !isEnableMultiselectMode );
    }

    /**
     * 多选模式启用状态
     * @return      是否启用
     */
    public boolean isEnableMultiselectMode() {
        return isEnableMultiselectMode;
    }

 

 

好了,基本就结束了。最后我们稍微调用一下看看:

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_view, viewGroup, false);
        View muView = new View(viewGroup.getContext());
        muView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
        muView.setBackgroundResource(R.color.colorBlack);
        View v = createMultiselectItemView(viewGroup, itemView, muView, MultiselectGravity.LEFT);

        return new Holder(v);
    }

这是大概的调用方式,主要是展示如何使用。

接下来是点击事件这块:

        //默认不启用
        mAdapter.setEnableMultiselectMode( false );

        //Item长按事件监听器(伪代码)
        mAdapter.setOnItemLongListener((view, position) -> {
            //取反,开启/关闭多选模式
            mOptAdapter.switchMultiselectMode();
        });

        //长按多选View事件监听器
        mOptAdapter.setOnItemClickMultiselectListener((adapter, v, position, id) -> {

            LogUtils.e("getItemMultiselectStatus", "ID:" + mOptAdapter.getItemMultiselectStatus(v) );
            LogUtils.e("getItemMultiselectStatus", "V :" + mOptAdapter.getItemMultiselectStatus(id) );
            LogUtils.e("getMultiselectPosition", "" + mOptAdapter.getMultiselectPosition(v) );

            for( boolean b : mOptAdapter.getItemMultiselectStatusAll() ) {
                LogUtils.e("getItemMultiselectStatusAll", "id:" + id + " ==> " + b );
            }
        });

最后是输出日志:

 /* 第一个按钮按下时 */
 E/getItemMultiselectStatus: ID:true
 E/getItemMultiselectStatus: V :true
 E/getMultiselectPosition: 1
 E/getItemMultiselectStatusAll: id:1000000 ==> true
 E/getItemMultiselectStatusAll: id:1000000 ==> false

 /* 第二个按钮按下时 */
 E/getItemMultiselectStatus: ID:true
 E/getItemMultiselectStatus: V :true
 E/getMultiselectPosition: 2
 E/getItemMultiselectStatusAll: id:1000001 ==> true
 E/getItemMultiselectStatusAll: id:1000001 ==> true

 

你可能感兴趣的:(Base)