Android开发之ChoiceListView

ListView实现单选和多选功能,系统自带的貌似不能满足我们需求,一般情况下自定义或者自己控制adapter的逻辑切换显示view、drawable,以前我们最常用的radioButton、checkbox等控件,item赋值一个属性isChecked,提供一个全局的集合记录position的isChecked属性,貌似这样能实现我们需求,也就没多想什么,做出来就好,然而当你重复着编码,编写这些你曾经写过很多次的代码,你不会觉得烦么!!是的我得承认我受不了了,查阅相关资料找到了CustomChoiceList simple.带给了我正确的姿势。


做单选和多选功能这块我们必须的了解以下几点姿势

  • choiceMode

  • SparseBooleanArray

  • Checkable

  • AbsListView

AbsListView提供了四种模式,默认正常模式

    /**
     * 正常模式,没有单选多选
     */
    public static final int CHOICE_MODE_NONE = 0;

    /**
     * 单选模式
     */
    public static final int CHOICE_MODE_SINGLE = 1;

    /**
     * 多选模式(不影响其他点击事件)
     */
    public static final int CHOICE_MODE_MULTIPLE = 2;

    /**
     * 多选模式(影响其他点击事件,会屏蔽onItemClick类似监听)
     */
    public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;

在单选和多选模式下,对于状态 的保存其实AbsListView已经帮我们做了实现,状态保存到SparseBooleanArray,AbsListView 内部常量定义mCheckStates,SparseBooleanArray实现Cloneable,实现原型拷贝keys values数组,内部主要操作position key 和 isChecked value,提供一些增删改查方法。

AbsListView还定义了一些公开方法方便子View调用

 // 获取选择item的总数
 getCheckedItemCount()

// 指定位置Item是否选中
isItemChecked(int position)

// 单选模式先获取当前选中位置
getCheckedItemPosition()

//多选模式小获取存储的所有选中位置
SparseBooleanArray getCheckedItemPositions()

// 清楚所有选中项
clearChoices()

// 设置指定位置被选中
setItemChecked(int position, boolean value)

// 设置当前模式 
setChoiceMode(int choiceMode)

// 绑定多选变化监听
setMultiChoiceModeListener(MultiChoiceModeListener listener)

// 使用默认的Item背景Selector
useDefaultSelector

// 获取被选中的View视图
getSelectedView

// 根据checked状态强制刷新视图
drawableStateChanged

// ...............略...............

下面再来说一下Checkable接口

public interface Checkable {

    /**
     * 改变View的选中状态
     * 
     * @param checked The new checked state
     */
    void setChecked(boolean checked);

    /**
     * 返回当前视图是否被选中
     */
    boolean isChecked();

    /**
     * 如果当前视图是选中的则改变为未选中,反之同理
     */
    void toggle();
}

我们自定义的容器实现Checkable接口,容器的内部子View可以根据checked状态显示不同的ui,至于怎么自定义这种容器呢,我们通过下面自定义LinearLayout来帮助我们理解。(该控件来自官方simple,一生二生三生万物,跟多ViewGroup子类实现参考自行实现即可)


public class CheckableLinearLayout extends LinearLayout implements Checkable {
    private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};

    private boolean mChecked = false;

    public CheckableLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public boolean isChecked() {
        return mChecked;
    }

    public void setChecked(boolean b) {
        if (b != mChecked) {
            mChecked = b;
            refreshDrawableState();
        }
    }

    public void toggle() {
        setChecked(!mChecked);
    }

    @Override
    public int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }
}

你是否会奇怪,这个图片是如何刷新的呢,通过跟踪源码发现setChecked内部调用的refreshDrawableState方法,跟进去发现调用了ViewGroup的drawableStateChanged(方法根源在View),强制刷新了视图,drawableStateChanged方法你是否还有印象呢,上面提到过AbsListViewy自定义的ViewGroup重写了该方法内部跟进发现最终调用了drawableSelector改变state以达到改变ui的目的。那么AbsListViewy又是在何时调用的drawableStateChanged呢,下面来看一段核心代码

   private void onTouchUp(MotionEvent ev) {
        // ..........省略后剩下的部分代码碎片............
            final int motionPosition = mMotionPosition;
            final View child = getChildAt(motionPosition - mFirstPosition);
            if (child != null) {
                if (mTouchMode != TOUCH_MODE_DOWN) {
                    child.setPressed(false);
                }
            final AbsListView.PerformClick performClick = mPerformClick;
             setSelectedPositionInt(mMotionPosition);

             positionSelector(mMotionPosition, child);
             setPressed(true);
            updateSelectorState();

    }

performItemClick内部调用保存状态,setAdapter清除状态,具体不细解,通过setPressed代码跟进发现内部代码flag判断有调用到refreshDrawableState,一切都不言而喻了吧

 public void setPressed(boolean pressed) {
        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

        if (pressed) {
            mPrivateFlags |= PFLAG_PRESSED;
        } else {
            mPrivateFlags &= ~PFLAG_PRESSED;
        }

        if (needsRefresh) {
            refreshDrawableState();
        }
        dispatchSetPressed(pressed);
    }

通过上面简短的叙述相信对于ChoiceList相关的开发我们一定得心印手了吧,某某些人一定要simple,我也无奈啊,还是简单的过一个官方simple吧,下面是效果图

xml布局(choiceMode:multipleChoice多选)


"http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:showDividers="middle"
    android:divider="?android:dividerHorizontal">

    "@style/Widget.DescriptionBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/intro_message" />

    "@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:paddingLeft="@dimen/page_margin"
        android:paddingRight="@dimen/page_margin"
        android:scrollbarStyle="outsideOverlay"
        android:choiceMode="multipleChoice" />
</LinearLayout>

item布局就一个ImageView和TextView,color和drawable资源使用selector就不贴了吧,代码调用如下

public class MainActivity extends ListActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_main);
        setListAdapter(new MyAdapter());
   }
 }

源码官网simple可以下载,github上有个哥们也上传了一个https://github.com/nlema0/CustomChoiceList

实际开发在o2o电商行业这块知识比较常用,特别是购物车一类,全选、取消等功能,当然用法也不会知识简单的单选多选,跟多的是叠加嵌套布局,当你看了这篇blog后相信你也会感到so easy!!


新增ListView+EditText需要注意控制EditText的焦点冲突问题,核心代码块

   holder.getView(R.id.edit).setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
             if (event.getAction() == MotionEvent.ACTION_UP) {
                touchedPosition = getPosition();
              }
                return false;
             }
         });

   if (touchedPosition == getPosition()) {
      // 如果当前的行下标和点击事件中保存的index一致,手动为EditText设置焦点。
      holder.getView(R.id.edit).requestFocus();
    }else {
      holder.getView(R.id.edit).clearFocus();
    }

EditText编辑的数据保存和赋值跟随Item对象变化,addTextChanged..

你可能感兴趣的:(Android)