美文网首页Android进阶之路Android开发Android开发
读书笔记|艺术探索第三章-View事件体系

读书笔记|艺术探索第三章-View事件体系

作者: 北国雪WRG | 来源:发表于2019-03-09 11:37 被阅读7次

    这两天的面试,我发现很多东西我并没有了解其机制,只是停留在表面。

    1. View位置参数

    参数 含义 获取方式
    (mLeft , mTop) (mRight , mBottom) 左上角点和右下角点相对于父容器的坐标 getXXX()
    x,y 可视View左上角的位置 getX/getY
    translationX,translationY 可视View相对于视图本体的偏移量 getTranslationX/Y
    mScrollX/Y 内容的相对于原始的偏移量 getScrollX/Y
    1. Left,Top,Right,Buttom 很好理解。

    2. 实际上View的类中并没有X,Y变量(我找了好久(=)。getX和getY实际上是:


      可见源码对x的解释是视图的可视x位置,以像素为单位。
    3. 那啥是translationX呢?这个属性和动画有关。在使用setTranslationX改变了View的位置但是没有改变LayoutParams里的margin属性。可以理解为translateX/Y和margin是同一级别的。

    参考对TranslationX和动画关系的理解

    1. 这个用Button举例子,默认scrollX/Y为0,文字是居中。当设置scrollX为正数的时候,文字会在Button中向左边移动。文字就是Button中的内容。

    2. View的滑动

    2.1 使用scrollTo/scrollBy。


    根据源码可见:(1)scrollBy实际上是调用scrollTo的。(2)scrollTo实际上是修改了mScrollX和mScrollY。而这两个参数表示内容的偏移量。不管怎么移动,文字都不会溢出Button。
    所以可以得出结论这种移动只能改变View的内容。并且mScrollX>0 内容向左滑动。mScrollY>0 内容向上滑动。

    2.2 使用动画

    动画实际上是操作translationXY,Scale等。View动画不能改变监听器的位置。Android 3.0 以下无法使用属性动画。属性动画解决了监听器不随视图变化的问题。
    具体在第七章读书笔记再学习。

     ObjectAnimator.ofFloat(button1, "translationX", 0, 500).setDuration(10000).start();
    

    2.3 改变布局参数

    可以通过修改布局参数中的Margin来实现滑动。

    ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)button2.getLayoutParams();
    params.leftMargin += 1;
    button2.requestLayout();
    

    3.弹性滑动

    3.1 使用Scroll

        public void smoothScrollTo(int dx, int dy) {
            scroller.startScroll(getScrollX(), 0, dx, dy, 9000); // 记录开始时间,设置标记为滑动
            invalidate(); // 重绘之后调用draw,
        }
        
        @Override
        public void computeScroll() {
            // 计算是否滑动到目的地,如果没有,修改mScrollX 和 mScrollY
            if (scroller.computeScrollOffset()) {
                scrollTo(scroller.getCurrX(), scroller.getCurrY()); // 真正的滑动
                postInvalidate(); //重绘
            }
        }
    

    滑动的入口是方法smoothScrollTo,这是自己再View内部定义的方法。可以看到首先调用了scroller.startScroll(....)。查看源码。


    可见startScroll方法主要是初始化一些值,并没有做关于滑动的操作。注意其设置了mMode为滑动,记录了当前时间,计算出了结束时间。然后调用invalidate()。看了一下介绍,这个方法是导致当前View无效,然后会重新绘制,也就是调用Draw。


    draw方法比较长,确实调用了computerScroll()方法。


    View的computerScroll()是一个空实现,我们重写一下。

        @Override
        public void computeScroll() {
            // 计算是否滑动到目的地,如果没有,修改mScrollX 和 mScrollY
            if (scroller.computeScrollOffset()) {
                scrollTo(scroller.getCurrX(), scroller.getCurrY()); // 真正的滑动
                postInvalidate(); //重绘
            }
        }
    

    首先看看scroller.computeScrollOffset()


    第一步检查标志位,这个我们再startScroll中设置为了false,表示没有滑动结束。
    然后计算timePassed,mStartTime我们再startScroll中也初始化了,从开始准备滑动到现在过了多少毫秒,如果time<mDuration,显然滑动还没有结束。那么要滑动到哪儿呢?可见mStartX+Math.round(x*mDeltaX),没错是通过时间过去的百分比计算出路程,然后加上初始值,计算出目的地。最后返回true。
    再后来就是调用scrollTo了完成正真的滑动。可见修改了mScrollX和mScrollY,这两个值在一开始就说了,是内容相对于最开始的偏移量。然后调用postInvalidate();进行重绘,然后又是draw,再次计算时间流逝比,计算路程滑动....

    总结一下:
    (1)mScroll.startScroll,计算开始时间,设置标志位,计算目的地
    (2)invalidate,导致视图重绘,从而调用draw
    (3)draw重绘的时候,调用了computeScroll方法
    (4)computeScroll由调用computerScrollOffset方法,这个方法返回boolean,返回false,表示没有滑动结束,其中计算了 时间流逝比,再通过时间流逝比,计算出等价的路程。
    (5) 最后通过scrollTo完成滑动。如此循环。

    还有一些细节没有展开。

    3.2 通过动画

    属性动画,移动View本身。

    ObjectAnimator.ofFloat(button1, "translationX", 0, 500).setDuration(10000).start();
    

    我们可以通过动画的性质来移动内容。原理很简短,就是利用ValueAnimator得到一个类似于时间流逝比的比值,再用scrollTo来更新视图。从而达到动画的效果。

            final int startX = 0;
            final int deltaX = 100;
            ValueAnimator animator = ValueAnimator.ofInt(0, 1).setDuration(1000);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float fraction = animation.getAnimatedFraction();
                    button1.scrollTo((int) (startX + deltaX * fraction), 0); 
                }
            });
    

    3.3 使用延迟策略

    其实也很简单,如果对消息机制比较熟悉的话。

      handler.sendEmptyMessageDelayed(1, 100);
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                params.leftMargin += 1;
                button2.requestLayout();
                count++;
    
                if (count > 200) return;
                handler.sendEmptyMessageDelayed(1, 10);
            }
        };
    

    4. 事件分发

    • public boolean dispatchTouchEvent(MotionEvent ev):是否将事件传递给下一级,返回的结果受下一级的dispatchTouchEvent()和当前的onTouchEvent()
    • public boolean onInterceptTouchEvent(MotionEvent ev):在上面函数内部调用,当前View是否拦截事件,返回true表示拦截,返回false表示不拦截。
    • public boolean onTouchEvent(MotionEvent ev):用来处理事件,返回true表示消耗,返回false表示不消耗。

    用《进阶之光》上的例子就是:

    敌人来挑战武当山,武当山上现在有掌门张三丰,武当七侠宋远桥,武当弟子宋青书。张三丰肯定不会直接出动(onInterceptTouchEvent == false),而是让宋远桥去(child.dispathcTouchEvent(ev)),宋远桥(ViewGroup)也威名远扬,也不会轻易应战(onInterceptTouchEvent == false),派遣宋青书(child.dispathchTouchEvent(ev),宋青书(底层的View) 没有徒弟了(没有child了),只好自己去迎战... 这就是事件的从上向下传递。
    结果挑战的是成昆,宋青书处理不掉他(onTouchEvent = false),于是叫宋远桥来,结果宋远桥也不是对手(onTouchEvent = false),于是张三丰只好亲自出马(调用 onTouchEvent())

    5. 滑动冲突

    1. 左右嵌套上下滑动冲突,根据滑动的左右分量和上下分量的大小来解决。
    2. 左右嵌套左右滑动冲突,根据具体的业务来解决。在实际开发中可以判断内存View是否滑动到了尽头,如果滑动到了尽头再滑动外层View,否则外层View不动。

    View的事件分发机制和滑动冲突解决方案

    相关文章

      网友评论

        本文标题:读书笔记|艺术探索第三章-View事件体系

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