博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
带你彻彻底底弄懂Scroller
阅读量:5338 次
发布时间:2019-06-15

本文共 16369 字,大约阅读时间需要 54 分钟。

Scroller的使用


这是一个滑动帮助类,并不可以使View真正的滑动,而是根据时间的流逝,获取插值器中的数据,传递给我们,让我们去配合scrollTo/scrollBy去让view产生缓慢滑动,产生动画的效果,其实是和属性动画同一个原理。下面是官方文档对于这个类所给的解释:

This class encapsulates scrolling. You can use scrollers (Scroller or OverScroller) to collect the data you need to produce a scrolling animation—for example, in response to a fling gesture. Scrollers track scroll offsets for you over time, but they don’t automatically apply those positions to your view. It’s your responsibility to get and apply new coordinates at a rate that will make the scrolling animation look smooth.

首先,我们要先获得这个对象,我们一起看看它的构造方法:

public Scroller (Context context)public Scroller (Context context, Interpolator interpolator)public Scroller (Context context, Interpolator interpolator, boolean flywheel)

一共有三个构造方法,我们通常用第二个比较多,给定Scroller一个插值器,让其从这种插值器中取值。那么,如何使用Scroller呢,只需调用下面的代码即可:

Scroller.(int startX, int startY, int dx, int dy, int duration);invalidate();

就可以为Scroller指定从起始位置和结束位置以及滑动的时间,Scroller就可以帮助我们获取某一时刻,我们的view所在的位置了。接着我们需要在view的computeScroll()的方法中判断scroller是否结束,如果没有结束就用scrollTo方法使view处于正确的位置即可。

@Override    public void computeScroll() {        //判断是否还在滚动,还在滚动为true        if (mScroller.computeScrollOffset()) {            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());            //更新界面            postInvalidate();            isMove = true;        }        super.computeScroll();    }

怎么样,用起来是不是很简单粗暴。

 

Scroller的源码分析


只是会使用可不行,我们不仅要知其然还要知其所以然,接下来,我们从源码的角度来分析下Scroller的工作原理。从哪里分析呢,其实也不用从Scroller类的第一行代码开始看,捡重要的看就行了。首先看看startScroll()这个方法吧:

public void startScroll(int startX, int startY, int dx, int dy, int duration) {        mMode = SCROLL_MODE;        mFinished = false;        mDuration = duration;        mStartTime = AnimationUtils.currentAnimationTimeMillis();        mStartX = startX;        mStartY = startY;        mFinalX = startX + dx;        mFinalY = startY + dy;        mDeltaX = dx;        mDeltaY = dy;        mDurationReciprocal = 1.0f / (float) mDuration;    }

我们可以从中看到,Scroller只是给他的成员变量一一赋值而已,比如模式啊,位置信息,时间等,并没有做关于view滑动的任何工作。我们接下来调用了View的invalidate()方法,让View树重新绘制,让它绘制什么呢?什么东西都没有,这不是坑我们么?其实,View的draw方法中都会调用我们上面说的computeScroll() 方法,但是这个方法在View中却是一个空方法。接下来,我们就继续分析Scroller的computeScrollOffset()方法:

public boolean computeScrollOffset() {        if (mFinished) {            return false;        }        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);        if (timePassed < mDuration) {            switch (mMode) {            case SCROLL_MODE:                float x = timePassed * mDurationReciprocal;                if (mInterpolator == null)                    x = viscousFluid(x);                 else                    x = mInterpolator.getInterpolation(x);                mCurrX = mStartX + Math.round(x * mDeltaX);                mCurrY = mStartY + Math.round(x * mDeltaY);                break;            case FLING_MODE:                final float t = (float) timePassed / mDuration;                final int index = (int) (NB_SAMPLES * t);                float distanceCoef = 1.f;                float velocityCoef = 0.f;                if (index < NB_SAMPLES) {                    final float t_inf = (float) index / NB_SAMPLES;                    final float t_sup = (float) (index + 1) / NB_SAMPLES;                    final float d_inf = SPLINE_POSITION[index];                    final float d_sup = SPLINE_POSITION[index + 1];                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;                }                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));                // Pin to mMinX <= mCurrX <= mMaxX                mCurrX = Math.min(mCurrX, mMaxX);                mCurrX = Math.max(mCurrX, mMinX);                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));                // Pin to mMinY <= mCurrY <= mMaxY                mCurrY = Math.min(mCurrY, mMaxY);                mCurrY = Math.max(mCurrY, mMinY);                if (mCurrX == mFinalX && mCurrY == mFinalY) {                    mFinished = true;                }                break;            }        }        else {            mCurrX = mFinalX;            mCurrY = mFinalY;            mFinished = true;        }        return true;    }

从这个方法中,我们可以看出,Scroller先判断滑动有没有结束,如果没有结束就去获取View此时应该所处的位置信息。这个方法中重要的是它的返回值,如果完成了返回false,没有完成才返回true。继续接着Scroller的使用段落往下看,如果mScroller.computeScrollOffset()的返回值是true的话,也就是Scroller还没结束,我们就让我们的View滑动到这里,并刷新。值得注意的是,View中的computeScroll()方法并不是运行在主线程中的,所以我们要使postInvalidate()方法来调用重绘。接着重绘又会调用View的draw方法,draw方法又会调用computeScroll()方法,直至Scroller结束。到这里我们根据平时使用的代码的走向,了解了Scroller的大致工作流程。怎么样,是不是对Scroller的理解更深刻一些了呢?

Scroller的实例


仅仅是理解还不够啊,我们得从实际开发中使用Scroller,才能真正的会用他。接下来,我会用两个实例,来升华你对Scroller的理解。

实例一:仿微信朋友圈刷新

废话不多说,直接上代码吧!

/** * 仿微信刷新 * @author Nipuream */public class WXLayout extends LinearLayout{
private static final String TAG = "WXLayout"; private int mTouchSlop; private boolean mIsBeingDragged = false; private float mLastMotionY; private float mInitialMotionY; private float resistance = 0.6f; private Scroller mScroller; private ListView mListView; private boolean isMove = false; private int duration = 300; private ScrollRershListener l; private boolean isRersh = false; public WXLayout(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub init(context); } private void init(final Context context){ ViewConfiguration config = ViewConfiguration.get(context); mTouchSlop = config.getScaledTouchSlop(); DecelerateInterpolator interpolator = new DecelerateInterpolator(); mScroller = new Scroller(context,interpolator); post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub mListView = (ListView) WXLayout.this.getChildAt(0); } }); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub final int action = ev.getAction(); if(action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP){ mIsBeingDragged = false; return false; } if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) { return true; } switch(action){ case MotionEvent.ACTION_DOWN:{ mLastMotionY = mInitialMotionY = ev.getY(); mIsBeingDragged = false; break; } case MotionEvent.ACTION_MOVE:{ final float y = ev.getY(), x = ev.getX(); final float diff, absDiff; diff = y - mLastMotionY; absDiff = Math.abs(diff); if(absDiff > mTouchSlop){ if(diff > 1){ if(mListView.getFirstVisiblePosition()==0){ View view = mListView.getChildAt(0); Rect rect = new Rect (); view.getLocalVisibleRect(rect); if(rect.top == 0){ mLastMotionY = y; mIsBeingDragged = true; } } } } break; } } return mIsBeingDragged; } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub //如果碰触到控件的边缘,就不接受这一系列的action了 if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) { return false; } //如果Scroller正在滑动,就不接受这次事件了 if(isMove){ return false; } switch(event.getAction()){ case MotionEvent.ACTION_DOWN:{ mLastMotionY = mInitialMotionY = event.getY(); return true; } case MotionEvent.ACTION_MOVE:{ if (mIsBeingDragged) { if(l!=null && !isRersh){ l.startRersh(); isRersh = true; } mLastMotionY = event.getY(); float moveY = mLastMotionY - mInitialMotionY; pullEvent(moveY); return true; } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP:{ if(mIsBeingDragged){ mIsBeingDragged = false; startMoveAnim(getScrollY(), Math.abs(getScrollY()), duration); if(l!= null && isRersh && (event.getY() - mInitialMotionY) > 0){ l.endRersh(event.getY() - mInitialMotionY); isRersh = false; } return true; } break; } } return super.onTouchEvent(event); } private void pullEvent(float moveY){ if(l != null){ l.Rersh(moveY); } if(moveY > 0){ int value = (int) Math.abs(moveY); scrollTo(0, - (int)(value*resistance)); } } public void startMoveAnim(int startY, int dy, int duration) { isMove = true; mScroller.startScroll(0, startY, 0, dy, duration); invalidate();//通知UI线程的更新 } @Override public void computeScroll() { //判断是否还在滚动,还在滚动为true if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //更新界面 postInvalidate(); isMove = true; } else { isMove = false; } super.computeScroll(); } public interface ScrollRershListener{
void Rersh(float value); void startRersh(); void endRersh(float value); } public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } public void setOnScrollRershListener(ScrollRershListener l){ this.l = l; }}

实例二:仿QQ侧滑删除

得多谢的博客,让我有了清晰的思路,对他的代码加以改造,从而完成了这个实例。

/** * 仿QQ侧滑删除 * @author Nipuream * */public class SlideListView extends ListView implements OnTouchListener,OnClickListener{
private static final String TAG = "SlideListView"; private Context mContext; private Scroller mScroller; /** * 初始值 */ private float initalXvalue,mLastXvalue; private float initalYvalue,mLastYvalue; /** * 确认滑动的最小速度 */ private int MIN_VELOCITY = 800; /** * 速度跟踪器 */ private VelocityTracker velocityTracker; /** * 正在被拖动的view */ private View dragView; /** * 正在被拖动的position */ private int touchPos; /** * 默认的最小滑动距离 */ private int mTouchSlop; /** * 滑动时间 */ private int DURATION_TIME = 300; /** * 出现删除按钮的Item */ private View tempView ; /** * 出现删除按钮的position */ private int tempPos ; /** * 删除按钮 */ private Button deleteBtn; /** * 移除接口 */ private RemoveItemListener l; /** * 是否可以滑动 */ private boolean isSlide = false; public SlideListView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub init(context); } private void init(Context context){ mContext = context; AccelerateInterpolator interpolator = new AccelerateInterpolator(); mScroller = new Scroller(context,interpolator); velocityTracker = VelocityTracker.obtain(); ViewConfiguration config = ViewConfiguration.get(context); mTouchSlop = config.getScaledTouchSlop(); setOnTouchListener(this); } /** * 捕捉用户到底拖动了哪个view * 拦截事件 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub final int action = ev.getAction(); if (action == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { return false; } if(action == MotionEvent.ACTION_DOWN){ mLastYvalue = initalYvalue = ev.getY(); initalXvalue = mLastXvalue = ev.getX(); if(!mScroller.isFinished()){ return super.dispatchTouchEvent(ev); } touchPos = pointToPosition((int)mLastXvalue, (int)mLastYvalue); if(touchPos == AdapterView.INVALID_POSITION){ return super.dispatchTouchEvent(ev); } GetTracker(ev); dragView = getChildAt(touchPos - getFirstVisiblePosition()); isSlide = false; }else if(action == MotionEvent.ACTION_MOVE){ mLastXvalue = ev.getX(); mLastYvalue = ev.getY(); if(velocityTracker == null){ GetTracker(ev); } velocityTracker.computeCurrentVelocity(1000); if(Math.abs(velocityTracker.getXVelocity()) > MIN_VELOCITY ||Math.abs(mLastXvalue - initalXvalue)> mTouchSlop && Math.abs(mLastYvalue - initalYvalue) < mTouchSlop){ isSlide = true; } }else if(action == MotionEvent.ACTION_UP){ CloseTracker(); } return super.dispatchTouchEvent(ev); } /** * 消费事件 */ @Override public boolean onTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub if(isSlide){ switch(ev.getAction()){ case MotionEvent.ACTION_MOVE: { mLastXvalue = ev.getX(); final float moveX = mLastXvalue - initalXvalue; if(Math.abs(moveX) > dip2px(mContext, 20)) { if(moveX < 0 && Math.abs(moveX)
dip2px(mContext, 50) ){ //滑动到底 if(dragView != null){ mScroller.startScroll(dragView.getScrollX(), 0, (dip2px(mContext, 100) - Math.abs(dragView.getScrollX())), 0,DURATION_TIME); invalidate(); tempView = dragView; tempPos = touchPos; setListener(); } } } } break; } } return super.onTouchEvent(ev); } private void GetTracker(MotionEvent ev){ if(velocityTracker == null){ velocityTracker = VelocityTracker.obtain(); } velocityTracker.addMovement(ev); } private void CloseTracker() { if(velocityTracker != null){ velocityTracker.recycle(); velocityTracker.clear(); velocityTracker = null; } } private void setListener(){ deleteBtn = (Button) tempView.findViewById(R.id.delete); deleteBtn.setOnClickListener(this); } @Override public void computeScroll() { // TODO Auto-generated method stub if(mScroller.computeScrollOffset()){ dragView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } super.computeScroll(); } public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(tempView != null){ dragView = tempView ; mScroller.startScroll(dragView.getScrollX(), 0, - dragView.getScrollX(), 0,DURATION_TIME); invalidate(); deleteBtn.setOnClickListener(null); deleteBtn = null; tempView = null; return true; } return false; } @Override public void onClick(View v) { // TODO Auto-generated method stub if(l != null){ l.remove(tempPos); dragView = tempView; dragView.scrollTo(0, 0); tempView = null; invalidate(); } } public interface RemoveItemListener{
void remove(int pos); } public void setOnRemoveItemListener(RemoveItemListener l){ this.l = l; }}

我觉得这两个例子都很简单,所以没有什么好解释的,我在下面也会给出下载的地址。如果有什么疑惑的,或者什么地方需要改进的请联系我QQ:571829491。

源码下载

 

转载于:https://www.cnblogs.com/Nipuream/p/5506136.html

你可能感兴趣的文章
php多维数组的取值基础知识
查看>>
Sym-GAN
查看>>
查询tensorflow中的函数用法
查看>>
杨玲 - 杨蓉庆 - 张燕 201771010133《面向对象程序设计(java)》第十四周学习总结...
查看>>
实用爬虫-01-检测爬虫的 IP
查看>>
081 Region的预分区
查看>>
Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---命令模式之SimpleRemoteControlTest [转]...
查看>>
用C++结束进程(恶搞你的计算机!!!!)
查看>>
Ubuntu14.04 安装 Sublime Text 3
查看>>
Spring入门之setter DI注入
查看>>
Sharepoint的处理IIS(Process)与账户处理模型
查看>>
C#执行DOS命令(CMD命令) (转)
查看>>
IDEA的常见的设置和优化(功能)
查看>>
TestNG 入门教程
查看>>
UVA-227 Puzzle(傻屌之王)
查看>>
[疑难杂症]__点击win10屏幕最上方的边界会莫名其妙打开Internet Explorer浏览器,不胜其烦(2次ps:已解决!!!)....
查看>>
跳上球台+换手重杀! 马龙7个亚洲冠军创纪录
查看>>
Oracle 查询今日、昨日、本周、本月和本季度的所有记录
查看>>
测试正则表达式
查看>>
Vijos P1389婚礼上的小杉
查看>>