Anroid ListView分组和悬浮Header实现方法
之前在使用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的文本,图片等,这个方法是由调用者去实现的。
/** * adapter interface. the list adapter must implement this interface. */ public interface pinnedheaderadapter { /** * pinned header state: don't show the header. */ public static final int pinned_header_gone = 0; /** * pinned header state: show the header at the top of the list. */ public static final int pinned_header_visible = 1; /** * pinned header state: show the header. if the header extends beyond * the bottom of the first shown element, push it up and clip. */ public static final int pinned_header_pushed_up = 2; /** * computes the desired state of the pinned header for the given * position of the first visible list item. allowed return values are * {@link #pinned_header_gone}, {@link #pinned_header_visible} or * {@link #pinned_header_pushed_up}. */ int getpinnedheaderstate(int position); /** * configures the pinned header view to match the first visible list item. * * @param header pinned header view. * @param position position of the first visible list item. * @param alpha fading of the header view, between 0 and 255. */ void configurepinnedheader(view header, int position, int alpha); }
1.2、如何绘制header view
这是在dispatchdraw方法中绘制的:
@override protected void dispatchdraw(canvas canvas) { super.dispatchdraw(canvas); if (mheaderviewvisible) { drawchild(canvas, mheaderview, getdrawingtime()); } }
1.3、配置header view
核心就是根据不同的状态值来控制header view的状态,比如pinned_header_gone(隐藏)的情况,可能需要设置一个flag标记,不绘制header view,那么就达到隐藏的效果。当pinned_header_pushed_up状态时,可能需要根据不同的位移来计算header view的移动位移。下面是具体的实现:
public void configureheaderview(int position) { if (mheaderview == null || null == madapter) { return; } int state = madapter.getpinnedheaderstate(position); switch (state) { case pinnedheaderadapter.pinned_header_gone: { mheaderviewvisible = false; break; } case pinnedheaderadapter.pinned_header_visible: { madapter.configurepinnedheader(mheaderview, position, max_alpha); if (mheaderview.gettop() != 0) { mheaderview.layout(0, 0, mheaderviewwidth, mheaderviewheight); } mheaderviewvisible = true; break; } case pinnedheaderadapter.pinned_header_pushed_up: { view firstview = getchildat(0); int bottom = firstview.getbottom(); int itemheight = firstview.getheight(); int headerheight = mheaderview.getheight(); int y; int alpha; if (bottom < headerheight) { y = (bottom - headerheight); alpha = max_alpha * (headerheight + y) / headerheight; } else { y = 0; alpha = max_alpha; } madapter.configurepinnedheader(mheaderview, position, alpha); if (mheaderview.gettop() != y) { mheaderview.layout(0, y, mheaderviewwidth, mheaderviewheight + y); } mheaderviewvisible = true; break; } } }
1.4、onlayout和onmeasure
在这两个方法中,控制header view的位置及大小
@override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); if (mheaderview != null) { measurechild(mheaderview, widthmeasurespec, heightmeasurespec); mheaderviewwidth = mheaderview.getmeasuredwidth(); mheaderviewheight = mheaderview.getmeasuredheight(); } } @override protected void onlayout(boolean changed, int left, int top, int right, int bottom) { super.onlayout(changed, left, top, right, bottom); if (mheaderview != null) { mheaderview.layout(0, 0, mheaderviewwidth, mheaderviewheight); configureheaderview(getfirstvisibleposition()); } }
好了,到这里,悬浮header view就完了,各位可能看不到完整的代码,只要明白这几个核心的方法,自己写出来,也差不多了。
2、listview section实现
有两种方法实现listview section效果:
方法一:
每一个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这两个方法的实现
private class listviewadapter extends baseadapter implements pinnedheaderadapter { private arraylist<contact> mdatas; private static final int type_category_item = 0; private static final int type_item = 1; public listviewadapter(arraylist<contact> datas) { mdatas = datas; } @override public boolean areallitemsenabled() { return false; } @override public boolean isenabled(int position) { // 异常情况处理 if (null == mdatas || position < 0|| position > getcount()) { return true; } contact item = mdatas.get(position); if (item.issection) { return false; } return true; } @override public int getcount() { return mdatas.size(); } @override public int getitemviewtype(int position) { // 异常情况处理 if (null == mdatas || position < 0|| position > getcount()) { return type_item; } contact item = mdatas.get(position); if (item.issection) { return type_category_item; } return type_item; } @override public int getviewtypecount() { return 2; } @override public object getitem(int position) { return (position >= 0 && position < mdatas.size()) ? mdatas.get(position) : 0; } @override public long getitemid(int position) { return 0; } @override public view getview(int position, view convertview, viewgroup parent) { int itemviewtype = getitemviewtype(position); contact data = (contact) getitem(position); textview itemview; switch (itemviewtype) { case type_item: if (null == convertview) { itemview = new textview(sectionlistview.this); itemview.setlayoutparams(new abslistview.layoutparams(viewgroup.layoutparams.match_parent, mitemheight)); itemview.settextsize(16); itemview.setpadding(10, 0, 0, 0); itemview.setgravity(gravity.center_vertical); //itemview.setbackgroundcolor(color.argb(255, 20, 20, 20)); convertview = itemview; } itemview = (textview) convertview; itemview.settext(data.tostring()); break; case type_category_item: if (null == convertview) { convertview = getheaderview(); } itemview = (textview) convertview; itemview.settext(data.tostring()); break; } return convertview; } @override public int getpinnedheaderstate(int position) { if (position < 0) { return pinned_header_gone; } contact item = (contact) getitem(position); contact itemnext = (contact) getitem(position + 1); boolean issection = item.issection; boolean isnextsection = (null != itemnext) ? itemnext.issection : false; if (!issection && isnextsection) { return pinned_header_pushed_up; } return pinned_header_visible; } @override public void configurepinnedheader(view header, int position, int alpha) { contact item = (contact) getitem(position); if (null != item) { if (header instanceof textview) { ((textview) header).settext(item.sectionstr); } } } }
在getpinnedheaderstate方法中,如果第一个item不是section,第二个item是section的话,就返回状态pinned_header_pushed_up,否则返回pinned_header_visible。
在configurepinnedheader方法中,就是将item的section字符串设置到header view上面去。
【重要说明】
adapter中的数据里面已经包含了section(header)的数据,数据结构中有一个方法来标识它是否是section。那么,在点击事件就要注意了,通过position可能返回的是section数据结构。
数据结构contact的定义如下:
public class contact { int id; string name; string pinyin; string sortletter = "#"; string sectionstr; string phonenumber; boolean issection; static characterparser sparser = characterparser.getinstance(); contact() { } contact(int id, string name) { this.id = id; this.name = name; this.pinyin = sparser.getspelling(name); if (!textutils.isempty(pinyin)) { string sortstring = this.pinyin.substring(0, 1).touppercase(); if (sortstring.matches("[a-z]")) { this.sortletter = sortstring.touppercase(); } else { this.sortletter = "#"; } } } @override public string tostring() { if (issection) { return name; } else { //return name + " (" + sortletter + ", " + pinyin + ")"; return name + " (" + phonenumber + ")"; } } }
完整的代码
package com.lee.sdk.test.section; import java.util.arraylist; import android.graphics.color; import android.os.bundle; import android.view.gravity; import android.view.view; import android.view.viewgroup; import android.widget.abslistview; import android.widget.adapterview; import android.widget.adapterview.onitemclicklistener; import android.widget.baseadapter; import android.widget.textview; import android.widget.toast; import com.lee.sdk.test.gabaseactivity; import com.lee.sdk.test.r; import com.lee.sdk.widget.pinnedheaderlistview; import com.lee.sdk.widget.pinnedheaderlistview.pinnedheaderadapter; public class sectionlistview extends gabaseactivity { private int mitemheight = 55; private int msecheight = 25; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); float density = getresources().getdisplaymetrics().density; mitemheight = (int) (density * mitemheight); msecheight = (int) (density * msecheight); pinnedheaderlistview mlistview = new pinnedheaderlistview(this); mlistview.setadapter(new listviewadapter(contactloader.getinstance().getcontacts(this))); mlistview.setpinnedheaderview(getheaderview()); mlistview.setbackgroundcolor(color.argb(255, 20, 20, 20)); mlistview.setonitemclicklistener(new onitemclicklistener() { @override public void onitemclick(adapterview<?> parent, view view, int position, long id) { listviewadapter adapter = ((listviewadapter) parent.getadapter()); contact data = (contact) adapter.getitem(position); toast.maketext(sectionlistview.this, data.tostring(), toast.length_short).show(); } }); setcontentview(mlistview); } private view getheaderview() { textview itemview = new textview(sectionlistview.this); itemview.setlayoutparams(new abslistview.layoutparams(viewgroup.layoutparams.match_parent, msecheight)); itemview.setgravity(gravity.center_vertical); itemview.setbackgroundcolor(color.white); itemview.settextsize(20); itemview.settextcolor(color.gray); itemview.setbackgroundresource(r.drawable.section_listview_header_bg); itemview.setpadding(10, 0, 0, itemview.getpaddingbottom()); return itemview; } private class listviewadapter extends baseadapter implements pinnedheaderadapter { private arraylist<contact> mdatas; private static final int type_category_item = 0; private static final int type_item = 1; public listviewadapter(arraylist<contact> datas) { mdatas = datas; } @override public boolean areallitemsenabled() { return false; } @override public boolean isenabled(int position) { // 异常情况处理 if (null == mdatas || position < 0|| position > getcount()) { return true; } contact item = mdatas.get(position); if (item.issection) { return false; } return true; } @override public int getcount() { return mdatas.size(); } @override public int getitemviewtype(int position) { // 异常情况处理 if (null == mdatas || position < 0|| position > getcount()) { return type_item; } contact item = mdatas.get(position); if (item.issection) { return type_category_item; } return type_item; } @override public int getviewtypecount() { return 2; } @override public object getitem(int position) { return (position >= 0 && position < mdatas.size()) ? mdatas.get(position) : 0; } @override public long getitemid(int position) { return 0; } @override public view getview(int position, view convertview, viewgroup parent) { int itemviewtype = getitemviewtype(position); contact data = (contact) getitem(position); textview itemview; switch (itemviewtype) { case type_item: if (null == convertview) { itemview = new textview(sectionlistview.this); itemview.setlayoutparams(new abslistview.layoutparams(viewgroup.layoutparams.match_parent, mitemheight)); itemview.settextsize(16); itemview.setpadding(10, 0, 0, 0); itemview.setgravity(gravity.center_vertical); //itemview.setbackgroundcolor(color.argb(255, 20, 20, 20)); convertview = itemview; } itemview = (textview) convertview; itemview.settext(data.tostring()); break; case type_category_item: if (null == convertview) { convertview = getheaderview(); } itemview = (textview) convertview; itemview.settext(data.tostring()); break; } return convertview; } @override public int getpinnedheaderstate(int position) { if (position < 0) { return pinned_header_gone; } contact item = (contact) getitem(position); contact itemnext = (contact) getitem(position + 1); boolean issection = item.issection; boolean isnextsection = (null != itemnext) ? itemnext.issection : false; if (!issection && isnextsection) { return pinned_header_pushed_up; } return pinned_header_visible; } @override public void configurepinnedheader(view header, int position, int alpha) { contact item = (contact) getitem(position); if (null != item) { if (header instanceof textview) { ((textview) header).settext(item.sectionstr); } } } } }
最后来一张截图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: MapTask阶段shuffle源码分析