Android_View详解
来源:互联网 发布:淘宝女童秋季外套新款 编辑:程序博客网 时间:2024/06/07 04:42
Android_View详解
本文由 Luzhuo 编写,转发请保留该信息.
原文: http://blog.csdn.net/Rozol/article/details/72848723
View的绘制
View的事件分发机制
View的时间冲突处理
Activity控件架构
- 可以使用SDK下Tools目录里的 hierarchyviewer.bat 进行布局分析
- 当我们在Activity中执行
setContentView(R.layout.activity_main)
时,是给ContentView设置布局 - 每个控件都会在界面上占一块矩形区域
-控件分为:- ViewGroup容器控件:
- 可以包含多个View和ViewGroup
- ViewGroup以树的结构管理View和ViewGroup, 主要负责下层控件的测量,绘制,事件传递.
- View控件
- ViewGroup容器控件:
View的绘制
- 关键词: measure [ˈmɛʒɚ] / layout [ˈleˌaʊt] / draw [drɔ]
- 绘制流程: measure -> layout -> draw
measure
- 测量View的宽高
代码的重写
public class MyView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}
我们看看 super.onMeasure(widthMeasureSpec, heightMeasureSpec) 做了什么?
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // ↓↓↓ setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // ↓↓↓ public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }}
- 看来源码中是用 MeasureSpec 这个类来计算大小的, 我们先来了解下这个类
MeasureSpec 这个类是用来测量View的
- 测量模式:
- UNSPECIFIED: 不指定测量模式, 你想多大就多大
- EXACTLY: 精确测量模式, layout_width=”100dp”/”match-parent”都是使用该模式
- AT_MOST: 最大值模式, layout_width=”wrap_content”时使用该模式
我们来测试下三种模式的区别,在测试之前先修改下
public static int getDefaultSize(int size, int measureSpec)
方法的代码我们在
case MeasureSpec.AT_MOST:
下添加两行代码,让他在 layout_width=”wrap_content” 时使用默认值和测量值的最小值, 其他代码不变case MeasureSpec.AT_MOST: result = Math.min(size, specSize); break;
- 效果:
- 测量模式:
- Measure 是对ViewGroup树进行自上而下遍历而确定View最大可用大小的
- 最后需要调用
setMeasuredDimension(width, height);
设置View的宽高值
layout
- 确定View在父容器的位置
代码的重写
@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom);}
Layout的位置是对ViewGroup树进行自上而下遍历而确定的
- 参数说明:
- changed: View大小是否发生改变
- left, top, right, bottom: 左上右下的坐标值
- 实际开发中,ViewGroup会需要重写onLayout来确定子View的位置,View没啥位置可确定的
draw
- 绘制View
代码的重写
@Overrideprotected void onDraw(Canvas canvas) { }
仅需调用canvas进行绘制即可
- Canvas就像画板,Paint就像画笔,使用Paint在Canvas上作画,作画的水平决定这个View控件是否优美.
案例代码
package me.luzhuo.viewdemo;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.util.AttributeSet;import android.util.Printer;import android.view.MotionEvent;import android.view.View;/** * ================================================= * <p> * Author: Luzhuo * <p> * Version: 1.0 * <p> * Creation Date: 2017/6/1 15:55 * <p> * Description: View的绘制机制 * <p> * Revision History: * <p> * Copyright: Copyright 2017 Luzhuo. All rights reserved. * <p> * ================================================= **/public class ViewDemo extends View { private Paint paint; private Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); // 测试图片 public ViewDemo(Context context) { super(context); init(); } public ViewDemo(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ViewDemo(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); } /** * 1. 测量 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(bitmap.getWidth() * 3, widthMeasureSpec), getDefaultSize(bitmap.getHeight() * 3, heightMeasureSpec)); } public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = View.MeasureSpec.getMode(measureSpec); int specSize = View.MeasureSpec.getSize(measureSpec); switch (specMode) { case View.MeasureSpec.UNSPECIFIED: result = size; break; case View.MeasureSpec.AT_MOST: result = Math.min(size, specSize); break; case View.MeasureSpec.EXACTLY: result = specSize; break; } return result; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(bitmap, x - bitmap.getWidth() / 2.0f, y - bitmap.getHeight() / 2.0f, paint); } private float x, y; @Override public boolean onTouchEvent(MotionEvent event) { x = event.getX(); // 控件x坐标 y = event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; } invalidate(); return true; }}
其他重要方法补充
- 回调方法:
protected void onFinishInflate() { } // XML控件加载完成后回调
protected void onSizeChanged(int w, int h, int oldw, int oldh) { } // 控件的大小改变时回调
- 更新方法:
invalidate(); // 重绘(会调用onMeasure()), 如果onMeasure()测量的大小没有发生变化,就不会调用onLayout(), 但是会调用onDraw()
requestLayout(); // 重新测量View的大小, 会调用onMeasure()和onLayout(),但是不会调用onDraw()
View的事件分发机制
- Android上的View可能是重叠在一起的,当我们在手机屏幕上按下时,哪个View该响应呢?事件分发机制就是为解决这个问题的.
触摸事件: 就是触摸屏幕的事件; 触摸事件分为:按下 / 滑动 / 抬起, Android将触摸事件封装为MotionEvent类
MotionEvent的几个重要方法:
ev.getX(); // 相对于父容器的x坐标
ev.getRawX(); // 屏幕x坐标
MotionEvent.ACTION_DOWN; // 按下
MotionEvent.ACTION_MOVE; // 滑动
MotionEvent.ACTION_UP; // 抬起
事件的传递流程
- 当我们点击最上面的View时, 完成的事件传递是这样的:
- Activity -> PhoneWindow -> DecorView -> ContentView -> ViewGroup -> ViewGroup2 -> View -> ViewGroup2 -> ViewGroup -> ContentView -> DecorView -> PhoneWindow -> Activity
- 可见如果事件在传递的过程中都没有被消费, 那么事件将被回传
- 以下我们将简化传递流程, 并演示 dispatchTouchEvent(事件分发) / onInterceptTouchEvent(事件拦截) / onTouchEvent(触摸事件)
- 默认
- ViewGroup2 的 dispatchTouchEvent 返回 true
- ViewGroup2 的 onInterceptTouchEvent 返回 true
- ViewGroup2 的 onTouchEvent 返回 true
- 默认
- 总结
- dispatchTouchEvent: 分发事件(return true:拦截(事件丢弃), false,不拦截(默认)) (注:返回true,事件不会交由onTouchEvent处理)
- onInterceptTouchEvent: 拦截事件 (View没有该回调接口) (return: true:拦截事件,交由onTouchEvent处理, false:传递给子View,由子View的dispatchTouchEvent接收(默认))
- onTouchEvent: 触摸事件(return true:消费了, false:向上级传递(默认))
View的事件冲突处理
事件冲突实际开发中主要为滑动冲突:
解决方案分为:
- 第一种: 写一个新的ViewGroup继承父ViewGroup, 并重写
onInterceptTouchEvent()
- 第二种: 子View(或ViewGroup)使用
getParent().requestDisallowInterceptTouchEvent(true);
请求父类不要拦截Touch事件 (父ViewGroup将不会执行onInterceptTouchEvent()回调)
- 第一种: 写一个新的ViewGroup继承父ViewGroup, 并重写
第一种讲解: 较简单,没什么好讲的,贴个代码做参考吧 (这是限制ViewPager是否可左右滑动的案例代码)
public class CustomViewPager extends ViewPager { private boolean setTouchModel = false; public CustomViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if(setTouchModel){ return super.onInterceptTouchEvent(ev); }else{ return false; } } @Override public boolean onTouchEvent(MotionEvent ev) { if(setTouchModel){ return super.onTouchEvent(ev); }else{ return false; } }}
第二种讲解:
先看容器ViewGroup2的代码, 拦截滑动事件,并进行处理
@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { Log.e("ViewGroupEvent2", "onInterceptTouchEvent"); switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: return true; } return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) { Log.e("ViewGroupEvent2", "onTouchEvent"); return true;}
然后触摸view执行的结果, 可见滑动事件都被ViewGroup2消费掉了,view都没有拿到
然后我们在子view中加入
getParent().requestDisallowInterceptTouchEvent(true)
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { Log.e("ViewEvent", "dispatchTouchEvent"); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("requestDisallowInterceptTouchEvent(true)"); getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: System.out.println("requestDisallowInterceptTouchEvent(false)"); getParent().requestDisallowInterceptTouchEvent(false); break; } return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) { Log.e("ViewEvent", "onTouchEvent"); return true;}
执行结果如下,可见view已经夺得了滑动事件的控制权
ViewGroup
measure
- ViewGroup是管理子View的, MeasureSpec.AT_MOST 模式时, ViewGroup会先子View进行遍历, 获取所有子View的大小, 然后决定自己的大小
- 当子View测量完成后可通过调用
view.getLayoutParams()
获取宽高信息
layout
- 可通过
view.layout(l, t, r, b);
设置子View的位置
draw
- 容器没啥可绘制的
- Android_View详解
- android_view
- android_view
- Android_VIEW
- android_View Animation
- Android_View动画
- Android_View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解
- Android_View的绘制过程
- android_View回调函数
- Android_view的触摸反馈
- Android_View和viewgroup测绘顺序
- Android_View与Activity的转化
- Android_View,ViewGroup,Window之间的关系
- Android_View,ViewGroup,Window之间的关系
- Android_View,ViewGroup,Window之间的关系
- Android_View类_基础(六)
- 【Android_View】ImageView源码简析笔记(一)
- 【Android_View】ImageView源码简析笔记(二)
- python——面向对象进阶
- HDOJ 2160 母猪的故事
- Maven学习(二)
- python——面向对象基础
- python字符串实战
- Android_View详解
- 51Nod-1134 最长递增子序列【LIS】
- python——迭代器和生成器
- 清除浮动的三种方式
- python——模块
- XML中CDATA及其字符实体的使用
- ython——杂货铺
- Jetson TX1 开发教程(5)--配置Qt Creator和远程桌面
- Kotlin Reference (五) 类的构造函数,类的继承,属性操作