ListView多选和单选模式重新整理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ListView多选和单选模式重新整理相关的知识,希望对你有一定的参考价值。

  •    超简单的单选和多选ListView

 

    在开发过程中,我们经常会使用ListView去呈现列表数据,比如商品列表,通话记录,联系人列表等等,在一些情况下,我们还需要去选择其中的一些列表数据进行编辑。以前,我在项目开发中,都是在自定义的Adapter中去维护一个SparseBooleanArray变量来保存当前ListView中已经被选中的项,然后在自定义Adapter的getView()和ListView的setOnItemClickListener()方法中去实时更新SparseBooleanArray变量,从而当用户选择提交数据的时候,直接遍历SparseBooleanArray中的值就可以了,这种做法,虽然也能实现功能,但是无疑增加了代码开销。

    今天,在看文档的时候,发现了一个更好的解决方案(很多人已经用过了吧):使用ListView的choiceMode,官方文档见如下:

                          技术分享


    根据上面的文档说明,可以知道,android:choiceMode有以下几个值:默认(不设置android:choiceMode属性,即不支持单选或多选),singleChoice(单选),multipleChoice(多选),mutipleChoiceModal(特殊多选模式,可以通过设置MultiChoiceModeListener进行监听选择模式,类似ActionBar的ActionMode)。在XML中给ListView设置了android:choiceMode属性一个值后,我们还需要给ListView一个适配器,这里我们使用默认的ArrayAdapter:

 

    //如果是单选模式,则可以使用 android.R.layout.simple_list_item_single_choice
    ArrayAdapter<String> myAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_multiple_choice, mDatas);
    mListView.setAdapter(myAdapter);


    这样我们就已经完成了ListView的多选或单选实现。

    另外一个问题是:我们怎样才能获取当前ListView中被选中的那些项呢?

    我们其实可以通过ListView的isItemChecked(int position)方法判断一项是否被选中,或直接使用ListView.getCheckedItemPositions()来获取所有选中的项,这个方法返回一个SparseBooleanArray对象,遍历它就可以获取所有选中的项。

  • 自定义ListView的多选和单选项布局


  对于一些简单的列表,上面的方法可能已经能够满足需求。其实,上面的列表项只显示了一个标题和一个复选框,但在实际开发中,UE或产品经理可能要求我们去实现的列表远比上面的列表复杂得多,所以往往就需要使用自定义的Adapter来填充ListView。

     但是,如果我们使用自定义的Adapter来填充ListView,那怎么让我们自定义的Checkbox能够无缝衔接ListView的选择状态呢?

     一种普遍的做法是在重写自定义Adapter的getView()时,先通过convertView.findViewById()获取到Checkbox后,通过mList.isItemChecked(int position)判断当前position的状态后,再去更新Checkbox的选择状态。

     这里,我介绍的是另外一种方法。
     首先,我们先来看下为什么我们使用android.R.layout.simple_list_item_multiple_choice布局来填充ArrayAdapter时,不需要我们自己去维护CheckBox的选择状态?
     查看ListView的源码,在ListView的setupChild方法中,有下面的一段代码:
    

    if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
        if (child instanceof Checkable) {
            ((Checkable) child).setChecked(mCheckStates.get(position));
        } else if (getContext().getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB)
       {
           child.setActivated(mCheckStates.get(position));
       }
    }


   即如果ListView的child(从自定义的Adapter的getView()方法中返回的View)实现了Checkable接口,那么当listView的项选择状态改变时,listView也会去同步更新这个child的状态(android 3.1或3.1以上平台,会触发setActivated方法),其实simple_list_item_multiple_choice.xml中只有一个CheckedTextView
  

<CheckedTextView          
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"    
    android:layout_height="?android:attr/listPreferredItemHeightSmall"
    android:textAppearance="?android:attr/textAppearanceListItemSmall"
    android:gravity="center_vertical"
    android:checkMark="?android:attr/listChoiceIndicatorMultiple"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" />


    而CheckedTextView 是实现了Checkable接口的,所以当我们使用simple_list_item_multiple_choic.xml布局作为Adapter的getView()的返回值时,是不需要我们额外去关心Checkbox的状态问题。
    通过上面的分析,我们自定义一个View时,只需要实现了Checkable接口,那么就不用我们在getView中去额外维护选中状态了。如果android3.1或android3.1以上的平台,我们还可以重写setActivated方法来更新我们的选中状态。相关示例代码如下:

package com.shaoxiong.li.marvel.myapplication;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Checkable;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * Created by lishaoxiong on 16-2-22.
 */
public class CustomCheckTextView extends LinearLayout implements Checkable {

    private TextView titleView;
    private CheckBox mCheckBox;

    public CustomCheckTextView(Context context) {
        this(context, null);
    }

    public CustomCheckTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomCheckTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);


        LayoutInflater mLayoutInflater = LayoutInflater.from(context);
        //将加载出来的View添加到当前View层级中去。
        //有两种方案,一种是加载布局时将rootView传进去,或直接使用addView添加进去
        //View v = mLayoutInflater.inflate(R.layout.layout_custom_ctv, null);
        View v = mLayoutInflater.inflate(R.layout.layout_custom_ctv, this, true);
        titleView = (TextView)v.findViewById(R.id.headListView_item_text);
        mCheckBox = (CheckBox)v.findViewById(R.id.headListView_item_cb);
        //this.addView(v);
    }

    @Override
    public void setChecked(boolean checked) {
        mCheckBox.setChecked(checked);
    }

    @Override
    public boolean isChecked() {
        return mCheckBox.isChecked();
    }

    @Override
    public void toggle() {
        mCheckBox.toggle();
    }

    public void setTitle(String title) {
        titleView.setText(title);
    }

    @Override
    public void setActivated(boolean activated) {
        super.setActivated(activated);
    }
}
<?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:orientation="horizontal"
   android:padding="20dp">
    <CheckBox
        android:id="@+id/headListView_item_cb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:focusable="false"
        android:clickable="false"
        android:focusableInTouchMode="false"/>

    <TextView
       android:id="@+id/headListView_item_text"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginRight="10dp"/>


</LinearLayout>
    public class MyAdapter extends BaseAdapter {

        private Context mContext;
        private ArrayList<String> dataList;

        public MyAdapter(Context context, ArrayList<String> dataList) {
            this.mContext = context;
            this.dataList= dataList;
        }

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

        @Override
        public Object getItem(int position) {
            return dataList.get(position);
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder = null;
            if(convertView == null) {
                convertView = LayoutInflater.from(mContext).inflate(R.layout.item_headlistview, null);
                viewHolder = new ViewHolder();
                //viewHolder.mTextView = (TextView)convertView.findViewById(R.id.headListView_item_text);
//                viewHolder.checkedTv = (CheckedTextView)convertView.findViewById(R.id.item_checked_tv);
                viewHolder.customCheckTextView = (CustomCheckTextView)convertView;
                convertView.setTag(viewHolder);
            }else {
                viewHolder = (ViewHolder)convertView.getTag();
            }
//            viewHolder.checkedTv.setText(dataList.get(position));
//            viewHolder.mTextView.setText(dataList.get(position));
            viewHolder.customCheckTextView.setTitle(dataList.get(position));
            return convertView;
        }
    }

    static class ViewHolder {
//        TextView mTextView;
//        CheckedTextView checkedTv;
        CustomCheckTextView customCheckTextView;
    }

  

    这样,通过上面的方法,你就可以去实现各种自己自定义好布局的多选或单选列表了。

以上是关于ListView多选和单选模式重新整理的主要内容,如果未能解决你的问题,请参考以下文章

js_ 复选框 取消多选和单选

js_ 复选框 取消多选和单选

Vue实现多选、单选的样式切换

booststrap select2的应用总结

Android实现选择题答题(包括单选多选和答题卡)

js实现元素的多选,或单选功能