Android~自定义View和事件分发

Android~自定义View和事件分发,第1张

概述老生常谈自定义View,我们去查阅安卓相关书籍总是会有那么一章讲述自定义View的原理。说明这是高级UI的基础,高级UI自然范围也很大,本篇文章总结一下自定义View的套路,因为实际开发中我们时不时会需要自定义View,目的是加速开发。两种坐标系Android坐标系,左上角为原点,触控事件

老生常谈自定义view,我们去查阅安卓相关书籍总是会有那么一章讲述自定义view的原理。说明这是高级UI的基础,高级UI自然范围也很大,本篇文章总结一下自定义view的套路,因为实际开发中我们时不时会需要自定义view,目的是加速开发。

两种坐标系

AndroID坐标系,左上角为原点,触控事件中的getRawX()和getRawY()获取的就是该坐标系下的值。


视图坐标系,描述的是子视图和在父视图的位置。可以获取到自身宽高,自身坐标。

事件分发介绍

首先我们先要知道Activity中VIEw的层级,是自上而下的,具体我们可以去参考Activity的setContentVIEw()跟踪源码。即:

Activity ——PhoneWindow——DectorVIEw——rootVIEwGroup——子VIEw

一个完整的事件流程是从Down开始的,UP结束,我们称作这为一个事件序列。某一事件序列经过触摸屏传递各个VIEw,由各个vIEw来处理这一事件的过程,即为事件分发。事件分发的三个重要方法:

dispatchtouchEvent(MotionEvent ev) :用来进行事件的分发onIntercepttouchEvent(MotionEvent ev) :用来进行事件的拦截,dispatchtouchEvent中调用该方法,vIEw中未提供该方法。ontouchEvent(MotionEvent ev) :用来处理touch事件,dispatchtouchEvent中调用。

点击事件传递的规则,用伪代码表示如下:

public boolean dispatchtouchEvent(MotionEvent ev){	boolean res = false;	if(onIntercepttouchEvent(ev)) { // 拦截后自己处理		res = ontouchEvent(ev);	}else {		res = child.dispatchtouchEvent(ev); // 分发	}	return res;}
自定义属性
<resources><!-- resource是跟标签,可以在里面定义若干个declare-styleable --><declare-styleable name="CustomVIEw"> <!-- 属性集名称-->    <attr name="color" format="color" /> <!-- 属性名称-->    <attr name="size" format="dimension" />    <!--每一个发生要定义format指定其类型,类型包括       reference   表示引用,参考某一资源ID      string   表示字符串      color   表示颜色值      dimension   表示尺寸值      boolean   表示布尔值      integer   表示整型值      float   表示浮点值      fraction   表示百分数      enum   表示枚举值      flag   表示位运算    -->    <attr name="background" format="reference|color" />    <!-- 注:属性可以有多种类型 --></declare-styleable>
attrs.xml文件declare-styleable标签定义及相关属性在布局文件中导入自定义的属性集。两种方法
<!-- 方法1 com.example 是应用的清单文件的包名 -->xmlns:custom="http://schemas.androID.com/apk/res/com.example"<!-- 方法2 -->xmlns:custom="http://schemas.androID.com/apk/res-auto"
代码中如何获取自定义属性值
TypedArray arry = context.obtainStyledAttributes(attrs, R.styleable.CustomVIEw);float size = arry.getDimension(R.styleable.CustomVIEw_size,10f);// to use
VIEwGroup绘制流程

VIEw和VIEwGroup绘制流程基本相同,只是VIEwGroup除了绘制自己还需要绘制子控件。绘制流程分为测量 ——布局——绘制 主要对应下面三个函数:

onMeasure():测量当前控件大小并为布局提供建议onLayout():使用layout()函数对所有子控件进行布局onDraw():根据测量布局的位置绘图measure流程和MeasureSpec

MeasureSpec是int型数字,但它由两部分组成mode+size,它转换为二进制前两位代表模式后30位代表数值,它有三种模式。MeasureSpec是VIEw的内部类,作用是在Measure的过程中,将VIEw的LayoutParams根据父容器所分发的规则转换成对应的MeasureSpec,最后在onMeasure根据该值确定VIEw的宽高。

UnspecIFIED: 未指定模式,子VIEw不受父VIEw的限制,子VIEw可以设置任意大小。一般用于系统内部的测量。EXACTLY:精确模式,对应于match_parent和具体数值,子元素被限定于给定的边界。AT_MOST:最大模式,对应于wrap_content,父控件给子控件分配的Specsize。

作为顶层的VIEw,它没有父容器。DecorVIEw的getRootMeasureSpec方法第一个参数windowsize是指窗口尺寸,它的MeasureSpec由自身的LayoutParams和窗口尺寸大小决定。
特别需要注意的是,wrap_content对应AT_MOST,当布局文件中配置为EXACTLY模式时,我们就直接使用该值即可,当模式为AT_MOST,我们还需要将大小设置为我们计算的值,该值应该是包含控件最大值。
VIEw的measure流程 : 先判断有无背景,取mMinWIDth和背景的最小宽度的最大值; 再通过measureSpec获取默认大小

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;}protected int getSuggestedMinimumWIDth() {    return (mBackground == null) ? mMinWIDth : max(mMinWIDth, mBackground.getMinimumWIDth());}

VIEwGroup源码中无onMeasure方法,它的measure流程 :直接遍历测量子VIEw的MeasureSpec,measureChild中则是先获取自己的LayoutParams ,再计算自己的getChildMeasureSpec。

protected voID measureChildren(int wIDthMeasureSpec, int heightmeasureSpec) {    final int size = mChildrenCount;    final VIEw[] children = mChildren;    for (int i = 0; i < size; ++i) {        final VIEw child = children[i];        if ((child.mVIEwFlags & VISIBIliTY_MASK) != GONE) {            measureChild(child, wIDthMeasureSpec, heightmeasureSpec);        }    }}protected voID measureChild(VIEw child, int parentWIDthMeasureSpec,            int parentHeightmeasureSpec) {    final LayoutParams lp = child.getLayoutParams();    final int chilDWIDthMeasureSpec = getChildMeasureSpec(parentWIDthMeasureSpec,            mpaddingleft + mpaddingRight, lp.wIDth);    final int childHeightmeasureSpec = getChildMeasureSpec(parentHeightmeasureSpec,            mpaddingtop + mpaddingBottom, lp.height);    child.measure(chilDWIDthMeasureSpec, childHeightmeasureSpec);}
layout流程

VIEw中的layout是用来确定自身的位置。调用层级是layout调用setFrame确定该VIEw在父容器中的位置,最后才调用onLayout。
VIEwGroup的layout则是用来确定子元素的位置,不通的布局有不同的摆放规则,但都离不开最终调用setChirdFrame方法,调用子VIEw的layout方法确定子VIEw的位置。为了满足多种需求,我们有时还需要获取子VIEw的marginLayoutParams和重写generateLayoutParams提取margin值。

draw流程如果需要绘制背景保存当前canvas层绘制VIEw的内容,即onDraw()方法 是一个空实现由我们自己实现绘制子VIEw,调用dispatchDraw()对子VIEw遍历,子VIEw绘制如果需要绘制VIEw的褪色边缘,类似于阴影效果绘制装饰,如滚动条。onDrawForeground()方法

注:getMeasureDWIDth()和getWIDth()函数的区别
他们大多时候是相同的,但含义是不一样的。getMeasureDWIDth一般被调用在layout中,getWIDth则被调用在onDraw中。我们时常会在onDraw混用两个方法,切记用错。

getMeasureDWIDth()函数在measure过程结束后就可以获取到,而getWIDth()需要layout结束后才能获取到。getMeasureDWIDth()的值时通过setMeasuredDimension()进行设置的,getWIDth()是通过layout(left,top,right,bottom)函数设置。总结

对于VIEwGroup我们需要重点关注measure和layout。获取子控件的margin方法。对于VIEw则需要关注measure darw,无需关注layout。自定义控件分为继承VIEw和继承系统控件,继承VIEwGroup和继承系统特定的VIEwGroup。我们再接到需求是就应判断最接近那种实现,那种实现方便后续维护。

总结

以上是内存溢出为你收集整理的Android~自定义View和事件分发全部内容,希望文章能够帮你解决Android~自定义View和事件分发所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1041548.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存