Android View相关
各种组件都是ViewGroup的子类,ViewGroup是View的子类,view结构如下
Android的UI界面是一个树形结构,View的嵌套.子View在父View中,这些View都经过一个相同的流程最终显示到屏幕上
view的位置由top,left,right,bottom四个值来确定,宽度是right-left,高度是bottom-top。
View绘制的过程
每一个View的绘制都有Measure,Layout,Draw三个步骤的过程
measure -> onMeasure() -> layout -> onLayout() -> draw -> onDraw()
- Measure() 测量视图的大小
- Layout() 计算视图的位置
- Draw() 视图绘制到屏幕上
meaure会经历performMeaure -> meaure -> onMeaure 的调用过程,其他两个也是同样
视图绘制过程中会回调onMeasure(), onLayout(), onDraw()方法,自定义View的时候需要重写这三个方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
widthMeasureSpec和heightMeasureSpec是测量的view的尺寸
其中高两位是模式,低两位是测量值
模式分为一下三种
- EXACTLY 当View的layout_width和layout_height设置的是固定值的时候
- AT_MOST 控件的layout_width和layout_height设置成wrap_content的时候,控件的大小随着子控件的大小变化.
- UNSPECIFIED 不指定测量的大小
MeasureSpec和LayoutParam的关系
系统使用MeasureSpec来测量View的尺寸,LayoutParam在绘制的时候会转换成MeasureSpec。LayoutParam需要和父容器一起决定MeasureSpec的值。
重写onMeasure
//widthMeasureSpec和heightMeasureSpec是当前的测量宽高
onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//getMode用来获取测量模式
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int hMode = MeasureSpec.getMode(widthMeasureSpec);
//getSize用来从Spec中获取尺寸
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
getPaddingLeft/Right/Top/Bottom获取Padding尺寸,需要在view中手动减去padding否则设置的padding值无效
//mode和size重新组合成Spec
int resultWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, wMode);
int resultHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, hMode);
//返回设置的尺寸
super.onMeasure(resultWidthMeasureSpec, resultHeightMeasureSpec);
}
ViewGroup的Measure过程
ViewGroup除了测量自己的尺寸还要测量所有子元素的尺寸.
getChildCount(); //获取子元素的个数
View view = getChildAt(index); //获取子元素
重写onLayout
onLayout方法确定ViewGroup所有的子元素的位置
draw绘制流程
- 如果需要,绘制背景。
- 有过有必要,保存当前canvas。
- 绘制View的内容。
- 绘制子View。
- 如果有必要,绘制边缘、阴影等效果。
- 绘制装饰,如滚动条等等。
自定义View需要注意的地方
- View支持wrap_content
- View支持padding,就是设置padding值的时候onMeasure函数返回的尺寸减去padding值
- 使用post方法来传递信息,尽量不使用Handler
- onAttachedToWindow方法加载动画和线程,onDetachedFromWindow方法停止动画和线程,防止一直占用系统资源导致OOM
- View有滑动时处理滑动冲突
onAttachedToWindow () 和 onDetachedFromWindow ()
onAttachedToWindow是在第一次onDraw前调用的。也就是我们写的View在没有绘制出来时调用的,但只会调用一次。 onDetachedFromWindow在所属的activity被销毁的时候调用
onAttachedToWindow方法是在Act resume的时候被调用的,也就是act对应的window被添加的时候,且每个view只会被调用一次,父view的调用在前,不论view的visibility状态都会被调用,适合做些view特定的初始化操作;
onDetachedFromWindow方法是在Act destroy的时候被调用的,也就是act对应的window被删除的时候,且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作;
View的事件分发机制
每一个view都有三个方法来处理点击事件
- dispatchTouchEvent 用于事件的分发,如果传递到当前view,一定会调用这个
- onInterruptTouchEvent 用于判断是否拦截某个事件,被dispatchTouchEvent调用
- onTouchEvent 拦截之后处理点击事件,被onInterruptTouchEvent调用
点击事件是递归传递的按顺序为 Activity -> Window -> View,从顶级ViewGroup一级一级向下直到目标view进行处理.
每个view收到点击事件后会寻找遍历自己的子view分发事件.如果子view能处理则由子view处理,没有的话就由当前view进行处理.
判断的依据是 1.能处理click事件 2.点击的坐标在view的范围内 3.view设置的允许响应事件enable=true.
如果View设置了touchListener,onTouch的返回值(boolean)决定onTouchEvent是否会被回调.如果onTouch返回了false,则会调用上级的onTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
MotionEvent 是手指触控屏幕产生的一系列事件, 主要有以下几种
- ACTION_DOWN 手指刚接触屏幕
- ACTION_MOVE 手指在屏幕上移动
- ACTION_UP 手指离开屏幕
事件触发顺序
点击屏幕松开 DOWN -> UP
点击屏幕滑动再松开 DOWN -> MOVE -> MOVE -> UP
TouchSlop 是能识别出来的最小滑动距离,小于这个距离系统不认为这是滑动操作.主要是屏蔽手抖动的误差
View滑动
一般有三种方法实现View滑动,ScrollTo或者ScrollBy方法,使用动画效果,通过改变LayoutParams使View重新布局
ScrollTo()和ScrollBy()的区别
- scrollTo的两个参数x,y表示的移动的具体位置(目标位置x,y),scrollTo实现了基于传递参数的绝对滑动。
-
scrollBy的两个参数x,y表示移动的偏移量,scrollBy实现了基于传递参数的相对滑动。
- getX():获取点击事件距离控件左边的距离,即视图坐标。
- getY():获取点击事件距离控件顶边的距离,即视图坐标。
- getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标。
- getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标。
Android 手势相关
首先定义GestureDetector
GestureDetector mGestureDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
//当手指按下的时候触发下面的方法
public boolean onDown(MotionEvent e) {
Toast.makeText(MainActivity.this, "Press Down", Toast.LENGTH_SHORT).show();
return true;
}
@Override
//当用户手指在屏幕上按下,而且还未移动和松开的时候触发这个方法
public void onShowPress(MotionEvent e) {
}
@Override
//当手指在屏幕上轻轻点击的时候触发下面的方法
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
//当手指在屏幕上滚动的时候触发这个方法
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
//当用户手指在屏幕上长按的时候触发下面的方法
public void onLongPress(MotionEvent e) {
Toast.makeText(MainActivity.this, "Long pressed", Toast.LENGTH_SHORT).show();
}
@Override
//当用户的手指在触摸屏上拖过的时候触发下面的方法,velocityX代表横向上的速度,velocityY代表纵向上的速度
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
组件应用touch事件, 将touch事件交给gesture处理
button = (Button) findViewById(R.id.test_btn);
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mGestureDetector.onTouchEvent(event);
return true;
}
});
处理滑动冲突的问题
- 内外滑动方向一致的情况
- 内外滑动方向不一致的情况
解决方法
- 重写父容器的onInterruptTouchEvent方法, 判断是否拦截事件.
- 父容器不拦截任何的方法,
解决滑动冲突的一个示例
public class ViewPagerX extends ViewPager {
private int lastX;
private int lastY;
public ViewPagerX(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
super.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - lastX;
int deltaY = y - lastY;
if (Math.abs(deltaX) < Math.abs(deltaY)) {
super.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
super.requestDisallowInterceptTouchEvent(false);
break;
}
lastX = x;
lastY = y;
return super.dispatchTouchEvent(ev);
}
}
自定义控件的实现过程
-
自定义属性的声明和获取
分析需要的自定义属性 在res/values/attrs.xml定义声明 在layout文件中进行使用 在View的构造方法中进行获取 -
测量onMeasure
-
布局onLayout(ViewGroup)
-
绘制onDraw
-
onTouchEvent
-
onInterceptTouchEvent(ViewGroup)
-
状态的恢复与保存