《Android 群英传》读书笔记:自定义 View 之对现有控件进行拓展

本文介绍如何通过重写onDraw()方法来自定义TextView,包括创建画笔、绘制背景及调整文字样式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明出处: http://blog.youkuaiyun.com/like_program/article/details/53141408

对现有控件进行拓展,是一个非常重要的自定义 View 方法,它可以在原生控件的基础上进行拓展,增加新的功能,修改新的 UI 等。一般来说,我们可以在 onDraw() 方法中对原生控件行为进行拓展。

下面以一个 TextView 为例,来看看如何使用拓展原生控件的方法创建新的控件。比如让 TextView 的背景更加丰富,如图所示:

最终效果图:

最终效果图

下面我们来一步一步的实现这个效果。

打开 Android Studio,新建 MyTextView 项目。

初步自定义 TextView

新建 MyTextView.java ,继承自 TextView,代码如下:

public class MyTextView extends TextView {

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

好了,我们已经成功的在 TextView 的基础上创建了一个新的控件 MyTextView。然后我们在 MyTextView 上点击右键,选择 Copy Reference,这样我们就复制了它的全限定类名,然后粘贴到 activity_main.xml 中,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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="com.example.mytextview.MainActivity">

    <com.example.mytextview.MyTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Android"/>
</RelativeLayout>

运行一下程序:

没有效果

我们发现,靠!这不跟以前的 TextView 一模一样嘛。

你TM在逗我

嗯,看上去它和 TextView 没有任何差别,只是名字不同而已,不过不要着急,那是因为我们还没有重写它的 onDraw 方法。

修改 MyTextView.java,重写 onDraw() 方法,代码如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

父类的 onDraw() 方法

好了,我们重写了 onDraw() 方法,我们可以看到,在 onDraw() 方法里,调用了父类的 onDraw() 方法:

super.onDraw(canvas);

原生的 TextView 控件就是调用这行语句来绘制要显示的文字,我们可以把这行语句注释掉,运行一下程序,看下还有没有文字:

没有文字

我们可以看到,屏幕上已经没有文字,这说明原生的 TextView 控件的确是调用这行语句来绘制要显示的文字。这也给了我们启示:如果我们要给文字加背景,只需要在 super.onDraw(canvas) 语句之前把背景绘制出来就行了。

绘制背景

接下来我们来绘制背景,再看一下我们的最终效果图:

最终效果图

我们可以看到,文字的背景是一个蓝色的矩形边框和一个黄色的矩形,更准确的说,是一个黄色的矩形盖住了一个稍大一点的蓝色矩形。导致蓝色矩形只露出了一个边框。

分析出来之后就好办了,我们只需要画一个黄色的矩形和一个稍大一点的蓝色矩形就可以了,不过要注意一下的是:蓝色的矩形必须先画出来,然后再画一个黄色的矩形盖在上面,这样才能有蓝色矩形边框的效果。如果先画黄色矩形的话,后画出来的蓝色矩形会把黄色矩形全部盖住,这样我们就看不到黄色矩形了。

好了,我们来画矩形,怎么画呢,看下刚才我们重写的 onDraw() 方法:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

里面有一个参数:canvas,canvas 的中文意思是 画布,我们可以把它理解为一张白纸,有了白纸,还要有画笔才能画出图案,Android 给我们提供了 Paint 类来作为我们画笔。

要创建画笔,只需要创建一个 Paint 类的实例即可。

Paint paint = new Paint();

注意:可能很多同学的第一反应就是在 onDraw() 方法中去创建画笔,这是不对的。因为 onDraw() 方法在控件绘制的过程中可能会被频繁调用,而 new Paint() 是要分配内存的,所以如果在 onDraw() 方法中去创建画笔,会浪费内存。

既然不能 onDraw() 方法中去创建画笔,那我们就在构造方法中去创建画笔,修改 MyTextView.java ,代码如下:

package com.example.mytextview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

public class MyTextView extends TextView {

    /**
     * 绘制外层矩形的画笔
     */
    private Paint mPaintOut;

    /**
     * 绘制内层矩形的画笔
     */
    private Paint mPaintIn;

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

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

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 创建绘制外层矩形的画笔
        mPaintOut = new Paint();
        // 颜色是 蓝色
        mPaintOut.setColor(getResources().getColor(android.R.color.holo_blue_bright));

        // 创建绘制内层矩形的画笔
        mPaintIn = new Paint();
        // 颜色是 黄色
        mPaintIn.setColor(Color.YELLOW);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 绘制外层矩形
        // 第一个参数是 矩形左边缘的 x 坐标
        // 第二个参数是 矩形上边缘的 y 坐标
        // 第三个参数是 矩形右边缘的 x 坐标
        // 第四个参数是 矩形下边缘的 y 坐标
        canvas.drawRect(0,
                0,
                getMeasuredWidth(),
                getMeasuredHeight(),
                mPaintOut);
        // 绘制内层矩形
        canvas.drawRect(10,
                10,
                getMeasuredWidth() - 10,
                getMeasuredHeight() - 10,
                mPaintIn);
        // 绘制文字
        super.onDraw(canvas);
    }
}

我们可以看到,绘制矩形的方法是 canvas.drawRect()

观察一下 canvas.drawRect() 方法的参数,我们发现有个特点,前面几个参数都是要绘制图案的属性,比如我们绘制矩形,就要指定矩形的四条边界,有了边界,系统才知道把矩形画在什么地方,最后一个参数是 画笔,画笔中也封装了一些属性,比如颜色。

有了画笔,画布,和绘制图案的属性,系统就能帮我们把图案绘制出来了。

为了让文字显示的更美观,我们再调整下文字的大小的位置,以及 MyTextView 控件的大小。

修改 activity_main.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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="com.example.mytextview.MainActivity">

    <com.example.mytextview.MyTextView
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="Android"
        android:textSize="30sp"/>
</RelativeLayout>

好了,运行一下程序:

最终效果图

如果在 XML 文件中不能实时查看自定义 View 的样式,可以选择 Build -> Rebuild Project,就可以直接在 XML 中实时查看自定义 View 的样式。

Build

关于自定义 View 的三个构造方法

不知道有没有同学发现,刚刚创建画笔的时候,我们是在 public MyTextView(Context context, AttributeSet attrs) 这个构造方法中创建的:

public MyTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initPaint();
}

这是因为:在 XML 布局文件中调用自定义 View 的时候,会调用这个构造函数。

<com.example.mytextview.MyTextView
    android:layout_width="200dp"
    android:layout_height="50dp"
    android:gravity="center"
    android:text="Android"
    android:textSize="30dp"/>

那么还有两个构造方法,是在什么时候调用的呢?这里,我只解释 public MyTextView(Context context) 这个构造方法,因为另外一个,我也不会。我也不会

public MyTextView(Context context) {
    super(context);
}

当我们在 activity 中直接 new 一个自定义 View 实例的时候,会调用这个构造函数。

要注意的是:由于我们在布局文件中给 MyTextView 控件指定了 宽度、高度、文字、文字居中、文字大小,这些属性。那么如果要 new 一个 MyTextView 控件,也达到刚才的效果,就要在 MyTextView 中也指定这些属性。修改下 MyTextView.java,代码如下:

public MyTextView(Context context) {
    super(context);

    // 设置控件的宽度和高度
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(dp2px(context,
            200), dp2px(context, 50));
    setLayoutParams(layoutParams);
    // 文字大小
    setTextSize(30);
    // 文字居中显示
    setGravity(Gravity.CENTER);
    // 文字
    setText("Android");

    initPaint();
}

public MyTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
//  initPaint();
}

这里有一点要注意一下,因为在 activity_main 中,我们给 MyTextView 控件设置宽度和高度时,使用的单位是:dp(密度无关像素),而在代码中,宽度和高度默认的单位是 px(像素),为了达到和之前一样的显示效果,我们需要把 px 转换为 dp,这里需要用到一个转换方法:

/**
 * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
 */
private int dp2px(Context context, float dpValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (dpValue * scale + 0.5f);
}

然后在 MainActivity.java 的 onCreate 方法中,我们直接 new 一个 MyTextView 的实例:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new MyTextView(MainActivity.this));
}

这样,就不会实例化布局文件,而是直接创建 MyTextView 实例了,这时会走 public MyTextView(Context context) 这个构造方法。

运行一下程序:

最终效果图2

我们可以看到,跟刚才的效果一模一样。

嗯,如果有同学好奇心比较强,想知道 public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) 这个构造方法什么情况下会被调用,可以看下这篇博客:

Android自定义View构造函数详解

最后,放上源码。

源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值