美文网首页Android开发
Android Scroller用法详解&ListVie

Android Scroller用法详解&ListVie

作者: 举子7先生 | 来源:发表于2017-01-07 22:15 被阅读250次

    参考:http://www.2cto.com/kf/201609/545981.html


    一、Android Scroller用法详解

    Scroller是用于实现View的弹性滑动,在使用View的scrollTo或者scrollBy来进行滑动时,过程是瞬间完成的,这样用户体验相当的不好。这时我们就要使用Scroller来实现这个滑动的过渡效果。

    public void smoothScrollTo(int destX, int destY) {
           int scrollY = getScrollY();
           int deltaY = destY - scrollY;
           mScroller.startScroll(0, scrollY, 0, deltaY, 1000);
           invalidate();
       }
       @Override
       public void computeScroll() {
           if (mScroller.computeScrollOffset()) {
               scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
               postInvalidate();
           }
       }
    

    上面几乎是Scroller的标准写法。Scroller本身是不能使View滑动的,void startScroll(int startX, int startY, int dx, int dy, int duration) 其实并不是开始滚动,点进去你会发现只是存储了滑动的开始点和滑动的距离时间,那它到底是怎么实现滑动的呢?其实主要还是调用View的scrollTo或者scrollBy方法,那先看看这两个方法。

    public void scrollTo(int x, int y) {
            if (mScrollX != x || mScrollY != y) {
                int oldX = mScrollX;
                int oldY = mScrollY;
                mScrollX = x;
                mScrollY = y;
                invalidateParentCaches();
                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
                if (!awakenScrollBars()) {
                    postInvalidateOnAnimation();
                }
            }
        }
    public void scrollBy(int x, int y) {
           scrollTo(mScrollX + x, mScrollY + y);
       }
    

    scrollBy内部也是调用scrollTo方法,不过加上了当前的mScrollX 和mScrollY ,表示相对滑动,而scrollTo是绝对滑动,看下面效果就会很明白了。
    ![](https://img.haomeiwen.com/i1753003/323abcf3e57e25c2.gif?imageMogr2/auto-orient/strip)
      那么问题来了,mScrollX和mScrollY到底是什么?首先我们要了解到scrollTo这个方法移动的只是View的内容,而并不会改变View本身的位置。比如一个TextView调用scrollTo的方法,只会移动里面的文字,并不会去移动这个TextView本身。

    mScrollX的值就是View内容左边缘到View左边缘的距离,内容在View的左边这个值为正,反之为负。

    mScrollY也是相同的原理,可以通过View的getScrollX、setScrollX来设置这个值

    下面看看它的实现过程

    调用smoothScrollTo方法,得到初始位置和滑动距离,调用invalidate()重绘界面,重绘后会在onDraw方法中调用computeScroll()方法,这个方法是空实现所以需要我们重写,于是在这个computeScroll()方法中调用scrollTo方法并调用mScroller的getCurX()和getCurY()来移动,再调用postInvalidate()方法来更新界面,又会调用onDraw方法里面的computeScroll()方法,从而又进入这里不断调用scrollTo方法,直至完成。

    那么这个CurY是在哪里进行变化的呢?其实这是在mScroller.computeScrollOffset()方法中完成的,这个方法返回一个boolean来判断动画是否完成,否则就不用调用scrollTo进行重绘界面,这个方法会根据时间的流逝来计算出当前的CurX和CurY,有点类似插值器的概念。

    这样就大致明白Scroller的工作原理了,总结一下:Scroller本身并不能滑动,必须配合View的computeScroll()方法才能实现滑动的效果,通过这个方法可以让View不断的重绘,每次重绘都有一个小的时间间隔,通过这个时间间隔就改变了Scroller里面的当前的ScrollX和ScrollY,再调用scrollTo就移动一点点,这样反复的调用就实现了动画效果。

    下面看看Scroller两个实际运用:
    ![](https://img.haomeiwen.com/i1753003/b2e9b1adfa60ceb0.gif?imageMogr2/auto-orient/strip)
      代码如下,看了上面的分析应该很简单了,都能看懂,主要就是自定义一个ViewGroup来装内容,改变ScrollY

    public class SortLinearLayout extends LinearLayout {
        private Scroller mScroller;
     
        public SortLinearLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            mScroller = new Scroller(context);
        }
     
        private void smoothScrollTo(int destX, int destY) {
            int scrollY = getScrollY();
            int deltaY = destY - scrollY;
            mScroller.startScroll(0, scrollY, 0, deltaY, 1000);
            invalidate();
        }
     
        @Override
        public void computeScroll() {
            if (mScroller.computeScrollOffset()) {
                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
                postInvalidate();
            }
        }
     
        private void dismiss() {
            setVisibility(View.GONE);
            scrollTo(0, 400);
        }
     
        public void showOrHide() {
            if (getScrollY() == 0) {//显示
                dismiss();
            } else {
                setVisibility(View.VISIBLE);
                smoothScrollTo(0, 0);
            }
        }
    }
    

    下面实现一个ListView侧滑菜单的效果
    ![](https://img.haomeiwen.com/i1753003/0f298b57a093e048.gif?imageMogr2/auto-orient/strip)

    public class SlideListView extends ListView {
     
     
        private static final String TAG = "lzy";
        private int maxLength;
        private int mStartX = 0;
        private LinearLayout itemLayout;
        private int pos;
        private Rect mTouchFrame;
        private int xDown, xMove, yDown, yMove, mTouchSlop;
        private Scroller mScroller;
        private Button textView;
        private ImageView imageView;
        private boolean isFirst = true;
        private OnSlideListener onSlideListener;
     
        public void setOnSlideListener(OnSlideListener onSlideListener) {
            this.onSlideListener = onSlideListener;
        }
     
        public SlideListView(Context context, AttributeSet attrs) {
            super(context, attrs);
            //滑动到最小距离
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
            //滑动的最大距离
            maxLength = ((int) (180 * context.getResources().getDisplayMetrics().density + 0.5f));
            //初始化Scroller
            mScroller = new Scroller(context, new LinearInterpolator(context, null));
        }
     
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            int x = (int) ev.getX();
            int y = (int) ev.getY();
     
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    xDown = x;
                    yDown = y;
                    //通过点击的坐标计算当前的position
                    int mFirstPosition = getFirstVisiblePosition();
                    Rect frame = mTouchFrame;
                    if (frame == null) {
                        mTouchFrame = new Rect();
                        frame = mTouchFrame;
                    }
                    int count = getChildCount();
                    for (int i = count - 1; i >= 0; i--) {
                        final View child = getChildAt(i);
                        if (child.getVisibility() == View.VISIBLE) {
                            //获取矩阵
                            child.getHitRect(frame);
                            //判断一个点是否在一个矩阵中
                            if (frame.contains(x, y)) {
                                //因为getChildCount()只会得到展现在屏幕中的子View个数,并不是总个数
                                pos = mFirstPosition + i;
                            }
                        }
                    }
                    //通过position得到item
                    itemLayout = (LinearLayout) getChildAt(pos - mFirstPosition);
                    textView = (Button) itemLayout.findViewById(R.id.bt_item);
                    imageView = (ImageView) itemLayout.findViewById(R.id.iv_item);
                    textView.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (onSlideListener != null) {
                                onSlideListener.onDelete(pos);
                                //恢复原状
                                mScroller.startScroll(maxLength, 0, -maxLength, 0);
                                invalidate();
                            }
                        }
                    });
                }
                break;
     
                case MotionEvent.ACTION_MOVE: {
                    xMove = x;
                    yMove = y;
                    int dx = xMove - xDown;
                    int dy = yMove - yDown;
     
                    //竖直方向小于最小滑动距离,水平方向大于
                    if (Math.abs(dy) < mTouchSlop * 2 && Math.abs(dx) > mTouchSlop) {
                        int scrollX = itemLayout.getScrollX();
                        int newScrollX = mStartX - x;
                        if (newScrollX < 0 && scrollX <= 0) {
                            newScrollX = 0;
                        } else if (newScrollX > 0 && scrollX >= maxLength) {
                            newScrollX = 0;
                        }
                        //当划过二分之一时,imageView做一个缩放动画
                        if (scrollX > maxLength / 2) {
                            if (isFirst) {
                                ObjectAnimator animatorX = ObjectAnimator.ofFloat(imageView, "scaleX", 0.8f, 1.2f, 0.8f);
                                ObjectAnimator animatorY = ObjectAnimator.ofFloat(imageView, "scaleY", 0.8f, 1.2f, 0.8f);
                                AnimatorSet animSet = new AnimatorSet();
                                animSet.play(animatorX).with(animatorY);
                                animSet.setDuration(800);
                                animSet.start();
                                isFirst = false;
                            }
                        }
                        itemLayout.scrollBy(newScrollX, 0);
                    }
                }
                break;
                case MotionEvent.ACTION_UP: {
                    int scrollX = itemLayout.getScrollX();
                    if (scrollX > maxLength / 2) {
                        //划过一半松手,显示全部
                        mScroller.startScroll(scrollX, 0, maxLength - scrollX, 0);
                        invalidate();
                    } else {
                        //恢复原状
                        mScroller.startScroll(scrollX, 0, -scrollX, 0);
                        invalidate();
                    }
                    isFirst = true;
                }
                break;
            }
            mStartX = x;
            return super.onTouchEvent(ev);
        }
     
        //view 里onDraw方法会调用
        @Override
        public void computeScroll() {
            if (mScroller.computeScrollOffset()) {
                itemLayout.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
                invalidate();
            }
        }
     
        public interface OnSlideListener {
            void onDelete(int pos);
        }
    }
    

    重点重写了onTouchEvent方法,在其实现逻辑,但是这样会影响ListView的点击事件,所以需要的话要自己写点击事件,可能把RecyclerView比较好一点,毕竟RecyclerView的定制性比较高。

    相关文章

      网友评论

        本文标题:Android Scroller用法详解&ListVie

        本文链接:https://www.haomeiwen.com/subject/obafbttx.html