之前在使用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,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,Alpha); if (mheaderVIEw.gettop() != y) { mheaderVIEw.layout(0,y,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,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); itemVIEw.setGravity(Gravity.CENTER_VERTICAL); //itemVIEw.setBackgroundcolor(color.argb(255,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 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)); mListVIEw.setonItemClickListener(new OnItemClickListener() { @OverrIDe public voID onItemClick(AdapterVIEw<?> parent,VIEw vIEw,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,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,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); itemVIEw.setGravity(Gravity.CENTER_VERTICAL); //itemVIEw.setBackgroundcolor(color.argb(255,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 Alpha) { Contact item = (Contact) getItem(position); if (null != item) { if (header instanceof TextVIEw) { ((TextVIEw) header).setText(item.sectionStr); } } } } }
最后来一张截图:
以上是内存溢出为你收集整理的Anroid ListView分组和悬浮Header实现方法全部内容,希望文章能够帮你解决Anroid ListView分组和悬浮Header实现方法所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)