ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Android~自定义View和事件分发

2021-03-01 00:00:31  阅读:286  来源: 互联网

标签:控件 MeasureSpec layout 自定义 int Android View


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

两种坐标系

Android坐标系,左上角为原点,触控事件中的getRawX()和getRawY()获取的就是该坐标系下的值。
安卓坐标系
视图坐标系,描述的是子视图和在父视图的位置。可以获取到自身宽高,自身坐标。
View坐标系

事件分发介绍

首先我们先要知道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中调用。
    ViewGroup事件分发源码

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

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>
  1. attrs.xml文件declare-styleable标签定义及相关属性
  2. 在布局文件中导入自定义的属性集。两种方法
<!-- 方法1 com.example 是应用的清单文件的包名 -->
xmlns:custom="http://schemas.android.com/apk/res/com.example"
<!-- 方法2 -->
xmlns:custom="http://schemas.android.com/apk/res-auto"
  1. 代码中如何获取自定义属性值
TypedArray arry = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
float size = arry.getDimension(R.styleable.CustomView_size,10f);
// to use
ViewGroup绘制流程

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

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

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

  1. UNSPECIFIED: 未指定模式,子View不受父View的限制,子View可以设置任意大小。一般用于系统内部的测量。
  2. EXACTLY:精确模式,对应于match_parent和具体数值,子元素被限定于给定的边界。
  3. 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流程
  1. 如果需要绘制背景
  2. 保存当前canvas层
  3. 绘制View的内容,即onDraw()方法 是一个空实现由我们自己实现
  4. 绘制子View,调用dispatchDraw()对子View遍历,子View绘制
  5. 如果需要绘制View的褪色边缘,类似于阴影效果
  6. 绘制装饰,如滚动条。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。我们再接到需求是就应判断最接近那种实现,那种实现方便后续维护。

标签:控件,MeasureSpec,layout,自定义,int,Android,View
来源: https://blog.csdn.net/Bluechalk/article/details/114105489

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有