ListView的Choice模式
/**
* Normal list that does not indicate choices
*/
public static final int CHOICE_MODE_NONE = 0;
/**
* The list allows up to one choice
*/
public static final int CHOICE_MODE_SINGLE = 1;
/**
* The list allows multiple choices
*/
public static final int CHOICE_MODE_MULTIPLE = 2;
/**
* The list allows multiple choices in a modal selection mode
*/
public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
如何设置ListView的选择模式?
在ListView的xml文件中添加如下代码
<ListView android:choiceMode="multipleChoice" android:id="@+id/list" android:layout_width="match_parent" android:layout_height="wrap_content" style="?android:attr/listViewWhiteStyle" />
在java代码中设置
targetLsitView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
默认情况下设置为NONE
ListView的单选模式
这个模式不常用,当我们进行某一个item的选择的时候,会消除掉其他的item的选择状态,应用场景不太多!
ListView的MULTIPLE模式
这种多选模式,可用于进行多项选择的时候使用,但是当我们开始的时候就进行多选可能不太符合我们的想法,所以我比较建议使用下一种模式!
ListView的MULTIPLE_MODEL模式
上边的模式选中一个item的时候,listView的onItemClick也同时触发,而一个ListView点击item的后续操作一般是切换到另外一个界面,所以实际应用中,我们还需要设置一个标志位,用来区别当前是多选状态还是普通状态 ,如果是多选状态,调用ListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 如果是普通状态调用mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); CHOICE_MODE_MULTIPLE模式的特点在于他本身没有排斥性,在能选择item的情况下,也可以响应普通点击事件。为了解决这个问题 ,在android3.0之后增加了CHOICE_MODE_MULTIPLE_MODAL模式。
CHOICE_MODE_MULTIPLE_MODAL和CHOICE_MODE_MULTIPLE恰恰相反,他是对普通点击操作和多选操作是排斥的,一旦有一个item被选中,即进入到多选状态,item的onclick事件被屏蔽。这种排斥性也是他比CHOICE_MODE_MULTIPLE多了个MODAL的原因。此外CHOICE_MODE_MULTIPLE_MODAL还结合了android3.0的actionmode,当进入多选状态,actionbar的位置会显示新的菜单。
我们来看看CHOICE_MODE_MULTIPLE_MODAL模式的实现原理:
如何实现两种状态的互斥:当点击一个item的时候absListView中会调用performItemClick,如果是CHOICE_MODE_MULTIPLE,则该item点击一次,mCheckStates中相应位置的状态变更一次,但是CHOICE_MODE_MULTIPLE_MODAL模式不同,必须要mChoiceActionMode!= null
的情况下,才会去变更mCheckStates中相应位置的状态;不光如此,如果mChoiceActionMode!= null
,他还会阻挡ItemClick事件的继续传播,从而屏蔽了ListView OnItemClickListener的onItemClick方法。
如何启用actionmode:一般我们使用actionmode都是在activity中调用startActionMode,但是如果你要使用ListView的CHOICE_MODE_MULTIPLE_MODAL,请不要这么做, 在absListView中有一个变量mChoiceActionMode,定义如下:
ActionMode mChoiceActionMode;
当长按item 或者是调用主动调用setItemChecked方法mChoiceActionMode将被实例化,而如果你是在activity中调用startActionMode,那么虽然actionbar上的菜单变化了,ListView 中的mChoiceActionMode却没有实例化,刚刚我们谈到mChoiceActionMode==null 表示未进入到多选状态,所以这时你点击一个item其实还是普通的点击行为。
因此在CHOICE_MODE_MULTIPLE_MODAL模式下要启用多选操作,只有两种办法:
(1) 长按当长按item ;
(2) 主动调用ListView的setItemChecked(int position, boolean value)方法选中一个item。
但是这两种进入多选状态的方法都有一个弊端,那就是进入多选状态之后,总是有一个item是被选中的, 方法(1)中长按item,被按的item被选中,这种结果是合理的可以接受的,但是如果你想主动进入多选状态(比如我在点击actionbar的某个菜单的时候想进入多选状态),就必须采用方法(2):调用setItemChecked,这就出现个问题,你该让哪个item被选中呢?貌似最合理的该是一个都不选中吧,我只是进入到这个状态,还没有开始选呢。幸运的是,我们可以使用一些技巧,实现能主动进入多选状态,且没有一个item被选中。
PS:
CHOICE_MODE_MULTIPLE_MODAL模式中,一旦有一个item被选中,即进入到多选状态,而他还有个相反的特性,一旦所有的Item被主动的设置为未选中,则退出多选状态,mChoiceActionMode会调用自己的finish()方法。为什么呢?在MultiChoiceModeWrapper类中:
@Override
public void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked) {
mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
// If there are no items selected we no longer need the selection mode.
if (getCheckedItemCount() == 0) {
mode.finish();
}
}
使用选择模式,item布局需要为Checkable
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp">
<ImageView
android:id="@+id/checkbox"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/checkedButtonMarginHorizontal"
android:layout_marginRight="@dimen/checkedButtonMarginHorizontal"
android:layout_gravity="center_vertical"
android:src="@drawable/checked_blue"
android:layout_width="@dimen/checkedButtonSize"
android:layout_height="@dimen/checkedButtonSize"/>
<TextView
android:id="@+id/time"
android:layout_marginLeft="75dp"
android:gravity="center_vertical|left"
android:textSize="@dimen/text_title_larger"
android:text="24:34"
android:layout_width="100dp"
android:layout_height="match_parent"/>
<LinearLayout
android:id="@+id/chooseDoseLayout"
android:orientation="horizontal"
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_marginRight="@dimen/checkedButtonMarginHorizontal"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ImageView
android:focusable="false"
android:id="@+id/incButton"
android:src="@drawable/add_blue"
android:layout_marginRight="3dp"
android:layout_gravity="center_vertical"
android:layout_width="@dimen/checkedButtonSize"
android:layout_height="@dimen/checkedButtonSize"/>
<EditText
android:id="@+id/dose"
android:inputType="numberDecimal"
android:gravity="center_vertical|center"
android:textSize="@dimen/text_title_larger"
android:text="15"
android:layout_width="50dp"
android:layout_height="match_parent"/>
<ImageView
android:focusable="false"
android:id="@+id/decButton"
android:src="@drawable/less_blue"
android:layout_marginLeft="3dp"
android:layout_gravity="center_vertical"
android:layout_width="@dimen/checkedButtonSize"
android:layout_height="@dimen/checkedButtonSize"/>
</LinearLayout>
</RelativeLayout>
View.java
package noclay.demos.Demos.Demo1;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Checkable;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.example.no_clay.toolsapp.R;
/**
* Created by noclay on 2017/5/15.
*/
public class CheckableChooseDoseView extends RelativeLayout implements Checkable, View.OnClickListener {
Context mContext;
ImageView mCheckbox;
TextView mTime;
ImageView mIncButton;
EditText mDose;
ImageView mDecButton;
LinearLayout mChooseDoseLayout;
private boolean isChecked = false;
OnChildClickListener onChildListener;
private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
private static final String TAG = "CheckableChooseDoseView";
public interface OnChildClickListener{
void onClickIncButton();
void onClickDecButton();
}
public CheckableChooseDoseView(Context context) {
super(context);
mContext = context;
initView();
}
public CheckableChooseDoseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initView();
}
public CheckableChooseDoseView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initView();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CheckableChooseDoseView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
initView();
}
private void initView() {
LayoutInflater.from(mContext).inflate(R.layout.demo1_checkable_choose_time_and_dose,
this, true);
mCheckbox = (ImageView) findViewById(R.id.checkbox);
mTime = (TextView) findViewById(R.id.time);
mIncButton = (ImageView) findViewById(R.id.incButton);
mDose = (EditText) findViewById(R.id.dose);
mDecButton = (ImageView) findViewById(R.id.decButton);
mChooseDoseLayout = (LinearLayout) findViewById(R.id.chooseDoseLayout);
clearCheck(isChecked);
}
public void clearCheck(boolean checked) {
mCheckbox.setVisibility(GONE);
mChooseDoseLayout.setVisibility(GONE);
if (checked) {
mChooseDoseLayout.setVisibility(VISIBLE);
mCheckbox.setVisibility(VISIBLE);
}
}
@Override
public void setChecked(boolean checked) {
if (checked != isChecked){
isChecked = checked;
refreshDrawableState();
}
}
@Override
public boolean isChecked() {
return isChecked;
}
@Override
public void toggle() {
setChecked(!isChecked);
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
// 在原有状态中添加一个空间space用于保存checked状态
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
// 将checked状态合并到原有的状态数组中
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
clearCheck(isChecked);
return drawableState;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "onInterceptTouchEvent: ");
return true;
}
@Override
public void refreshDrawableState() {
super.refreshDrawableState();
clearCheck(isChecked);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.incButton:{
onChildListener.onClickIncButton();
break;
}
case R.id.decButton:{
onChildListener.onClickDecButton();
break;
}
}
}
public void setTime(String time) {
mTime.setText(time);
}
public void setFloat(Float value){
mDose.setText(value.toString());
}
public void setOnChildListener(OnChildClickListener onChildListener) {
this.onChildListener = onChildListener;
}
}
item.xml
<?xml version="1.0" encoding="utf-8"?>
<noclay.demos.Demos.Demo1.CheckableChooseDoseView
android:descendantFocusability="blocksDescendants"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="vertical">
</noclay.demos.Demos.Demo1.CheckableChooseDoseView>
PS
需要在getView中这样写才能够实现,但不知道为什么?(应该是ListView在设置itemchecked的时候是这样使用的)
public View getView(int position, View convertView, ViewGroup parent) {
CheckableChooseDoseView view;
final ViewHolder holder;
if (convertView == null) {
view = (CheckableChooseDoseView) LayoutInflater.from(mContext).inflate(
R.layout.demo1_item_checkable_choose_time, parent, false)
.findViewById(R.id.list_item);
}else{
view = (CheckableChooseDoseView) convertView;
}
view.setTime(mStrings[position]);
view.setOnChildListener(new CheckableChooseDoseView.OnChildClickListener() {
@Override
public void onClickIncButton() {
Log.d("toggle", "onClickIncButton: ");
}
@Override
public void onClickDecButton() {
Log.d("toggle", "onClickDecButton: ");
}
});