网站建设 本溪红海传媒,建设部网站官网建筑施工合同,长沙网络营销,上海网站公司电话本文将会介绍有阻尼下拉刷新列表的实现#xff0c;先来看看效果预览#xff1a;这是下拉状态#xff1a;这是下拉松开手指后listView回滚到刷新状态时的样子#xff1a;1. 如何调用虽然效果图看起来样子不太好看#xff0c;主要是因为那个蓝色的背景对不对#xff0c;没关…本文将会介绍有阻尼下拉刷新列表的实现先来看看效果预览这是下拉状态这是下拉松开手指后listView回滚到刷新状态时的样子1. 如何调用虽然效果图看起来样子不太好看主要是因为那个蓝色的背景对不对没关系这只是一个背景而已在了解了我们这个下拉刷新列表的实现之后你就可以很轻松地修改这个背景从而实现你想要的UI效果话不多说下面我们先来讲讲这个下拉刷新列表是如何使用的这也是我们编写代码所要实现的目标。final PullToRefreshListView eListView (PullToRefreshListView) rootView.findViewById(R.id.profile_listView);eListView.setOnLoadCallBack(new PullToRefreshListView.OnLoadCallBack() {Overridepublic int whereToLoad() {return PullToRefreshListView.DEFAULT_WHERE_TO_LOAD;}Overridepublic void onLoad() {eListView.postDelayed(new Runnable() {Overridepublic void run() {eListView.setLoadingFinish();}}, 5000);}Overridepublic void cancelLoad() {}Overridepublic Drawable refreshDrawable() {return new ColorDrawable(Color.CYAN);}});eListView.setAdapter(new BaseAdapter() {Overridepublic int getCount() {return 30;}Overridepublic Object getItem(int position) {return null;}Overridepublic long getItemId(int position) {return 0;}Overridepublic View getView(int position, View convertView, ViewGroup parent) {TextView tv;if (convertView null) {tv new TextView(getActivity());tv.setGravity(Gravity.CENTER_VERTICAL);tv.setHeight(200);tv.setBackgroundColor(Color.WHITE);} else {tv (TextView) convertView;}tv.setText(position);return tv;}});在上述代码中我们可以看到PullToRefreshListView的使用在adapter上跟ListView是一样的这个当然因为我们实现下拉刷新功能并不需要修改数据适配器。我们也看到PullToRefreshListView的实例需要设置一个OnLoadCallBack回调该回调需要实现4个方法包括/*** 下拉刷新的回调*/public interface OnLoadCallBack {/*** 下拉结束后将listView定位到哪个位置等待刷新完成* return listView的定位y坐标值in dp*/int whereToLoad();/*** 下拉结束后进行刷新的回调*/void onLoad();/*** 取消刷新*/void cancelLoad();/*** 下拉刷新的背景* return 背景drawable*/Drawable refreshDrawable();}whereToLoad方法告知PullToRefreshListView对象下拉刷新时停留在哪个位置具体点说也就是上述第二章效果图中蓝色背景的高度。onLoad方法是下拉刷新的回调调用者可以在这里实现刷新动作。cancelLoad方法是取消刷新动作的回调调用者需要在这里将刷新动作取消。根据上述方法我们可以猜测在onLoad方法中执行的应该是一个线程或者AsyncTask而在cancelLoad方法中要做的就是将这个线程或者AsyncTask取消掉。最后还有一个refreshDrawable方法这个方法是为修改listView的背景而提供给调用者的调用者可以返回任意一个喜欢的背景Drawable。知道如何调用以后我们就要一步一步地实现这个PullToRefreshListView了。2. 在dispatchDraw中重画子View实现下拉视觉PullToRefreshListView实现的关键在于重画该listVIew的子View。重画ViewGroup的子View一般是在dispatchDraw方法中实现的。因此我们的PullToRefreshListView继承自ListView类重载其dispatchDraw方法。Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);if (distanceY 0) {if (refreshDrawable null) {refreshDrawable onLoadCallBack.refreshDrawable();}if (refreshDrawable null) {canvas.drawColor(Color.GRAY);} else {int left getPaddingLeft();int top getPaddingTop();refreshDrawable.setBounds(left, top, getWidth()left, getHeight()top);refreshDrawable.draw(canvas);}canvas.save();canvas.translate(getPaddingLeft(), getPaddingTop() distanceY);for (int i0;iView child getChildAt(i);drawChild(canvas, child, getDrawingTime());}canvas.restore();}}重画子View的关键在于这一句代码canvas.translate(getPaddingLeft(), getPaddingTop() distanceY);在重画子View之前我们需要先将canvas向上移动distanceY距离。这是为什么呢我们先来看看在canvas画子View的方法drawChild方法的文档是怎么说的。protected boolean drawChild (Canvas canvas, View child, long drawingTime)Added in API level 1 Draw one child of this View Group. This method is responsible for getting the canvas in the right state. This includes clipping, translating so that the childs scrolled origin is at 0, 0, and applying any animation transformations.Parameters canvas The canvas on which to draw the child child Who to draw drawingTime The time at which draw is occurring Returns True if an invalidate() was issued我来翻译一下drawChild方法可以画出这个View Group的一个子View。该方法需要使canvas处于一个正确的状态该状态就是通过对canvas进行clip裁剪translate评议操作等以使得该子View位于canvas的(0,0)位置。什么意思呢简单来说就是drawChild方法会将child view画在canvas的(0,0)位置因此为了使得该child view位于canvas的正确位置我们需要在重画之前对canvas进行裁剪平移等操作。举个例子有一个canvas和一个child view本来child view要画在(0,0)位置上于是呈现在我们眼前的child view就是位于canvas的顶部但是如果在画之前我们将canvas向上移动100个像素单位然后再将child view画在(0,0)位置上那么呈现在我们眼前的child view的位置将会是位于canvas的(0,100)位置上。根据以上分析我们可以知道重画子View的原理就是当PullToRefreshListView已经滚动到顶部的时候通过监控滑动手势来计算distanceY从而确定要将canvas向上移动多少再重画子View就可以实现PullToRefreshListView跟随滑动手势进行下拉的功能了。3. 计算下拉距离实现了重画以后我们需要做的就是如何计算distanceY。我们的初步想法是根据滑动的距离来计算考虑到我们要实现阻尼效果即随着滑动距离的变长PullToRefreshListView的下拉距离会越来越短。在PullToRefreshListView实现中我使用指数函数来实现这一阻尼效果具体计算如下distanceY ev.getY() - pullStartY;distanceY (float) (Math.exp(-ev.getY() / pullStartY / 40) * distanceY);我们知道负指数是加速度随距离变小的单调递增函数我使用手指滑动距离计算负指数作为PullToRefreshListView的滑动距离的参考标准便可以实现有阻尼下拉效果。4. 监控手势判断ListView是否进入下拉状态并更新distanceY更进一步我们要实现的就是对手势的监控在PullToRefreshListView中我们在onTouchEvent方法中进行处理。Overridepublic boolean onTouchEvent(MotionEvent ev) {if (lastAction -1 ev.getActionMasked() MotionEvent.ACTION_DOWN) {// 按下的时候lastAction MotionEvent.ACTION_DOWN;cancelAnimating();L.d(TAG, touch down);} else if (lastAction MotionEvent.ACTION_MOVE ev.getActionMasked() MotionEvent.ACTION_UP) {// 放开手指开始回滚isPulling false;lastAction -1;startAnimating();L.d(TAG, touch up);} else if (lastAction MotionEvent.ACTION_DOWN) {if (ev.getActionMasked() MotionEvent.ACTION_MOVE) {// 在按下手指的基础上开始滑动if (isTop !isPulling) {// listView在顶部而且不处于下拉刷新状态开始下拉pullStartY ev.getY();lastAction MotionEvent.ACTION_MOVE;isPulling true;}}} else if (lastAction MotionEvent.ACTION_MOVE) {if (isTop) {// 下拉distanceY ev.getY() - pullStartY;L.d(TAG, distanceY );if (distanceY 0) {distanceY (float) (Math.exp(-ev.getY() / pullStartY / 40) * distanceY);// 在下拉状态时取消系统对move动作的响应完全由本类响应ev.setAction(MotionEvent.ACTION_DOWN);} else {distanceY 0;// 在下拉过程中往上拉动该listView使得其回到顶部位置则将该move动作交由系统进行响应ev.setAction(MotionEvent.ACTION_MOVE);}} else {// 在下拉过程中往上拉动listView使listView往下滚动到其没有滚动到顶部则取消其下拉状态回到手指按下的初始状态lastAction MotionEvent.ACTION_DOWN;isPulling false;distanceY 0;}}return super.onTouchEvent(ev);}这一段代码相对有一点复杂我们慢慢解析。首先我们有一个lastAction变量来记录上一个手势是什么有一个isPulling变量来记录当前PullToRefreshListView是否处于下拉状态有一个isTop变量记录当前PullToRefreshListView是否已经滚动到顶部。在onTouchEvent方法的重载实现中一开始PullToRefreshListView没有接受任何手势然后当用户按下手指出发ACTION_DOWN事件时我记录下这个动作然后当用户进行滑动时如果此时PullToRefreshListView没有“滚动到顶部”则不做任何处理反之则将lastAction更新为ACTION_MOVE状态更新isPulling变量记录当前手指的位置作为计算下拉距离的起始位置开始下拉刷新然后在下拉的过程中计算PullToRefreshListView下拉的距离以重画子View。在这个手势处理的实现中当用户在下拉过程中突然将PullToRefreshListView往上拉如果将PullToRefreshListView 拉到不处于“滚动到顶部的状态”时则重置下拉状态使得lastAction MotionEvent.ACTION_DOWN;于是PullToRefreshListView接下来的下滑手势响应权被交还给系统知道用户又将PullToRefreshListView下拉到“滚动到顶部”状态则又重新执行上述操作使PullToRefreshListView进入下拉状态。5. 如何判断ListView是否已经滚动到顶部下一步我们如何判断ListView是否处于“滚动到顶部”状态呢这一问题我PullToRefreshListView的onScroll中解决。setOnScrollListener(new OnScrollListener() {Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {}Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {// 没有子view的时候(没有数据或者被拉到看不到子view)意味着该listView滚动到顶部if (getChildCount() 0) {isTop true;return;}if (firstVisibleItem 0) {View firstView getChildAt(0);if (firstView.getTop() distanceY 0) {// 第一个view可见且其相对parent(该listView)的顶部距离大于等于0意味着该listView也是滚动到顶部isTop true;return;}}isTop false;}});为PullToRefreshListView设置一个OnScrollListener回调并在其onScroll方法中监控其滚动位置具体看注释也已经一目了然我就不多解释了。6. 下拉后的回滚动画最后当下拉结束松开手指时我们需要为PullToRefreshListView执行一个回滚的动画我们在onTouchEvent方法中看到// ......else if (lastAction MotionEvent.ACTION_MOVE ev.getActionMasked() MotionEvent.ACTION_UP) {// 放开手指开始回滚isPulling false;lastAction -1;startAnimating();L.d(TAG, touch up);}// ......startAnimating方法的实现如下/*** 下拉结束时进行回滚动画并执行刷新动作*/private void startAnimating() {int whereToLoad dp2px(onLoadCallBack.whereToLoad());final boolean toLoad;if (distanceY whereToLoad) {pullCancelAnimator ValueAnimator.ofFloat(distanceY, 0);toLoad false;} else {pullCancelAnimator ValueAnimator.ofFloat(distanceY, whereToLoad);toLoad true;}pullCancelAnimator.setDuration((long) (DEFAULT_BASE_ANIMATING_TIME_PER_100DP*px2dp(distanceY)/100));pullCancelAnimator.setInterpolator(new AccelerateDecelerateInterpolator());pullCancelAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {Overridepublic void onAnimationUpdate(ValueAnimator animation) {distanceY (float) animation.getAnimatedValue();ViewCompat.postInvalidateOnAnimation(PullToRefreshListView.this);}});pullCancelAnimator.addListener(new Animator.AnimatorListener() {Overridepublic void onAnimationStart(Animator animation) {}Overridepublic void onAnimationEnd(Animator animation) {post(new Runnable() {Overridepublic void run() {pullCancelAnimator null;if (toLoad) {onLoadCallBack.onLoad();}}});}Overridepublic void onAnimationCancel(Animator animation) {post(new Runnable() {Overridepublic void run() {pullCancelAnimator null;if (toLoad) {onLoadCallBack.cancelLoad();}}});}Overridepublic void onAnimationRepeat(Animator animation) {}});pullCancelAnimator.start();}我使用ValueAnimator来实现这一回滚动画其中为ValueAnimator设置的回调中在动画更新和动画结束以及动画取消中分别调用了OnLoadCallBack的3歌回调方法从而实现PullToRefreshListView的下拉刷新动作。我们可以看到onLoad方法是在UI线程执行的因此如果在onLoad方法中执行耗时操作的话需要在后台线程中操作这与我们前面的解析是对应的。7. 改进和问题(1) 我们可以将onLoad回调修改成一个返回一个异步任务对象的方法然后PullToRefreshListView在下拉结束后执行这个异步任务因此我们就可以不需要cancelLoading回调了直接就可以在PullToRefreshListView内部进行取消操作这样做可以增强封装性但相对目前的做法自由度就没有那么高了。(2) 回滚动画应该也可以进行优化具体怎么优化我也不清楚。。。各位朋友有好的想法可以在评论区提议一下谢谢~(3) 下拉的时候对多点触碰的响应并不完美虽然也可以接受但是做不到像qq客户端的聊天列表那样。8. 源码至此我已经解析了如何实现一个下拉刷新列表PullToRefreshListView的源码如下。import android.animation.Animator;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.drawable.Drawable;import android.support.v4.view.ViewCompat;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.AccelerateDecelerateInterpolator;import android.widget.AbsListView;import android.widget.ListView;import com.ivan.healthcare.healthcare_android.log.L;/*** 支持下拉刷新的的listView* Created by Ivan on 16/2/14.*/public class PullToRefreshListView extends ListView {private final String TAG PullToRefreshListView;private final int DEFAULT_BASE_ANIMATING_TIME_PER_100DP 150;public static final int DEFAULT_WHERE_TO_LOAD 80;private int lastAction -1;private float pullStartY -1;private boolean isTop true;private float distanceY 0;private boolean isPulling false;private ValueAnimator pullCancelAnimator;private Context context;private Drawable refreshDrawable;private OnLoadCallBack onLoadCallBack new OnLoadCallBack() {Overridepublic int whereToLoad() {return DEFAULT_WHERE_TO_LOAD;}Overridepublic void onLoad() {}Overridepublic void cancelLoad() {}Overridepublic Drawable refreshDrawable() {return null;}};public PullToRefreshListView(Context context) {super(context);initView(context);}public PullToRefreshListView(Context context, AttributeSet attrs) {super(context, attrs);initView(context);}private void initView(Context context) {this.context context;setOnScrollListener(new OnScrollListener() {Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {}Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {// 没有子view的时候(没有数据或者被拉到看不到子view)意味着该listView滚动到顶部if (getChildCount() 0) {isTop true;return;}if (firstVisibleItem 0) {View firstView getChildAt(0);if (firstView.getTop() distanceY 0) {// 第一个view可见且其相对parent(该listView)的顶部距离大于等于0意味着该listView也是滚动到顶部isTop true;return;}}isTop false;}});}Overridepublic boolean onTouchEvent(MotionEvent ev) {if (lastAction -1 ev.getActionMasked() MotionEvent.ACTION_DOWN) {// 按下的时候lastAction MotionEvent.ACTION_DOWN;cancelAnimating();L.d(TAG, touch down);} else if (lastAction MotionEvent.ACTION_MOVE ev.getActionMasked() MotionEvent.ACTION_UP) {// 放开手指开始回滚isPulling false;lastAction -1;startAnimating();L.d(TAG, touch up);} else if (lastAction MotionEvent.ACTION_DOWN) {if (ev.getActionMasked() MotionEvent.ACTION_MOVE) {// 在按下手指的基础上开始滑动if (isTop !isPulling) {// listView在顶部而且不处于下拉刷新状态开始下拉pullStartY ev.getY();lastAction MotionEvent.ACTION_MOVE;isPulling true;}}} else if (lastAction MotionEvent.ACTION_MOVE) {if (isTop) {// 下拉distanceY ev.getY() - pullStartY;L.d(TAG, distanceY );if (distanceY 0) {distanceY (float) (Math.exp(-ev.getY() / pullStartY / 40) * distanceY);// 在下拉状态时取消系统对move动作的响应完全由本类响应ev.setAction(MotionEvent.ACTION_DOWN);} else {distanceY 0;// 在下拉过程中往上拉动该listView使得其回到顶部位置则将该move动作交由系统进行响应ev.setAction(MotionEvent.ACTION_MOVE);}} else {// 在下拉过程中往上拉动listView使listView往下滚动到其没有滚动到顶部则取消其下拉状态回到手指按下的初始状态lastAction MotionEvent.ACTION_DOWN;isPulling false;distanceY 0;}}return super.onTouchEvent(ev);}Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);if (distanceY 0) {if (refreshDrawable null) {refreshDrawable onLoadCallBack.refreshDrawable();}if (refreshDrawable null) {canvas.drawColor(Color.GRAY);} else {int left getPaddingLeft();int top getPaddingTop();refreshDrawable.setBounds(left, top, getWidth()left, getHeight()top);refreshDrawable.draw(canvas);}canvas.save();canvas.translate(getPaddingLeft(), getPaddingTop() distanceY);for (int i0;iView child getChildAt(i);drawChild(canvas, child, getDrawingTime());}canvas.restore();}}/*** 下拉结束时进行回滚动画并执行刷新动作*/private void startAnimating() {int whereToLoad dp2px(onLoadCallBack.whereToLoad());final boolean toLoad;if (distanceY whereToLoad) {pullCancelAnimator ValueAnimator.ofFloat(distanceY, 0);toLoad false;} else {pullCancelAnimator ValueAnimator.ofFloat(distanceY, whereToLoad);toLoad true;}pullCancelAnimator.setDuration((long) (DEFAULT_BASE_ANIMATING_TIME_PER_100DP*px2dp(distanceY)/100));pullCancelAnimator.setInterpolator(new AccelerateDecelerateInterpolator());pullCancelAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {Overridepublic void onAnimationUpdate(ValueAnimator animation) {distanceY (float) animation.getAnimatedValue();ViewCompat.postInvalidateOnAnimation(PullToRefreshListView.this);}});pullCancelAnimator.addListener(new Animator.AnimatorListener() {Overridepublic void onAnimationStart(Animator animation) {}Overridepublic void onAnimationEnd(Animator animation) {post(new Runnable() {Overridepublic void run() {pullCancelAnimator null;if (toLoad) {onLoadCallBack.onLoad();}}});}Overridepublic void onAnimationCancel(Animator animation) {post(new Runnable() {Overridepublic void run() {pullCancelAnimator null;if (toLoad) {onLoadCallBack.cancelLoad();}}});}Overridepublic void onAnimationRepeat(Animator animation) {}});pullCancelAnimator.start();}private void cancelAnimating() {if (pullCancelAnimator ! null) {pullCancelAnimator.cancel();}}private float px2dp(float pxvalue) {return (pxvalue - 0.5f) /context.getResources().getDisplayMetrics().density;}private int dp2px(float dpvalue) {return (int) (dpvalue * context.getResources().getDisplayMetrics().density 0.5f);}/*** 下拉刷新的回调*/public interface OnLoadCallBack {/*** 下拉结束后将listView定位到哪个位置等待刷新完成* return listView的定位y坐标值in dp*/int whereToLoad();/*** 下拉结束后进行刷新的回调*/void onLoad();/*** 取消刷新*/void cancelLoad();/*** 下拉刷新的背景* return 背景drawable*/Drawable refreshDrawable();}/*** 设置下拉刷新回调* param cb 回调*/public void setOnLoadCallBack(OnLoadCallBack cb) {this.onLoadCallBack cb;}/*** 刷新动作结束后调用该方法结束刷新使得listView回滚到顶部*/public void setLoadingFinish() {startAnimating();}}以上这篇android 有阻尼下拉刷新列表的实现方法就是小编分享给大家的全部内容了希望能给大家一个参考也希望大家多多支持脚本之家。