当前位置: 移动技术网 > IT编程>移动开发>Android > 详解Android Checkbox的使用方法

详解Android Checkbox的使用方法

2019年07月24日  | 移动技术网IT编程  | 我要评论

广州电台天韵网,毕铭鑫,苹果全集

0和1是计算机的基础,数理逻辑中0和1代表两种状态,真与假.0和1看似简单,其实变化无穷. 今天我就来聊聊android控件中拥有着0和1这种特性的魔力控件checkbox.

先来讲讲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中居然也会有被选中的.当然,我刚学android时候也遇到过这种情况,问题的关键就在于要用数据来控制视图的显示.因此在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:

public class mainactivity extends appcompatactivity {

  string[] filter_type_strs = {"音乐", "书籍", "电影"};
  checkbox cbx;
  private boolean lockstate=false;
  int current_filter_type=0;
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);
    cbx = (checkbox) findviewbyid(r.id.cbx);

    cbx.settext(filter_type_strs[current_filter_type]);
    cbx.setoncheckedchangelistener(new compoundbutton.oncheckedchangelistener() {
      @override
      public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) {
        if (lockstate) return;
        try {
          if (ischecked) {
            //此处传入了cbx做参数
            popupwindow pw = new filterlinepw(buttonview.getcontext(), cbx, filter_type_strs);
            pw.showasdropdown(cbx);
          } else {
            //此处的buttonview就是cbx
            integer pos = (integer) buttonview.gettag();
            if (pos == null || pos == -1) return;
            current_filter_type = pos;

            toast.maketext(mainactivity.this, "搜索"+filter_type_strs[current_filter_type], toast.length_short).show();
          }
        } catch (nullpointerexception e) {
          //以防万一
          lockstate = true;
          buttonview.setchecked(!ischecked);
          lockstate = false;
        }
      }
    });

  }
}

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();
  }
}

效果图:

简单解释一下:其实重点在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在里面。

以上就是本文的全部内容,希望对大家的学习有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网