expandableListView 自定义样式以及scrollerView嵌套

来源:互联网 发布:夏一行 c语言 编辑:程序博客网 时间:2024/06/13 23:48

            最近写项目需要用到ExpandableListView,由于第一次使用,被它各种虐。网上也查看很多资料,但都没有实质的对它做个总结。本人不才,把自己项目中遇到的问题小记一下,分享给初学者。(部分是网上copy的,敬请谅解)

         ExpandableListView组件是android中一个比较常用的组件,当点击一个父item的时候可以将他的子item显示出来,像手机QQ中的好友列表就是实现的类型效果。

         1、Expandablelistview的基本使用:

      第一步:在main.xml中定义ExpandableListView(由于项目的需求要scrollerView嵌套ExpandableListView所以这里引用的事自定义的)

                    <com.demo.widget.ScrollDisabledExpandableListView
                                  android:id="@+id/yuncomputer_screen_id_expandablelist"
                                  android:layout_width="match_parent"
                                  android:layout_height="match_parent"
                                  android:layout_below="@id/yuncomputer_screen_id_tips"
                                  android:dividerHeight="1dp"
                                  android:childDivider="#e3e3e3"
                                  android:divider="#e3e3e3"
                                  android:listSelector="@color/no_color" />

                    注:如果要定义分割线的颜色,divider属性要直接写颜色值,要不然会不显示

         第二步:定义group.xml

 
                      <TextView android:id="@+id/group_id_name"
                                 android:layout_width="wrap_content"
                                 android:layout_height="wrap_content"
                                 android:layout_gravity="center_vertical"
                                 android:textColor="@color/black"
                                 android:textSize="17sp"
                                 android:paddingLeft="10dp"
                                 />

                     注:由于我要自定义箭头而且要更改箭头位置所以这里多了个imageView。

         第三步:定义chilid.xml

                      <TextView android:id="@+id/child_id_pkg_name"
                                   android:layout_width="wrap_content"
                                   android:layout_height="wrap_content"
                                   android:textColor="@color/black"
                                   android:textSize="17sp"
                                   />

          第四步:重头戏,适配BaseExpandableListAdapter

public class MyAdapter extends BaseExpandableListAdapter {

    List<String> groups;
    Map<String, List<String>> packages;
    Context context;
    
    public YunPackageAdapter(Context context,List<String> groups,Map<String, List<String>> packages){
        super();
        this.context = context;
        this.groups = groups;
        this.packages = packages;
    }
    
    /**
     * 获取父item的个数
     */
    @Override
    public int getGroupCount() {
        // TODO Auto-generated method stub
        return groups.size();
    }

    /**
     * 获取当前父item下的子item的个数
     */
    @Override
    public int getChildrenCount(int groupPosition) {
        // TODO Auto-generated method stub
        String strKey = groups.get(groupPosition);
        return packages.get(strKey).size();
    }

    /**
     * 获取当前父item的数据
     */
    @Override
    public Object getGroup(int groupPosition) {
        // TODO Auto-generated method stub
        return groups.get(groupPosition);
    }

    /**
     * 得到子item需要关联的数据
     */
    @Override
    public Object getChild(int groupPosition, int childPosition) {
        // TODO Auto-generated method stub
        String strKey = groups.get(groupPosition);
        return packages.get(strKey).get(childPosition);
    }
    
    /**
     * 获取父item的ID
     */
    @Override
    public long getGroupId(int groupPosition) {
        // TODO Auto-generated method stub
        return groupPosition;
    }

    /**
     * 获取子item的ID
     */
    @Override
    public long getChildId(int groupPosition, int childPosition) {
        // TODO Auto-generated method stub
        return childPosition;
    }

    @Override
    public boolean hasStableIds() {
        // TODO Auto-generated method stub
        return true;
    }

    /**
     * 设置父item组件(这里是自定义的箭头)
     */
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
            View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        convertView = LayoutInflater.from(context).inflate(R.layout.group, null);
        ImageView imgIndicator = (ImageView) convertView.findViewById(R.id.group_id_indicator);
        TextView tvName = (TextView) convertView.findViewById(R.id.group_id_name);
        tvName.setText(groups.get(groupPosition));
        //key值必须为资源ID
        convertView.setTag(R.layout.item_yuncomputer_group, groupPosition);
        convertView.setTag(R.layout.item_yuncomputer_child,-1);//设置-1表示长按时点击的是父项。
        return convertView;
    }


    /**
     * 设置item的组件
     */
    @Override
    public View getChildView(int groupPosition, int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        convertView = LayoutInflater.from(context).inflate(R.layout.child, null);
        TextView tvPackageName = (TextView) convertView.findViewById(R.id.child_id_name);

        convertView.setTag(R.layout.item_yuncomputer_child,childPosition);

        convertView.setTag(R.layout.item_yuncomputer_group, groupPosition);
        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        // TODO Auto-generated method stub
        return true;
    }
    
}

2、自定义groupIndicator

      a、直接给groupIndicator自定义图片,为了图片不变形,要.9图片。缺点:只能通过.9图片大致调整箭头的位置,indicatorLeft属性对于某些终端不起作用,具体为啥我也不清楚。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/list_item_expand" android:state_expanded="true"/>
<item android:drawable="@drawable/list_item_collapse"></item>
</selector>
===================================================
<ExpandableListView
  …
        android:groupIndicator="@drawable/list_expand"
    …
/>

      b、网上零零碎碎很多方法,我认为的最简单有效的是:

在group中自定义箭头,这样样式、位置就能精准配置了:首先

在group的xml中加入:

                   <ImageView android:id="@+id/group_id_indicator"
                                  android:layout_width="wrap_content"
                                  android:layout_height="wrap_content"
                                  android:layout_gravity="center_vertical"
                                  android:scaleType="fitCenter"
                                  android:src="@drawable/yuncomputer_close"
                                 />

再者:android:groupIndicator="@null"必须设置为null要不然会出现重叠的箭头而且自定义的无效;

然后在adapter中的getGroupView方法中加入:

if(isExpanded){
            imgIndicator.setImageResource(R.drawable.open);
        }else{
            imgIndicator.setImageResource(R.drawable.close);
        }

大功告成,啦啦啦啦......

3、对ExpandableListView的item点击事件是OnChildClickListener:  onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)  同时控制groupPosition和childPosition才能精确控制点击的是哪一个item。

4、对ExpandableListView的item的长按事件:(此处为转载:转载地址:http://blog.csdn.net/t5721654/article/details/6857357)

关于ExpandableListView长按事件处理,网上很多都是使用将上下文菜单注册到ExpandableListView上实现长按事件。

  1.       /** 
  2.  * 长按邮箱快捷选项 
  3.  * @author King 
  4.  */  
  5. private class QuickWayListener implements OnItemLongClickListener{  
  6.     @Override  
  7.     public boolean onItemLongClick(AdapterView<?> arg0, View view,  
  8.             int pos, long id) {  
  9.         int groupPos = (Integer)view.getTag(R.id.xxx01); //参数值是在setTag时使用的对应资源id号  
  10.         int childPos = (Integer)view.getTag(R.id.xxx02);  
  11.         if(childPos == -1){//长按的是父项  
  12.             //根据groupPos判断你长按的是哪个父项,做相应处理(弹框等)  
  13.         } else {  
  14.             //根据groupPos及childPos判断你长按的是哪个父项下的哪个子项,然后做相应处理。   
  15.         }  
  16.         return false;  
  17.     }  
  18. }
这样做弊端显而易见,不够灵活,不能分别对父项、子项、父项之间、子项之间弹出内容做区分。

下面来说我的解决方法,方法有点投机取巧。首先说明一点,使用我这种方法必须使用自定义的BaseExpandableListAdapter,至于为什么,具体后面讲到。

ExpandableListView本身有继承自AdapterView的setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener)方法。

实现监听器:

  1. /** 
  2.      * 长按邮箱快捷选项 
  3.      * @author King 
  4.      */  
  5.     private class QuickWayListener implements OnItemLongClickListener{  
  6.         @Override  
  7.         public boolean onItemLongClick(AdapterView<?> arg0, View view,  
  8.                 int pos, long id) {  
  9.             //pos不可用说明见下文  
  10.             return false;  
  11.         }  
  12.     } 

如果这个方法是用在ListView长按事件中刚刚好,但在ExpandableListView中,第三个参数pos不能区分开点击的是父项还是子项,以及哪个父项或子项。

在ExpandableListView响应的onItemLongCkick方法中,pos参数值为:从上到下,父项+展现的子项到点击位置的数目(注意:是展现的,隐藏的子项不包括,从0开始)。

例如:

父项1(隐藏3个子项)

父项2

 |—子项2-0

 |—子项2-1

 |—子项2-2

长按子项2-1时,pos值为3。显然根据pos值是无法确定点击的是哪个子项或父项的。

因此依赖pos是很难处理点击位置的。

如果可以直接在onItemLongClick方法中获取groupPos,及childPos该多好呢?

于是看到了onItemLongClick方法第二个参数:view。这里的view是你按中的位置对应的view。view有个方法getTag(int key)。如果在创建此view的时候就把groupPos,childPos通过setTag(int key, Object value)设置进去,在响应onItemLongClick不就可以直接拿出来用了么。

现在就要讲到必须使用自定义的BaseExpandableListAdapter的理由了。

要把groupPos,childPos通过setTag的方式绑定到view中,就必须操作该view的创建过程。要控制这个过程就必须要在自定义BaseExpandableListAdapter中重写getGroupView及getChildView方法进行操作。如下:

  1. public class AccountListAdapter extends BaseExpandableListAdapter {  
  2.   
  3.     ...省略其他方法  
  4.   
  5.     @Override  
  6.     public View getChildView(int groupPosition, int childPosition,  
  7.             boolean isLastChild, View convertView, ViewGroup parent) {  
  8.         //我这里仅通过自己写的mkChildView()方法创建TextView来显示文字,更复杂的可以通过LayoutInflater来填充一个view  
  9.         TextView childTv = mkChildView();  
  10.         // 标记位置  
  11.         // 必须使用资源Id当key(不是资源id会出现运行时异常),android本意应该是想用tag来保存资源id对应组件。  
  12.         // 将groupPosition,childPosition通过setTag保存,在onItemLongClick方法中就可以通过view参数直接拿到了!  
  13.                 childTv.setTag(R.id.xxx01, groupPosition);  
  14.         childTv.setTag(R.id.xxx02, childPosition);  
  15.         return childTv;  
  16.     }  
  17.   
  18.     @Override  
  19.     public View getGroupView(int groupPosition, boolean isExpanded,  
  20.             View convertView, ViewGroup parent) {  
  21.         TextView groupTv = mkGroupView();  
  22.         // 设置同getChildView一样  
  23.         groupTv.setTag(R.id.xxx01, groupPosition);  
  24.         groupTv.setTag(R.id.xxx02, -1); //设置-1表示长按时点击的是父项,到时好判断。  
  25.         groupTv.setText(groups[groupPosition]);  
  26.         return groupTv;  
  27.     }  

完成了这一步,我们只需要在ExpandableListView响应的onItemLongClick方法时通过view.getTag(R.id.xxx01),view.getTag(R.id.xxx02)即可拿到groupPos,childPos.
  1.       /** 
  2.  * 长按邮箱快捷选项 
  3.  * @author King 
  4.  */  
  5. private class QuickWayListener implements OnItemLongClickListener{  
  6.     @Override  
  7.     public boolean onItemLongClick(AdapterView<?> arg0, View view,  
  8.             int pos, long id) {  
  9.         int groupPos = (Integer)view.getTag(R.id.xxx01); //参数值是在setTag时使用的对应资源id号  
  10.         int childPos = (Integer)view.getTag(R.id.xxx02);  
  11.         if(childPos == -1){//长按的是父项  
  12.             //根据groupPos判断你长按的是哪个父项,做相应处理(弹框等)  
  13.         } else {  
  14.             //根据groupPos及childPos判断你长按的是哪个父项下的哪个子项,然后做相应处理。   
  15.         }  
  16.         return false;  
  17.     }  
  18. }
到这就写完了,貌似比较啰嗦。重写BaseExpandableListAdapter写的比较简洁,没看明白的朋友可以先到网上查下怎么自定义BaseExpandableListAdapter,和自定义BaseAdapter其实是一样的。

这个部分为转载,亲测实在是好用得不得了(偷笑)。

5、scrollerView嵌套ExpandableListView:

a、重写ExpandableListView

    public class CustomExpandableListView extends ExpandableListView {  
      
        public CustomExpandableListView(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            // TODO Auto-generated constructor stub  
        }  
      
        @Override  
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
            // TODO Auto-generated method stub  
            int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,  
      
            MeasureSpec.AT_MOST);  
      
            super.onMeasure(widthMeasureSpec, expandSpec);  
        }  
    } 
b、动态计算expandablelistview的高度,xxx_group.xml和xxx_child.xml的最外层要用linearlayout,反正relativelayout不行,不知道为什么

private void setListViewHeight(ExpandableListView listView) {  
            ListAdapter listAdapter = listView.getAdapter();  
            int totalHeight = 0;  
            int count = listAdapter.getCount();  
            for (int i = 0; i < listAdapter.getCount(); i++) {  
                View listItem = listAdapter.getView(i, null, listView);  
                listItem.measure(0, 0);  
                totalHeight += listItem.getMeasuredHeight();  
            }  
      
            ViewGroup.LayoutParams params = listView.getLayoutParams();  
            params.height = totalHeight  
                    + (listView.getDividerHeight() * (listAdapter.getCount() - 1));  
            listView.setLayoutParams(params);  
            listView.requestLayout();  
        } 
c、较为复杂

public class ViewGroupForListView extends LinearLayout implements View.OnClickListener {  
          
        private ListAdapter mAdapter = null;  
        private OnItemClickListener mListener = null;  
          
        public ViewGroupForListView(Context context) {  
            super(context);  
        }  
      
        public ViewGroupForListView(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            // TODO Auto-generated constructor stub  
            this.setOrientation(VERTICAL);  
        }  
          
        /**
         * 绑定数据
         */  
        protected void bindData() {  
            int count = mAdapter.getCount();  
            for(int i = 0; i < count; i++) {  
                View v = mAdapter.getView(i, null, null);  
                v.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));  
                v.setOnClickListener(this);  
                v.setId(i);  
                addView(v, i);  
            }  
        }  
          
        /**
         * 设置adapter
         * @param adapter
         */  
        public void setAdapter(ListAdapter adapter) {  
            mAdapter = adapter;  
            if(this.getChildCount() != 0) {  
                removeAllViews();  
            }  
            bindData();  
        }  
          
        /**
         * 获取adapter
         * @return
         */  
        public ListAdapter getAdapter() {  
            return mAdapter;  
        }  
          
        /**
         * 绑定监听
         * @param listener
         */  
        public void setOnItemClickListener(OnItemClickListener listener) {  
            this.mListener = listener;  
        }  
          
        @Override  
        public void onClick(View v) {  
            // TODO Auto-generated method stub  
            if(mListener != null) {  
                mListener.onItemClick(v.getId(), mAdapter);  
            }  
        }  
          
        /**
         * 监听接口
         * @author Visual
         *
         */  
        public interface OnItemClickListener {  
            public void onItemClick(int position, ListAdapter adapter);  
        } 


          

1 0