第三章: View体系与自定义View(上)

1.View与ViewGroup
2.坐标系
3.View的滑动
4.属性动画
5.解析Scroller
6.View的事件分发机制

第三章主要是从最基础的View与ViewGroup, 到View的滑动, View的事件分发, View的三个工作流程measure, layout, draw,这些原理.从而打通自定义VIew的整个流程.

View与ViewGroup

View是什么:

View是所有Android控件的基类, 我们平时所使用的Ui控件都是继承与View的.

ViewGroup是什么:

ViewGroup继承自VIew, 作为 View与ViewGroup的容器,是一系列Layout…的父类, 比如LinearLayout等等.

坐标系

分类:

Android 坐标系
View坐标系

Android 坐标系:

概念:

将屏幕 左上角 的顶点作为坐标系 原点, 原点向右是 X轴正方向, 原点向下是 Y轴正方向.

Android坐标系

获取坐标方法:

getRawX()
getRawY()

View坐标系

概念:

view坐标描述的是这个view视图在父视图中的位置.
View和位置主要由它的四个顶点来决定,分别对应View的四个属性:top、left、right、bottom,top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标.
View坐标系

方法

四个顶点分别为:

getLeft();
getRight();
getTop();
getBottom();

获取自身宽高:

width = getRight() - getLeft()
height = getBottom() - getTop()

在MotionEvent提供的方法

获取在View中的触摸点坐标:
getX();
getY();

获取在整个屏幕的触摸点坐标:
getRawX();
getRawY();

View的滑动

概念:

在Android 中要自定义控件, 肯定要做滑动处理. 哪种滑动方式本质上都是记下手指的触摸坐标, 然后不断记录下移动后的坐标, 然后计算偏移量, 修改View的坐标.这个就是View的滑动的原理.

代码示例

private int lastX;
private int lastY;

 @Override
    public boolean onTouchEvent(MotionEvent event) {

        //获取手指触摸点的横坐标和纵坐标
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //记录按下的点
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //计算移动距离(偏移量)
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                
		//在这里重新设置View的坐标

                break;
            default:
        }
        return true;
    }

滑动方法:

layout()
layout(left, top, right, bottom);// 传入View的四个坐标即可
offsetLeftAndRight()与offsetTopAndBottom()
offsetLeftAndRight(偏移量X); //传入偏移量X即可
offsetTopAndBottom(偏移量Y);//传入偏移量Y即可
LayoutParams(改变布局参数)
//根据父控件来创建LayoutParams
                ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                layoutParams.rightMargin = getRight() + offsetX;
                layoutParams.bottomMargin = getBottom() + offsetY;
                setLayoutParams(layoutParams);
动画
//可在XML文件和Java文件定义动画, 然后用View调用.
view.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate))
Scroller

scrollTo(x, y);
scrollTo表示移动到某一个具体的坐标点.

scrollBy(dx, dy)
而scrollBy表示移动到的增量为dx, dy.scrollBy最终也是调用scrollTo方法进行移动的.

scrollTo和scrollBy特点
这两个方法并不是移动View这个对象, 而是移动手机屏幕这个窗口, 比如View是一个报纸, 手机屏幕是一个放大镜. scroll方法只是在移动这个放大镜, 而不是移动报纸本身.
scroll

假如我们要根据上面的例子来使view移动:

((View)getParent()).scrollBy(-offsetX, -offsetY);

就需要传入负的偏移量.这样看的效果就正常了.

属性动画

更加详细的可以参考这个文章:

Android 属性动画:这是一篇很详细的 属性动画 总结&攻略 作者:Carson_Ho

属性动画是什么:

属性动画(Property Animation)是Android 3.0开始推出的, 在之前Android 只提供了两种动画, 帧动画(Frame Animation) 和 补间动画( Tweened animation ).
但是补间动画存在很大的问题.

  1. 没有改变View的属性,只是改变视觉效果. 响应也是在原来的地方.
  2. 对象局限于View
  3. 动画效果单一, 只能实现平移、旋转、缩放 & 透明度这些简单的动画需求.

原理:

属性动画通过控制一个对象, 一个属性值, 从而达到精细化控制View的目的.

常用类:

ObjectAnimator:

控制一个对象, 一个属性值, 实现动画.

ValueAnimator:

本质是只操作值, 不提供动画效果, 只产生有一定规律的数字, 通常在AnimatorUpdateListener监听数值的变化.

AnimatorSet:

使用多个ObjectAnimator组合成一个动画

ObjectAnimator的简单使用:
ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);
// ofFloat()作用有两个 
// 1. 创建动画实例
// 2. 参数设置:参数说明如下
// Object object:需要操作的对象 
// String property:需要操作的对象的属性 
// float ....values:动画初始值 & 结束值(不固定长度) 
// 若是两个参数a,b,则动画效果则是从属性的a值到b值 
// 若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值 
// 以此类推 
// 至于如何从初始值 过渡到 结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator,同ValueAnimator讲解

 anim.setDuration(500); // 设置动画运行的时长 
 
 anim.setStartDelay(500); // 设置动画延迟播放时间 
  // 动画播放次数 = infinite时,动画无限重复 
  // 设置动画重复播放次数 = 重放次数+1 
 anim.setRepeatCount(0);
 
  // 设置重复播放动画模式 
 // ValueAnimator.RESTART(默认):正序重放 
 // ValueAnimator.REVERSE:倒序回放
anim.setRepeatMode(ValueAnimator.RESTART); 

 animator.start(); // 启动动画
ValueAnimator的简单使用:
final ValueAnimator animator = ValueAnimator.ofInt(0, 100);
43         animator.setDuration(5000);
44         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
45             @Override
46             public void onAnimationUpdate(ValueAnimator animation) {
47                 /**
48                  * 通过这样一个监听事件,我们就可以获取
49                  * 到ValueAnimator每一步所产生的值。
50                  *
51                  * 通过调用getAnimatedValue()获取到每个时间因子所产生的Value。
52                  * */
53                 Integer value = (Integer) animation.getAnimatedValue();
54                 btn_click.setText(value + "");
55             }
56         });
57         animator.start();
AnimatorSet的简单使用:
AnimatorSet bouncer = new AnimatorSet();

 // 右移300f 
 ObjectAnimator objectAnimatorA = ObjectAnimator.ofFloat(btnProperty, PropertyConstant.PROPERTY_TRANSLATION_X, 0f, 300f); 
 
 // 下移300f 
 ObjectAnimator objectAnimatorB = ObjectAnimator.ofFloat(btnProperty, PropertyConstant.PROPERTY_TRANSLATION_Y, 0f, 300f); 
 
 // 从右边300f左移至原位置 
 ObjectAnimator objectAnimatorC = ObjectAnimator.ofFloat(btnProperty, PropertyConstant.PROPERTY_TRANSLATION_X, 300f, 0f); 
 
 // 旋转360度 
 ObjectAnimator objectAnimatorD = ObjectAnimator.ofFloat(btnProperty, PropertyConstant.PROPERTY_ROTATION, 0f, 360f);

bouncer.play(objectAnimatorA).before(objectAnimatorB); 

bouncer.play(objectAnimatorB).with(objectAnimatorD); 

bouncer.play(objectAnimatorC).after(objectAnimatorB); 

bouncer.setDuration(6000); bouncer.start();

解析Scroll

可参考Android Scroll详解(三):Android 绘制过程详解 作者:ztelur

解析原理:

startScroll():

当我们调用startScroll()时, 一般也会调用invalidate()方法.

startScroll();
invalidate();

invalidate()又会导致View的重绘, 而View的重绘会调用 View的draw()方法, 而draw()方法又会调用View的computeScroll()方法.

computeScroll();

我们一般会重写computeScroll()方法

@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			invalidate();
		}
	}

我们在computeScroll()方法中通过, Scroller来获取当前的ScrollX 和ScrollY, 然后调用ScrollTo来进行View的滑动, 接着调用invalidate()进行重绘, 重绘就会computeScroll()方法, 这样就通过不断移动一个小小的距离来实现平滑移动的效果.

我们是如何拿到ScrollX和ScrollY的呢?
当我们在调用ScrollTo()的方法时, 会调用Scroll的ComputeScrollOffset()方法, 里面会赋值给mCurrX 和 mCurrY, 这样我们就能获取到ScrollX和ScrollY了.
ComputeScrollOffset()的返回值如果为true则表示滑动结束, false则表示滑动结束.

View的事件分发机制

更加详细的可以参考这个博客:

Android事件分发机制详解:史上最全面、最易懂 作者:Carson_Ho

概念:

当我们点击屏幕时, 就产生了一个点击事件, 这个事件被封装成了一个 MotionEvent类. 这个事件产生后, 系统将会传递给View
的层级, MotionEvent在传递的过程就是事件分发.

三个重要方法

dispatchTouchEvent(MotionEvent ev):

用来进行事件的分发.

onInterceptTouchEvent(MotionEvent ev):

用来进行事件的拦截, 在dispatchTouchEvent()中调用, 需要注意的是View没有提供该方法.

onTouchEvent(MotionEvent ev)

用来处理点击事件, 在dispatchTouchEvent()方法中进行调用.

事件序列:

一个完整的事件序列是以Down开始, 以UP结束.
如果是滑动, 就是Down–>Move…–>UP.
每一次Down都是一个新的事件.
事件序列

@Override
public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //按下

            break;
        case MotionEvent.ACTION_MOVE: //移动

            break;
        case MotionEvent.ACTION_UP: //松开

            break;
    } return super.onTouchEvent(event);
}

事件分发传递流程:

由上而下的(由外到内)传递规则:

当点击事件产生后, 会首先给Activity处理, 然后传递给根ViewGroup的dispatchTouchEvent()方法, 如果该ViewGroup的onInterceptTouchEvent()方法返回true, 表示它要拦截此事件, 这个事件就会交给它的onTouchEvent()方法处理; 如果返回false. 则表示它不拦截这个事件, 这个事件将会给它的子元素的dispatchTouchEvent()方法来处理, 如此反复下去. 如果传递给底层的View, View是没有子View的, 就会调用View的dispatchTouchEvent()方法, 大部分情况会最终调用View的onTouchEvent方法.

由下而上的(由内到外的)传递规则:

当点击事件传给底层的View时候, 如果其onTouchEvent()方法返回true, 则由底层View处理, 如果返回false则表示该View不做处理, 则传递给该父View的onTouchEvent()处理; 如果父View的onTouchEvent()仍旧返回false, 则继续传递给该父View的父View处理, 如此反复下去.
事件分发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值