看完这篇还不会自定义 View ,我跪搓衣板

自定义 View

在实际使用的过程当中,咱们常常会接到这样一些需求,好比环形计步器,柱状图表,圆形头像等等,这时咱们一般的思路是去Google 一下,看看 github 上是否有咱们须要的这些控件,可是若是网上收不到这样的控件呢?这时咱们常常须要自定义 View 来知足需求。php


接下来让咱们开启自定义控件之路

关于自定义控件,通常辉遵循一下几个套路html

  • 首先重写 onMeasure() 方法
  • 其次重写 onDraw() 方法
  • 总所周知 onMeasure()

方法是用来从新测量,并设定控件的大小,咱们知道控件的大小是用 width 和 height 两个标签来设定的。一般有三种赋值状况 :java

  • 首先直接赋值,好比直接给定 15dp 这样确切的大小
  • 其次 match_parent
  • 固然还有 wrap_parent

这时也许你就会有疑问,既然都已经有了这些属性,那还重写 onMeasure 干吗,直接调用 View 的方法不就好了吗?可是你想一想,好比你设计了一个圆形控件,用户在 width 和 height 都设置了 wrap_parent 属性,同时又给你传了一张长方形的图片,那结果会怎么样?必然得让你“方”啊。。因此这时就须要重写 onMeasure 方法,设定其宽高相等。android


那么该如何重写 onMeasure() 方法呢?

首先把 onMeasure() 打出来git

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
复制代码

这时你们不眠会好奇,明明是重绘大小,那么给我提供宽高就好了呀?这个 int widthMeasureSpec, int heightMeasureSpec ,是个什么鬼?其实很好理解,你们都知道计算机中数据是已二进制存储的。同时,就像我以前讲的 View 的大小赋值形式有三种,那么在计算机中,要存储二进制数,须要几位二进制呢,答案很明了 -> 两位。同时你们也发现,这两个参数都是 int 型的。int 型数据在计算机中用 32 位存储。因此聪明的 Google 就把这 30 位划分为两部分。第一部分两位拿来存类型,后面 28 位拿来存数据大小。github


开始重写 onMeasure() 方法

首先,不管是 width 仍是 height ,咱们都得先判断类型,再去计算大小,so~ 咱先写个方法专门用于计算并返回大小。canvas

测量模式 表示意思
UNSPECIFIED 父容器没有对当前View有任何限制,当前View能够任意取尺寸
EXACTLY 当前的尺寸就是当前View应该取的尺寸
AT_MOST 当前尺寸是当前View能取的最大尺寸
private int getMySize(int defaultSize, int measureSpec) {
        // 设定一个默认大小 defaultSize
        int mySize = defaultSize;
        // 得到类型
        int mode = MeasureSpec.getMode(measureSpec);
        // 得到大小
        int size = MeasureSpec.getSize(measureSpec);
        
        switch (mode) {
            case MeasureSpec.UNSPECIFIED: {//若是没有指定大小,就设置为默认大小
                mySize = defaultSize;
                break;
            }
            case MeasureSpec.AT_MOST: {//若是测量模式是最大取值为size
                //咱们将大小取最大值,你也能够取其余值
                mySize = size;
                break;
            }
            case MeasureSpec.EXACTLY: {//若是是固定的大小,那就不要去改变它
                mySize = size;
                break;
            }
        }
        return mySize;
    }
复制代码

而后,咱们再从 onMeasure() 中调用它app

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 分别得到长宽大小
        int width = getMySize(100, widthMeasureSpec);
        int height = getMySize(100, heightMeasureSpec);
 
        // 这里我已圆形控件举例
        // 因此设定长宽相等
        if (width < height) {
            height = width;
        } else {
            width = height;
        }
        // 设置大小
        setMeasuredDimension(width, height);
    }
复制代码

在 xml 中应用试试效果ide

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" tools:context=".activities.MainActivity">
 
    <com.entry.android_view_user_defined_first.views.MyView android:layout_width="100dp" android:layout_height="100dp" app:default_size="@drawable/ic_launcher_background"/>
 
</LinearLayout>
复制代码

到这里图就已经重绘出来了,让咱们运行一下下函数

咱们惊呆了,说好的控件呢??! 别急,咱还没给他上色呢,因此它天然是透明的。因此如今重写 onDraw() 方法,在 onDraw() 方法中 (这里我为了写的方便,在 onDraw 方法中直接 new 了对象 { 嗷我没有对象} ,但这是一种很容易致使内存泄露的行为,你们注意下评论区)

咱们经过 canvas (安卓的一个绘图类对象进行图形的绘制)

@Override
    protected void onDraw(Canvas canvas) {
        // 调用父View的onDraw函数,由于View这个类帮咱们实现了一些
        // 基本的而绘制功能,好比绘制背景颜色、背景图片等
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;//也能够是getMeasuredHeight()/2,本例中咱们已经将宽高设置相等了
        Log.d(TAG, r + "");
        // 圆心的横坐标为当前的View的左边起始位置+半径
        int centerX = r;
        // 圆心的纵坐标为当前的View的顶部起始位置+半径
        int centerY = r;
        // 定义灰色画笔,绘制圆形
        Paint bacPaint = new Paint();
        bacPaint.setColor(Color.GRAY);
        canvas.drawCircle(centerX, centerY, r, bacPaint);
        // 定义蓝色画笔,绘制文字
        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setTextSize(60);
        canvas.drawText("大傻瓜", 0, r+paint.getTextSize()/2, paint);
    }
复制代码

运行一下

大功告成!可是善于思考的可能会发现:使用这种方式,咱们只能使用父类控件的属性,可是咱们有时须要更多的功能,好比:图片控件须要改变透明度,卡片控件须要设定阴影值等等,那么父类控件的属性显然不够用了,这时咱们就要开始实现自定义布局。

自定义布局属性 xml 属性


开始

因为自定义布局属性通常只须要对 onDraw() 进行操做。因此 onMeasure() 等方法的重写我就再也不啰嗦了,这里我打算继承字 view 实现一个相似 TextView 的控件。

首先,让咱们如今 res/values/styles 文件中增长一个自定义布局属性。

<resources>
 
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style>
 
    <!--定义属性集合名-->
    <declare-styleable name="MyView">
        <!--咱们定义为 default_size 属性为 屈指类型 像素 dp 等-->
        <attr name="text_size" format="dimension"/>
        <attr name="text_color" format="color"/>
        <attr name="text_text" format="string"/>
    </declare-styleable>
 
</resources>
复制代码

这些标签都是什么意思呢?

首先:

MyView 是自定义布局属性的名字,也就是标签也就是入口,在 onDraw 中,用 context.obtainStyledAttributes(attrs, R.styleable.MyView); 得到自定义布局属性的所有子项。

其次:

attr 中的 name 即是你属性的名字,好比说这个 text_size 、text_color 、text_text  这三个属性,在 布局文件中就是:

<com.entry.android_view_user_defined_first.views.MyView android:layout_width="100dp" android:layout_height="100dp" app:text_text="hello world" app:text_size="20sp" app:text_color="@color/colorAccent"/>
复制代码

最后:

format 标签,format 标签指定的是数据类型,具体能够看这篇,我在这里就不重复了 -> blog.csdn.net/pgalxx/arti…


解析和引用

上面咱们先定义了属性,又在布局中对其赋值,那么实际中,咱们如何在自定义控件里,得到它的实际值呢?让咱们先写下构造方法,在构造方法中得到这些值的大小:

private int textSize;
    private String textText;
    private int textColor;
 
    public MyView(Context context) {
        super(context);
    }
 
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
 
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
 
        textSize = array.getDimensionPixelSize(R.styleable.MyView_text_size, 15);
        textText = array.getString(R.styleable.MyView_text_text);
        textColor = array.getColor(R.styleable.MyView_text_color,Color.BLACK);
 
        array.recycle();
    }
复制代码
  • 创建一个 TypeArray 对象,用于存储自定义属性所传入的的值。obtainStyledAttributes 方法又两个参数,第二个参数就是咱们在styles.xml文件中的 标签,即属性集合的标签,在R文件中名称为R.styleable+name
  • 而后根据 array 对象,获取传入的值。通常来讲,它的方法有两个属性,第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称,第二个参数为,若是没有设置这个属性,则设置的默认的值
  • 最后记得将TypedArray对象回收

来重写下 onDraw() 方法。

因为在构造方法中,咱们已经得到基本的值,因此在 onDraw() 中,将这些东西绘制出来就好了,这里直接上代码:

@Override
    protected void onDraw(Canvas canvas) {
        // 调用父View的onDraw函数,由于View这个类帮咱们实现了一些
        // 基本的而绘制功能,好比绘制背景颜色、背景图片等
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;//也能够是getMeasuredHeight()/2,本例中咱们已经将宽高设置相等了
        // 圆心的横坐标为当前的View的左边起始位置+半径
        int centerX = r;
        // 圆心的纵坐标为当前的View的顶部起始位置+半径
        int centerY = r;
        // 定义灰色画笔,绘制圆形
        Paint bacPaint = new Paint();
        bacPaint.setColor(Color.GRAY);
        canvas.drawCircle(centerX, centerY, r, bacPaint);
        // 定义蓝色画笔,绘制文字
        Paint paint = new Paint();
        paint.setColor(textColor);
        paint.setTextSize(textSize);
        canvas.drawText(textText, 0, r+paint.getTextSize()/2, paint);
    }
复制代码

运行一下下:perfect !

写在最后

本文是我对我的学习过程的总结,若是你们有发现错误,但愿能在评论区指出,谢谢 🙏

相关文章
相关标签/搜索