美文网首页
PinnedHeaderListView

PinnedHeaderListView

作者: MissmoBaby | 来源:发表于2017-07-13 13:28 被阅读0次

    组件介绍

    所有的View分组,每一组都有一个Header,上下滑动到某一个组的时候,它的Header都会悬浮在顶部

    1. 当组的头部从屏幕顶部消失,而且组还有成员在屏幕内的时候,组的头部悬浮在屏幕顶部
    2. 当下一个组的头部滑到屏幕顶部与悬浮头部挨着的时候,把悬浮头部
      顶走,最终悬浮的头部被替代
    pinnedHeaderView.gif

    为了更容易理解,需要首先来说说明一下PinnedHeaderListView的大概思路。它是通过将ListView所有的子item分成不同的section,每个section的item的数目不一样,每一个section的第一个item称为header。我们姑且将这两种不同的类型称之为section header和section item。不过值得注意的是,只是从逻辑上做了这样的划分,实际上所有的item,无论是否是header都是ListView里普通的一项。
    基于上面的说明,首先看一下SectionedBaseAdapter,它就是一个BaseAdapter,

    private static int HEADER_VIEW_TYPE = 0;  
    private static int ITEM_VIEW_TYPE = 0;  
      
    /**  
     * Holds the calculated values of @{link getPositionInSectionForPosition}  
     */  
    private SparseArray<Integer> mSectionPositionCache;  
    /**  
     * Holds the calculated values of @{link getSectionForPosition}  
     */  
    private SparseArray<Integer> mSectionCache;  
    /**  
     * Holds the calculated values of @{link getCountForSection}  
     */  
    private SparseArray<Integer> mSectionCountCache;  
      
    /**  
     * Caches the item count  
     */  
    private int mCount;  
    /**  
     * Caches the section count  
     */  
    private int mSectionCount;  
      
    public SectionedBaseAdapter() {  
        super();  
        mSectionCache = new SparseArray<Integer>();  
        mSectionPositionCache = new SparseArray<Integer>();  
        mSectionCountCache = new SparseArray<Integer>();  
        mCount = -1;  
        mSectionCount = -1;  
    }  
    

    1-2行,定义了两种类型HEADER_VIEW_TYPE和ITEM_VIEW_TYPE,分别对应section header和section item。
    7、11、15行定义了三个SparseArray,它是Android上对HashMap<Integer, Object>的性能更优的替代品。我们可以将他们当做HashMap<Integer, �Integer>来理解。他们的作用是用来对section的信息做记录(缓存)。具体来讲,mSectionPositionCache表示第i个位置的item在对应的section中是第几个位置,mSectionCache表示第i个位置的item是属于第几个section,mSectionCountCache表示每个section有几个item。
    20、24行,表示总的item数(包括每个section的header)和section数。

    @Override  
    public final int getCount() {  
        if (mCount >= 0) {  
            return mCount;  
        }  
        int count = 0;  
        for (int i = 0; i < internalGetSectionCount(); i++) {  
            count += internalGetCountForSection(i);  
            count++; // for the header view  
        }  
        mCount = count;  
        return count;  
    }  
      
    @Override  
    public final Object getItem(int position) {  
        return getItem(getSectionForPosition(position), getPositionInSectionForPosition(position));  
    }  
      
    @Override  
    public final long getItemId(int position) {  
        return getItemId(getSectionForPosition(position), getPositionInSectionForPosition(position));  
    }  
      
    @Override  
    public final View getView(int position, View convertView, ViewGroup parent) {  
        if (isSectionHeader(position)) {  
            return getSectionHeaderView(getSectionForPosition(position), convertView, parent);  
        }  
        return getItemView(getSectionForPosition(position), getPositionInSectionForPosition(position), convertView, parent);  
    }  
      
    @Override  
    public final int getItemViewType(int position) {  
        if (isSectionHeader(position)) {  
            return getItemViewTypeCount() + getSectionHeaderViewType(getSectionForPosition(position));  
        }  
        return getItemViewType(getSectionForPosition(position), getPositionInSectionForPosition(position));  
    }  
      
    @Override  
    public final int getViewTypeCount() {  
        return getItemViewTypeCount() + getSectionHeaderViewTypeCount();  
    }  
    

    这段代码就是重写了BaseAdapter的几个方法,大家对此应该很熟悉,所不同的是在方法的内部实现,对section的item和header做了区分处理。

    public abstract Object getItem(int section, int position);  
      
    public abstract long getItemId(int section, int position);  
      
    public abstract int getSectionCount();  
      
    public abstract int getCountForSection(int section);  
      
    public abstract View getItemView(int section, int position, View convertView, ViewGroup parent);  
      
    public abstract View getSectionHeaderView(int section, View convertView, ViewGroup parent);  
    

    这几个方法是需要子类去实现的。

    public final int getSectionForPosition(int position) {  
        // first try to retrieve values from cache  
        Integer cachedSection = mSectionCache.get(position);  
        if (cachedSection != null) {  
            return cachedSection;  
        }  
        int sectionStart = 0;  
        for (int i = 0; i < internalGetSectionCount(); i++) {  
            int sectionCount = internalGetCountForSection(i);  
            int sectionEnd = sectionStart + sectionCount + 1;  
            if (position >= sectionStart && position < sectionEnd) {  
                mSectionCache.put(position, i);  
                return i;  
            }  
            sectionStart = sectionEnd;  
        }  
        return 0;  
    }  
    private int internalGetCountForSection(int section) {  
        Integer cachedSectionCount = mSectionCountCache.get(section);  
        if (cachedSectionCount != null) {  
            return cachedSectionCount;  
        }  
        int sectionCount = getCountForSection(section);  
        mSectionCountCache.put(section, sectionCount);  
        return sectionCount;  
    }  
      
    private int internalGetSectionCount() {  
        if (mSectionCount >= 0) {  
            return mSectionCount;  
        }  
        mSectionCount = getSectionCount();  
        return mSectionCount;  
    }  
    

    这三个方法与开头的三个SparseArray对应,方法中先分别从这三个Cache中获取对应的值,如果获取不到,就根据条件进行计算,将计算后的结果放入Cache中。
    getPositionInSectionForPosition(int position)用于获取指定位置的item在它所在的section是第几个位置。
    internalGetCountForSection(int section)用于获取指定section中item的数目。
    internalGetSectionCount()用户获得section总的数目。
    之前提到有几个抽象方法需要实现,下面就看一下SectionedBaseAdapter的实现类TestSectionedAdapter。

    public class TestSectionedAdapter extends SectionedBaseAdapter {  
      
        @Override  
        public Object getItem(int section, int position) {  
            // TODO Auto-generated method stub  
            return null;  
        }  
      
        @Override  
        public long getItemId(int section, int position) {  
            // TODO Auto-generated method stub  
            return 0;  
        }  
      
        @Override  
        public int getSectionCount() {  
            return 7;  
        }  
      
        @Override  
        public int getCountForSection(int section) {  
            return 15;  
        }  
      
        @Override  
        public View getItemView(int section, int position, View convertView, ViewGroup parent) {  
            LinearLayout layout = null;  
            if (convertView == null) {  
                LayoutInflater inflator = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
                layout = (LinearLayout) inflator.inflate(R.layout.pinned_header_listview_list_item, null);  
            } else {  
                layout = (LinearLayout) convertView;  
            }  
            ((TextView) layout.findViewById(R.id.textItem)).setText("Section " + section + " Item " + position);  
            return layout;  
        }  
      
        @Override  
        public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {  
            LinearLayout layout = null;  
            if (convertView == null) {  
                LayoutInflater inflator = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
                layout = (LinearLayout) inflator.inflate(R.layout.pinned_header_listview_header_item, null);  
            } else {  
                layout = (LinearLayout) convertView;  
            }  
            ((TextView) layout.findViewById(R.id.textItem)).setText("Header for section " + section);  
            return layout;  
        }  
      
    }  
    

    由于在这个例子中,getItem和getItemId两个方法没有实际的作用,所以直接返回0和null了。
    15-23行可以看出,要实现的这个ListView有7个section,每个section有15个item。
    25-49行可以获得section header和section item的View。
    Adapter的代码我们就分析完了,大致就是将ListView分成section,然后其他的方法都是围绕着section的管理来做的。

    下面来看一下PinnedHeaderListView这个类,它有一个内部接口,这个接口就是在上面提到的Adapter中实现的,在这里都会用到,相信通过上面的讲解,大家可以看出来每个接口的大概意思。

    public static interface PinnedSectionedHeaderAdapter {  
       public boolean isSectionHeader(int position);  
      
       public int getSectionForPosition(int position);  
      
       public View getSectionHeaderView(int section, View convertView, ViewGroup parent);  
      
       public int getSectionHeaderViewType(int section);  
      
       public int getCount();  
      
    }  
    

    这个类里的变量定义和构造函数等内容我们不在这里啰嗦了,直接看最重要的一部分代码,这也是实现这个功能的关键。

    @Override  
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {  
       if (mOnScrollListener != null) {  
           mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);  
       }  
      
       if (mAdapter == null || mAdapter.getCount() == 0 || !mShouldPin || (firstVisibleItem < getHeaderViewsCount())) {  
           mCurrentHeader = null;  
           mHeaderOffset = 0.0f;  
           for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {  
               View header = getChildAt(i);  
               if (header != null) {  
                   header.setVisibility(VISIBLE);  
               }  
           }  
           return;  
       }  
      
       firstVisibleItem -= getHeaderViewsCount();  
      
       int section = mAdapter.getSectionForPosition(firstVisibleItem);  
       int viewType = mAdapter.getSectionHeaderViewType(section);  
       mCurrentHeader = getSectionHeaderView(section, mCurrentHeaderViewType != viewType ? null : mCurrentHeader);  
       ensurePinnedHeaderLayout(mCurrentHeader);  
       mCurrentHeaderViewType = viewType;  
      
       mHeaderOffset = 0.0f;  
      
       for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {  
           if (mAdapter.isSectionHeader(i)) {  
               View header = getChildAt(i - firstVisibleItem);  
               float headerTop = header.getTop();  
               float pinnedHeaderHeight = mCurrentHeader.getMeasuredHeight();  
               header.setVisibility(VISIBLE);  
               if (pinnedHeaderHeight >= headerTop && headerTop > 0) {  
                   mHeaderOffset = headerTop - header.getHeight();  
               } else if (headerTop <= 0) {  
                   header.setVisibility(INVISIBLE);  
               }  
           }  
       }  
      
       invalidate();  
    }  
    

    ListView滚动的时候,会不断的回调这个方法,然后在这个方法里实现悬浮header显示逻辑的控制。
    7-17行先对ListView添加的Header的情况进行处理,这里的Header不是我们说的section header,而是我们通过ListView的addHeaderView()添加的,文章开始的使用方法介绍中就是添加了两个Header。这种情况下,刚开始是不会有悬浮效果的,因为还没有进入section。
    23行得到了mCurrentHeader,就是要悬浮显示的View。
    24行代码保证mCurrentHeader可以悬浮在ListView顶部的固定位置。
    29-41行代码就是用来控制header移动的。因为当下方section的header快要到达顶端时,会将之前悬浮的header顶出显示区域,然后直到之前header消失,新的header就会悬浮在ListView顶端。这里的关键就是通过View的位置来计算之前悬浮header的偏移量mHeaderOffset,然后通过invalidate触发dispatchDraw方法以重绘View。

    @Override  
    protected void dispatchDraw(Canvas canvas) {  
       super.dispatchDraw(canvas);  
       if (mAdapter == null || !mShouldPin || mCurrentHeader == null)  
           return;  
       int saveCount = canvas.save();  
       canvas.translate(0, mHeaderOffset);  
       canvas.clipRect(0, 0, getWidth(), mCurrentHeader.getMeasuredHeight()); // needed  
       // for  
       // <  
       // HONEYCOMB  
       mCurrentHeader.draw(canvas);  
       canvas.restoreToCount(saveCount);  
    }  
    

    从dispatchDraw的实现中我们可以看到,确实是用到了偏移量mHeaderOffset。其中,先将canvas在Y轴方向上移动了mHeaderOffset的距离,然后截取画布,在截取后的画布上绘制header。
    通过上面一系列的处理,最终实现了我们在开头看到的ListView的悬浮效果。总结一下PinnedHeaderListView的基本思路:将ListView逻辑上分成若干个section,每个section有一个header,当header滑动到顶端时,会在ListView上绘制一个悬浮的View,View的内容就是这个header,当下面的header2达到顶部与header相交时,根据滑动距离将header向上移,直到header消失,header2会悬浮在顶端,这样就实现了我们看到的效果。

    版本控制

    版本号 更新内容 修改人 修改时间
    1.0 初次发布 lucky_tiger 2017/7/13

    项目地址

    所在文件夹 demo位置
    widget.PinnedHeaderListView com.qr.demo.widget.PinnedHeaderListViewActivity

    相关文章

      网友评论

          本文标题:PinnedHeaderListView

          本文链接:https://www.haomeiwen.com/subject/oiqwhxtx.html