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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值