Android开源框架分析系列-StickyListHeaders源码解析

来源:互联网 发布:c语言函数库百度文库 编辑:程序博客网 时间:2024/06/06 02:14

Author:Hyman Lee
Email:hyman.dev@gmail.com
Github:MrBigBang
项目地址:StickyListHeaders

1.功能介绍

这个开源库可以实现的UI效果:粘性头部列表以及可隐藏展开列表。效果很赞,“开发者头条”这个app上有用到。
主要特点:
(1)使用静态代理模式,对adapter进行封装,外界使用过程中定义的adapter只需要按照常规实现即可,但必须实现StickyListHeadersAdapter接口,可以选择性实现SectionIndexer接口。
(2)WrapperView继承自ViewGroup,其内部包含一个item(View)、一个header(View)和一个divider(drawable),但是header和divider是不能共存的有一个必为null。并实现了对item、header、divider的复用。

2.总体设计

StickyListHeaders这个开源库结构还是比较简单的,总体结构图如下:
UML ![这里写图片描述]
(1)主体为StickyListHeadersListView这个类,这个类继承值FrameLayout,其中有两个布局,一个就是ListView的子类WrapperViewList,另一个就是所谓的粘性Header(下面统称为StickyHeader,防止和WrapperView中的header混淆)。
  这个类内部定义了三个接口,供外界实现交互:

  - OnHeaderClickListener
  - OnStickyHeaderOffsetChangedListener
  - OnStickyHeaderChangedListener

  主要负责:测量StickyHeader(有个measureHeader()方法),在回调函数onDispatchDrawOccurred中绘制StickyHeader,控制内部StickyHeader的显示及变化(通过swapHeader()方法),以及控制点击事件分发到StickyHeader还是WrapperViewList。
(2)WrapperViewList继承自ListView,在绘制自身的时候会根据mTopClippingLength的值是否为0将canvas进行相应的裁剪,留出空间给StickyListHeadersListView绘制StickyHeader以及paddingTop,同时这样子处理可以保证StickyHeader绘制在ListView的上面。
  主要负责确定点击时的选中区域,通过performItemClick方法确定点击背景大小为WrapperView中的item大小,并通过反射获取点击时出现点击背景效果的区域Rect,在dispatchDraw方法中调用positionSelectorRect确定该Rect的top大小。
(3)AdapterWrapper继承自BaseAdapter,实现了StickyListHeadersAdapter接口,其中有个StickyListHeadersAdapter接口的实例(其实就是外界定义的adapter,后面就统称为mDelegate),该mDelegate负责代理实现AdapterWrapper的功能。
  主要负责通过getView方法将从mDelegate中的getView方法获得的item和getHeaderView方法获得的header组装成一个WrapperView,但是其中会通过方法previousPositionHasSameHeader(position)判断当前位置的WrapperView是否是一个新Section的第一个元素。如果是第一个就会通过configureHeader(wv, position)方法获取一个header,这个方法实现中会对header进行复用;如果不是第一个,就调用recycleHeaderIfExists(wv)方法将这个header缓存在mHeaderCache(List<View>)中。
(4) DistinctMultiHashMap是一个建立一对多关系的集合(基于LinkedHashMap)。DualHashMap是一个实现可以通过key获取value也可以通过value获取key的数据结构(基于两个HashMap)。
(5)ExpandableStickyListHeadersAdapterExpandableStickyListHeadersListView实现了点击列表某个Section第一个元素对应的header时会进行展开或收起的功能,主要是使用(4)中的两个数据结构将headerId与对应Section所有WrapperView建立一对多的存储关系。在外界调用的时候设置OnHeaderClickListener根据点击的header当前状态进行展开或收起,其中ExpandableStickyListHeadersListView中定义了一个接口IAnimationExecutor,让调用者自行定义展开和收起的动画效果。

3.杂谈

(1)巧妙处理点:

  其中源码中对StickyListHeadersListView显示出来的divider进行了巧妙的处理,将WrapperViewList的divider设置为null。

 @TargetApi(Build.VERSION_CODES.HONEYCOMB)    public StickyListHeadersListView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();        // Initialize the wrapped list        mList = new WrapperViewList(context);        // null out divider, dividers are handled by adapter so they look good with headers        mDivider = mList.getDivider();        mDividerHeight = mList.getDividerHeight();        mList.setDivider(null);        mList.setDividerHeight(0);        ...    }

  并将其取出来设置给mAdapter(AdapterWrapper):

public void setAdapter(StickyListHeadersAdapter adapter) {        ...        mAdapter.setDivider(mDivider, mDividerHeight);        mList.setAdapter(mAdapter);        clearHeader();    }

  在AdapterWrapper中,通过调用WrapperView的update(View item, View header, Drawable divider, int dividerHeight)方法将divider以及dividerHeight设置给WrapperView。

@Override    public WrapperView getView(int position, View convertView, ViewGroup parent) {        ...        wv.update(item, header, mDivider, mDividerHeight);        return wv;    }

  更新完divider和dividerHeight后会调用invalidate()方法进行重新绘制,在下面代码中会完成divider的绘制。

@Override    protected void dispatchDraw(Canvas canvas) {        super.dispatchDraw(canvas);        if (mHeader == null && mDivider != null&&mItem.getVisibility()!=View.GONE) {            // Drawable.setBounds() does not seem to work pre-honeycomb. So have            // to do this instead            if (Build.VERSION.SDK_INT 
(2)通过一些自定义属性可以改变展现效果:

  - hasStickyHeaders,是否显示粘性header效果。
  - isDrawingListUnderStickyHeader,表示ListView的绘制区域是从屏幕可见区域的top开始还是从StickyHeader的bottom处开始。

1、文章最初于2016-5-5发布在https://github.com/aosp-exchange-group/android-open-project-analysis/tree/master/view/list-view/sticky-list-headers
2、本人基于该框架源码中的一些思想,写了一个树形菜单控件TreeMenu

1 0
原创粉丝点击