自定义View简介
经过前面两篇文章的介绍,相信大多数同学已经清楚的认识了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
直接声明引用的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CustomViewActivity">
<com.example.myapplication.MyCustomView
android:id="@+id/custom_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
对应的java
代码如下:
public class CustomViewActivity extends AppCompatActivity {
private MyCustomView mCustomView = null;
@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
树创建时系统默认添加了包路径,所以就不需要自己指定了
哈。
END!
往期推荐
原文始发于微信公众号(小海编码日记):View绘制系列(3)-自定义View简介
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/67970.html