Android里的View滑动

作者: 猪_队友 | 来源:发表于2018-12-19 12:00 被阅读25次

    我们平时会经常遇到View的滑动,不管自定义View,还是动画,都需要这个东西,但是往往最熟悉的最陌生。

    View滑动可以分为三大类

    1、自身的ScrollTo和ScrollBy

    2、通过动画给View平移效果

    3、改变View的LayoutParams进行修改View的位置

    很传统的三大分类。但是我们要思考为什么要分出这三类,各自的实现意义。

    一、ScrollTo和ScrollBy。

    首先看源码:

     public void scrollBy(int x, int y) {
            scrollTo(mScrollX + x, mScrollY + y);
        }
    
     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();
                }
            }
        }
    
      postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
    

    其实都是本质都是ScrollTo方法。我们看一下mScrollX和mScrollY的含义是什么?
    View里的内容在横向的偏移像素量。记住View和View里的内容。
    就好比:view = 盒子 内容 = 盒子里的东西,我们改变的是盒子里的东西的位置,而不是这个盒子的位置。所以我们在运用这个方法的时候,一定是移动View里的子View。而不是这个View本身。

      /**
         * The offset, in pixels, by which the content of this view is scrolled
         * horizontally.
         * {@hide}
         */
        @ViewDebug.ExportedProperty(category = "scrolling")
        protected int mScrollX;
    

    我们来看一下具体实现,x,y就是我们要scroll的位置,首先要把之前scroll的值赋为oldX,oldY,也就是之前的偏移量,那么如果我们要到达x,y,就需要计算差值,也就是:

    desX - oldX = 移动的值 =dx。

    如果我们之前的偏移量为100,100,ScrollTo(200,200),那么移动的值就是 200-100 = 100;需要在移动100,而不必从原始位置再到目的位置了,而是从现有位置移动到目的位置。

    那么这个移动的方向是基于什么的呢?通过实际测试,我们发现是

    View左边界 - View内容左边界 = scrollX
    View上边界 - View内容上边界 = scrollY

    这么一说 我们发现当scrollX 为正的时候,我们看到内容的移动方向是反方向,其实就去想象你自己用手去拉这个黄纸,左边和上边为正方向,得到的值就是scroll的值。


    当我们在运用的时候一定要切记

    1、如果我们想让这个View移动,那么一定要在它的父View里运用这个方法。
    2、一定要是反方向

    点击子view,在父View(全屏View)中移动。
    点击view,内容在其内移动。
    至于代码大家自己可以亲自实现一下。

    但是我们平时移动不会这么僵硬的,更多时候需要Scroller来实现一些阻尼效果。既然讲到这里了,不讲Scroller肯定是过不去的了。

    Scroller

    用法很简单:

    1. 创建Scroller的实例
    2. 调用startScroll()方法来初始化滚动数据并刷新界面
    3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑

    没啥需要多说的,我们借用郭神的例子来实际探究一下Scroller的魅力所在。

    我们要模仿的是ViewPager的效果。

    ezgif.com-video-to-gif.gif

    首先我们要具备基本的自定义View的知识不了解的可以看一下 谷哥的小弟大神的系列文章。

    public class XViewPage extends ViewGroup {
        /**
         * 手指此刻所在的屏幕坐标
         */
        private float mMoveX;
        /**
         * Scroller 的实例
         */
        private Scroller mScroller;
    
        /**
         * 判定拖动的最小距离像素值
         */
        private int mTouchSlop;
    
        /**
         * 按下X坐标值
         */
        private float mDownX;
    
        /**
         * 上一次触发ACTION_MOVE的X坐标值
         */
        private float mLastMoveX;
        /**
         * 界面可滚动右边界
         */
        private int rightBorder;
        /**
         * 界面可滚动左边界
         */
        private int leftBorder;
    
        public XViewPage(Context context) {
            this(context, null);
        }
    
        public XViewPage(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
    
        }
    
        public XViewPage(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mScroller = new Scroller(context);
            ViewConfiguration configuration = ViewConfiguration.get(context);
            mTouchSlop = configuration.getScaledPagingTouchSlop();
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (changed) {
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    View childView = getChildAt(i);
                    //把这几个子控件平铺
                    childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
    
                }
                //初始化左右边界
                leftBorder = getChildAt(0).getLeft();
                rightBorder = getChildAt(getChildCount() - 1).getRight();
    
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int chileCount = getChildCount();
            for (int i = 0; i < chileCount; i++) {
                View childView = getChildAt(i);
                //测量每一个子View的大小
                measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            }
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDownX = ev.getRawX();
                    mLastMoveX = mDownX;
                    break;
                case MotionEvent.ACTION_MOVE:
                    mMoveX = ev.getRawX();
                    float diff = Math.abs(mMoveX - mDownX);
                    if (diff > mTouchSlop) {
                        return true;
                    }
                    break;
            }
    
    
            return super.onInterceptTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    mMoveX = event.getRawX();
                    int scrolledX = (int) (mLastMoveX - mMoveX);
                    if (getScrollX() + scrolledX < leftBorder) {
                        scrollTo(leftBorder, 0);
                        return true;
                    } else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
                        scrollTo(rightBorder-getWidth(), 0);
                        return true;
                    }
    
                    scrollBy(scrolledX, 0);
                    mLastMoveX = mMoveX;
                    break;
                case MotionEvent.ACTION_UP:
                    //当手抬起来的时候 根据当前的滚动值 来判断应该滚动到哪一个子view的界面
                    int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
                    int dx = targetIndex * getWidth() - getScrollX();
                    //调用startScroll() 来初始化滚动数据 并刷新界面
                    mScroller.startScroll(getScrollX(), 0, dx, 0);
                    postInvalidate();
    
                    break;
            }
    
    
            return super.onTouchEvent(event);
        }
    
        @Override
        public void computeScroll() {
          if(mScroller.computeScrollOffset()){
              scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
              invalidate();
          }
        }
    }
    

    总结一下过程:
    1、重写onMeasure() 测量每一个子View的大小
    2、重写onLayout() 横向平铺这些子View
    3、 重写 onTouchEvent 和 onInterceptTouchEvent的 事件分发处理
    4、重写 computeScroll()实现mScroller的scrollTo方法,达到顺滑的效果

    我们发现ScrollTo在Android中的而运用实在是太广泛了。

    动画给View平移效果

    这个我们在做交互,转场动画的时候经常会用到。特别是3.0以上的属性动画,解决了身形不同的问题。

    当3.0以下的时候,采用nineoldndroids这个开源动画,效果和属性动画一致,不同的是移动的只是一个影像而不是本体,在点击事件方面有着天生的缺陷,解决的办法也只能在以后后的位置隐藏一个相同的View来显示并做事件处理,这样移花接木的手段。

    ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f);
    animator.setDuration(1000);//时间1s
    animator.start();
    

    动画我们将在之后的文章中详细讲解,这里就不多介绍了。

    改变布局参数

    这是一个治本的方式。可以对View的大小位置坐各种改变。

     ViewGroup.LayoutParams params = view.getLayoutParams();
        ViewGroup.MarginLayoutParams marginParams = null;
        if (params instanceof  ViewGroup.MarginLayoutParams) {
            marginParams = (ViewGroup.MarginLayoutParams) params;
        } else {
            marginParams = new ViewGroup.MarginLayoutParams(params);
        }
            marginParams.height = 500;
            marginParams.setMargins(10, 200, 30, 40);
            view.setX(100);
            view.setLayoutParams(marginParams);
           // view.requestLayout();
    

    最后

    总结一下三个滑动的使用场景

    1、scrollX 和 scrollY 操作简单,适合对View的内容进行滑动(比如模仿ViewPager)
    2、动画,操作简单适合,没有交互的View,实现复杂的动画效果
    3、改变布局和View的参数,比较复杂,但是适用于有交互的View

    大家根据不同需求选择和配合使用。

    参考:

    相关文章

      网友评论

        本文标题:Android里的View滑动

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