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

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

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的事件分发机制和滑动冲突解决方案

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容