PullToRefresh源码分析(I)

来源:互联网 发布:java如何制作图片上传 编辑:程序博客网 时间:2024/06/12 21:40

最近提测期,开发任务相对轻松。作为一个Android菜鸟,就要利用好这些时间来提升自己。

虽然博客写得很烂,但是也得写。不为给多少人带去帮助,只为重新整理一下自己的思路。如果有人看且觉得有益的话,我很高兴的。

        

        恩。那么开始。拿到PullToRefresh这么大一坨代码的时候, 我先做的是找到其核心类,然后找出其关系(集成、实现等),作为一条主线。把核心类的成员变量和函数大致列出来。

然后它是下面这样的:


PullToRefreshBase 总共1600多行,而PullToRefreshAdapterView 400多行,于是我决定从后者看起。

先看看它集成OnScrollListener都干了些什么。OnScrollListener一共就2个方法,找到对应的实现代码,先看onScroll:

<span style="white-space:pre"></span>if (null != mOnLastItemVisibleListener) {mLastItemVisible = (totalItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount - 1);}// If we're showing the indicator, check positions...if (getShowIndicatorInternal()) {updateIndicatorViewsVisibility();}// Finally call OnScrollListener if we have oneif (null != mOnScrollListener) {mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);}
很清楚,一共干了3件事情:

        第1件:给mLastItemVisible赋值(有没有滑到底端)

        第2件:如果需要显示mIndicatorIvTop和mIndicatorBottom,更新它们的状态

        第3件:如果我们调用过setOnScrollListener的话,这里会执行回调

总结一下就是滑动的时候更新一下mIndicatorIvTop和mIndicatorBottom的状态,然后回调我们自己写的onScroll方法。


再看onScrollStateChanged()方法,它回调了我们set进去(当然也可以不set,那就什么都没做)的2个listener的方法。

<span style="white-space:pre"></span>public final void onScrollStateChanged(final AbsListView view, final int state) {if (state == OnScrollListener.SCROLL_STATE_IDLE && null != mOnLastItemVisibleListener && mLastItemVisible) {mOnLastItemVisibleListener.onLastItemVisible();}if (null != mOnScrollListener) {mOnScrollListener.onScrollStateChanged(view, state);}}
 到这里可以得出结论,实现OnScrollListener接口,主要是为了给使用者一些扩展的空间,可以去实现一些自己想要的逻辑。

 到这里还剩3个关键成员变量没有分析,分别是mIndicatorIvTop、mIndicatorBottom和mEmptyView。

         先看mIndicatorIvTop、mIndicatorBottom,找到它们对应的类。

         

成员变量中有1个ImageView,4个动画。6个成员方法中使用了到了这4个动画。很明显IndicatorLayout是下拉刷新和松开释放时显示的动画效果。

在之前的onScroll方法中调用了updateIndicatorViewsVisibility(),而updateIndicatorViewsVisibility()中正是调用此处的show()和hide()方法来实现状态更新。

而releaseToRefresh()和pullToRefresh()分别在PullToRefreshAdapterViewBase的onPullToRefresh()和onReleaseToRefresh()中调用,选择其中1个在这里分析一下:

<span style="white-space:pre"></span>@Overrideprotected void onPullToRefresh() {super.onPullToRefresh();if (getShowIndicatorInternal()) {switch (getCurrentMode()) {case PULL_FROM_END:mIndicatorIvBottom.pullToRefresh();break;case PULL_FROM_START:mIndicatorIvTop.pullToRefresh();break;default:// NO-OPbreak;}}}
先调用了PullToRefreshBase的onPullToRefresh()方法,然后调用IndicatorLayout的pullToRefresh()方法。我们下拉控件时的所有操作都在这里了,先看看PullToRefreshBase的onPullToRefresh()方法干了些什么。

  

<span style="white-space:pre"></span>protected void onPullToRefresh() {switch (mCurrentMode) {case PULL_FROM_END:mFooterLayout.pullToRefresh();break;case PULL_FROM_START:mHeaderLayout.pullToRefresh();break;default:// NO-OPbreak;}}

这里又调用了LoadingLayout的pullToRefresh()方法。LoadingLayout类如下:

        


似曾相识,和IndicatorLayout差不多嘛。1个ImageView+1个TextView,这不就是下拉刷新时的刷新头么。看看pullToRefresh()干了什么:

<span style="white-space:pre"></span>public final void pullToRefresh() {if (null != mHeaderText) {mHeaderText.setText(mPullLabel);}// Now call the callbackpullToRefreshImpl();}

         设置了TextView的值,这就是为什么下拉刷新时各个阶段显示的字符串不同的原因所在了。此外这里调用了pullToRefreshImpl()方法,这是一个抽象方法,我们可以在子类中去实现它,在里面实现我们想加的逻辑。

 回到PullToRefreshAdapterViewBase中,追踪mIndicatorIvTop、mIndicatorBottom。找到addIndicatorViews方法里面的代码:

  

<span style="white-space:pre"></span>private void addIndicatorViews() {Mode mode = getMode();FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();if (mode.showHeaderLoadingLayout() && null == mIndicatorIvTop) {// If the mode can pull down, and we don't have one set alreadymIndicatorIvTop = new IndicatorLayout(getContext(), Mode.PULL_FROM_START);FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);params.gravity = Gravity.TOP | Gravity.RIGHT;refreshableViewWrapper.addView(mIndicatorIvTop, params);} else if (!mode.showHeaderLoadingLayout() && null != mIndicatorIvTop) {// If we can't pull down, but have a View then remove itrefreshableViewWrapper.removeView(mIndicatorIvTop);mIndicatorIvTop = null;}  …………} 
mIndicatorIvTop、mIndicatorBottom这两个控件分别被放到了FramLayout refreshableViewWrapper的右上角和右下角。先看看refreshableViewWrapper是个什么鬼东西,跟进到getRefreshablViewWrapper()方法中,它在PullToRefreshBase中。

<span style="white-space:pre"></span>protected FrameLayout getRefreshableViewWrapper() {return mRefreshableViewWrapper;}
直接返回了PullToRefreshBase的一个成员变量,这个成员变量的初始化是这样的:

       

<span style="white-space:pre"></span>private void addRefreshableView(Context context, T refreshableView) {mRefreshableViewWrapper = new FrameLayout(context);mRefreshableViewWrapper.addView(refreshableView, ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);addViewInternal(mRefreshableViewWrapper, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));}protected final void addViewInternal(View child, ViewGroup.LayoutParams params) {super.addView(child, -1, params);}

FrameLayout中放了一个充满整个FrameLayout的refreshableView(它就是那个被封装的控件,ListView、ScrollView之类的东西)。然后FrameLayout被添加到当前的LinearLayout中。

<span style="white-space:pre"></span>if (mMode.showHeaderLoadingLayout()) {addViewInternal(mHeaderLayout, 0, lp);}if (mMode.showFooterLoadingLayout()) {addViewInternal(mFooterLayout, lp);}

         mHeaderLayout和mFooterLayout也被加到了当前LinearLayout中,其中mHeaderLayout在LinearLayout顶层,mFooterLayout在底层。lp的属性是这样的(LinearLayout是Vertical的时候):

<span style="white-space:pre"></span>return new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);

至此,整个下拉刷新框架的布局比较清楚了,完整的它是这样的:

        

PullToRefresh是一个很庞大的框架,以我目前的水平很难1天时间就理解透彻,更不要说一篇博文写完。

        第一篇的最后,从源码的角度来分析下这个框架的一些API使用。

找到PullToRefreshAdapterViewBase的handleStyleAttributes方法:

        

<span style="white-space:pre"></span>@Overrideprotected void handleStyledAttributes(TypedArray a) {// Set Show Indicator to the XML value, or default valuemShowIndicator = a.getBoolean(R.styleable.PullToRefresh_ptrShowIndicator, !isPullToRefreshOverScrollEnabled());}
这个方法会在各个实现类的handleStyleAttributes中调用,用Xml初始化mShowIndicator的值。因此如果我们想要显示mIndicatorIvTop/mIndicatorIvBottom,只需要在Xml中写1句:ptrShowIndicator="true";否则设置ptrShowIndicator="false"。

@Overridepublic final T getRefreshableView() {return mRefreshableView;}
调用getRefreshableView()方法就能得到封装的ListView/ScrollView/GridView等。然后就可以为所欲为了。

PullToRefreshBase中提供了mHeaderLayout和mFooterLayout的代理。通过getLoadingLayoutProxy()方法获取。

<span style="white-space:pre"></span>public final ILoadingLayout getLoadingLayoutProxy() {<span style="white-space:pre"></span>return getLoadingLayoutProxy(true, true);<span style="white-space:pre"></span>}


<span style="white-space:pre"></span>@Overridepublic final ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd) {return createLoadingLayoutProxy(includeStart, includeEnd);}
当includeStart为true时代理包含mHeaderLayout,includeEnd为true时代理包含mFooterLayout。

<span style="white-space:pre"></span>if (includeStart && mMode.showHeaderLoadingLayout()) {proxy.addLayout(mHeaderLayout);}if (includeEnd && mMode.showFooterLoadingLayout()) {proxy.addLayout(mFooterLayout);}

<span style="white-space:pre"></span>/** * Set the Last Updated Text. This displayed under the main label when * Pulling *  * @param label - Label to set */public void setLastUpdatedLabel(CharSequence label);/** * Set the drawable used in the loading layout. This is the same as calling * <code>setLoadingDrawable(drawable, Mode.BOTH)</code> *  * @param drawable - Drawable to display */public void setLoadingDrawable(Drawable drawable);/** * Set Text to show when the Widget is being Pulled * <code>setPullLabel(releaseLabel, Mode.BOTH)</code> *  * @param pullLabel - CharSequence to display */public void setPullLabel(CharSequence pullLabel);/** * Set Text to show when the Widget is refreshing * <code>setRefreshingLabel(releaseLabel, Mode.BOTH)</code> *  * @param refreshingLabel - CharSequence to display */public void setRefreshingLabel(CharSequence refreshingLabel);/** * Set Text to show when the Widget is being pulled, and will refresh when * released. This is the same as calling * <code>setReleaseLabel(releaseLabel, Mode.BOTH)</code> *  * @param releaseLabel - CharSequence to display */public void setReleaseLabel(CharSequence releaseLabel);/** * Set's the Sets the typeface and style in which the text should be * displayed. Please see * {@link android.widget.TextView#setTypeface(Typeface) * TextView#setTypeface(Typeface)}. */public void setTextTypeface(Typeface tf);
ILoadingLayout接口中提供了以方法设置mHeaderLayout,mFooterLayout的不同状态下显示的动画和字符串值、属性,具体见代码注释。
其实现类LoadingLayoutProxy中用1个HashSet保存着代理对象,其值由getLoadingLayoutProxy()的参数决定。

                第一篇就到这里结束。

0 0
原创粉丝点击