自定义View(一)之初识自定义View

一,前言

看过我的blog的小伙伴都知道,我的博客的第一条都是概述,这是第一次写前言。此次写前言一方面是为了对自己未来一段时间关于写博客的规划,另外一方法将我的规划告诉志同道合的小伙伴,接下来很长一段时间我们将一起学习自定义View以及Android中View的绘制机制。

我的自定义View的学习起源于CSDN,当时由于工作的需要开始在CSDN上搜索自定义view的blog查看,慢慢的可以实现了简单的自定义View的需求。随着时间的推移,自定义View的blog看的多了,也在工作中积累了相关经验,慢慢的自定义View的知识点掌握了很多,也做了很多笔记,大家都知道我早期总结的笔记都保存到本地。最近又看View相关的源码,又在网上看了很多精彩的blog,也看了任玉刚大神的《Android开发艺术探索》一书上对Android中View的绘制机制的讲解,现在对View的绘制机制,有了整体的理解。明白了setContentView方法是怎么将View添加到Activity上的,为什么view的显示是在onResume方法执行之后,ViewRootImpl与DecorView在View机制中的作用,View显示的触发机制,以及View树的绘制机制。下面会花很长一段时间将关于view方面的知识逐渐分享到CSDN上,与志同道合的小伙伴们一起学习。

任玉刚大神的《Android开发艺术探索》一书中首先讲解的是View的绘制原理,之后再讲解自定义View,这个顺序是对的,明白了View的绘制原理后在自定义View中就会明白各个方法的原理和作用。但是若直接讲解View的绘制原理对不了解自定义View的小伙伴来说是非常苦涩难懂的。所以我从我自己的体会出发,首先讲解怎么自定义View,当学会了自定义View时再讲解view的绘制原理。当然首先讲解自定义View大家可能会对用到的某些方法不明白原理,但是我会告诉大家这些方法的作用,大家只需先记住,当讲解View的绘制原理时肯定会恍然大悟,肯定会发出一个响彻大地的声音:哦,原来是这样!

二,概述

在Android体系中View扮演者重要的角色,可以说它是android中唯一可以“看得见,摸得着”的东西,尤其对于用户来说View是他们对APP的唯一认识,对于我们以“用户至上”为理念的程序员来说,必须重视View。

在Android中提供了一整套GUI库,比如TextView,ImageView,还有LinearLayout和RelativeLayout等等,这些View可以帮助我们实现绝大多数的界面需求。但是往往我们会遇到一个“艺术家”产品经理,他就想与众不同,就想有新意,他会设计出各种奇葩的UI,此时Android提供的View可能满足不了我们的需求,此时就需要自定义View。

自定义View很简单,一般分为四种情况:
1. 直接继承于View类。查看源码会发现TextView和Imageview都是继承的View类。
2. 直接继承ViewGroup类。Linearlayout和RelativeLayout都是继承ViewGroup类,注意ViewGroup也是继承View类。
3. 继承于其它View类。此种情况常用于对特定View的功能进行增强,比如:继承ListView变成下拉刷新ListView。
4. 组合方式。这种情况常用于某个特性样式的view组合在App中可能会出现多次,此时就把这个组合样式定义成一个View。

注意:在实际开发中有时某个需求使用多个方案都可以实现,我们要根据实际情况进行选择,力求view执行高效且节约开发时间。

下面首先实现一个自定义View类,先直观的感受一下自定义View。

三,需求和步骤总述

在写代码之前首先要定义需求,我们的需求是:
1,自定义一个View,显示“女神”两个字。
2,字体颜色为red。
3,背景色为yellow。
4,提供设置文本,字体颜色,背景颜色的方法。
5,可以在布局中文件中设置文本,字体颜色,背景颜色。

有人会说,这个直接用TextView就可以实现,何必费大劲去自定义View呢。注意,此时我们是学习如何自定义View,所以要从简单的开始,每一种自定义View原理都是相同的,学会简单的自定义View后便能发挥我们的聪明才智去实现复杂的View。

四,最简单的方式实现自定义View

首先以最简单的方式,把我们的“女神”显示出来。

扫描二维码关注公众号,回复: 1628581 查看本文章

1,自定义一个类GoddessView,继承View,并重写构造方法

直接上代码,具体的讲解在代码注释中。

public class GoddessView extends View {

    /**
     * 在继承View的类中必须实现一个有参的构造方法。实现一个即可,但具体实现哪一个要看应用场景。
     *
     * 我们在继承View时,如果明确知道在哪使用,则重写一个即可。若为了增加使用场景最好三个都写上。
     */

    /**
     * 此构造方法用于使用new创建对象时。
     */
    public GoddessView(Context context) {
        super(context);
    }

    /**
     * 此构造方法用于在布局文件中使用时
     */
    public GoddessView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 此构造方法用于需要设置主题样式时。
     */
    public GoddessView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

2,定义一个方法,初始化画笔

定义一个方法,方法里面初始化画笔,这个方法在三个构造方法中都要调用。

private void initPaint(){
        paint = new Paint();//创建paint对象
        paint.setAntiAlias(true);//设置锯齿,让边界光滑
        paint.setColor(Color.RED);//设置画笔颜色
        paint.setStrokeWidth(2f);//设置画笔的宽度,单位是px.
        paint.setStyle(Paint.Style.FILL);//设置画笔的样式,在画矩形或圆形时STROKE表示空心,FILL表示实心。
        paint.setTextSize(60f);//设置字体的大小,
    }

3,重写onMeasure方法

重写onMeasure方法,方法中只调用super的onMeasure方法,其他什么也不做

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

4,重写onDraw方法

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.YELLOW);//画背景颜色
        canvas.drawText("女神",100f,100f,paint);//画文字,并指定位置
    }

5,在Activity中使用

在Activity的onCreate方法中会调用setContentView设置view,这个方法可以接收一个布局文件,也可以接收一个view对象,此时接收一个view对象。代码如下:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new GoddessView(this));
    }

这5步就显示出来了女神两个字,效果如图:
goddess

6,总结

这里说下三个注意点:
1,构造方法。构造方法的作用是创建对象和初始化参数。与view的显示机制没有任何关系。第一个构造方法只有创建对象的作用,所以适用于直接new的情况。第二个构造方法可以接收布局文件中的参数,所以可以使用在布局文件中。第三个构造方法可以接收主题参数。
2,onMeasure方法。这个方法很重要,但此时并没有显示出来其重要性,下面会修改onMeasure方法来体会onMeasure的重要性。
3,onDraw方法。这个方法也很重要,view上显示的内容全是这个方法画出来的。注意canvas和paint类的使用。

五,初始onMeasure方法

在上面的例子中并没有对onMeasure方法进行任何操作,但Text也显示出来了。难道说onMeasure方法不重要吗。其实不是,onMeasure方法是个很重要的方法,它决定view的显示大小。在onMeasure方法中有两个参数widthMeasureSpec和heightMeasureSpec,这两个参数称为测量规则,在测量规则中包含两层含义,一个是测量模式,另外一个是测量大小。我们可以从这个参数规则中获取测量大小。

在自定义的GoddessView中并没有指定大小,但为什么全屏显示呢?这是因为测量大小的默认值是全屏显示。我们做如下实验:
此时只在onMeasure方法中添加两个打印log,其他都不变,onMeasure方法代码如下:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//获取宽度
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//获取高度
        Log.d("kwwl","widthSize====="+widthSize);
        Log.d("kwwl","heightSize====="+heightSize);
    }

执行结果如下:
D/kwwl: widthSize=====720
D/kwwl: heightSize=====1022

此时测试手机的分辨率是720*1080。由于statuBar和ActionBar的存在所以高度会小于手机高度分辨率。

那么这个值是从哪儿来的呢?此时就需要小伙伴先记住这个答案,这个值是从view所在的容器View中传递过来的,onMeasure方法的调用也是在上级容器View中。

系统默认的是全屏显示,但我们可以修改显示大小,代码如下:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取测量模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获取测量模式
        int mWidthMeasureSpec = MeasureSpec.makeMeasureSpec(300,widthMode);//得到新的测量规则,宽度为300px
        int mHeightMeasureSpec = MeasureSpec.makeMeasureSpec(300,heightMode);//得到新的测量规则,高度为300px
        super.onMeasure(mWidthMeasureSpec, mHeightMeasureSpec);
    }

此时的显示效果如下:
这里写图片描述

我们发现此时就显示为我们指定的宽高了。

实现指定宽高还有下面方式:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(300,300);//设置宽高都为300px
    }

setMeasuredDimension是个很重要的方法,此时一定要记住,在super的onMeasure方法内部也是调用这个方法来设置宽高的。

总结:
这部分主要是体会onMeasure方法的作用,具体原理后面blog会讲解,这里必须记住以下几个方法:
1,MeasureSpec.getSize(widthMeasureSpec);
2,MeasureSpec.getMode(widthMeasureSpec);
3,MeasureSpec.makeMeasureSpec(300,widthMode);
4,setMeasuredDimension(300,300);

六,在GoddessView类中定义方法

在使用TextView时我们知道,可以在代码中使用方法改变文本,文本的颜色,背景颜色等。此时我们也实现这个方法。
修改后的代码如下:

public class GoddessView extends View {
    private String mText = "女神";//将要显示的文本声明为成员变量,初始为“女神”
    private int textColorId = R.color.myRed;//将文本颜色的colorId声明为成员变量
    private int backColorId = R.color.myYellow;//将背景颜色的colorId声明为成员变量
    private Paint paint;

    public GoddessView(Context context) {
        super(context);
        initPaint();
    }
    public GoddessView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }
    public GoddessView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initPaint(){
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(getResources().getColor(textColorId));//设置画笔颜色
        paint.setStrokeWidth(2f);
        paint.setStyle(Paint.Style.FILL);
        paint.setTextSize(60f);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(300,300);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(getResources().getColor(backColorId));//画背景颜色
        canvas.drawText(mText,100f,100f,paint);//画文字,并指定位置
    }

   /**
     * 设置显示的文本
     */
    public void setMyText(String text){
        mText = text;
    }

    /**
     * 设置文本的颜色
     */
    public void setMyTextColor(int colorId){
        textColorId = colorId;
        paint.setColor(getResources().getColor(textColorId));//改变paint的颜色
    }

    /**
     * 设置背景颜色
     */
    public void setMyBackgroudColor(int colorId){
        backColorId = colorId;
    }
}

此时直接运行,显示效果同上图。下面修改activity中的代码:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GoddessView goddessView = new GoddessView(this);
        setContentView(goddessView);
        goddessView.setMyText("God");//修改文本为“God”
        goddessView.setMyTextColor(R.color.myYellow);//修改文本颜色未Yellow
        goddessView.setMyBackgroudColor(R.color.myRed);//修改背景颜色未Red
    }

此时的显示效果如下:

这里写图片描述

修改成功。

方法这样写有一定的问题,如果把goddessView.setMyText写在点击事件响应方法中会发现没有效果。原因是:只是修改了mText的字段值,但并没有要求view重新绘制,所以不会改变UI显示。

那么怎么能让view重新绘制呢,方法应该这样写:

    public void setMyText(String text){
        mText = text;
        invalidate();//会重新执行onDraw方法
    }

此时就可以随时调用setMyText方法了。注意invalidate的使用,它会重新执行onDraw方法。

七,可以在布局中文件中设置文本,字体颜色,背景颜色

在android SDK中提供的View中几乎都可以在布局中使用,并设置相关属性。自定义View也可以实现在布局文件中设置相关属性。具体实现如下:

1,创建attrs.xml文件

在res->values目录下新建一个attrs.xml文件,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="GoddessView">
        <attr name="myText" format="string"/><!--文本-->
        <attr name="myTextColor" format="color"/><!--文本颜色-->
        <attr name="myBackgorudColor" format="color"/><!--背景颜色-->
    </declare-styleable>
</resources>

注意:一个Module中只有一个attrs文件,一个文件可以供很多View使用,一个declare-styleable代表一个View。

2,在GoddessView类的第二个构造方法的参数中获取属性值

    public GoddessView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.GoddessView);
        mText = ta.getString(R.styleable.GoddessView_myText);
        textColorId = ta.getResourceId(R.styleable.GoddessView_myTextColor, 0);
        backColorId = ta.getResourceId(R.styleable.GoddessView_myBackgorudColor, 0);

        initPaint();
    }

3,在布局文件中使用GoddessView类

代码如下;

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="honor.com.customview.MainActivity">

    <honor.com.customview.view.GoddessView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:myText="God"
        app:myTextColor="@color/myYellow"
        app:myBackgorudColor="@color/myRed"/>
</RelativeLayout>

注意:
1,GoddessView类要全类名使用,必须带包名。
2,自定义的属性以app开头。
3,使用自定义的属性前先在根容器中增加命名空间:
xmlns:app=”http://schemas.android.com/apk/res-auto”

4,在activity中使用该布局文件

  setContentView(R.layout.activity_main);

此时就显示出GoddessView了。

八,总结

实现上面的demo后发现自定义View并不难,所有的自定义view都是这个流程,总结如下:
1,重写构造方法,如果需要自定义属性就创建attrs.xml文件。
2,重写onMeasure方法,指定view的测量大小。
3,创建Paint对象并设置参数。
4,重写onDraw方法,绘制UI。
5,若需要提供修改属性的方法则定义方法。

自定义View基本都是这个流程,而且1,3,5,这三步几乎都是这样来写,在复杂的View中只是onMeasure方法和onDraw方法比较麻烦。后面会对onMeasure方法,Paint类,Canvas类专门进行讲解。要求不会自定义View的小伙伴要熟练掌握这篇blog中用到的知识点,等这篇blog熟练后再向下看。

猜你喜欢

转载自blog.csdn.net/fightingxia/article/details/72572391