详解AndroidCheckbox的使⽤⽅法
0和1是计算机的基础,数理逻辑中0和1代表两种状态,真与假.0和1看似简单,其实变化⽆穷. 今天我就来聊聊android控件中拥有着0和1这种特性的魔⼒控件checkbox.
先来讲讲Checkbox的基本使⽤.在XML中定义.
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="schemas.android/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) Tag();
if(isChecked){
item.setCheckState(true);
//do something
}else{
item.setCheckState(false);
//do something else
}
}
});
cbx.CheckState());
这种⽅法基本正确,但是我们要额外的给每个数据项⾥⾯添加⼀个字段来记录状态,这代价就有点⼤了.⼀是不必这么做,⼆是这会导致本地数据结构跟服务端结构不⼀致.通常,列表中使⽤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 =(Tag();
if(isChecked){
mCheckStates.put(pos,true);
//do something
}else{
mCheckStates.delete(pos);
//do something else
}
}
});
cbx.(position,false));
}
@Override
public int getItemCount() {
//...
}
}
这样列表就能正常显⽰了,⽽且在你选中CheckBox的时候,会⾃动触发onCheckedChanged来对mCheckStates来进⾏更新.此时,如果你想⽤程序来选中某个item的时候,那么直接这样就⾏了.
mCheckStates.put(pos,true);
如果我们想要取出列表列中所有的数据项,那么有了SparseBooleanArray,这个就⾮常好办啦.
ArrayList<Item> selItems=new ArrayList<>();
for(int i=0;i < mCheckStates.size();i++){
if(mCheckStates.valueAt(i)){
selItems.(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跟(pos)
⼀致的话,这会导致什么情况呢.
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int pos =(Tag();
if(isChecked){
mCheckStates.put(pos,true);
//do something
}else{
mCheckStates.delete(pos);
//do something elseandroid radiogroup
}
}
这就会让你的//do something做两次,这么做就是没有必要的啦,⽽且如果你的//do something是⽹络请求的话,这样就会导致更⼤问题.所以,我们有必要对这种情况做过滤.
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(lockState)return;
int pos =(Tag();
(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) {
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 Context(), cbx, filter_type_strs);
pw.showAsDropDown(cbx);
} else {
/
/此处的buttonView就是cbx
Integer pos = (Integer) Tag();
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, LayoutParams());
if (items[i].Text())) {
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 = CheckedRadioButtonId();
RadioButton rbt = (RadioButton) radioGroup.findViewById(id);
Integer old_tag = (Integer) Tag();
if (old_tag == null) {
super.dismiss();
return;
}
if (old_tag != id) {
outCbx.setTag(id);
outCbx.Text());
} 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 = ve(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.ChildAt(i));
}
}
}
return null;
}
@Override
protected void onFinishInflate() {
if (getChildCount() > 0) {
View child = findCheckBox(this);
if (child instanceof CompoundButton) cbx = (CompoundButton) child;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int 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的点击区域就是整个Fra
meLayout的区域啦.当然这个类也适⽤于RadioButton,但
是你不能放多个CompoundButton在⾥⾯。
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。