讲讲Checkbox的基本使用.在XML中定义

<?          xml          version          =          "1.0"          encoding          =          "utf-8"          ?>         


          <          CheckBox          xmlns:android          =          "http://schemas.android.com/apk/res/android"         


                    android:id          =          "@+id/cbx"         


                    android:layout_width          =          "wrap_content"         


                    android:layout_height          =          "wrap_content"         


                    android:checked          =          "false"          />


在Activity中使用

CheckBox cbx = (CheckBox) findViewById(R.id.cbx);         


          cbx.setOnCheckedChangeListener(          new          CompoundButton.OnCheckedChangeListener() {         


                    @Override         


                    public          void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    //do something         


                    }         


          });


很简单.要注意的是,CheckBox本身是一个视图,是展示给用户看的,因此我们要用数据来控制它的展示.所以,我们的CheckBox在Activity中要这么写

boolean           isChecked=           false          ;         


          CheckBox cbx = (CheckBox) findViewById(R.id.cbx);         


          cbx.setOnCheckedChangeListener(          new          CompoundButton.OnCheckedChangeListener() {         


                    @Override         


                    public          void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    if          (isChecked){         


                    //do something         


                    }          else          {         


                    //do something else         


                    }         


                    }         


          });         


          cbx.setChecked(isChecked);


这样,我们改变数据的时候,视图的状态就会跟着数据来做改变了.注意,监听器一定要这setChecked之前设置,这样才能体现出来数据来控制视图的展示.

单独用CheckBox很easy,接下来,复杂的情况来啦,CheckBox如何跟ListView/RecyclerView(以下简称LV/RV)配合使用.这就不能简单的考虑问题啦,要知道LV/RV中的视图个数跟数据集的里面的数据并不一致,真正的视图个数远小于数据集中数据项的个数.因为屏幕上在列表中的视图是可以复用的.由于LV/RV的复用机制,如果我们没有用数据来控制CheckBox状态的话,将会导致CheckBox的显示在列表中错乱.比方说你只对第一个Item中的CheckBox做了选中操作,当列表向上滚动的时候,你会发现,下面的Item中居然也会有被选中的.当然,问题的关键就在于要用数据来控制视图的显示.因此在getView/onBindViewHolder中,我们应该这么写.

holder.cbx.setTag(item);         


          holder.cbx.setOnCheckedChangeListener(          new          CompoundButton.OnCheckedChangeListener() {         


                    @Override         


                    public          void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    Item item =(Item) buttonView.getTag();         


                    if          (isChecked){         


                    item.setCheckState(          true          );         


                    //do something         


                    }          else          {         


                    item.setCheckState(          false          );         


                    //do something else         


                    }         


                    }         


          });         


          cbx.setChecked(item.getCheckState());


这种方法基本正确,但是我们要额外的给每个数据项里面添加一个字段来记录状态,这代价就有点大了.一是不必这么做,二是这会导致本地数据结构跟服务端结构不一致.通常,列表中使用CheckBox的话,很明显是把选中的item给记录下来,可以这么理解,选中的状态是列表给的,而item本身应该是无状态的.那么,如果重构我们的代码呢,Android为我们提供了一种完美的数据结构来解决这个问题.你可以用SparseArray,也可以用SparseBooleanArray,我现在习惯使用SparseBooleanArray,ok,请看代码

private           class          Adapter           extends          RecyclerView.Adapter<RecyclerView.ViewHolder>{         


                    SparseBooleanArray mCheckStates=          new          SparseBooleanArray();         


                    @Override         


                    public          RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,           int          viewType) {         


                    //...         


                    }         


                    @Override         


                    public          void          onBindViewHolder(RecyclerView.ViewHolder holder,           int          position) {         


                    holder.cbx.setTag(position);         


                    holder.cbx.setOnCheckedChangeListener(          new          CompoundButton.OnCheckedChangeListener() {         


                    @Override         


                    public          void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    int          pos =(          int          )buttonView.getTag();         


                    if          (isChecked){         


                    mCheckStates.put(pos,          true          );         


                    //do something         


                    }          else          {         


                    mCheckStates.delete(pos);         


                    //do something else         


                    }         


                    }         


                    });         


                    cbx.setChecked(mCheckStates.get(position,          false          ));         


                    }         


                    @Override         


                    public          int          getItemCount() {         


                    //...         


                    }         


          }


这样列表就能正常显示了,而且在你选中CheckBox的时候,会自动触发onCheckedChanged来对mCheckStates来进行更新.此时,如果你想用程序来选中某个item的时候,那么直接这样就行了.

mCheckStates.put(pos,          true          );         


          adapter.notifyDatasetChanged();



如果我们想要取出列表列中所有的数据项,有了SparseBooleanArray,

ArrayList<Item> selItems=          new          ArrayList<>();         


          for          (          int          i=          0          ;i < mCheckStates.size();i++){         


                    if          (mCheckStates.valueAt(i)){         


                    selItems.add(allItems.get(mCheckStates.keyAt(i)));         


                    }         


          }


由于CheckBox这个控件太容易变了,为什么这么说呢,因为就算你把它设成disabled的话,它依然是可以点选的,它的onCheckedChanged依然会触发.那么我们该怎么办呢.程序员考虑问题呢,一般都是先想最笨的方法啦,既然onCheckedChanged依然会触发,那我就在里面把buttonView再设置成!isCheck的不就行了嘛.

holder.cbx.setOnCheckedChangeListener(          new          CompoundButton.OnCheckedChangeListener() {         


                    @Override         


                    public          void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    buttonView.setChecked(!isChecked);         


                    //...         


                    }         


          });


但是这么写的话,就会调用buttonView的onCheckedChanged,其实buttonView就是外面的holder.cbx,这就会造成死循环.因此我们如果用cbx本身去改变状态的话,那么一定要加锁.

boolean           lockState=          false          ;         


          holder.cbx.setOnCheckedChangeListener(          new          CompoundButton.OnCheckedChangeListener() {         


                    @Override         


                    public          void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    if          (lockState)          return          ;         


                    //不然cbx改变状态.         


                    lockState=          true          ;         


                    buttonView.setChecked(!isChecked);         


                    lockState=          false          ;         


                    //...         


                    }         


          });


对cbx加锁其实还是挺常用的,比方说在onCheckedChanged中,你要发一个请求,而请求的结果反过来会更新这个cbx的选中状态,你就必须要用lockState来直接改变cbx的状态了,以便于cbx的状态跟mCheckStates里面的是一致的.

mada mada,还有一种情况,如果在onCheckedChanged的时候,isChecked跟mCheckStates.get(pos)一致的话,这会导致什么情况呢.

@Override         


          public           void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    int          pos =(          int          )buttonView.getTag();         


                    if          (isChecked){         


                    mCheckStates.put(pos,          true          );         


                    //do something         


                    }          else          {         


                    mCheckStates.delete(pos);         


                    //do something else         


                    }         


          }


这就会让你的//do something做两次,这么做就是没有必要的啦,而且如果你的//do something是网络请求的话,这样就会导致更大问题.所以,我们有必要对这种情况做过滤.

@Override         


          public           void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    if          (lockState)          return          ;         


                    int          pos =(          int          )buttonView.getTag();         


                    if          (mCheckStates.get(pos,          false          ) == isChecked)          return          ;         


                    if          (isChecked){         


                    mCheckStates.put(pos,          true          );         


                    //do something         


                    }          else          {         


                    mCheckStates.delete(pos);         


                    //do something else         


                    }         


          }


好啦,如果你能将CheckBox跟SparseBooleanArray联用,并且能考虑到加锁和过滤重选的话,那么说明你使用CheckBox的姿势摆正了.一个列表仅仅能让用户上滚下滑,那是最简单的使用,通常,由于列表项过多,产品会给列表项添加筛选的功能,而通常我们做筛选,会考虑到使用Spinner来做,但是,有用android自身提供的Spinner扩展性太差,而且长得丑,往往导致大家一怒之下,弃而不用.PopupWindow(介绍),我先介绍一下原理,首先给CheckBox设置setOnCheckedChangeListener,然后在onCheckedChanged里面,isChecked分支中弹出PopupWindow,!isChecked中,读取Popupwindow中的结果,用新的筛选条件来更新列表.ok,上代码:

MainActivity:

@Override         


          public           void          onCheckedChanged(CompoundButton buttonView,           boolean          isChecked) {         


                    if          (lockState)          return          ;         


                    int          pos =(          int          )buttonView.getTag();         


                    if          (mCheckStates.get(pos,          false          ) == isChecked)          return          ;         


                    if          (isChecked){         


                    mCheckStates.put(pos,          true          );         


                    //do something         


                    }          else          {         


                    mCheckStates.delete(pos);         


                    //do something else         


                    }         


          }


FilterLinePw

public           class          FilterLinePw           extends          PopupWindow {         


                    RadioGroup radioGroup;         


                    CheckBox outCbx;         


                    


                    //为动态生成radioButton生成id         


                    int          [] rbtIds = {          0          ,           1          ,           2          };         


                    


                    public          FilterLinePw(Context context, CheckBox outCbx, String[] items) {         


                    super          (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);         


                    View contentview = LayoutInflater.from(context).inflate(R.layout.filter_line_popupwindow,           null          );         


                    setContentView(contentview);         


                    setFocusable(          true          );         


                    setOutsideTouchable(          true          );         


                    this          .outCbx = outCbx;         


                    contentview.setOnKeyListener(          new          View.OnKeyListener() {         


                    @Override         


                    public          boolean          onKey(View v,           int          keyCode, KeyEvent event) {         


                    if          (keyCode == KeyEvent.KEYCODE_BACK) {         


                    dismiss();         


                    return          true          ;         


                    }         


                    return          false          ;         


                    }         


                    });         


                    contentview.setFocusable(          true          );           // 这个很重要         


                    contentview.setFocusableInTouchMode(          true          );         


                    contentview.setOnClickListener(          new          View.OnClickListener() {         


                    @Override         


                    public          void          onClick(View v) {         


                    dismiss();         


                    }         


                    });         


                    


                    init(context, contentview,items);         


                    


                    }         


                    


                    private          void          init(Context context, View contentview, String[] items) {         


                    /**         


                    * 用传入的筛选条件初始化UI         


                    */         


                    radioGroup = (RadioGroup) contentview.findViewById(R.id.filter_layout);         


                    radioGroup.clearCheck();         


                    if          (items ==           null          )           return          ;         


                    for          (          int           i =           0          ; i < items.length; i++) {         


                    


                    RadioButton radioButton = (RadioButton) LayoutInflater.from(context).inflate(R.layout.line_popupwindow_rbt,           null          );         


                    radioButton.setId(rbtIds[i]);         


                    radioButton.setText(items[i]);         


                    


                    radioGroup.addView(radioButton, -          1          , radioGroup.getLayoutParams());         


                    


                    if          (items[i].equals(outCbx.getText())) {         


                    outCbx.setTag(i);         


                    radioButton.setChecked(          true          );         


                    }         


                    


                    }         


                    radioGroup.setOnCheckedChangeListener(          new          RadioGroup.OnCheckedChangeListener() {         


                    @Override         


                    public          void          onCheckedChanged(RadioGroup group,           int          checkedId) {         


                    dismiss();         


                    }         


                    });         


                    }         


                    


                    //重点内容,重写dismiss();         


                    @Override         


                    public          void          dismiss() {         


                    if          (outCbx !=           null          && outCbx.isChecked()) {         


                    int          id = radioGroup.getCheckedRadioButtonId();         


                    RadioButton rbt = (RadioButton) radioGroup.findViewById(id);         


                    Integer old_tag = (Integer) outCbx.getTag();         


                    if          (old_tag ==           null          ) {         


                    super          .dismiss();         


                    return          ;         


                    }         


                    if          (old_tag != id) {         


                    outCbx.setTag(id);         


                    outCbx.setText(rbt.getText());         


                    }           else          {         


                    outCbx.setTag(-          1          );         


                    }         


                    //下面执行之后,会执行MainActivity中的onCheckedChanged里的否定分支         


                    outCbx.setChecked(          false          );         


                    }         


                    super          .dismiss();         


                    }         


          }


效果图:

Android checkbox自定义图标如何改变大小 安卓checkbox属性_android




其实重点在PopupWindow里面,MainActivity的CheckBox作为参数传递到了 PopupWindow里.首先,用户点击MainActivity的CheckBox,接着会执行isChecked分支,这样PopupWindow就展示给了用户,这样用户操作的环境就到了PopupWindow里面,等用户选择好筛选条件后,PopupWindow就把筛选条件设给outCbx,然后改变outCbx状态,从而触发MainActivity中onCheckedChanged中的否定分支,此时展示的是一个Toast,实际应用中可以是一个网络请求.同时,由于PopupWindow的代码并没有阻塞操作,所以会接着执行下一句 super.dismiss(),这样你在MainActivity就不用担心PopupWindow的关闭问题啦.最后,在MainActivity中还加入了try-catch来以防万一,这种机制真是太神奇啦.这种机制把筛选操作从Activity中分离了出来,以后我们写筛选可以完全独立于Activity啦,随后我会把其他筛选的情况开源,

CheckBox是继承自TextView,很多时候,我们的CheckBox的button属性设置的图片都不大,这就导致点击CheckBox的区域也小,因此,我们需要用到TouchDelegate来扩大CheckBox的可点击区域上代码:

public           class          FrameLayoutCheckBox           extends          FrameLayout {         


                    CompoundButton cbx;         


                    


                    public          FrameLayoutCheckBox(Context context) {         


                    super          (context);         


                    }         


                    


                    public          FrameLayoutCheckBox(Context context, AttributeSet attrs) {         


                    super          (context, attrs);         


                    }         


                    


                    public          FrameLayoutCheckBox(Context context, AttributeSet attrs,           int          defStyleAttr) {         


                    super          (context, attrs, defStyleAttr);         


                    }         


                    


                    private          CheckBox findCheckBox(View view) {         


                    //无递归广度优先遍历寻找CheckBox - -!我只是想重温一下C         


                    ArrayList<View> views =           new          ArrayList<>();         


                    views.add(view);         


                    while          (!views.isEmpty()) {         


                    View c = views.remove(          0          );         


                    if          (c           instanceof           CheckBox) {         


                    return          (CheckBox) c;         


                    }           else          if          (c           instanceof           ViewGroup) {         


                    ViewGroup fa = (ViewGroup) c;         


                    for          (          int           i =           0          ; i < fa.getChildCount(); i++) {         


                    views.add(fa.getChildAt(i));         


                    }         


                    }         


                    }         


                    return          null          ;         


                    }         


                    


                    @Override         


                    protected          void          onFinishInflate() {         


                    super          .onFinishInflate();         


                    if          (getChildCount() >           0          ) {         


                    View child = findCheckBox(          this          );         


                    if          (child           instanceof          CompoundButton) cbx = (CompoundButton) child;         


                    }         


                    }         


                    


                    @Override         


                    protected          void          onMeasure(          int          widthMeasureSpec,           int          heightMeasureSpec) {         


                    super          .onMeasure(widthMeasureSpec, heightMeasureSpec);         


                    if          (cbx !=           null          ) {         


                    Rect bounds =           new          Rect(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + getMeasuredWidth() + getPaddingRight(), getPaddingTop() + getMeasuredHeight() + getPaddingBottom());         


                    TouchDelegate delegate =           new          TouchDelegate(bounds, cbx);         


                    setTouchDelegate(delegate);         


                    }         


                    }         


          }


这个类可以当成FrameLayout,我们可以把CheckBox放里面,然后CheckBox的点击区域就是整个FrameLayout的区域啦.当然这个类也适用于RadioButton,但是你不能放多个CompoundButton在里面。


Android性能优化之谈谈SparseArray,SparseBooleanArray和SparseIntArray

相关内容转自http://www.lxway.com/42920216.htm

相信大家都明白,手机软件的开发不同于PC软件的开发,因为手机性能相对有限,内存也有限,所谓“寸土寸金”,可能稍有不慎,就会导致性能的明显降低。Android为了方便开发者,特意在android.util这个包中提供了几个提高效率的工具类,比如之前用过的LruCache类,这次我们来谈谈其他工具类,SparseArray,SparseBooleanArray和 SparseIntArray。


总体说,它们都是类似map这样key-value的存储方式,但是由于查找的算法不一样。因此效率也各不同。但要明白,没有说哪个一定是最好的。只有根据不同需求在不同场景去应用,才能获取较优的结果。


SparseArray


package android.util;

import com.android.internal.util.ArrayUtils;

/
 * SparseArrays 利用integer去管理object对象。不像一个正常的object对象数组,它能在索引数中快速的查找到所需的结果。(这
 * 句话是音译,原意是能在众多索引数中“撕开一个缺口”,为什么原文这么表达?下面会慢慢说清楚。)它比HashMap去通过Integer索引
 * 查找object对象时在内存上更具效率,不仅因为它避免了用来查找的自动“装箱”的keys,并且它的数据结构不依赖额外的对象去
 * 各个映射中查找匹配。
 * 
 * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
 * there can be gaps in the indices.  It is intended to be more memory efficient
 * than using a HashMap to map Integers to Objects, both because it avoids
 * auto-boxing keys and its data structure doesn't rely on an extra entry object
 * for each mapping.
 *
 * 请注意,这个容器会保持它的映射关系在一个数组的数据结构中,通过二分检索法驱查找key。(这里我们终于知道,为何这个工具类中,
 * 提供的添加映射关系的操作中,key的类型必须是integer。因为二分检索法,将从中间“切开”,integer的数据类型是实现这种检索过程的保证。)
 * 
 * 如果保存大量的数据,这种数据结构是不适合的,换言之,SparseArray这个工具类并不应该用于存储大量的数据。这种情况下,它的效率
 * 通常比传统的HashMap更低,因为它的查找方法并且增加和移除操作(任意一个操作)都需要在数组中插入和删除(两个步骤才能实现)。
 * 
 * 如果存储的数据在几百个以内,它们的性能差异并不明显,低于50%。
 * 
 * (OK,那么光看Android官方的介绍我们就有初步结论了,大量的数据我们相对SparseArray会优先选择HashMap,如果数据在几百个这个数目,
 *  那么选择它们任意一个去实现区别不大,如果数量较少,就选择SparseArray去实现。 其实如果我们理解了二分法,就很容易了SparseArray的
 *  实现原理,以及SparseArray和HashMap它们之间的区别了。)
 * 
 * <p>Note that this container keeps its mappings in an array data structure,
 * using a binary search to find keys.  The implementation is not intended to be appropriate for
 * data structures
 * that may contain large numbers of items.  It is generally slower than a traditional
 * HashMap, since lookups require a binary search and adds and removes require inserting
 * and deleting entries in the array.  For containers holding up to hundreds of items,
 * the performance difference is not significant, less than 50%.</p>
 *
 *	
 * 为了提高性能,这个容器包含了一个实现最优的方法:当移除keys后为了立刻使它的数组紧密,它会“遗留”已经被移除(标记了要删除)的条目(entry) 。
 * 所被标记的条目(entry)(还未被当作垃圾回收掉前)可以被相同的key复用,也会在垃圾回收机制当作所有要回收的条目的一员被回收,从而使存储的数组更紧密。
 * 
 * (我们下面看源码就会发现remove()方法其实是调用delete()方法的。印证了上面这句话所说的这种优化方法。
 * 因为这样,能在每次移除元素后一直保持数组的数据结构是紧密不松散的。)
 * 
 * 垃圾回收的机制会在这些情况执行:数组需要扩充,或者映射表的大小被恢复,或者条目值被重新检索后恢复的时候。
 *	
 * <p>To help with performance, the container includes an optimization when removing
 * keys: instead of compacting its array immediately, it leaves the removed entry marked
 * as deleted.  The entry can then be re-used for the same key, or compacted later in
 * a single garbage collection step of all removed entries.  This garbage collection will
 * need to be performed at any time the array needs to be grown or the the map size or
 * entry values are retrieved.</p>
 *
 * 当调用keyAt(int)去获取某个位置的key的键的值,或者调用valueAt(int)去获取某个位置的值时,可能是通过迭代容器中的元素
 * 去实现的。
 *
 * <p>It is possible to iterate over the items in this container using
 * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
 * <code>keyAt(int)</code> with ascending values of the index will return the
 * keys in ascending order, or the values corresponding to the keys in ascending
 * order in the case of <code>valueAt(int)<code>.</p>
 */
public class SparseArray<E> implements Cloneable {
	//...
}


这里总结下几个重要的点:

1,SparseArray的原理是二分检索法,也因此key的类型都是整型。

2,(HashMap和SparseArray比较)当存储大量数据(起码上千个)的时候,优先选择HashMap。如果只有几百个,用哪个区别不大。如果数量不多,优先选择SparseArray。

3,SparseArray有自己的垃圾回收机制。(当数量不是很多的时候,这个不必关心。)

接着将里面的主要方法列出来:


private int index = 1;
	private String value = "value";
	
	public void testSparseArray()
	{
		//创建一个SparseArray对象
		SparseArray<String> sparseArray = new SparseArray<String>();
		
		//向sparseArray存入元素value,key为index
		sparseArray.put(index, value);
		
		//这个方法本质也是利用put(key, value)去存入数据
		sparseArray.append(index, value);
		
		
		sparseArray.indexOfKey(index);
		//查找value所在的位置,如果不存在,则返回-1
		sparseArray.indexOfValue(value);
		
		
		
		//更新某个key的值
		sparseArray.setValueAt(index, value);
		
		
		
		//获取index所对应的值,没有则返回null
		sparseArray.get(index);
		//获取index所对应的值,没有则返回自定义的默认值"default-value"
		sparseArray.get(index,"default-value");
		
		
		
		//删除index对应的元素
		sparseArray.delete(index);
		//移除,本质也是调用delete(int)方法
		sparseArray.remove(index);
		
		
		
		//清空所有数据
		sparseArray.clear();
		
	}

SparseBooleanArray和SparseIntArray

SparseBooleanArray和SparseIntArray,其实看名字也知道,它们跟SparseArray极其类似,只是存储类型加以限制了。SparseBooleanArray只能存储boolean值,而SparseIntArray只能存储integer类型的值。它们也同样实现了Cloneable接口,可以直接调用clone方法,也同样是以二分法为依据。而其他的主要方法也是一样的。下面以SparseBooleanArray为简单例子写出主要的方法,从方法看出,两者和SparseArray的确是灰常类似的。SparseIntArray的代码就不再贴出来了,因为都一样的。我们在使用的过程中举一反三,会用一个,其他2个也就会用了呢。


public void testSparseBooleanArray()
	{
		
//		SparseBooleanArray sparseBooleanArray = new SparseBooleanArray();
		//这种创建方式可以设置容器的大小
		SparseBooleanArray sparseBooleanArray = new SparseBooleanArray(5);
		
		
		//存入数据,同样有两种方法
		sparseBooleanArray.put(int, boolean);
		
		sparseBooleanArray.append(int, boolean);
		
		//根据key获取对应的boolean值,没有则返回false
		sparseBooleanArray.get(key);
		//跟上面类似,valueIfKeyNotFound是自定义的假设不存在则返回的默认值
		sparseBooleanArray.get(key, valueIfKeyNotFound);
		
		//获取第5个位置的键值
		sparseBooleanArray.keyAt(5);
		//获取第5个元素的值
		sparseBooleanArray.valueAt(5);
		
		//删除某个key的元素
		sparseBooleanArray.delete(key);
		//清除所有
		sparseBooleanArray.clear();
		
	}