学习资料:
Android群英传
Android开发艺术探索
- 刘望舒Android View体系系列
- 爱哥AigeStudio自定义控件其实很简单系列
感谢以上各位大神前辈们 :)
除了爱哥的自定义系列博客,再推荐一个非常不错的系列,GcsSloop的自定义系列,可以看完我写的再去看,我写的很基础,适合刚开始学习自定义View
的同学来了解基础知识,原理及更深入的知识,可看两位大神的系列 : )
安利,安利,安利
整个系列学习,记录的大概过程:
- 准备
- 开始了解Canvas和Paint
- Paint 绘制文字属性
- Paint 关于ColorMatrix学习
- Paint 关于PorterDuffXfermode学习
- Paint 关于Shader的学习
- 补充学习Bitmap
- Canvas 方法 以及属性学习
- Matrix学习
- Drawable补充学习
- 贝塞尔曲线入门学习
- SurfaceView学习
- Camera 图像处理入门学习
- View测量
- ViewGroup知识学习
- 注意事项和继承View知识点学习
- 触摸事件学习
- 滑动学习
- ViewDragHelper学习使用
- PathMeasure学习
- 学习总结
-
画图板练习
是一个长期的学习计划 : )
本篇就是个读书笔记而已
1.Android控件架构
Android中的每个控件都会在界面中占据一个矩形区域。控件大致分为View
和ViewGroup
。ViewGroup
控件作为父类控件可以包含多个View
控件。
通过ViewGroup
,整个界面控件形成树形结构,也即是控件树。上层控件负责下层控件的测量和绘制,并传递交互事件。在一个Activity
中,findViewById()
就是在控件树中以树的深度优先遍历来查找对应的元素。
在每棵树的顶部,都有一个ViewParent
对象,所有的交互管理事件都有这个ViewParent
对象调度和分配
通常情况下,在Activity
中使用setContent()
方法设置一个布局在调用本方法后,布局内容才会真正显示出来。
每个Activity
都包含有一个Window
对象,通常是PhoneWindow
。PhoneWindow
将一个DecorView
设置为整个应用的窗口的根View
。DecorView
作为窗口界面顶层视图,里面封装了一些窗口操作的通用方法。
DecorView
将内容显示在PhoneWindow
上,并通过WindowManagerService
来进行接收,并通过Activity
对象来回调对应的onClickListener
。显示时,将屏幕分成两个部分,TitleView
和ContentView
。Content
是一个id
为content
的FrameLayout
,activity_main.xml
就在其中。
通过以上就可以得到下面的标准视图树:
标准视图树视图树的第二层加载一个LinearLayout
作为ViewGroup
。这一层布局结构会根据对应的参数设置不同的布局。
最常用的布局,上面显示TitleBar
下面是Content
。如果用户通过设置requestWindowFeature(Window.FEATURE_NO_TITLE)
来设置全屏显示,视图树就只有Content
。这也是为什么requestWindowFeature()
要在setContent()
之前生效的原因。
当程序在onCreate()
方法中调用了setContentView()
方法后,ActivityManagerService
会回调onResume()
方法,系统会把整个DecorView
添加进PhoneWindow
中,显示出来后,完成界面的绘制。
2.坐标体系
View
的位置只要由它的四个顶点来决定。分别对应于View
的四个属性:
-
top
,getTop()
左上角的纵坐标 -
left
,getLeft()
左上角的横坐标 -
right
,getRight()
右下角的横坐标 -
bottom
,getBottom()
右下角的纵坐标
View
的这些坐标都是相对于View
的父容器来说。
在Android 3.0 后,
View
增加了:x
,y
,tranlastionX
和translationY
。x
,y
是View
左上角的坐标,tranlastionX
和translationY
是View
左上角相对于父容器的偏移量。
换算关系:
x = left + translationX
y = top + translationY
需要注意的是,View
在平移过程中,top和left表示的原始左上角的位置信息,其值并不会发生改变,此时发生改变的是x
,y
,tranlastionX
和translationY
图颜色配的有点多。 :)
3.View的测量
一个View
显示在屏幕上需要经历三个流程:测量,布局,绘制
测量的目的在于告诉系统绘制一个多大的,位置在哪里。这个过程在onMeasure()
方法中进行。
测量主要依赖MeasureSppec
类。MeasureSpec
是一个32位的int值,高2位为测量的模式,低30位为测量的大小。
测量的模式共有三种:
-
EXACTLY
精确模式,两种情况
控件的layout_width,layout_height
- 指定数值时,例如
layout_width="100dp"
- 指定为
match_parent
-
AT_MOST
最大值模式
控件的layout_width,layout_height
指定为wrap_content
控件大小一般会随着子空间的或内容的变化而变化,此时要求控件的尺寸只要不超过父类控件允许的最大尺寸即可
-
UNSPECIFIED
未指明模式
不指定控件大小,View
想多大就多大。
View
类默认的onMeasure()
方法只支持EXACTLY
模式。自定义View
时,需要重写onMeasure()
方法后,才可以支持其他的模式。假如想要自定义的控件支持wrap_content
,就要在onMeasure()
中告诉系统自定义控件wrap_content
时的大小。
3.1 最基础的实现
用来测试onMeasure()
方法。
public class MeasureView extends View {
public MeasureView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
在布局中使用:
<com.szlk.customview.custom.MeasureView
android:id="@+id/mv_custom_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent" />
wrap_content效果
运行之后,wrap_content
在此时和match_parent
效果是一样的。都是沾满全屏。此时在Acitivity
中拿到的MeasureView
的大小和match_parent
是一样的。
3.2 修改onMeasure()方法
重写onMeasure()
方法后
public class MeasureView extends View {
public MeasureView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),measuredHeight(heightMeasureSpec));
}
/**
* 测量宽
* @param widthMeasureSpec
*/
private int measureWidth(int widthMeasureSpec) {
int result ;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else {
result = 200;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
/**
* 测量高
* @param heightMeasureSpec
*/
private int measuredHeight(int heightMeasureSpec) {
int result ;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 200;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
}
加入了利用MeasureSpec
来判断模式。根据不同模式,进行对宽高赋值。在AT_MOST
也就是wrap_content
时,默认最大的宽高都是200px
3.3 涉及的部分源码
getMode()
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
由@return
可知,返回结果就是MeasureSpec
的三种模式
getSize()
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
返回的是在布局文件中声明的值,结果是px
onMeasure()
/**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overridden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在View
的onMeasure()
方法源码中,调用了setMeasuredDimension()
方法来确定View
的宽和高
View
的源码23000行。 : )
4.最后
写的都比较表面,目前深入不了,基本就是看Android 群英传
第三章的读写笔记。也不晓得能不能对学习自定义View
有些帮助。后面学习,遇到一些知识点也会再补充。
下一篇学习了解一下Canvas
和Paint
都是干嘛的。
网友评论
我这里也是人云亦云的结论,我也尝试去看了MeasureSpec的代码,里面的位运算操作看的懵懵懂懂的,你也可以去看看,多少对理解有好处。最近一直不再写新的博客原因也是这样,很多以前写的东西,并没有在实际开发中用过,很多理解都有误,就想着以后再写东西,有一定程度的理解再发。