使用ActionMode实现ListView的多选功能

在平时的使用中,我们经常会用到ListView的多选功能,一般比较常见的方式是通过在itemView中加入一个checkBox来实现。其实ListView的API中提供了用于实现多选功能的相关接口。可以通过设置ListView的choiceMode属性来实现listview的item项在长按时激活多选的功能,再配合ListView.MultiChoiceModeListener的回调实现多选。
下面具体来看一下实现方法。

1. 在布局中加入一个listview控件

使用ActionMode实现ListView的多选功能_第1张图片
然后在MainActivity中对这个listview进行初始化,下面是onCreate中的代码

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        mDatas = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            mDatas.add("Item " + i);
        }

        mAdapter = new MyAdapter(this, mDatas);

        mMultiChoiceListener = new MultiChoiceListener();

        mListView = (ListView) findViewById(R.id.lv);
        mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
        mListView.setMultiChoiceModeListener(mMultiChoiceListener);
    }

其中关键的代码是mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(mMultiChoiceListener);

第一行是设置listview的选择模式,第二行是为listview设置一个多选时的回调事件。这个mMultiChoiceListener是一个实现了AbsListView.MultiChoiceModeListener接口的类。通过接口的名字就可以猜到这个一个在多选模式激活时候的回调,在这里我们可以处理多选时的逻辑。来看一下AbsListView.MultiChoiceModeListener里提供的几个方法:

class MultiChoiceListener implements AbsListView.MultiChoiceModeListener {

        @Override
        public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
            Log.d(TAG, "onItemCheckedStateChanged");
        }

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            Log.d(TAG, "onCreateActionMode");
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            Log.d(TAG, "onPrepareActionMode");
            return true;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            Log.d(TAG, "onActionItemClicked");
            return false;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            Log.d(TAG, "onDestroyActionMode");
        }
    }

完成上述的代码后,当我们长按listview的item时通过Log日志可以看到onCreateActionMode
onPrepareActionMode
onItemCheckedStateChanged

这三个方法执行了,
使用ActionMode实现ListView的多选功能_第2张图片
并且这时候手机的主界面也有了变化:
使用ActionMode实现ListView的多选功能_第3张图片
可以看到在Toolbar上方又多出来了一条和Toolbar类似的东西(两个bar的位置问题后面会说到),这就是ListView的ActionMode模式。
而当我们点击左上角的箭头时,这个bar就会消失,并且会执行MultiChoiceListener中的onDestroyActionMode方法。
这就是一个完整的listview多选功能的激活和取消的过程,我们可以通过在MultiChoiceListener类中编写相关的逻辑实现listview的多选。

2. 实现ActionMode 菜单并为其设置CustomView.

在res/menu文件夹中创建menu_multi_choice.xml文件

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
    <item android:id="@+id/action_delete" android:title="@string/action_delete" android:orderInCategory="100" android:icon="@mipmap/ic_menu_delete_36dp" app:showAsAction="always"/>
    <item android:id="@+id/action_choose_all" android:title="@string/action_choose_all" android:orderInCategory="100" android:icon="@mipmap/ic_menu_edit_36dp" app:showAsAction="always"/>
</menu>

在这个菜单文件中加入了两个按钮,分别是删除和全选.
然后在layout文件夹中新建一个customt_view.xml的布局文件,用作actionmode上的自定义view。

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/custom_title_root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal">

    <TextView  android:id="@+id/title" android:maxLines="1" android:singleLine="true" android:layout_gravity="center_vertical" android:layout_centerVertical="true" android:textColor="#ccffffff" android:textAppearance="?android:attr/textAppearanceMediumInverse" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="wrap_content" />

    <TextView  android:id="@+id/selected_conv_count" android:maxLines="1" android:singleLine="true" android:textAppearance="?android:attr/textAppearanceLarge" android:textColor="#ccffffff" android:layout_gravity="center_vertical" android:paddingRight="10dip" android:layout_width="wrap_content" android:layout_height="wrap_content" />

</LinearLayout>

然后在onCreateActionMode中编写代码实现多选菜单。
在MultiChoiceListener类中声明几个成员变量:

private View mMultiSelectionbarView;

private TextView mSelectedItemCount;

private boolean allCheckedMode;

下面是OncreateActionMode中的代码:

@Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            Log.d(TAG, "onCreateActionMode");
            allCheckedMode = false;
            getMenuInflater().inflate(R.menu.menu_multi_choice, menu);
            if (mMultiSelectionbarView == null) {
                mMultiSelectionbarView = LayoutInflater.from(MainActivity.this).inflate(R.layout.custom_view, null, false);
                mSelectedItemCount = (TextView) mMultiSelectionbarView.findViewById(R.id.selected_item_count);
            }
            mode.setCustomView(mMultiSelectionbarView);
            ((TextView) mMultiSelectionbarView.findViewById(R.id.title)).setText("选择项目");
            mSelectedItemCount.setText(mAdapter.getCheckedItemCount() + "");
            return true;
        }

记得要return true,否则不会有效果。mAdapter.getCheckedItemCount()可以返回当前选中的条目的数量。
之前说过ActionBar和ActionModeBar会重复出现,可以通过在AppTheme中配置windowActionModeOverlay为true,来解决,这样就不会出现两个actionbar的情况了:

        <item name="windowActionModeOverlay">true</item>

3. 实现onItemCheckedStateChanged()方法,为选中的item进行相应的处理。

激活actionMode之后,再次点击listview中的item项时,就会触发onItemCheckedStateChanged(ActionMode mode,int position,long id,boolean checked)方法,可以在Adapter类中添加相关的方法当这个方法执行时,对item项进行相应的处理,如改变背景等。
下面是onItemCheckedStateChanged方法的实现:

@Override
        public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
            int checkedCount = 0;
            if (allCheckMode) {
                if (checked) {
                    mAdapter.getItemState()[position] = 0;
                } else {
                    mAdapter.getItemState()[position] = 1;
                }
                checkedCount = mAdapter.getCheckedItemCount();
            } else {
                if (checked) {
                    mAdapter.getItemState()[position] = 1;
                } else {
                    mAdapter.getItemState()[position] = 0;
                }
                checkedCount = mAdapter.getCheckedItemCount();
            }
            mSelectedConvCount.setText(checkedCount + "");
            mAdapter.notifyDataSetChanged();
        }

为Adapter添加updateBackground方法,当item选中时,更新item的背景,在getView()里执行,当notifyDatasetChanged方法被调用时,就会执行到这里刷新item的背景。

@Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder holder;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
            holder.mTv = (TextView) convertView.findViewById(R.id.tv);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        //首先缓存原来的background
        if (!isCachedBackground) {
            isCachedBackground = true;
            mBackground = convertView.getBackground();
        }
        updateBackground(position,convertView);
        holder.mTv.setText(getItem(position));
        return convertView;
    }
private void updateBackground(int position,View convertView) {
        if (getItemState()[position] == 1) {
            convertView.setBackgroundColor(0xFFDFDFDF);
        } else if (getItemState()[position] == 0){
            convertView.setBackgroundDrawable(mBackground);
        }
    }

在onDestroyActionMode()中将状态重置:

@Override
        public void onDestroyActionMode(ActionMode mode) {
            Log.d(TAG, "onDestroyActionMode");
            allCheckedMode = false;
            mAdapter.unCheckAll();
        }

4. 实现onActionItemClicked方法

onActionItemClicked用来处理ActionMode中菜单的点击事件,如删除和全选
下面是实现代码:

@Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            Log.d(TAG, "onActionItemClicked");
            switch (item.getItemId()) {
                case R.id.action_delete:
                    mAdapter.deleteSelectedItems();
                    mode.finish();
                    break;
                case R.id.action_choose_all:
                    if (mAdapter.isAllChecked()) {
                        mAdapter.unCheckAll();
                        mListView.clearChoices();
                        mode.finish();
                    } else {
                        mAdapter.checkAll();
                        for (int i = 0; i < mAdapter.getCount(); i++) {
                            mListView.setSelection(i);
                        }
                        allCheckedMode = true;
                    }
                    mAdapter.notifyDataSetChanged();
                    mSelectedItemCount.setText(mAdapter.getCheckedItemCount() + "");
                    break;
                default:
                    break;
            }
            return true;
        }

至此,listview的多选已经基本实现。当长按item项时会激活actionmode,点击要删除的item将其选中,然后点击删除按钮就可以将所选中的item删除。也可以通过全选按钮来进行全选。

5.下面附上Adapter类的代码:

package com.chaoyang805.actionmodedemo;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

/** * Created by chaoyang805 on 2015/11/5. */
public class MyAdapter extends BaseAdapter {

    private List<String> mList;

    private Context mContext;
    /** * 用来记录item的状态,1表示选中,0表示未选中 */
    private int[] mItemState;
    private boolean isCachedBackground = false;
    private Drawable mBackground;
    private boolean mActionModeStarted = false;

    public MyAdapter(Context context, List<String> list) {
        mList = list;
        mContext = context;
        mItemState = new int[mList.size()];
        for (int i = 0; i < mItemState.length; i++) {
            mItemState[i] = 0;
        }

    }

    public int[] getItemState() {
        return mItemState;
    }


    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public String getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
            holder.mTv = (TextView) convertView.findViewById(R.id.tv);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        //首先缓存原来的background
        if (!isCachedBackground) {
            isCachedBackground = true;
            mBackground = convertView.getBackground();
        }
        updateBackground(position, convertView);
        holder.mTv.setText(getItem(position));
        return convertView;
    }

    public int getCheckedItemCount() {
        int count = 0;
        for (int i : mItemState) {
            if (i == 1) count++;
        }
        return count;
    }

    private void updateBackground(int position,View convertView) {
        if (getItemState()[position] == 1) {
            convertView.setBackgroundColor(0xFFDFDFDF);
        } else if (getItemState()[position] == 0){
            convertView.setBackgroundDrawable(mBackground);
        }
    }

    public void checkAll() {
        for (int i = 0; i < mItemState.length; i++) {
            mItemState[i] = 1;
        }
    }

    public void unCheckAll() {
        for (int i = 0; i < mItemState.length; i++) {
            mItemState[i] = 0;
        }
    }

    public boolean isAllChecked() {
        for (int i : mItemState) {
            if (i == 0) {
                return false;
            }
        }
        return true;
    }

    public void setActionModeState(boolean flag) {
        mActionModeStarted = flag;
    }

    public boolean isActionModeStarted() {
        return mActionModeStarted;
    }

    public void deleteSelectedItems() {
        for (int i = mItemState.length - 1; i >= 0; i--) {
            if (mItemState[i] == 1) {
                mList.remove(i);
            }
        }
        notifyDataSetChanged();
        mItemState = new int[mList.size()];
        for (int i : mItemState) {
            i = 0;
        }
    }

    public boolean isActionModeStart() {
        return mActionModeStarted;
    }

    static class ViewHolder{

        TextView mTv;

    }
}

你可能感兴趣的:(ListView)