View绘制系列(3)-自定义View简介

自定义View简介

经过前面两篇文章的介绍,相信大多数同学已经清楚的认识了View,那么我们来看下自定义View这个主题,在小册简介中,我们已经描述了自定义View的目的,同学们还记得吗?用于解决使用系统控件实现不了或实现比较复杂的UI效果。

View简介部分,我们看到不管是XXXLayout还是XXXView,这些系统组件都直接或间接继承自View,那么自定义View的方式自然也有区分,根据继承父类的不同,我们大致可以将自定义View分为三类:

  • 继承自View
  • 继承自ViewGroup
  • 继承自已有控件(ImageViewTextViewLinearLayout等诸如此类系统控件)

有同学们要问了,你前两节讲的是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;

    @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树创建时系统默认添加了包路径,所以就不需要自己指定了哈。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值