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轴正方向.
获取坐标方法:
getRawX()
getRawY()
View坐标系
概念:
view坐标描述的是这个view视图在父视图中的位置.
View和位置主要由它的四个顶点来决定,分别对应View的四个属性:top、left、right、bottom,top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标.
方法
四个顶点分别为:
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方法只是在移动这个放大镜, 而不是移动报纸本身.
假如我们要根据上面的例子来使view移动:
((View)getParent()).scrollBy(-offsetX, -offsetY);
就需要传入负的偏移量.这样看的效果就正常了.
属性动画
更加详细的可以参考这个文章:
Android 属性动画:这是一篇很详细的 属性动画 总结&攻略 作者:Carson_Ho
属性动画是什么:
属性动画(Property Animation)是Android 3.0开始推出的, 在之前Android 只提供了两种动画, 帧动画(Frame Animation) 和 补间动画( Tweened animation ).
但是补间动画存在很大的问题.
- 没有改变View的属性,只是改变视觉效果. 响应也是在原来的地方.
- 对象局限于View
- 动画效果单一, 只能实现平移、旋转、缩放 & 透明度这些简单的动画需求.
原理:
属性动画通过控制一个对象, 一个属性值, 从而达到精细化控制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处理, 如此反复下去.