分类: Android2014-01-27 12:26 6585人阅读 评论(13) 收藏 举报
 

目录(?)[+]

 

之前在使用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的文本,图片等,这个方法是由调用者去实现的。
  1. /**
  2. * Adapter interface.  The list adapter must implement this interface.
  3. */
  4. public interface PinnedHeaderAdapter {
  5. /**
  6. * Pinned header state: don't show the header.
  7. */
  8. public static final int PINNED_HEADER_GONE = 0;
  9. /**
  10. * Pinned header state: show the header at the top of the list.
  11. */
  12. public static final int PINNED_HEADER_VISIBLE = 1;
  13. /**
  14. * Pinned header state: show the header. If the header extends beyond
  15. * the bottom of the first shown element, push it up and clip.
  16. */
  17. public static final int PINNED_HEADER_PUSHED_UP = 2;
  18. /**
  19. * Computes the desired state of the pinned header for the given
  20. * position of the first visible list item. Allowed return values are
  21. * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or
  22. * {@link #PINNED_HEADER_PUSHED_UP}.
  23. */
  24. int getPinnedHeaderState(int position);
  25. /**
  26. * Configures the pinned header view to match the first visible list item.
  27. *
  28. * @param header pinned header view.
  29. * @param position position of the first visible list item.
  30. * @param alpha fading of the header view, between 0 and 255.
  31. */
  32. void configurePinnedHeader(View header, int position, int alpha);
  33. }

1.2、如何绘制Header View

这是在dispatchDraw方法中绘制的:
  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的移动位移。下面是具体的实现:
  1. public void configureHeaderView(int position) {
  2. if (mHeaderView == null || null == mAdapter) {
  3. return;
  4. }
  5. int state = mAdapter.getPinnedHeaderState(position);
  6. switch (state) {
  7. case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
  8. mHeaderViewVisible = false;
  9. break;
  10. }
  11. case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
  12. mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA);
  13. if (mHeaderView.getTop() != 0) {
  14. mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
  15. }
  16. mHeaderViewVisible = true;
  17. break;
  18. }
  19. case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
  20. View firstView = getChildAt(0);
  21. int bottom = firstView.getBottom();
  22. int itemHeight = firstView.getHeight();
  23. int headerHeight = mHeaderView.getHeight();
  24. int y;
  25. int alpha;
  26. if (bottom < headerHeight) {
  27. y = (bottom - headerHeight);
  28. alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
  29. } else {
  30. y = 0;
  31. alpha = MAX_ALPHA;
  32. }
  33. mAdapter.configurePinnedHeader(mHeaderView, position, alpha);
  34. if (mHeaderView.getTop() != y) {
  35. mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);
  36. }
  37. mHeaderViewVisible = true;
  38. break;
  39. }
  40. }
  41. }

1.4、onLayout和onMeasure

在这两个方法中,控制Header View的位置及大小
  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. @Override
  11. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  12. super.onLayout(changed, left, top, right, bottom);
  13. if (mHeaderView != null) {
  14. mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
  15. configureHeaderView(getFirstVisiblePosition());
  16. }
  17. }

好了,到这里,悬浮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这两个方法的实现
  1. private class ListViewAdapter extends BaseAdapter implements PinnedHeaderAdapter {
  2. private ArrayList<Contact> mDatas;
  3. private static final int TYPE_CATEGORY_ITEM = 0;
  4. private static final int TYPE_ITEM = 1;
  5. public ListViewAdapter(ArrayList<Contact> datas) {
  6. mDatas = datas;
  7. }
  8. @Override
  9. public boolean areAllItemsEnabled() {
  10. return false;
  11. }
  12. @Override
  13. public boolean isEnabled(int position) {
  14. // 异常情况处理
  15. if (null == mDatas || position <  0|| position > getCount()) {
  16. return true;
  17. }
  18. Contact item = mDatas.get(position);
  19. if (item.isSection) {
  20. return false;
  21. }
  22. return true;
  23. }
  24. @Override
  25. public int getCount() {
  26. return mDatas.size();
  27. }
  28. @Override
  29. public int getItemViewType(int position) {
  30. // 异常情况处理
  31. if (null == mDatas || position <  0|| position > getCount()) {
  32. return TYPE_ITEM;
  33. }
  34. Contact item = mDatas.get(position);
  35. if (item.isSection) {
  36. return TYPE_CATEGORY_ITEM;
  37. }
  38. return TYPE_ITEM;
  39. }
  40. @Override
  41. public int getViewTypeCount() {
  42. return 2;
  43. }
  44. @Override
  45. public Object getItem(int position) {
  46. return (position >= 0 && position < mDatas.size()) ? mDatas.get(position) : 0;
  47. }
  48. @Override
  49. public long getItemId(int position) {
  50. return 0;
  51. }
  52. @Override
  53. public View getView(int position, View convertView, ViewGroup parent) {
  54. int itemViewType = getItemViewType(position);
  55. Contact data = (Contact) getItem(position);
  56. TextView itemView;
  57. switch (itemViewType) {
  58. case TYPE_ITEM:
  59. if (null == convertView) {
  60. itemView = new TextView(SectionListView.this);
  61. itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  62. mItemHeight));
  63. itemView.setTextSize(16);
  64. itemView.setPadding(10, 0, 0, 0);
  65. itemView.setGravity(Gravity.CENTER_VERTICAL);
  66. //itemView.setBackgroundColor(Color.argb(255, 20, 20, 20));
  67. convertView = itemView;
  68. }
  69. itemView = (TextView) convertView;
  70. itemView.setText(data.toString());
  71. break;
  72. case TYPE_CATEGORY_ITEM:
  73. if (null == convertView) {
  74. convertView = getHeaderView();
  75. }
  76. itemView = (TextView) convertView;
  77. itemView.setText(data.toString());
  78. break;
  79. }
  80. return convertView;
  81. }
  82. @Override
  83. public int getPinnedHeaderState(int position) {
  84. if (position < 0) {
  85. return PINNED_HEADER_GONE;
  86. }
  87. Contact item = (Contact) getItem(position);
  88. Contact itemNext = (Contact) getItem(position + 1);
  89. boolean isSection = item.isSection;
  90. boolean isNextSection = (null != itemNext) ? itemNext.isSection : false;
  91. if (!isSection && isNextSection) {
  92. return PINNED_HEADER_PUSHED_UP;
  93. }
  94. return PINNED_HEADER_VISIBLE;
  95. }
  96. @Override
  97. public void configurePinnedHeader(View header, int position, int alpha) {
  98. Contact item = (Contact) getItem(position);
  99. if (null != item) {
  100. if (header instanceof TextView) {
  101. ((TextView) header).setText(item.sectionStr);
  102. }
  103. }
  104. }
  105. }
getPinnedHeaderState方法中,如果第一个item不是section,第二个item是section的话,就返回状态PINNED_HEADER_PUSHED_UP,否则返回PINNED_HEADER_VISIBLE。
configurePinnedHeader方法中,就是将item的section字符串设置到header view上面去。
 
【重要说明】
Adapter中的数据里面已经包含了section(header)的数据,数据结构中有一个方法来标识它是否是section。那么,在点击事件就要注意了,通过position可能返回的是section数据结构。
 

数据结构Contact的定义如下:

  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. Contact() {
  11. }
  12. Contact(int id, String name) {
  13. this.id = id;
  14. this.name = name;
  15. this.pinyin = sParser.getSpelling(name);
  16. if (!TextUtils.isEmpty(pinyin)) {
  17. String sortString = this.pinyin.substring(0, 1).toUpperCase();
  18. if (sortString.matches("[A-Z]")) {
  19. this.sortLetter = sortString.toUpperCase();
  20. } else {
  21. this.sortLetter = "#";
  22. }
  23. }
  24. }
  25. @Override
  26. public String toString() {
  27. if (isSection) {
  28. return name;
  29. } else {
  30. //return name + " (" + sortLetter + ", " + pinyin + ")";
  31. return name + " (" + phoneNumber + ")";
  32. }
  33. }
  34. }
 

完整的代码

  1. package com.lee.sdk.test.section;
  2. import java.util.ArrayList;
  3. import android.graphics.Color;
  4. import android.os.Bundle;
  5. import android.view.Gravity;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.widget.AbsListView;
  9. import android.widget.AdapterView;
  10. import android.widget.AdapterView.OnItemClickListener;
  11. import android.widget.BaseAdapter;
  12. import android.widget.TextView;
  13. import android.widget.Toast;
  14. import com.lee.sdk.test.GABaseActivity;
  15. import com.lee.sdk.test.R;
  16. import com.lee.sdk.widget.PinnedHeaderListView;
  17. import com.lee.sdk.widget.PinnedHeaderListView.PinnedHeaderAdapter;
  18. public class SectionListView extends GABaseActivity {
  19. private int mItemHeight = 55;
  20. private int mSecHeight = 25;
  21. @Override
  22. protected void onCreate(Bundle savedInstanceState) {
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.activity_main);
  25. float density = getResources().getDisplayMetrics().density;
  26. mItemHeight = (int) (density * mItemHeight);
  27. mSecHeight = (int) (density * mSecHeight);
  28. PinnedHeaderListView mListView = new PinnedHeaderListView(this);
  29. mListView.setAdapter(new ListViewAdapter(ContactLoader.getInstance().getContacts(this)));
  30. mListView.setPinnedHeaderView(getHeaderView());
  31. mListView.setBackgroundColor(Color.argb(255, 20, 20, 20));
  32. mListView.setOnItemClickListener(new OnItemClickListener() {
  33. @Override
  34. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  35. ListViewAdapter adapter = ((ListViewAdapter) parent.getAdapter());
  36. Contact data = (Contact) adapter.getItem(position);
  37. Toast.makeText(SectionListView.this, data.toString(), Toast.LENGTH_SHORT).show();
  38. }
  39. });
  40. setContentView(mListView);
  41. }
  42. private View getHeaderView() {
  43. TextView itemView = new TextView(SectionListView.this);
  44. itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  45. mSecHeight));
  46. itemView.setGravity(Gravity.CENTER_VERTICAL);
  47. itemView.setBackgroundColor(Color.WHITE);
  48. itemView.setTextSize(20);
  49. itemView.setTextColor(Color.GRAY);
  50. itemView.setBackgroundResource(R.drawable.section_listview_header_bg);
  51. itemView.setPadding(10, 0, 0, itemView.getPaddingBottom());
  52. return itemView;
  53. }
  54. private class ListViewAdapter extends BaseAdapter implements PinnedHeaderAdapter {
  55. private ArrayList<Contact> mDatas;
  56. private static final int TYPE_CATEGORY_ITEM = 0;
  57. private static final int TYPE_ITEM = 1;
  58. public ListViewAdapter(ArrayList<Contact> datas) {
  59. mDatas = datas;
  60. }
  61. @Override
  62. public boolean areAllItemsEnabled() {
  63. return false;
  64. }
  65. @Override
  66. public boolean isEnabled(int position) {
  67. // 异常情况处理
  68. if (null == mDatas || position <  0|| position > getCount()) {
  69. return true;
  70. }
  71. Contact item = mDatas.get(position);
  72. if (item.isSection) {
  73. return false;
  74. }
  75. return true;
  76. }
  77. @Override
  78. public int getCount() {
  79. return mDatas.size();
  80. }
  81. @Override
  82. public int getItemViewType(int position) {
  83. // 异常情况处理
  84. if (null == mDatas || position <  0|| position > getCount()) {
  85. return TYPE_ITEM;
  86. }
  87. Contact item = mDatas.get(position);
  88. if (item.isSection) {
  89. return TYPE_CATEGORY_ITEM;
  90. }
  91. return TYPE_ITEM;
  92. }
  93. @Override
  94. public int getViewTypeCount() {
  95. return 2;
  96. }
  97. @Override
  98. public Object getItem(int position) {
  99. return (position >= 0 && position < mDatas.size()) ? mDatas.get(position) : 0;
  100. }
  101. @Override
  102. public long getItemId(int position) {
  103. return 0;
  104. }
  105. @Override
  106. public View getView(int position, View convertView, ViewGroup parent) {
  107. int itemViewType = getItemViewType(position);
  108. Contact data = (Contact) getItem(position);
  109. TextView itemView;
  110. switch (itemViewType) {
  111. case TYPE_ITEM:
  112. if (null == convertView) {
  113. itemView = new TextView(SectionListView.this);
  114. itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  115. mItemHeight));
  116. itemView.setTextSize(16);
  117. itemView.setPadding(10, 0, 0, 0);
  118. itemView.setGravity(Gravity.CENTER_VERTICAL);
  119. //itemView.setBackgroundColor(Color.argb(255, 20, 20, 20));
  120. convertView = itemView;
  121. }
  122. itemView = (TextView) convertView;
  123. itemView.setText(data.toString());
  124. break;
  125. case TYPE_CATEGORY_ITEM:
  126. if (null == convertView) {
  127. convertView = getHeaderView();
  128. }
  129. itemView = (TextView) convertView;
  130. itemView.setText(data.toString());
  131. break;
  132. }
  133. return convertView;
  134. }
  135. @Override
  136. public int getPinnedHeaderState(int position) {
  137. if (position < 0) {
  138. return PINNED_HEADER_GONE;
  139. }
  140. Contact item = (Contact) getItem(position);
  141. Contact itemNext = (Contact) getItem(position + 1);
  142. boolean isSection = item.isSection;
  143. boolean isNextSection = (null != itemNext) ? itemNext.isSection : false;
  144. if (!isSection && isNextSection) {
  145. return PINNED_HEADER_PUSHED_UP;
  146. }
  147. return PINNED_HEADER_VISIBLE;
  148. }
  149. @Override
  150. public void configurePinnedHeader(View header, int position, int alpha) {
  151. Contact item = (Contact) getItem(position);
  152. if (null != item) {
  153. if (header instanceof TextView) {
  154. ((TextView) header).setText(item.sectionStr);
  155. }
  156. }
  157. }
  158. }
  159. }

关于数据加载,分组的逻辑这里就不列出了,数据分组请参考:

最后来一张截图:
 
 
 

Anroid ListView分组和悬浮Header实现的更多相关文章

  1. Android 自定义组件之 带有悬浮header的listview

    最近做项目遇到一个需求,要做一个带有悬浮header的listview,即,当listview滑动时,header消失,静止时header浮现. 这个需求看似简单,实际做起来还是会遇到不少的困难,特此 ...

  2. WPF:ListView 分组合并

    CollectionViewSource 绑定的是从数据库取出的数据ListBind 以DeptName为分组依据 <Window.Resources> <CollectionVie ...

  3. WPF ListView 使用GridView 带有Header 以及点击header排序 sort

    ListView: <ListView x:Name="lvFiles" VerticalAlignment="Stretch" Background=& ...

  4. WPF ListView 分组 Grouping

    在Resource里定义数据源和分组字段: <CollectionViewSource x:Key="listData" Source="{Binding Cate ...

  5. Android ListView分组显示

    ListView的实现方法也是普通的实现方法.只不过在list列表中加入groupkey信息.在渲染的时候要判断是否是分组的标题. 就是在使用不同的两个View的时候存在这种情况,convertVie ...

  6. 移动端解决悬浮层(悬浮header、footer)会遮挡住内容的方法

    固定Footer Bootstrap框架提供了两种固定导航条的方式: ☑  .navbar-fixed-top:导航条固定在浏览器窗口顶部 ☑  .navbar-fixed-bottom:导航条固定在 ...

  7. 仿照支付宝账单界面--listview分组显示 用来做!发!财树充值交易明细

    QQ图片20150430155638.png (151.65 KB, 下载次数: 32)     下载链接: http://pan.baidu.com/s/1kVMY1SV 密码: i8ta

  8. 收藏的技术文章链接(ubuntu,python,android等)

    我的收藏 他山之石,可以攻玉 转载请注明出处:https://ahangchen.gitbooks.io/windy-afternoon/content/ 开发过程中收藏在Chrome书签栏里的技术文 ...

  9. React-Native 常用组件学习资料链接

    以下链接是自己开发RN工程时参考的一些不错的资料,给喜欢学习的朋友分享以下. React-Native组件用法详解之ListViewhttp://www.jianshu.com/p/1293bb8ac ...

随机推荐

  1. 在线GET/POST API接口请求模拟测试工具

    在前后端开发过程中经常需要对HTTP接口进行测试,推荐几款比较好用的测试工具 Postman https://www.getpostman.com/ 强大的HTTP请求测试工具 支持多平台 Advan ...

  2. Sikuli:创新的图形化编程技术

    Sikuli是一种使用截图进行UI自动化测试的技术.Sikuli包括sikul脚本,基于Jython的API以及sikuli IDE.Sikuli可以实现任何你可以在显示器上看到ui对象的自动化,你可 ...

  3. linux上安装配置samba服务器

    linux上安装配置samba服务器 在linux上安装配置samba服务器 在这给大家介绍一个不错的家伙,samba服务.如果您正在犯愁,如何在Windows和Linux之间实现资源共享,就请看看这 ...

  4. OpenCV中Mat的列向量归一化

    OpenCV中Mat的列向量归一化 http://blog.csdn.net/shaoxiaohu1/article/details/8287528 OpenCV中Mat的列向量归一化 标签: Ope ...

  5. json格式数据,将数据库中查询的结果转换为json, 然后调用接口的方式返回json(方式一)

    调用接口,无非也就是打开链接 读取流 将结果以流的形式输出 将查询结果以json返回,无非就是将查询到的结果转换成jsonObject ================================ ...

  6. hdu_5754_Life Winner Bo(博弈)

    题目链接:hdu_5754_Life Winner Bo 题意: 一个棋盘,有国王,车,马,皇后四种棋子,bo先手,都最优策略,问你赢的人,如果双方都不能赢就输出D 题解: 全部都可以直接推公式, 这 ...

  7. ECOS高可用集群

    此架构由8台PC .2台防火墙.2台24口三层交换机组成, 注意点: 1)Load Balance:Haprxoy+keepalived 实现高可用. 2)web:Nginx+php-fpm 3)DB ...

  8. SVN和GIT的使用

    一.SVN通用流程 1.从服务器仓库的项目上右键拷贝项目地址,然后来到你的电脑桌面上右键“SVN checkout...”,这样就跟服务器建立了关联 2.如果有创建新文件,则右键选择“Tortoise ...

  9. Tomcat配置远程调试端口

    Tomcat配置远程调试端口 1.Linxu系统: apach/bin/startup.sh开始处中增加如下内容: declare -x CATALINA_OPTS="-server -Xd ...

  10. virt

    www.itwhy.org/linux/debian7-%E5%AE%89%E8%A3%85-kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA.html www.storageonlin ...