本文共 2852 字,大约阅读时间需要 9 分钟。
经过前面两篇文章的介绍,相信大多数同学已经清楚的认识了View
,那么我们来看下自定义View
这个主题,在小册简介中,我们已经描述了自定义View
的目的,同学们还记得吗?用于解决使用系统控件实现不了或实现比较复杂的UI效果。
在View
简介部分,我们看到不管是XXXLayout
还是XXXView
,这些系统组件都直接或间接继承自View
,那么自定义View
的方式自然也有区分,根据继承父类的不同,我们大致可以将自定义View
分为三类:
View
ViewGroup
ImageView
,TextView
,LinearLayout
等诸如此类系统控件)有同学们要问了,你前两节讲的是View
,这里又提到了ViewGroup
,是不是又要讲下ViewGroup
的生命周期?当然不需要啦。ViewGroup
作为View
的子类,它的特点是什么呢?管理其内部的多个子View
,其生命周期与View
生命周期基本相同,同样要经过构造,绑定,布局,绘制的过程,只不过在布局过程中的测量部分,其需要驱动内部子View
进行自身测量,在布局过程的layout
部分,其需要驱动子View
layout
,在绘制过程中其既要完成自身绘制,又要驱动内部子View
绘制。从这里可以看出View
树的创建过程实际上是一个对布局的深度优先遍历过程,因为整个Activity
布局的根布局肯定是ViewGroup
的直接或间接子类。
那么自定义View
究竟怎么做呢?那么多函数是否都需要重写?答案是不需要,通常情况下我们只需要重写构造,布局及绘制过程即可,一个基本的自定义View
代码如下图所示(以继承自View
为例):
public class MyCustomView extends View { /****** 构造过程 ****/ public MyCustomView(Context context) { super(context); } public MyCustomView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } /****** 布局过程中的测量部分 ****/ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /****** 布局过程中的布局部分 ****/ @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) { super.onDraw(canvas); }}
完成了自定义View
的声明,我们怎么使用它呢?有两种方式:
new
关键词调用构造函数创建,随后添加到View
树中xml
文件中声明引用使用new
关键词创建并添加到View
树的示例代码如下:
public class CustomViewActivity extends AppCompatActivity { private MyCustomView mCustomView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCustomView = new MyCustomView(this); setContentView(mCustomView); }}
使用xml
直接声明引用的代码如下:
对应的java
代码如下:
public class CustomViewActivity extends AppCompatActivity { private MyCustomView mCustomView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_custom_view); mCustomView = findViewById(R.id.custom_view); }}
总体来说和系统控件的使用方式是一样的,需要注意的是在xml
文件中引用时,一定要使用完整包路径并保证路径和实际一致,在代码重构中,移动了自定义View
的包路径后,记得修改xml
中引用的路径,否则会爆ClassNotFoundException
,这是因为View
树的创建过程中,是通过反射进行View
对象初始化的。有些好奇的同学要问了,为啥系统组件不用写包路径,自己写的View
就需要包路径,这不是赤果果的歧视吗?当然不是,其实对于系统组件而言,其在View
树创建时系统默认添加了包路径,所以就不需要自己指定了哈。
转载地址:http://sqpei.baihongyu.com/