600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Android自定义控件实战——实现仿IOS下拉刷新上拉加载 PullToRefreshLayout

Android自定义控件实战——实现仿IOS下拉刷新上拉加载 PullToRefreshLayout

时间:2020-07-14 17:52:59

相关推荐

Android自定义控件实战——实现仿IOS下拉刷新上拉加载 PullToRefreshLayout

下拉刷新控件,网上有很多版本,有自定义Layout布局的,也有封装控件的,各种实现方式的都有。但是很少有人告诉你具体如何实现的,今天我们就来一步步实现自己封装的 PullToRefreshLayout 完美的解决下拉刷新,上拉加载问题。

首先来分析一下原理,为什么一下拉就可以拉出来一个布局,请看下图,从图中可以看到整个屏幕来说有可见部分,有隐藏部分,当我们手指在屏幕上下拉的时候滑动距离到一定程度了就会拉出 下拉头布局,这样就达到了下拉效果。那么具体代码如何实现待我慢慢像大家解析。

1、想要实现 PullToRefreshLayout 下拉刷新控件那么我们就必须要有个容器,也就是如上图的容器,知道了需要什么那么我们就开始自定义一个容器。

这里如果不会自定义控件的同学可以参考博客 /cscfas/article/details/51330505

/*** Created by ZQY on /5/17.* <p/>* 这个是上拉加载和下拉刷新的 View* <p/>* 注:这里的 android:orientation="vertical" 只能为这个值*/public class PullToRefreshLayout extends LinearLayout {public PullToRefreshLayout(Context context) {super(context);initAnim();}public PullToRefreshLayout(Context context, AttributeSet attrs) {super(context, attrs);initAnim();}public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initAnim();}}

2、有了容器,接下来就拉实现下拉头,上拉脚。LinearLayout 我们都用过线性布局嘛,在这里要注意 android:orientation=“vertical” 只能是垂直布局。这里重写了该控件,目的是在代码中动态添加布局到控件中,实现组合控件,就是PullToRefreshLayout ,这里调用了LinearLayout的addView() 方法将布局添加到PullToRefreshLayout中。

(1)、添加头部布局,这里也就是下拉头

private void addHeaderView() {mHeaderView = mInflater.inflate(R.layout.refresh_header, this, false);mHeaderImageView = (ImageView) mHeaderView.findViewById(R.id.pull_to_refresh_image);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_text);mHeaderUpdateTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_updated_at);mHeaderUpdateTextView.setText(DataUtil.getRefreshCompleteTime());mHeaderProgressBar = (ProgressBar) mHeaderView.findViewById(R.id.pull_to_refresh_progress);measureView(mHeaderView);mHeaderViewHeight = mHeaderView.getMeasuredHeight();LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeaderViewHeight);//设置 topMargin 的值为负的 header View 高度,即将其隐藏在最上方params.topMargin = -(mHeaderViewHeight);//添加头部到布局addView(mHeaderView, params);}

(2)、添加脚部布局,这里也就是 上拉脚

private void addFooterView() {mFooterView = mInflater.inflate(R.layout.refresh_footer, this, false);mFooterImageView = (ImageView) mFooterView.findViewById(R.id.pull_to_load_image);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pull_to_load_text);mFooterProgressBar = (ProgressBar) mFooterView.findViewById(R.id.pull_to_load_progress);// 底部布局measureView(mFooterView);mFooterViewHeight = mFooterView.getMeasuredHeight();LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,mFooterViewHeight);/*** int top = getHeight();params.topMargin=getHeight();//在这里getHeight()==0,但在onInterceptTouchEvent()方法里getHeight()已经有值了,不再是0;getHeight()什么时候会赋值,稍候再研究一下由于是线性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那么footer view就会被添加到最后,并隐藏*/addView(mFooterView, params);}

看完以上代码你肯定会想就这么简单嘛!当然不是,细心的同学会发现两个函数都有调用 measureView()函数,它是干嘛的呢!下面就来看下这个函数,这个函数看起来代码和注释很多,这里的功能无非就是计算子控件在父控件中的大小。

private void measureView(View child) {/*** child.getLayoutParams();** 返回 该视图的布局参数** 此视图的父视图指定如何安排它的供应参数**/ViewGroup.LayoutParams p = child.getLayoutParams();if (p == null) {/*** 用指定的 宽度和高度 创建一组新的布局参数** @param width 宽度,或者 {@link #WRAP_CONTENT},* {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in* API Level 8),或一个固定大小的像素* @param height 高度,或者 {@link #WRAP_CONTENT},* {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in* API Level 8), 或一个固定大小的像素*/p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}/*** 是否measureChildren困难的部分:搞清楚MeasureSpec传递给特定的子控件。这种方法计算出正确的MeasureSpec一个子视图中的一维(高度或宽度)。* 目标是信息从我们MeasureSpec与子控件的的LayoutParams结合,以获得最佳的可能结果。例如,如果这个观点知道它的大小(因为它MeasureSpec有整整模式),* 子控件在其的LayoutParams已经表示,它想成为的尺寸与父控件一样,父控件应让子控件布置给精确的尺寸。* @param spec 该视图的要求* @param padding 该视图为当前维的填充和利润(如果适用)** @param childDimension 希望为子控件设置的尺寸* @return MeasureSpec 一个MeasureSpec整数为孩子**/int childWidthSpec=ViewGroup.getChildMeasureSpec(0,0+0,p.width);int lpHeight = p.height;int childHeightSpec;if (lpHeight > 0) {/***创建基于所提供的大小和模式的量度规范。该模式必须是下列之一:UNSPECIFIEDEXACTLYAT_MOST* @param size 该措施说明书的大小* @param mode 该措施规范的模式* @return 基于规模和模式的措施规范*/childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,MeasureSpec.EXACTLY);} else {childHeightSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);}/*** 这就是所谓的大一个视图应该如何。父控件 约束信息的宽度和高度参数。一个视图的实际测量工作是在onMeasure(int,int),称为该方法。因此,只有onMeasure(int,int)可以而且必须由子类重写。@param widthMeasureSpec 横向空间的需求添加到的父控件大小@param heightMeasureSpec 垂直间距需求添加到的父控件大小*/child.measure(childWidthSpec, childHeightSpec);}

3、知道了 下拉头,上拉脚 怎么实现了,接下来就看在哪里加入到PullToRefreshLayout控件中,又是如何实现动画的。请看下面代码。

(1)、这里动画实现的是刷新箭头的方向旋转,最后一行 addHeaderView() 实现了头部的添加。

/*** 初始化动画*/private void initAnim() {//加载所有的动画,我们需要的代码,而不是通过 XMLmFlipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF, 0.5f);//设置动画 均速mFlipAnimation.setInterpolator(new LinearInterpolator());/*** 动画应该持续多久,持续时间不能为负*@param durationMillis* @throws java.lang.IllegalArgumentException 如果 durationMillis < 0* @attr 参考 R.styleable #Animation_duration*/mFlipAnimation.setDuration(250);/*** 如果 fillafter 是 true ,这个动画进行改造将坚持当它完成。* 默认为 false ,如果不设置。**请注意,这适用于个别动画,当使用 {@link android.view.animation.AnimationSet AnimationSet} 链动画** @param fillAfter 如果动画结束后,动画应该应用它的转换* @attr ref android.R.styleable#Animation_fillAfter** @see #setFillEnabled(boolean)*/mFlipAnimation.setFillAfter(true);/***构造函数使用时建立一个rotateanimation 对象*** @param fromDegrees 在动画开始时应用旋转偏移。** @param toDegrees 在动画结束时应用旋转偏移。** @param pivotXType 指定如何pivotxvalue应解释。什么之中的一个* Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or* Animation.RELATIVE_TO_PARENT.** @param pivotXValue X坐标的对象被旋转的点,指定一个绝对数量,0是左边缘。这个值可以是绝对数如果pivotxtype是绝对的,或一个百分比(1是100%)否则。*** @param pivotYType 指定如何pivotyvalue应解释。什么之中的一个* Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or* Animation.RELATIVE_TO_PARENT.** @param pivotYValue X坐标的对象被旋转的点,指定一个绝对数量,0是左边缘。这个值可以是绝对数如果pivotxtype是绝对的,或一个百分比(1是100%)否则。*/mReverseFlipAnimation = new RotateAnimation(-180, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);//设置此动画的加速曲线。默认为线性插值。 这里是匀速mReverseFlipAnimation.setInterpolator(new LinearInterpolator());mReverseFlipAnimation.setDuration(250);mReverseFlipAnimation.setFillAfter(true);mInflater = LayoutInflater.from(getContext());// header view 在此添加,保证是第一个添加到linearlayout的最上端addHeaderView();}

(2)、知道了头部如何加入PullToRefreshLayout中,那么底部是如何添加的呢!其实底部的加入是有技巧的,接下来请看代码。onFihishInflate() 看到@Override 你就知道这个函数是 LinearLayout 提供,那么它有何作用呢,它的作用就是在所有的XML和头部布局都添加了的情况下加入 脚部布局。

/***完成 填充 XML格式的视图。这就是所谓的 UI填充 的最后阶段,所有子视图已被添加之后。即使子类覆盖onFinishInflate,他们应始终确保调用超级方法,使我们得到调用。 既必须调用 super.onFinishInflate();*/@Overrideprotected void onFinishInflate() {super.onFinishInflate();// footer view 在此添加保证添加到linearlayout中的最后addFooterView();initContentAdapterView();}

(3)、在上面的代码中你会看到 initContentAdapterView() 这个函数,你会想它又是什么鬼,它有什么作用呢?请看代码。

如果你有了解过,我的上一篇博客:/cscfas/article/details/51330505 ;那么你就知道在自定义控件中,如果XML布局中引入了控件,会加载该自定义控件的第二个构造函数,那么addHeaderView() 会被加载到布局中,PullToRefreshLayout 在xml 中加入的布局也会被添加到控件中。该布局可以包裹 ListView 和 GridView 及 ScrollView 控件。

/**** 初始化 adapterview像ListView,GridView等;或init ScrollView*/private void initContentAdapterView(){int count=getChildCount();if (count<3)throw new IllegalArgumentException("this layout must contain 3 child views,and AdapterView or ScrollView must in the second position!");View view=null;for (int i=0;i<count-1;++i){view=getChildAt(i);if (view instanceof AdapterView<?>){System.out.println("the type is AdapterView");mAdapterView=(AdapterView<?>)view;}if (view instanceof ScrollView){System.out.println("thie type is ScrollView");mScrollView= (ScrollView) view;}}if (mAdapterView==null&&mScrollView==null){throw new IllegalArgumentException("must contain a AdapterView or ScrollView in this layout!");}}

4、接下来看下项目中用到的常量和变量注释,这对阅读后续代码有帮助。

/*** 下拉刷新*/private static final int PULL_TO_REFRESH = 2;/*** 释放刷新*/private static final int RELEASE_TO_REFRESH = 3;/*** 刷新*/private static final int REFRESHING = 4;/*** 上拉加载*/private static final int PULL_UP_STATE = 10;/*** 下拉刷新*/private static final int PULL_DOWN_STATE = 11;/*** 最后Y轴距离*/private int mLastMotionY;/*** 锁定*/private boolean mLock;

5、了解了布局如何实现,接下来就到了手势如何实现,也就是我们下拉为什么可以拉出 下拉头,这里涉及到手势相关的概念,如果不了解手可以参考博客:/cscfas/article/details/51372342

这里就不讲事件是如何拦截,如何分发的了,我们重点来看如下代码,这里在 ACTION_DOWN时并没有拦截事件只是记录下了 Y轴坐标,为什么呢?因为PullToRefreshLayout是属于 ViewGroup 容器型的控件,如果ACTION_DOWN 直接被拦截了那么 ListVeiw 和 GridView 中的 item点击事件及 ScrollView中点击事件和长按事件将无法触发。

/*** 事件拦截* @param ev* @return*/@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {int y = (int) ev.getRawY();switch (ev.getAction()) {case MotionEvent.ACTION_DOWN: //手指按下时记录 Y轴坐标// 首先拦截down事件,记录y坐标mLastMotionY = y;break;case MotionEvent.ACTION_MOVE: //滑动时 拿到移动距离 判断是否拦截手势// deltaY > 0 是向下运动,< 0是向上运动int deltaY = y - mLastMotionY;if (isRefreshViewScroll(deltaY)) {//System.out.println("正在移动:返回true");return true;}case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:break;}return false;}

细心的同学会发现在 ACTION_MOVE 中有调用 isRefreshViewScroll() 函数,那么它又有什么功能呢!仔细看代码会发现它返回了一个 boolean 类型的值,是它控制这事件是否拦截,看到这里你是不是觉得它至关重要,那么就来分析一下它的结构吧!

mAdapterView 这个控件从何而来,有认真看过上面代码你就应该知道了。那么它是何方神圣呢?它就是 适配器填充控件后得到的结果,AdapterView 是适配器和控件的组合,这里主要是拿到AdapterView中的子控件,也就是ListView或 GridView中的Item,通过获取子控件的状态来动态设置 是否要拦截手势,以及设置 mPullState 状态。

mScrollView 控件也是同理,拿到子控件的状态来判断是否要拦截事件。具体代码都有注释请看代码,这里就不详解了。

/*** 是否应该到了父View,即PullToRefreshView滑动** @param deltaY* , deltaY > 0 是向下运动,< 0是向上运动* @return*/private boolean isRefreshViewScroll(int deltaY) {// 当头部状态是 刷新 或 底部状态是刷新时 返回 false 不拦截if (mHeaderState == REFRESHING || mFooterState == REFRESHING) {return false;}//对于ListView和GridViewif (mAdapterView != null) {// 子view(ListView or GridView)滑动到最顶端if (deltaY > 0) {View child = mAdapterView.getChildAt(0);if (child == null) {//设置状态为下拉刷新mPullState = PULL_DOWN_STATE;//设置状态为拦截return true;}// 适配中 第一个控件高度为 0 且 第一个控件可见if (mAdapterView.getFirstVisiblePosition() == 0&& child.getTop() == 0) {//设置状态为下拉刷新mPullState = PULL_DOWN_STATE;return true;}int top = child.getTop();int padding = mAdapterView.getPaddingTop();if (mAdapterView.getFirstVisiblePosition() == 0&& Math.abs(top - padding) <= 8) {//这里之前用3可以判断,但现在不行,还没找到原因mPullState = PULL_DOWN_STATE;return true;}} else if (deltaY < 0) { //如果移动的距离为 负值//获取适配中最后一个控件View lastChild = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1);if (lastChild == null) {mPullState = PULL_UP_STATE;// 如果mAdapterView中没有数据,不拦截return true;}// 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view,// 等于父View的高度说明mAdapterView已经滑动到最后if (lastChild.getBottom() <= getHeight()&& mAdapterView.getLastVisiblePosition() == mAdapterView.getCount() - 1) {mPullState = PULL_UP_STATE;return true;}}}// 对于ScrollViewif (mScrollView != null) {// 子scroll view滑动到最顶端View child = mScrollView.getChildAt(0);//当移动距离为 正值 且滚动条没有滚动if (deltaY > 0 && mScrollView.getScrollY() == 0) {mPullState = PULL_DOWN_STATE; //设置状态为下拉刷新return true;} else if (deltaY < 0&& child.getMeasuredHeight() <= getHeight()+ mScrollView.getScrollY()) {mPullState = PULL_UP_STATE; //设置为上拉加载return true;}}return false;}

6、接下来就要见证奇迹了,就是具体如何实现 下拉刷新上拉加载更多效果的业务了,还记得上面我们有讲到手势拦截吧!如果你了解手势就知道被拦截后会执行什么函数,那就是 onTouchEvent() 函数了。

(1)、首先看下 ACTION_MOVE 这里我们来计算用户手指在屏幕上的滑动距离,还记得在 onInterceptTounchEvent()中已经对 mPullState 状态做过改变,这里开始就通过判断当前状态是下拉还是上拉来处理 HeaderView 和 FootView的显示及动画效果。

/** 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return false)** 则由PullToRefreshView 的子View来处理;否则由下面的方法来处理(即由PullToRefreshView自己来处理)*/@Overridepublic boolean onTouchEvent(MotionEvent event) {if (mLock) { //当处于锁定状态时return true;}//拿到Y轴坐标int y = (int) event.getRawY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //手指按下时触发 ACTION_DOWN// onInterceptTouchEvent已经记录// mLastMotionY = y;break;case MotionEvent.ACTION_MOVE: //手指在屏幕上滑动时触发 ACTION_MOVE//拿到用户滑动的距离int deltaY = y - mLastMotionY;if (mPullState == PULL_DOWN_STATE) { //如果当前状态处于下拉刷新 PULL_DOWN_STATE 那么执行 headerPrepareToRefresh() 函数实现刷新效果// PullToRefreshView执行下拉Log.i(TAG, " pull down!parent view move!");headerPrepareToRefresh(deltaY);// setHeaderPadding(-mHeaderViewHeight);} else if (mPullState == PULL_UP_STATE) { //如果当前状态处于上拉加载 PULL_UP_STATEif (pullUpLoad) { //判断用户是否启用上拉加载// PullToRefreshView执行上拉Log.i(TAG, "pull up!parent view move!");footerPrepareToRefresh(deltaY);}}mLastMotionY = y;break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL: //当事件被取消时//获取当前header view 的topMargin 值int topMargin = getHeaderTopMargin();if (mPullState == PULL_DOWN_STATE) { //如果当前状态是下拉刷新if (topMargin >= 0) {// 开始刷新headerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}} else if (mPullState == PULL_UP_STATE) { //如果当前状态处于上拉加载if (pullUpLoad) {if (Math.abs(topMargin) >= mHeaderViewHeight+ mFooterViewHeight) {// 开始执行footer 刷新footerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}}}break;}return super.onTouchEvent(event);}

(2)、处理下拉或上拉布局被拉出效果,接下来看 headPrepareToRefresh() 和 footerPrepareToRefresh() 这两个函数实现了上拉及下拉效果 ,这里要注意 mHeaderState、mFooterState 的状态改变,它决定这是否释放刷新

/*** header 准备刷新,手指移动过程,还没有释放** @param deltaY* ,手指滑动的距离*/private void headerPrepareToRefresh(int deltaY) {int newTopMargin = changingHeaderViewTopMargin(deltaY);// 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {mHeaderTextView.setText(R.string.pull_to_refresh_release_label);mHeaderUpdateTextView.setVisibility(View.VISIBLE);mHeaderImageView.clearAnimation();mHeaderImageView.startAnimation(mFlipAnimation);//改变状态为释放刷新mHeaderState = RELEASE_TO_REFRESH;} else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放mHeaderImageView.clearAnimation();mHeaderImageView.startAnimation(mFlipAnimation);mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);mHeaderState = PULL_TO_REFRESH;}}

/*** footer 准备刷新,手指移动过程,还没有释放 移动footer view高度同样和移动header view* 高度是一样,都是通过修改header view的topmargin的值来达到** @param deltaY* ,手指滑动的距离*/private void footerPrepareToRefresh(int deltaY) {int newTopMargin = changingHeaderViewTopMargin(deltaY);// 如果header view topMargin 的绝对值大于或等于header + footer 的高度// 说明footer view 完全显示出来了,修改footer view 的提示状态if (Math.abs(newTopMargin) >= (mHeaderViewHeight + mFooterViewHeight)&& mFooterState != RELEASE_TO_REFRESH) {mFooterTextView.setText(R.string.pull_to_refresh_footer_release_label);mFooterImageView.clearAnimation();mFooterImageView.startAnimation(mFlipAnimation);mFooterState = RELEASE_TO_REFRESH;} else if (Math.abs(newTopMargin) < (mHeaderViewHeight + mFooterViewHeight)) {mFooterImageView.clearAnimation();mFooterImageView.startAnimation(mFlipAnimation);mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);mFooterState = PULL_TO_REFRESH;}}

(3)、仔细阅读上面代码,会发现所有的判断跟随着这个 headerPrepareToRefresh() 函数的返回值决定,接下来看下这个函数。判断当前 mPullState 状态 及 拉动距离是否大于设置距离,动态返回 TopMargin 及拉出的距离

/*** 修改Header view top margin的值** @description* @param deltaY*/private int changingHeaderViewTopMargin(int deltaY) {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();float newTopMargin = params.topMargin + deltaY * 0.4f;//这里对上拉做一下限制,因为当前上拉后然后不释放手指直接下拉,会把下拉刷新给触发了//表示如果是在上拉后一段距离,然后直接下拉if(deltaY>0&&mPullState == PULL_UP_STATE&&Math.abs(params.topMargin) <= mHeaderViewHeight){return params.topMargin;}//同样地,对下拉做一下限制,避免出现跟上拉操作时一样的bugif(deltaY<0&&mPullState == PULL_DOWN_STATE&&Math.abs(params.topMargin)>=mHeaderViewHeight){return params.topMargin;}params.topMargin = (int) newTopMargin;mHeaderView.setLayoutParams(params);/*** 无效整个视图。如果视图是可见的,** {@link #onDraw(android.graphics.Canvas)} 将在某个时候被调用** 这必须从UI线程调用。从非UI线程,致电致电** {@link #postInvalidate()}.*/invalidate();return params.topMargin;}

(4)、ACTION_UP、ACTION_CANCEL 处理释放刷新和取消执行刷新,首先拿到 topMargin 既拉动的距离,通过判断拉动距离和 mPullState 状态来决定是释放刷新还是取消执行刷新。

case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL: //当事件被取消时//获取当前header view 的topMargin 值int topMargin = getHeaderTopMargin();if (mPullState == PULL_DOWN_STATE) { //如果当前状态是下拉刷新if (topMargin >= 0) {// 开始刷新headerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}} else if (mPullState == PULL_UP_STATE) { //如果当前状态处于上拉加载if (pullUpLoad) {if (Math.abs(topMargin) >= mHeaderViewHeight+ mFooterViewHeight) {// 开始执行footer 刷新footerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}}}break;

(5)、headerRefreshing() 、footerRefreshing() 释放刷新,这里将 Runnable 添加到UI线程中,延迟1500 毫秒达到,下拉头或上拉脚停顿效果,这里主要回调监听接口,该接口是调用 PullToRefreshLayout 控件的 Activity或FrangMent 中实现。

/*** 下拉头释放刷新**/private void headerRefreshing() {mHeaderState = REFRESHING;setHeaderTopMargin(0);mHeaderImageView.setVisibility(View.GONE);mHeaderImageView.clearAnimation();mHeaderImageView.setImageDrawable(null);mHeaderProgressBar.setVisibility(View.VISIBLE);mHeaderTextView.setText(R.string.pull_to_refresh_refreshing_label);if (mOnHeaderRefreshListener != null) {/*** 使Runnable被添加到消息队列,经过规定的时间之后运行。** 运行将运行在用户界面线程。既UI线程中*/this.postDelayed(new Runnable() {@Overridepublic void run() {mOnHeaderRefreshListener.onHeaderRefresh(PullToRefreshView.this);}}, 1500);}}

/*** 底部释放刷新*/private void footerRefreshing() {mFooterState = REFRESHING;int top = mHeaderViewHeight + mFooterViewHeight;setHeaderTopMargin(-top);mFooterImageView.setVisibility(View.GONE);mFooterImageView.clearAnimation();mFooterImageView.setImageDrawable(null);mFooterProgressBar.setVisibility(View.VISIBLE);mFooterTextView.setText(R.string.pull_to_refresh_footer_refreshing_label);if (mOnFooterRefreshListener != null) {this.postDelayed(new Runnable() {@Overridepublic void run() {mOnFooterRefreshListener.onFooterRefresh(PullToRefreshView.this);}}, 1500);}}

(5)、注意在刷新失败的时候会执行 setHeaderMargin() 该函数作用主要是实现布局的隐藏

/*** 设置header view 的topMargin的值** @description* @param topMargin* ,为0时,说明header view 刚好完全显示出来; 为-mHeaderViewHeight时,说明完全隐藏了*/private void setHeaderTopMargin(int topMargin) {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();params.topMargin = topMargin;mHeaderView.setLayoutParams(params);invalidate();}

7、以上步骤基本实现了整个下拉刷新,上拉加载的功能,但是美中不足,刷新完成后我们还需要隐藏我们的布局,下面的代码是更新完后恢复初始化状态

/*** header view 完成更新后恢复初始状态** @description hylin -7-31上午11:54:23*/public void onHeaderRefreshComplete() {setHeaderTopMargin(-mHeaderViewHeight);mHeaderImageView.setVisibility(View.VISIBLE);mHeaderImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow);mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);mHeaderProgressBar.setVisibility(View.GONE);mHeaderState = PULL_TO_REFRESH;}

/*** footer view 完成更新后恢复初始状态*/public void onFooterRefreshComplete() {setHeaderTopMargin(-mHeaderViewHeight);mFooterImageView.setVisibility(View.VISIBLE);mFooterImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow_up);mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);mFooterProgressBar.setVisibility(View.GONE);mFooterState = PULL_TO_REFRESH;}

8、以上基本实现了下拉刷新上拉加载,博客也写累了,剩余的功能我就不贴代码了,可以参看Demo

下载地址:/detail/cscfas/9524306

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。