带你彻彻底底弄懂Scroller

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();
    }

怎么样,用起来是不是非常easy粗暴。

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;
    /**
     * 出现删除button的Item
     */
    private View tempView ;
    /**
     * 出现删除button的position
     */
    private int tempPos ;
    /**
     * 删除button
     */
    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, 100))
                    {
                        if(dragView != null){
                            dragView.scrollTo(Math.abs((int)moveX), 0);
                        }
                        return true;
                    }
                }
            }
            break;
            case MotionEvent.ACTION_UP:
            {
                mLastXvalue = ev.getX();
                float scrollDistance = mLastXvalue - initalXvalue;
                if(scrollDistance < 0){
                    if(Math.abs(scrollDistance) < dip2px(mContext, 50)){
                        //滑动回去
                        if(dragView != null){
                            mScroller.startScroll(dragView.getScrollX(), 0, -dragView.getScrollX(), 0, DURATION_TIME);
                            invalidate();
                        }
                    }else if(Math.abs(scrollDistance) > 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;
    }

}

我认为这两个样例都非常easy。所以没有什么好解释的,我在以下也会给出下载的地址。假设有什么疑惑的,或者什么地方须要改进的请联系我QQ:571829491。

源代码下载

仿微信刷新

仿QQ側滑删除

时间: 2024-10-13 01:16:48

带你彻彻底底弄懂Scroller的相关文章

[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础

关于管道 Channel Channel 用来同步并发执行的函数并提供它们某种传值交流的机制. Channel 的一些特性:通过 channel 传递的元素类型.容器(或缓冲区)和 传递的方向由“<-”操作符指定. c <- 123,把值 123 输入到管道 c,<-c,把管道 c 的值读取到左边,value := <-c,这样就是读到 value 变量里面. 管道分类 无缓冲的 与 有缓冲 channel 有着重大差别,那就是一个是同步的(阻塞的) 一个是非同步的(非阻塞的).

必须弄懂的495个C语言问题

必须弄懂的495个C语言问题 1.1 我如何决定使用那种整数类型? 如果需要大数 值(大于32, 767 或小于?32, 767), 使用long 型.否则, 如果空间很重要(如有大数组或很多结构), 使用short 型.除此之外, 就使用int 型.如果严格定义的溢出特征很重要而负值无关紧要, 或者你希望在操作二进制位和字节时避免符号扩展的问题, 请使用对应的无符号类型.但是, 要注意在表达式中混用有符号和无符号值的情况. 尽管字符类型(尤其是无符号字符型) 可以当成"小" 整型使用

如何继承Date对象?由一道题彻底弄懂JS继承。

前言 见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会及时修正. ----------长文+多图预警,需要花费一定时间---------- 故事是从一次实际需求中开始的... 某天,某人向我寻求了一次帮助,要协助写一个日期工具类,要求: 此类继承自Date,拥有Date的所有属性和对象 此类可以自由拓展方法 形象点描述,就是要求可以这样: // 假设最终的类是 MyDate,有一个getTest拓展方法 let date = new MyDate(); // 调用Date的方法,输出GM

30分钟彻底弄懂flex布局

目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用flex进行布局了.什么是flex布局以及它的好处,这里就不再赘述. 在这篇文章里,想说说flex布局的属性语法及其细节.那么网上也有不少flex布局的教程,为什么又要再写一篇? 首先,flex布局的迷之属性们,如果一知半解,机械记忆的话,那不到半个月基本忘光光.先感受一下这12个flex布局属性,是不是很“迷”人. 容器属性 flex-flow flex-direction flex-wrap justify-content

【CodeForces】343D Water tree (线段树好题!还未弄懂)

/* 此题的方法除了用线段树求子树,通过标记父亲,更新儿子的方法,来更新祖先,学习了. 对于建树的方法由于并没有说明父亲与儿子的顺序,所以需要通过两次添加. 并且pre变量可以获得父亲的位置,还未弄懂! */ #define _CRT_SECURE_NO_WARNINGS #include<cstring> #include<cstdio> #include<iostream> #include<algorithm> using namespace std;

【转】彻底弄懂最短路径问题(图论)

来源:彻底弄懂最短路径问题 http://www.cnblogs.com/hxsyl/p/3270401.html P.S.根据个人需要,我删改了不少 问题引入 问题:从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径——最短路径.解决最短路的问题有以下算法,Dijkstra算法,Bellman-Ford算法,Floyd算法和SPFA算法,另外还有著名的启发式搜索算法A*,不过A*准备单独出一篇,其中Floyd算法可以求解任意两点间的最短路径的长度.笔者认为任意一个最

SEOer都想弄懂的百度权重

百度权重我相信是SEOer都想弄懂弄透的一个东西,百度权重的算法经常会让SEOer们感到头疼,今天我们来详细分析一下. 百度权重,本来的含义应该是百度对一个网站的整体评价.这里说的百度权重,是站长工具等网站上的根据网站关键词(指数)在百度的排名给出的一个数值 .注意,是非官方的定义. 关于百度权重的权威性.首先,大多数站长已经了解到一点,很多网站给出的百度权重数值很多时候是不一致的.所以可以确定的一点是,百度权重并不权 威,没有统一的答案.其次,爱站网和站长工具的百度权重是怎么算出来的.这两家的

学习PS必须弄懂的专业术语

在学习PS的过程中,我们经常会遇到一些专业术语,下面我们来对一些常用的.比较难理解的术语进行简单讲解. 像素:像素是构成图像的最基本元素,它实际上是一个个独立的小方格,每个像素都能记录它所在的位置和颜色信息.下图中每一个小方格就是一个像素点,它记载着图像的各种信息. 选区:也叫选取范围,是PS对图像做编辑的范围,任何编辑对选区外无效.当图像上没有建立选择区时,相当于全部选择.下图中的黑白相间的细线就是选择区的边界,对图像的操作只对选择区内有效. 羽化:对选择区的边缘做软化处理,其对图像的编辑在选

彻底弄懂 JavaScript 执行机制

本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序.因为javascript是一门单线程语言,所以我们可以得出结论: javascript是按照语句出现的顺序执行的 看到这里读者要打人了:我难道不知道js是一行一行执行的?还用你说?稍安勿躁,正因为js是一行一行执行的,所以我们以为js都是这样的: let a