Android之IphoneTreeView带组指示器的ExpandableListView

来源:互联网 发布:python to datetime 编辑:程序博客网 时间:2024/05/16 10:03

先看看效果图:

  


首先让我们看看封装得比较完善的IphoneTreeView:

[java] view plaincopy
  1. public class IphoneTreeView extends ExpandableListView implements  
  2.         OnScrollListener, OnGroupClickListener {  
  3.     public IphoneTreeView(Context context, AttributeSet attrs, int defStyle) {  
  4.         super(context, attrs, defStyle);  
  5.         registerListener();  
  6.     }  
  7.   
  8.     public IphoneTreeView(Context context, AttributeSet attrs) {  
  9.         super(context, attrs);  
  10.         registerListener();  
  11.     }  
  12.   
  13.     public IphoneTreeView(Context context) {  
  14.         super(context);  
  15.         registerListener();  
  16.     }  
  17.   
  18.     /** 
  19.      * Adapter 接口 . 列表必须实现此接口 . 
  20.      */  
  21.     public interface IphoneTreeHeaderAdapter {  
  22.         public static final int PINNED_HEADER_GONE = 0;  
  23.         public static final int PINNED_HEADER_VISIBLE = 1;  
  24.         public static final int PINNED_HEADER_PUSHED_UP = 2;  
  25.   
  26.         /** 
  27.          * 获取 Header 的状态 
  28.          *  
  29.          * @param groupPosition 
  30.          * @param childPosition 
  31.          * @return  
  32.          *         PINNED_HEADER_GONE,PINNED_HEADER_VISIBLE,PINNED_HEADER_PUSHED_UP 
  33.          *         其中之一 
  34.          */  
  35.         int getTreeHeaderState(int groupPosition, int childPosition);  
  36.   
  37.         /** 
  38.          * 配置 QQHeader, 让 QQHeader 知道显示的内容 
  39.          *  
  40.          * @param header 
  41.          * @param groupPosition 
  42.          * @param childPosition 
  43.          * @param alpha 
  44.          */  
  45.         void configureTreeHeader(View header, int groupPosition,  
  46.                 int childPosition, int alpha);  
  47.   
  48.         /** 
  49.          * 设置组按下的状态 
  50.          *  
  51.          * @param groupPosition 
  52.          * @param status 
  53.          */  
  54.         void onHeadViewClick(int groupPosition, int status);  
  55.   
  56.         /** 
  57.          * 获取组按下的状态 
  58.          *  
  59.          * @param groupPosition 
  60.          * @return 
  61.          */  
  62.         int getHeadViewClickStatus(int groupPosition);  
  63.   
  64.     }  
  65.   
  66.     private static final int MAX_ALPHA = 255;  
  67.   
  68.     private IphoneTreeHeaderAdapter mAdapter;  
  69.   
  70.     /** 
  71.      * 用于在列表头显示的 View,mHeaderViewVisible 为 true 才可见 
  72.      */  
  73.     private View mHeaderView;  
  74.   
  75.     /** 
  76.      * 列表头是否可见 
  77.      */  
  78.     private boolean mHeaderViewVisible;  
  79.   
  80.     private int mHeaderViewWidth;  
  81.   
  82.     private int mHeaderViewHeight;  
  83.   
  84.     public void setHeaderView(View view) {  
  85.         mHeaderView = view;  
  86.         AbsListView.LayoutParams lp = new AbsListView.LayoutParams(  
  87.                 ViewGroup.LayoutParams.MATCH_PARENT,  
  88.                 ViewGroup.LayoutParams.WRAP_CONTENT);  
  89.         view.setLayoutParams(lp);  
  90.   
  91.         if (mHeaderView != null) {  
  92.             setFadingEdgeLength(0);  
  93.         }  
  94.   
  95.         requestLayout();  
  96.     }  
  97.   
  98.     private void registerListener() {  
  99.         setOnScrollListener(this);  
  100.         setOnGroupClickListener(this);  
  101.     }  
  102.   
  103.     /** 
  104.      * 点击 HeaderView 触发的事件 
  105.      */  
  106.     private void headerViewClick() {  
  107.         long packedPosition = getExpandableListPosition(this  
  108.                 .getFirstVisiblePosition());  
  109.   
  110.         int groupPosition = ExpandableListView  
  111.                 .getPackedPositionGroup(packedPosition);  
  112.   
  113.         if (mAdapter.getHeadViewClickStatus(groupPosition) == 1) {  
  114.             this.collapseGroup(groupPosition);  
  115.             mAdapter.onHeadViewClick(groupPosition, 0);  
  116.         } else {  
  117.             this.expandGroup(groupPosition);  
  118.             mAdapter.onHeadViewClick(groupPosition, 1);  
  119.         }  
  120.   
  121.         this.setSelectedGroup(groupPosition);  
  122.     }  
  123.   
  124.     private float mDownX;  
  125.     private float mDownY;  
  126.   
  127.     /** 
  128.      * 如果 HeaderView 是可见的 , 此函数用于判断是否点击了 HeaderView, 并对做相应的处理 , 因为 HeaderView 
  129.      * 是画上去的 , 所以设置事件监听是无效的 , 只有自行控制 . 
  130.      */  
  131.     @Override  
  132.     public boolean onTouchEvent(MotionEvent ev) {  
  133.         if (mHeaderViewVisible) {  
  134.             switch (ev.getAction()) {  
  135.             case MotionEvent.ACTION_DOWN:  
  136.                 mDownX = ev.getX();  
  137.                 mDownY = ev.getY();  
  138.                 if (mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight) {  
  139.                     return true;  
  140.                 }  
  141.                 break;  
  142.             case MotionEvent.ACTION_UP:  
  143.                 float x = ev.getX();  
  144.                 float y = ev.getY();  
  145.                 float offsetX = Math.abs(x - mDownX);  
  146.                 float offsetY = Math.abs(y - mDownY);  
  147.                 // 如果 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触发 headerClick()  
  148.                 if (x <= mHeaderViewWidth && y <= mHeaderViewHeight  
  149.                         && offsetX <= mHeaderViewWidth  
  150.                         && offsetY <= mHeaderViewHeight) {  
  151.                     if (mHeaderView != null) {  
  152.                         headerViewClick();  
  153.                     }  
  154.   
  155.                     return true;  
  156.                 }  
  157.                 break;  
  158.             default:  
  159.                 break;  
  160.             }  
  161.         }  
  162.   
  163.         return super.onTouchEvent(ev);  
  164.   
  165.     }  
  166.   
  167.     @Override  
  168.     public void setAdapter(ExpandableListAdapter adapter) {  
  169.         super.setAdapter(adapter);  
  170.         mAdapter = (IphoneTreeHeaderAdapter) adapter;  
  171.     }  
  172.   
  173.     /** 
  174.      *  
  175.      * 点击了 Group 触发的事件 , 要根据根据当前点击 Group 的状态来 
  176.      */  
  177.     @Override  
  178.     public boolean onGroupClick(ExpandableListView parent, View v,  
  179.             int groupPosition, long id) {  
  180.         if (mAdapter.getHeadViewClickStatus(groupPosition) == 0) {  
  181.             mAdapter.onHeadViewClick(groupPosition, 1);  
  182.             parent.expandGroup(groupPosition);  
  183.             parent.setSelectedGroup(groupPosition);  
  184.   
  185.         } else if (mAdapter.getHeadViewClickStatus(groupPosition) == 1) {  
  186.             mAdapter.onHeadViewClick(groupPosition, 0);  
  187.             parent.collapseGroup(groupPosition);  
  188.         }  
  189.   
  190.         // 返回 true 才可以弹回第一行 , 不知道为什么  
  191.         return true;  
  192.   
  193.     }  
  194.   
  195.     @Override  
  196.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  197.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  198.         if (mHeaderView != null) {  
  199.             measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);  
  200.             mHeaderViewWidth = mHeaderView.getMeasuredWidth();  
  201.             mHeaderViewHeight = mHeaderView.getMeasuredHeight();  
  202.         }  
  203.     }  
  204.   
  205.     private int mOldState = -1;  
  206.   
  207.     @Override  
  208.     protected void onLayout(boolean changed, int left, int top, int right,  
  209.             int bottom) {  
  210.         super.onLayout(changed, left, top, right, bottom);  
  211.         final long flatPostion = getExpandableListPosition(getFirstVisiblePosition());  
  212.         final int groupPos = ExpandableListView  
  213.                 .getPackedPositionGroup(flatPostion);  
  214.         final int childPos = ExpandableListView  
  215.                 .getPackedPositionChild(flatPostion);  
  216.         int state = mAdapter.getTreeHeaderState(groupPos, childPos);  
  217.         if (mHeaderView != null && mAdapter != null && state != mOldState) {  
  218.             mOldState = state;  
  219.             mHeaderView.layout(00, mHeaderViewWidth, mHeaderViewHeight);  
  220.         }  
  221.   
  222.         configureHeaderView(groupPos, childPos);  
  223.     }  
  224.   
  225.     public void configureHeaderView(int groupPosition, int childPosition) {  
  226.         if (mHeaderView == null || mAdapter == null  
  227.                 || ((ExpandableListAdapter) mAdapter).getGroupCount() == 0) {  
  228.             return;  
  229.         }  
  230.   
  231.         int state = mAdapter.getTreeHeaderState(groupPosition, childPosition);  
  232.   
  233.         switch (state) {  
  234.         case IphoneTreeHeaderAdapter.PINNED_HEADER_GONE: {  
  235.             mHeaderViewVisible = false;  
  236.             break;  
  237.         }  
  238.   
  239.         case IphoneTreeHeaderAdapter.PINNED_HEADER_VISIBLE: {  
  240.             mAdapter.configureTreeHeader(mHeaderView, groupPosition,  
  241.                     childPosition, MAX_ALPHA);  
  242.   
  243.             if (mHeaderView.getTop() != 0) {  
  244.                 mHeaderView.layout(00, mHeaderViewWidth, mHeaderViewHeight);  
  245.             }  
  246.   
  247.             mHeaderViewVisible = true;  
  248.   
  249.             break;  
  250.         }  
  251.   
  252.         case IphoneTreeHeaderAdapter.PINNED_HEADER_PUSHED_UP: {  
  253.             View firstView = getChildAt(0);  
  254.             int bottom = firstView.getBottom();  
  255.   
  256.             // intitemHeight = firstView.getHeight();  
  257.             int headerHeight = mHeaderView.getHeight();  
  258.   
  259.             int y;  
  260.   
  261.             int alpha;  
  262.   
  263.             if (bottom < headerHeight) {  
  264.                 y = (bottom - headerHeight);  
  265.                 alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;  
  266.             } else {  
  267.                 y = 0;  
  268.                 alpha = MAX_ALPHA;  
  269.             }  
  270.   
  271.             mAdapter.configureTreeHeader(mHeaderView, groupPosition,  
  272.                     childPosition, alpha);  
  273.   
  274.             if (mHeaderView.getTop() != y) {  
  275.                 mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight  
  276.                         + y);  
  277.             }  
  278.   
  279.             mHeaderViewVisible = true;  
  280.             break;  
  281.         }  
  282.         }  
  283.     }  
  284.   
  285.     @Override  
  286.     /** 
  287.      * 列表界面更新时调用该方法(如滚动时) 
  288.      */  
  289.     protected void dispatchDraw(Canvas canvas) {  
  290.         super.dispatchDraw(canvas);  
  291.         if (mHeaderViewVisible) {  
  292.             // 分组栏是直接绘制到界面中,而不是加入到ViewGroup中  
  293.             drawChild(canvas, mHeaderView, getDrawingTime());  
  294.         }  
  295.     }  
  296.   
  297.     @Override  
  298.     public void onScroll(AbsListView view, int firstVisibleItem,  
  299.             int visibleItemCount, int totalItemCount) {  
  300.         final long flatPos = getExpandableListPosition(firstVisibleItem);  
  301.         int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos);  
  302.         int childPosition = ExpandableListView.getPackedPositionChild(flatPos);  
  303.   
  304.         configureHeaderView(groupPosition, childPosition);  
  305.     }  
  306.   
  307.     @Override  
  308.     public void onScrollStateChanged(AbsListView view, int scrollState) {  
  309.     }  
  310. }  


使用起来也是比较简单的,先在布局文件中声明activity_main.xml:

[html] view plaincopy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context=".MainActivity" >  
  6.   
  7.     <com.way.iphonetreeview.IphoneTreeView  
  8.         android:id="@+id/iphone_tree_view"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent"  
  11.         android:background="@android:color/transparent"  
  12.         android:cacheColorHint="@android:color/transparent"  
  13.         android:divider="@null"  
  14.         android:transcriptMode="normal" />  
  15.   
  16. </RelativeLayout>  

然后在MainActivity中调用,为了缩减代码,我把Adapter作为内部类放在MainActivity中了:

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.     private LayoutInflater mInflater;  
  3.     private IphoneTreeView iphoneTreeView;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         initView();  
  10.     }  
  11.   
  12.     private void initView() {  
  13.         // TODO Auto-generated method stub  
  14.         mInflater = LayoutInflater.from(this);  
  15.         iphoneTreeView = (IphoneTreeView) findViewById(R.id.iphone_tree_view);  
  16.         iphoneTreeView.setHeaderView(getLayoutInflater().inflate(  
  17.                 R.layout.list_head_view, iphoneTreeView, false));  
  18.         iphoneTreeView.setGroupIndicator(null);  
  19.         iphoneTreeView.setAdapter(new IphoneTreeViewAdapter());  
  20.     }  
  21.   
  22.     public class IphoneTreeViewAdapter extends BaseExpandableListAdapter  
  23.             implements IphoneTreeHeaderAdapter {  
  24.         // Sample data set. children[i] contains the children (String[]) for  
  25.         // groups[i].  
  26.         private HashMap<Integer, Integer> groupStatusMap;  
  27.         private String[] groups = { "第一组""第二组""第三组""第四组" };  
  28.         private String[][] children = {  
  29.                 { "Way""Arnold""Barry""Chuck""David""Afghanistan",  
  30.                         "Albania""Belgium""Lily""Jim""LiMing""Jodan" },  
  31.                 { "Ace""Bandit""Cha-Cha""Deuce""Bahamas""China",  
  32.                         "Dominica""Jim""LiMing""Jodan" },  
  33.                 { "Fluffy""Snuggles""Ecuador""Ecuador""Jim""LiMing",  
  34.                         "Jodan" },  
  35.                 { "Goldy""Bubbles""Iceland""Iran""Italy""Jim",  
  36.                         "LiMing""Jodan" } };  
  37.   
  38.         public IphoneTreeViewAdapter() {  
  39.             // TODO Auto-generated constructor stub  
  40.             groupStatusMap = new HashMap<Integer, Integer>();  
  41.         }  
  42.   
  43.         public Object getChild(int groupPosition, int childPosition) {  
  44.             return children[groupPosition][childPosition];  
  45.         }  
  46.   
  47.         public long getChildId(int groupPosition, int childPosition) {  
  48.             return childPosition;  
  49.         }  
  50.   
  51.         public int getChildrenCount(int groupPosition) {  
  52.             return children[groupPosition].length;  
  53.         }  
  54.   
  55.         public Object getGroup(int groupPosition) {  
  56.             return groups[groupPosition];  
  57.         }  
  58.   
  59.         public int getGroupCount() {  
  60.             return groups.length;  
  61.         }  
  62.   
  63.         public long getGroupId(int groupPosition) {  
  64.             return groupPosition;  
  65.         }  
  66.   
  67.         public boolean isChildSelectable(int groupPosition, int childPosition) {  
  68.             return true;  
  69.         }  
  70.   
  71.         public boolean hasStableIds() {  
  72.             return true;  
  73.         }  
  74.   
  75.         @Override  
  76.         public View getChildView(int groupPosition, int childPosition,  
  77.                 boolean isLastChild, View convertView, ViewGroup parent) {  
  78.             // TODO Auto-generated method stub  
  79.             if (convertView == null) {  
  80.                 convertView = mInflater.inflate(R.layout.list_item_view, null);  
  81.             }  
  82.             TextView tv = (TextView) convertView  
  83.                     .findViewById(R.id.contact_list_item_name);  
  84.             tv.setText(getChild(groupPosition, childPosition).toString());  
  85.             TextView state = (TextView) convertView  
  86.                     .findViewById(R.id.cpntact_list_item_state);  
  87.             state.setText("爱生活...爱Android...");  
  88.             return convertView;  
  89.         }  
  90.   
  91.         @Override  
  92.         public View getGroupView(int groupPosition, boolean isExpanded,  
  93.                 View convertView, ViewGroup parent) {  
  94.             // TODO Auto-generated method stub  
  95.             if (convertView == null) {  
  96.                 convertView = mInflater.inflate(R.layout.list_group_view, null);  
  97.             }  
  98.             TextView groupName = (TextView) convertView  
  99.                     .findViewById(R.id.group_name);  
  100.             groupName.setText(groups[groupPosition]);  
  101.   
  102.             ImageView indicator = (ImageView) convertView  
  103.                     .findViewById(R.id.group_indicator);  
  104.             TextView onlineNum = (TextView) convertView  
  105.                     .findViewById(R.id.online_count);  
  106.             onlineNum.setText(getChildrenCount(groupPosition) + "/"  
  107.                     + getChildrenCount(groupPosition));  
  108.             if (isExpanded) {  
  109.                 indicator.setImageResource(R.drawable.indicator_expanded);  
  110.             } else {  
  111.                 indicator.setImageResource(R.drawable.indicator_unexpanded);  
  112.             }  
  113.             return convertView;  
  114.         }  
  115.   
  116.         @Override  
  117.         public int getTreeHeaderState(int groupPosition, int childPosition) {  
  118.             final int childCount = getChildrenCount(groupPosition);  
  119.             if (childPosition == childCount - 1) {  
  120.                 return PINNED_HEADER_PUSHED_UP;  
  121.             } else if (childPosition == -1  
  122.                     && !iphoneTreeView.isGroupExpanded(groupPosition)) {  
  123.                 return PINNED_HEADER_GONE;  
  124.             } else {  
  125.                 return PINNED_HEADER_VISIBLE;  
  126.             }  
  127.         }  
  128.   
  129.         @Override  
  130.         public void configureTreeHeader(View header, int groupPosition,  
  131.                 int childPosition, int alpha) {  
  132.             // TODO Auto-generated method stub  
  133.             ((TextView) header.findViewById(R.id.group_name))  
  134.                     .setText(groups[groupPosition]);  
  135.             ((TextView) header.findViewById(R.id.online_count))  
  136.                     .setText(getChildrenCount(groupPosition) + "/"  
  137.                             + getChildrenCount(groupPosition));  
  138.         }  
  139.   
  140.         @Override  
  141.         public void onHeadViewClick(int groupPosition, int status) {  
  142.             // TODO Auto-generated method stub  
  143.             groupStatusMap.put(groupPosition, status);  
  144.         }  
  145.   
  146.         @Override  
  147.         public int getHeadViewClickStatus(int groupPosition) {  
  148.             if (groupStatusMap.containsKey(groupPosition)) {  
  149.                 return groupStatusMap.get(groupPosition);  
  150.             } else {  
  151.                 return 0;  
  152.             }  
  153.         }  
  154.   
  155.     }  
  156. }  

好了,简单的一个例子就完成了,

总结一下:

1. 原理: 在正在显示的最上面的组的标签位置添加一个和组视图完全一样的视图,作为组标签。这个标签的位置要随着列表的滑动不断变化,以保持总是显示在最上方,并且该消失的时候就消失。给这个标签添加点击事件,实现打开和关闭分组的功能。 

2. 组标签总是显示在上方,这是通过不断的调整其在布局中的位置来实现的。这个调整的过程,在初始化的时候,在 onLayout 方法中实现一次,后面都是在滚动过程中,根据对滚动状态的监听来实现的。

3. 实例化要添加的标签的时候(在外面实现,即使调用 setTreeHeaderView之前),parent 要设为该ExpandableListView. 

4. 要学习以及好好理解这个,最好的方法是将添加进来的组标签设为半透明,便于观察整个过程。

源码奉上:http://download.csdn.net/detail/weidi1989/5576879

0 0