zoukankan      html  css  js  c++  java
  • Anroid ListView分组和悬浮Header实现

     

    Anroid ListView分组和悬浮Header实现

    分类: Android
     

    目录(?)[+]

     

    之前在使用iOS时,看到过一种分组的View,每一组都有一个Header,在上下滑动的时候,会有一个悬浮的Header,这种体验觉得很不错,请看下图:

    上图中标红的1,2,3,4四张图中,当向上滑动时,仔细观察灰色条的Header变化,当第二组向上滑动时,会把第一组的悬浮Header挤上去。

    这种效果在Android是没有的,iOS的SDK就自带这种效果。这篇文章就介绍如何在Android实现这种效果。

    1、悬浮Header的实现

    其实Android自带的联系人的App中就有这样的效果,我也是把他的类直接拿过来的,实现了PinnedHeaderListView这么一个类,扩展于ListView,核心原理就是在ListView的最顶部绘制一个调用者设置的Header View,在滑动的时候,根据一些状态来决定是否向上或向下移动Header View(其实就是调用其layout方法,理论上在绘制那里作一些平移也是可以的)。下面说一下具体的实现:
    1.1、PinnedHeaderAdapter接口
    这个接口需要ListView的Adapter来实现,它定义了两个方法,一个是让Adapter告诉ListView当前指定的position的数据的状态,比如指定position的数据可能是组的header;另一个方法就是设置Header View,比如设置Header View的文本,图片等,这个方法是由调用者去实现的。
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /** 
    2.  * Adapter interface.  The list adapter must implement this interface. 
    3.  */  
    4. public interface PinnedHeaderAdapter {  
    5.   
    6.     /** 
    7.      * Pinned header state: don't show the header. 
    8.      */  
    9.     public static final int PINNED_HEADER_GONE = 0;  
    10.   
    11.     /** 
    12.      * Pinned header state: show the header at the top of the list. 
    13.      */  
    14.     public static final int PINNED_HEADER_VISIBLE = 1;  
    15.   
    16.     /** 
    17.      * Pinned header state: show the header. If the header extends beyond 
    18.      * the bottom of the first shown element, push it up and clip. 
    19.      */  
    20.     public static final int PINNED_HEADER_PUSHED_UP = 2;  
    21.   
    22.     /** 
    23.      * Computes the desired state of the pinned header for the given 
    24.      * position of the first visible list item. Allowed return values are 
    25.      * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or 
    26.      * {@link #PINNED_HEADER_PUSHED_UP}. 
    27.      */  
    28.     int getPinnedHeaderState(int position);  
    29.   
    30.     /** 
    31.      * Configures the pinned header view to match the first visible list item. 
    32.      * 
    33.      * @param header pinned header view. 
    34.      * @param position position of the first visible list item. 
    35.      * @param alpha fading of the header view, between 0 and 255. 
    36.      */  
    37.     void configurePinnedHeader(View header, int position, int alpha);  
    38. }  
    1.2、如何绘制Header View
    这是在dispatchDraw方法中绘制的:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Override  
    2. protected void dispatchDraw(Canvas canvas) {  
    3.     super.dispatchDraw(canvas);  
    4.     if (mHeaderViewVisible) {  
    5.         drawChild(canvas, mHeaderView, getDrawingTime());  
    6.     }  
    7. }  
    1.3、配置Header View
    核心就是根据不同的状态值来控制Header View的状态,比如PINNED_HEADER_GONE(隐藏)的情况,可能需要设置一个flag标记,不绘制Header View,那么就达到隐藏的效果。当PINNED_HEADER_PUSHED_UP状态时,可能需要根据不同的位移来计算Header View的移动位移。下面是具体的实现:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public void configureHeaderView(int position) {  
    2.     if (mHeaderView == null || null == mAdapter) {  
    3.         return;  
    4.     }  
    5.       
    6.     int state = mAdapter.getPinnedHeaderState(position);  
    7.     switch (state) {  
    8.         case PinnedHeaderAdapter.PINNED_HEADER_GONE: {  
    9.             mHeaderViewVisible = false;  
    10.             break;  
    11.         }  
    12.   
    13.         case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {  
    14.             mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA);  
    15.             if (mHeaderView.getTop() != 0) {  
    16.                 mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);  
    17.             }  
    18.             mHeaderViewVisible = true;  
    19.             break;  
    20.         }  
    21.   
    22.         case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {  
    23.             View firstView = getChildAt(0);  
    24.             int bottom = firstView.getBottom();  
    25.               int itemHeight = firstView.getHeight();  
    26.             int headerHeight = mHeaderView.getHeight();  
    27.             int y;  
    28.             int alpha;  
    29.             if (bottom < headerHeight) {  
    30.                 y = (bottom - headerHeight);  
    31.                 alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;  
    32.             } else {  
    33.                 y = 0;  
    34.                 alpha = MAX_ALPHA;  
    35.             }  
    36.             mAdapter.configurePinnedHeader(mHeaderView, position, alpha);  
    37.             if (mHeaderView.getTop() != y) {  
    38.                 mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);  
    39.             }  
    40.             mHeaderViewVisible = true;  
    41.             break;  
    42.         }  
    43.     }  
    44. }  
    1.4、onLayout和onMeasure
    在这两个方法中,控制Header View的位置及大小
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Override  
    2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    3.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    4.     if (mHeaderView != null) {  
    5.         measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);  
    6.         mHeaderViewWidth = mHeaderView.getMeasuredWidth();  
    7.         mHeaderViewHeight = mHeaderView.getMeasuredHeight();  
    8.     }  
    9. }  
    10.   
    11. @Override  
    12. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
    13.     super.onLayout(changed, left, top, right, bottom);  
    14.     if (mHeaderView != null) {  
    15.         mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);  
    16.         configureHeaderView(getFirstVisiblePosition());  
    17.     }  
    18. }  
    好了,到这里,悬浮Header View就完了,各位可能看不到完整的代码,只要明白这几个核心的方法,自己写出来,也差不多了。

    2、ListView Section实现

    有两种方法实现ListView Section效果,请参考http://cyrilmottier.com/2011/07/05/listview-tips-tricks-2-section-your-listview/
    方法一:
    每一个ItemView中包含Header,通过数据来控制其显示或隐藏,实现原理如下图:
     
    优点:
    1,实现简单,在Adapter.getView的实现中,只需要根据数据来判断是否是header,不是的话,隐藏Item view中的header部分,否则显示。
    2,Adapter.getItem(int n)始终返回的数据是在数据列表中对应的第n个数据,这样容易理解。
    3,控制header的点击事件更加容易
    缺点:
    1、使用更多的内存,第一个Item view中都包含一个header view,这样会费更多的内存,多数时候都可能header都是隐藏的。
     
    方法二:
    使用不同类型的View:重写getItemViewType(int)和getViewTypeCount()方法。
     
    优点:
    1,允许多个不同类型的item
    2,理解更加简单
    缺点:
    1,实现比较复杂
    2,得到指定位置的数据变得复杂一些
     
    到这里,我的实现方式是选择第二种方案,尽管它的实现方式要复杂一些,但优点比较明显。
     

    3、Adapter的实现

    这里主要就是说一下getPinnedHeaderState和configurePinnedHeader这两个方法的实现
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private class ListViewAdapter extends BaseAdapter implements PinnedHeaderAdapter {  
    2.       
    3.     private ArrayList<Contact> mDatas;  
    4.     private static final int TYPE_CATEGORY_ITEM = 0;    
    5.     private static final int TYPE_ITEM = 1;    
    6.       
    7.     public ListViewAdapter(ArrayList<Contact> datas) {  
    8.         mDatas = datas;  
    9.     }  
    10.       
    11.     @Override  
    12.     public boolean areAllItemsEnabled() {  
    13.         return false;  
    14.     }  
    15.       
    16.     @Override  
    17.     public boolean isEnabled(int position) {  
    18.         // 异常情况处理    
    19.         if (null == mDatas || position <  0|| position > getCount()) {  
    20.             return true;  
    21.         }   
    22.           
    23.         Contact item = mDatas.get(position);  
    24.         if (item.isSection) {  
    25.             return false;  
    26.         }  
    27.           
    28.         return true;  
    29.     }  
    30.       
    31.     @Override  
    32.     public int getCount() {  
    33.         return mDatas.size();  
    34.     }  
    35.       
    36.     @Override  
    37.     public int getItemViewType(int position) {  
    38.         // 异常情况处理    
    39.         if (null == mDatas || position <  0|| position > getCount()) {  
    40.             return TYPE_ITEM;  
    41.         }   
    42.           
    43.         Contact item = mDatas.get(position);  
    44.         if (item.isSection) {  
    45.             return TYPE_CATEGORY_ITEM;  
    46.         }  
    47.           
    48.         return TYPE_ITEM;  
    49.     }  
    50.   
    51.     @Override  
    52.     public int getViewTypeCount() {  
    53.         return 2;  
    54.     }  
    55.   
    56.     @Override  
    57.     public Object getItem(int position) {  
    58.         return (position >= 0 && position < mDatas.size()) ? mDatas.get(position) : 0;  
    59.     }  
    60.   
    61.     @Override  
    62.     public long getItemId(int position) {  
    63.         return 0;  
    64.     }  
    65.   
    66.     @Override  
    67.     public View getView(int position, View convertView, ViewGroup parent) {  
    68.         int itemViewType = getItemViewType(position);  
    69.         Contact data = (Contact) getItem(position);  
    70.         TextView itemView;  
    71.           
    72.         switch (itemViewType) {  
    73.         case TYPE_ITEM:  
    74.             if (null == convertView) {  
    75.                 itemView = new TextView(SectionListView.this);  
    76.                 itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
    77.                         mItemHeight));  
    78.                 itemView.setTextSize(16);  
    79.                 itemView.setPadding(10, 0, 0, 0);  
    80.                 itemView.setGravity(Gravity.CENTER_VERTICAL);  
    81.                 //itemView.setBackgroundColor(Color.argb(255, 20, 20, 20));  
    82.                 convertView = itemView;  
    83.             }  
    84.               
    85.             itemView = (TextView) convertView;  
    86.             itemView.setText(data.toString());  
    87.             break;  
    88.               
    89.         case TYPE_CATEGORY_ITEM:  
    90.             if (null == convertView) {  
    91.                 convertView = getHeaderView();  
    92.             }  
    93.             itemView = (TextView) convertView;  
    94.             itemView.setText(data.toString());  
    95.             break;  
    96.         }  
    97.           
    98.         return convertView;  
    99.     }  
    100.   
    101.     @Override  
    102.     public int getPinnedHeaderState(int position) {  
    103.         if (position < 0) {  
    104.             return PINNED_HEADER_GONE;  
    105.         }  
    106.           
    107.         Contact item = (Contact) getItem(position);  
    108.         Contact itemNext = (Contact) getItem(position + 1);  
    109.         boolean isSection = item.isSection;  
    110.         boolean isNextSection = (null != itemNext) ? itemNext.isSection : false;  
    111.         if (!isSection && isNextSection) {  
    112.             return PINNED_HEADER_PUSHED_UP;  
    113.         }  
    114.           
    115.         return PINNED_HEADER_VISIBLE;  
    116.     }  
    117.   
    118.     @Override  
    119.     public void configurePinnedHeader(View header, int position, int alpha) {  
    120.         Contact item = (Contact) getItem(position);  
    121.         if (null != item) {  
    122.             if (header instanceof TextView) {  
    123.                 ((TextView) header).setText(item.sectionStr);  
    124.             }  
    125.         }  
    126.     }  
    127. }  
    getPinnedHeaderState方法中,如果第一个item不是section,第二个item是section的话,就返回状态PINNED_HEADER_PUSHED_UP,否则返回PINNED_HEADER_VISIBLE。
    configurePinnedHeader方法中,就是将item的section字符串设置到header view上面去。
     
    【重要说明】
    Adapter中的数据里面已经包含了section(header)的数据,数据结构中有一个方法来标识它是否是section。那么,在点击事件就要注意了,通过position可能返回的是section数据结构。
     
    数据结构Contact的定义如下:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public class Contact {  
    2.     int id;  
    3.     String name;  
    4.     String pinyin;  
    5.     String sortLetter = "#";  
    6.     String sectionStr;  
    7.     String phoneNumber;  
    8.     boolean isSection;  
    9.     static CharacterParser sParser = CharacterParser.getInstance();  
    10.       
    11.     Contact() {  
    12.           
    13.     }  
    14.       
    15.     Contact(int id, String name) {  
    16.         this.id = id;  
    17.         this.name = name;  
    18.         this.pinyin = sParser.getSpelling(name);  
    19.         if (!TextUtils.isEmpty(pinyin)) {  
    20.             String sortString = this.pinyin.substring(0, 1).toUpperCase();  
    21.             if (sortString.matches("[A-Z]")) {  
    22.                 this.sortLetter = sortString.toUpperCase();  
    23.             } else {  
    24.                 this.sortLetter = "#";  
    25.             }  
    26.         }  
    27.     }  
    28.       
    29.     @Override  
    30.     public String toString() {  
    31.         if (isSection) {  
    32.             return name;  
    33.         } else {  
    34.             //return name + " (" + sortLetter + ", " + pinyin + ")";  
    35.             return name + " (" + phoneNumber + ")";  
    36.         }  
    37.     }  
    38. }    
     
    完整的代码
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package com.lee.sdk.test.section;  
    2.   
    3. import java.util.ArrayList;  
    4.   
    5. import android.graphics.Color;  
    6. import android.os.Bundle;  
    7. import android.view.Gravity;  
    8. import android.view.View;  
    9. import android.view.ViewGroup;  
    10. import android.widget.AbsListView;  
    11. import android.widget.AdapterView;  
    12. import android.widget.AdapterView.OnItemClickListener;  
    13. import android.widget.BaseAdapter;  
    14. import android.widget.TextView;  
    15. import android.widget.Toast;  
    16.   
    17. import com.lee.sdk.test.GABaseActivity;  
    18. import com.lee.sdk.test.R;  
    19. import com.lee.sdk.widget.PinnedHeaderListView;  
    20. import com.lee.sdk.widget.PinnedHeaderListView.PinnedHeaderAdapter;  
    21.   
    22. public class SectionListView extends GABaseActivity {  
    23.   
    24.     private int mItemHeight = 55;  
    25.     private int mSecHeight = 25;  
    26.       
    27.     @Override  
    28.     protected void onCreate(Bundle savedInstanceState) {  
    29.         super.onCreate(savedInstanceState);  
    30.         setContentView(R.layout.activity_main);  
    31.           
    32.         float density = getResources().getDisplayMetrics().density;  
    33.         mItemHeight = (int) (density * mItemHeight);  
    34.         mSecHeight = (int) (density * mSecHeight);  
    35.           
    36.         PinnedHeaderListView mListView = new PinnedHeaderListView(this);  
    37.         mListView.setAdapter(new ListViewAdapter(ContactLoader.getInstance().getContacts(this)));  
    38.         mListView.setPinnedHeaderView(getHeaderView());  
    39.         mListView.setBackgroundColor(Color.argb(255, 20, 20, 20));  
    40.         mListView.setOnItemClickListener(new OnItemClickListener() {  
    41.             @Override  
    42.             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
    43.                 ListViewAdapter adapter = ((ListViewAdapter) parent.getAdapter());  
    44.                 Contact data = (Contact) adapter.getItem(position);  
    45.                 Toast.makeText(SectionListView.this, data.toString(), Toast.LENGTH_SHORT).show();  
    46.             }  
    47.         });  
    48.   
    49.         setContentView(mListView);  
    50.     }  
    51.       
    52.     private View getHeaderView() {  
    53.         TextView itemView = new TextView(SectionListView.this);  
    54.         itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
    55.                 mSecHeight));  
    56.         itemView.setGravity(Gravity.CENTER_VERTICAL);  
    57.         itemView.setBackgroundColor(Color.WHITE);  
    58.         itemView.setTextSize(20);  
    59.         itemView.setTextColor(Color.GRAY);  
    60.         itemView.setBackgroundResource(R.drawable.section_listview_header_bg);  
    61.         itemView.setPadding(10, 0, 0, itemView.getPaddingBottom());  
    62.           
    63.         return itemView;  
    64.     }  
    65.   
    66.     private class ListViewAdapter extends BaseAdapter implements PinnedHeaderAdapter {  
    67.           
    68.         private ArrayList<Contact> mDatas;  
    69.         private static final int TYPE_CATEGORY_ITEM = 0;    
    70.         private static final int TYPE_ITEM = 1;    
    71.           
    72.         public ListViewAdapter(ArrayList<Contact> datas) {  
    73.             mDatas = datas;  
    74.         }  
    75.           
    76.         @Override  
    77.         public boolean areAllItemsEnabled() {  
    78.             return false;  
    79.         }  
    80.           
    81.         @Override  
    82.         public boolean isEnabled(int position) {  
    83.             // 异常情况处理    
    84.             if (null == mDatas || position <  0|| position > getCount()) {  
    85.                 return true;  
    86.             }   
    87.               
    88.             Contact item = mDatas.get(position);  
    89.             if (item.isSection) {  
    90.                 return false;  
    91.             }  
    92.               
    93.             return true;  
    94.         }  
    95.           
    96.         @Override  
    97.         public int getCount() {  
    98.             return mDatas.size();  
    99.         }  
    100.           
    101.         @Override  
    102.         public int getItemViewType(int position) {  
    103.             // 异常情况处理    
    104.             if (null == mDatas || position <  0|| position > getCount()) {  
    105.                 return TYPE_ITEM;  
    106.             }   
    107.               
    108.             Contact item = mDatas.get(position);  
    109.             if (item.isSection) {  
    110.                 return TYPE_CATEGORY_ITEM;  
    111.             }  
    112.               
    113.             return TYPE_ITEM;  
    114.         }  
    115.   
    116.         @Override  
    117.         public int getViewTypeCount() {  
    118.             return 2;  
    119.         }  
    120.   
    121.         @Override  
    122.         public Object getItem(int position) {  
    123.             return (position >= 0 && position < mDatas.size()) ? mDatas.get(position) : 0;  
    124.         }  
    125.   
    126.         @Override  
    127.         public long getItemId(int position) {  
    128.             return 0;  
    129.         }  
    130.   
    131.         @Override  
    132.         public View getView(int position, View convertView, ViewGroup parent) {  
    133.             int itemViewType = getItemViewType(position);  
    134.             Contact data = (Contact) getItem(position);  
    135.             TextView itemView;  
    136.               
    137.             switch (itemViewType) {  
    138.             case TYPE_ITEM:  
    139.                 if (null == convertView) {  
    140.                     itemView = new TextView(SectionListView.this);  
    141.                     itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
    142.                             mItemHeight));  
    143.                     itemView.setTextSize(16);  
    144.                     itemView.setPadding(10, 0, 0, 0);  
    145.                     itemView.setGravity(Gravity.CENTER_VERTICAL);  
    146.                     //itemView.setBackgroundColor(Color.argb(255, 20, 20, 20));  
    147.                     convertView = itemView;  
    148.                 }  
    149.                   
    150.                 itemView = (TextView) convertView;  
    151.                 itemView.setText(data.toString());  
    152.                 break;  
    153.                   
    154.             case TYPE_CATEGORY_ITEM:  
    155.                 if (null == convertView) {  
    156.                     convertView = getHeaderView();  
    157.                 }  
    158.                 itemView = (TextView) convertView;  
    159.                 itemView.setText(data.toString());  
    160.                 break;  
    161.             }  
    162.               
    163.             return convertView;  
    164.         }  
    165.   
    166.         @Override  
    167.         public int getPinnedHeaderState(int position) {  
    168.             if (position < 0) {  
    169.                 return PINNED_HEADER_GONE;  
    170.             }  
    171.               
    172.             Contact item = (Contact) getItem(position);  
    173.             Contact itemNext = (Contact) getItem(position + 1);  
    174.             boolean isSection = item.isSection;  
    175.             boolean isNextSection = (null != itemNext) ? itemNext.isSection : false;  
    176.             if (!isSection && isNextSection) {  
    177.                 return PINNED_HEADER_PUSHED_UP;  
    178.             }  
    179.               
    180.             return PINNED_HEADER_VISIBLE;  
    181.         }  
    182.   
    183.         @Override  
    184.         public void configurePinnedHeader(View header, int position, int alpha) {  
    185.             Contact item = (Contact) getItem(position);  
    186.             if (null != item) {  
    187.                 if (header instanceof TextView) {  
    188.                     ((TextView) header).setText(item.sectionStr);  
    189.                 }  
    190.             }  
    191.         }  
    192.     }  
    193. }  

    关于数据加载,分组的逻辑这里就不列出了,数据分组请参考:
    最后来一张截图:
     
     
     
  • 相关阅读:
    罗振宇 知识就是力量之怎样做一个不冲动的人
    C++中stl的map
    stl中顺序性容器,关联容器两者粗略解释
    stl之容器、迭代器、算法几者之间的关系
    mysql技术内幕之常规使用
    essential c++ 第一章 array及vector相关使用
    由函数clock想到的
    编程获得CPU的主频
    Markdown 基本语法
    Future FutrueTask Callable类源码说明以及原理使用
  • 原文地址:https://www.cnblogs.com/qingchen1984/p/4155584.html
Copyright © 2011-2022 走看看