zoukankan      html  css  js  c++  java
  • 带你彻彻底底弄懂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側滑删除

  • 相关阅读:
    用C语言代码实现n进制数转换为十进制数
    RAID简介
    很久没更新自己的博客园的博客了
    微软之于程序员==铁饭碗破了
    sql编译执行过程
    sql server性能终结者锁
    sysprocesses
    SQL SERVER 2008的几个新东西:插入,删除,修改一起来(适合数据的同步)merger
    http Status Code Definitions
    sql server talbe valued parameters (tvp)
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/7105450.html
Copyright © 2011-2022 走看看