自定义 view 篇

思维导图

知识点补充

坐标详解

View提供获取坐标方法
1
View的坐标系:是相对于父控件而言的

getTop(): 获取View自身顶边到其父布局顶边的距离。

getLeft(): 获取View自身左边到其父布局左边的距离。

getRight(): 获取View自身右边到其父布局左边的距离。

getBottom(): 获取View自身底边到其父布局顶边的距离。

getTranslationX(): 计算的是该View在X轴的偏移量。初始值为0,向左偏移值为负,向右偏移值为正。

getTranslationY(): 计算的是该View在Y轴的偏移量。初始值为0,向上偏移为负,向下偏移为证。

MotionEvent提供的方法

getX():获取点击点距离所在组件坐标系的左边距离,即视图坐标。

getY():获取点击点距离所在组件坐标系的顶边距离,即视图坐标。

getRawX():获取点击点距离整个屏幕左边距离,即绝对坐标。

getRawY():获取点击点距离整个屏幕顶边距离,即绝对坐标。

getMeasureWidth() 和 getWidth()

一般情况下相等,获取时机不一样。

  1. getMeasureWidth(): measure() 结束后可以获取,通过 setMeasuredDimension 设置的。

  2. getWidth(): onLayout()之后才能获取,通过视图 右边的坐标 - 左边的坐标, 计算来的。

硬件加速

onMeasure, onLayout, onDraw 流程详解

onMeasure

Q: 最终测量的大小是如何确定的,有什么规则?

A: 大小是由设置的子元素的大小和所在父元素的测量规则一起决定。

Q: 在哪里实现遍历,测量子元素的宽高?自定义 view 的时候需要手动调用吗?

A: 需要手动调用。在父元素( ViewGroup ) 的 onMeasure 里面对子元素进行测量。根据控件需要,可以如下:

  • 逐个测量的方法。比如 child.measure (包括内边距) 或者 child.measureChildWithMargins (包括内边距 + 外边距) 。

  • 全部测量方法。 parent.measureChildren, 其内部实现也是去遍历 child.measure

onLayout

Q: 在哪里循环遍历,确定子元素的位置?自定义 view 的时候需要手动调用吗?

A: 需要手动调用。在父元素( ViewGroup )的 onLayout 里面,循环调用 child.layout 方法。以 RelativeLayout.java 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

// The layout has actually already been performed and the positions

// cached. Apply the cached values to the children.

final int count = getChildCount();

for (int i = 0; i < count; i++) {

View child = getChildAt(i);

if (child.getVisibility() != GONE) {

RelativeLayout.LayoutParams st =

(RelativeLayout.LayoutParams) child.getLayoutParams();

child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);

}

}
}

onDraw

Q: 在哪里循环遍历,绘制子元素?自定义 view 的时候需要手动调用吗?

A: 不需要手动调用,View 在 onDraw 方法会调用 dispatchDraw ,而 ViewGroup 会实现该方法,在 dispatchDraw 进一步调用 drawChild 实现遍历绘制子元素。如下:ViewGroup.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
protected void dispatchDraw(Canvas canvas) {
/**
* 省略代码.........
*/
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}

final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
/**
* 省略代码.........
*/
易忘点
1
2
3
4
5
Path.Direction.CCW	逆时针 

Path.Direction.CW 顺时针

rect.exactCenterX() 获取中心点X轴坐标

Q&A

自定义 view , xml预览空白

what: 控制台提示报错

1
2
3
4
5

The following classes could not be instantiated: - com.green.tomato.datusi.module.base.widget.RemainEditText (Open Class, Show Exception, Clear Cache)

Tip: Use View.isInEditMode() in your custom views to skip code or show sample data when shown in the IDE.

when: 自定义view,在 xml 预览的时候。

why:自定义 view 里面,context 为 null,导致报错。

how:增加判断

if ( null == mContext ) return;

获取系统控件的某些xml定义值

Q: 在继承以后控件进行扩展的时候,有时候需要获取一些 xml的属性值,但是该值没有暴露接口,无法获取。或者获取的方法有版本兼容性问题,那么改怎么办呢?

A:举个例子:【实战1练习】我有个控件是继承 EditText, 实现右下角显示数字。这个时候我要获取 editText 的 maxLength。获取方法可以这样。

  1. 用反射

  2. API > 21 的时候,系统有提供方法

1
2
3
4
5
6
7
8
for (InputFilter filter : mEditText.getFilters()) { 

if (filter instanceof InputFilter.LengthFilter) {

((InputFilter.LengthFilter) filter).getMax());

}
}

明显这些都不是好选择,那我们可以怎么做呢?

重新定义一个字段,在xml定义使用该字段代表 maxLength 的值,自定义 view 里面获取该字段。

  1. 定义 edit_text_count_max_length 字段

  2. xml 声明的时候,使用该字段

  3. 自定义 view 时候使用该字段

实战练习

  1. 自定义 EditText 实现右下角显示使用字数(kotlin)

  2. 饼状图练习(kotlin)

感谢

本文中思维导图里面引用的图片,来自下面参考文章里面的截图,感谢大佬们的总结。

参考文章

  1. Android 开发艺术探索 - 第3 ,4 章

  2. 安卓自定义View教程目录

  3. 图解View测量、布局及绘制原理

  4. 自定义View,有这一篇就够了 - 简书

  5. Android 自定义 View 合集 - Android - 掘金

  6. Android开发之getX,getRawX,getWidth,getTranslationX等的区别

  7. GitHub - chenzongwen/MiClockView

  8. onDraw顺序动态图

  9. How to Get EditText maxLength setting in code

坚持原创技术分享,您的支持将鼓励我继续创作!