支持多项选择的ExpandableListView

目标(需求):

1. 创建一个可展开可收缩的列表;

2. 其列表项包含多个checkable的部件,当选择某一行时,该行包含的checkable的部件需要作出相应的变化;

3. 可以选择多个列表项,并且这些列表项可被读出

结果图:

可选择多项的ExpandableListView


实现:

1. 创建主layout用于规划列表显示。对于具体的列表项,为了实现的方便我们也创建一个layout文件。

<?xml version="1.0" encoding="utf-8"?>
<com.home.mymultichecklistview.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
 <com.home.mymultichecklistview.CheckableTextView 
     android:id="@+id/item"
     android:layout_width="match_parent"
	 android:layout_height="wrap_content"
	 android:layout_marginTop="6dip"
	 style="@style/text"
	 android:layout_weight="1"
 />
    
 <com.home.mymultichecklistview.InertCheckBox
     android:id="@+id/checkbox"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_marginRight="5dp"
     android:maxWidth="40dp"  
     android:maxHeight="40dp"  
     android:focusable="false" 
	 android:layout_gravity="right"
     android:button="@drawable/checkbox"
 /> 
 
 
</com.home.mymultichecklistview.CheckableLinearLayout>

2. 类似ListView,ExpandableListView也是通过Adapter来管理其包含的各种元素和操作,这里我们创建一个扩展自BaseExpandableListAdapter的Adapter。与ListView不同的是,ExpandableListAdapter要渲染实现两级View(Group级和列表项级)的操作。它通过getGroupView()渲染Group项,通过getChildView()渲染列表子项。

	@Override
	public View getGroupView(int groupPosition, boolean isExpanded,
			View convertView, ViewGroup parent) {
		View groupView = convertView;
		if (groupView == null) {
			groupView = new TextView(context);
			((TextView)groupView).setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
			groupView.setPadding(50,0,0,0);
		}
		((TextView)groupView).setText(groupData[groupPosition]);
		((TextView)groupView).setTextColor(context.getResources().getColor(R.color.fgcolor));
		
		return groupView;
	}

    @Override
    public View getChildView(final int groupPosition, final int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {
        View itemView = convertView;
        final ViewHolder vh;
        if (itemView == null) {
            LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            itemView = inflater.inflate(R.layout.item_view, null);
            
            vh = new ViewHolder();
            vh.layout = (CheckableLinearLayout)itemView.findViewById(R.id.layout);
            vh.item = (TextView)itemView.findViewById(R.id.item);
            itemView.setTag(vh);
        } else {
            vh = (ViewHolder)itemView.getTag();
        }
        vh.item.setText(itemData[groupPosition][childPosition]);
        final ExpandableListView listView = ((ExpandableListView)((MainActivity)context).findViewById(R.id.list));
        final int position = listView.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
        listView.setItemChecked(position, checkedState[groupPosition][childPosition]);
        vh.layout.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                ((CheckableLinearLayout)v).toggle();
                checkedState[groupPosition][childPosition] = !checkedState[groupPosition][childPosition]; 
                listView.setItemChecked(position, ((CheckableLinearLayout)v).isChecked());
            }
        });
        return itemView;
    } 

3. 为每一列表子项容器创建OnClickListener监听鼠标的点击事件。在这里要注意,由于列表子项包含了CheckBox,所以为了使点击事件不要被CheckBox捕获,我们需要创建一个扩展自CheckBox的类来屏蔽鼠标和键盘事件。同时,需要在这个容器里搜索其包含的checkable的部件并将check操作传给这些部件。

Adapter中的方法getChildView()需要实现鼠标点击监听器:

	public View getChildView(final int groupPosition, final int childPosition,
			boolean isLastChild, View convertView, ViewGroup parent) {
	View itemView = convertView;
	final ViewHolder vh;
...

        final int position = listView.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
        listView.setItemChecked(position, checkedState[groupPosition][childPosition]);
	vh.layout.setOnClickListener(new OnClickListener() {

	@Override
	public void onClick(View v) {
 	   ((CheckableLinearLayout)v).toggle();
	    checkedState[groupPosition][childPosition] = !checkedState[groupPosition][childPosition]; 
	     listView.setItemChecked(position, ((CheckableLinearLayout)v).isChecked());
	}
  });
  return itemView;
}


扩展自CheckBox的InertCheckBox需要屏蔽键盘和鼠标事件


public class InertCheckBox extends CheckBox {
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
   //直接返回false
    return false;
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
   //直接返回false
	return false;
}

    @Override
    public boolean onTouchEvent(MotionEvent event) {
   //直接返回false
   return false;
   }

     @Override
     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   //直接返回false
   return false;
   ...
}

列表项容器需要实现Checkable接口并且把check操作传递给其checkable的子部件


public class CheckableLinearLayout extends LinearLayout implements Checkable {
...
    @Override
    public void setChecked(boolean checked) {
    this.isChecked = checked;
    for (Checkable view : checkableViews) {
      view.setChecked(checked);
     }
    }
	
   @Override
   public boolean isChecked() {
      return this.isChecked;
   }

   @Override
   public void toggle() {
      isChecked = !isChecked;
      for (Checkable view : checkableViews)       {
	view.toggle();
      }
    }

    @Override
    protected void onFinishInflate() {
 	super.onFinishInflate();
		
        for (int i=0; i<this.getChildCount(); i++) {
	findCheckableChild(this.getChildAt(i));
    }
 }

    private void findCheckableChild(View child) {
	if (child instanceof Checkable) {
		checkableViews.add((Checkable)child);
	}
		
	if (child instanceof ViewGroup) {
    	   for (int i=0; i<((ViewGroup)child).getChildCount(); i++) {
		findCheckableChild(((ViewGroup) child).getChildAt(i));
	    }
	}
    }
...
}

开发中遇到的问题:

1. 渲染后的child view类似于放在一个cache中,下一次再通过convertView取时,由于Group的收缩或扩展操作会隐藏/显示一些child view,导致某一child View的flat position发生变化,获取到的convertView不是原来希望获取的view。所以,每次获取到view后都需要对其内容重新设置(比如设置正确文本,设置监听器等)

2. check的状态设置很tricky。我开始认为直接在监听器中调用容器的toggle()方法即可。结果发现一旦某个group做了expand或collapse操作后,所有列表项的check状态全没了。后来发现原来group做了expand/collapse操作后,ListView会对其所有子项重新设置check状态,而check状态的值是存在ListView的一个SparseBooleanArray表里(mCheckStates)。由于没有对这个表进行设置,所以一刷新check状态就全丢了。并且由于这个表的key是基于拉平后所有可见的列表项的位置定的,当group扩展或收缩后,同一个列表项,它拉平后的位置还会变化。所以,为了解决这个问题,我在adapter里增加了一个二维表用于记录每一列表项的check状态。在执行 listView的setItemChecked函数时,其check状态是从这个自己创建的表中读出的,不能通过ListView的mCheckStates来读。这个我认为是ExpandableListView的一个缺陷。

遗留的已知问题:

我使用了@drawable/checkbox 来定义checkbox check 和uncheck时的图片,但当checkbox被check上时,这个checked的图片没有生效。不知道为什么,还需要进一步debug.


源程序:

https://github.com/swingseagull/Multi-check-in-expandablelistview

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值