自定义View —View属性和基本方法

本文介绍了Android中自定义View的常见场景和分类,包括继承View、组合控件以及扩展系统控件等。详细讲解了构造函数、View的坐标、自定义属性的设置和获取,以及onMeasure()和onDraw()函数在自定义尺寸和绘制过程中的作用。通过实例展示了如何绘制圆形,并强调了Paint和Canvas在自定义View中的应用。
摘要由CSDN通过智能技术生成

写在前面

首先我们要明白,为什么要自定义View?主要是Android系统内置的View无法实现我们的需求,我们需要针对我们的业务需求定制我们想要的View。

开发俩年,多多少少写了些自定义控件和自定义View,这次还是好好整理下,面的有些东西老是遗漏。不过自己在公司前辈面前算是菜鸟了,所以水平有限,记录一些比较基础的和网上参考的,仅作个人总结和分享。

一、自定义View常用场景和分类

需求永远是在变更的,花样多的不行,很多时候系统自带的控件和布局无法完成的时候就可以使用自定义View来实现,或者,在某些多处复用的布局,我们可以将其封装成一个组合控件,用到的地方直接复用,减少了代码量也更加便于维护。

类型定义
1.继承View不复用系统控件逻辑,继承View进行功能定义
2.自定义组合控件多个控件组合成为一个新的控件,方便多处复用
3.继承系统View控件继承自TextView等系统控件,在系统控件的基础功能上进行扩展
4.继承系统ViewGroup继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展
5.继承ViewViewGroup不复用系统控件逻辑,继承ViewGroup进行功能定义

后面会单独的每个类型都写个Demo

二、基本属性

2.1 构造函数

构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载

无论是继承系统控件还是直接继承View,我们都需要写出他的构造函数,例如一个TestView

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

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

来个kotlin的

class TestView: View  {

    constructor(context: Context): super(context){
    }

    constructor(context: Context, attributeSet: AttributeSet): super(context, attributeSet){
    }

    constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int): super(context, attributeSet, defStyleAttr){
    }

    init {

    }
}

kotlin还可以简化,Kotlin中@JvmOverloads的作用

class TestView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : View(context, attrs, defStyleAttr) {

    init{
    }
}

2.2 View自身的坐标 

View的坐标就相当于在屏幕上有个X,Y轴,根据坐标位置可以进行很多自定义操作,默认坐标原点为左上角。当然可以通过path.moveto等方法更改操作的坐标位置

通过如下方法可以获取View到其父控件的距离。

  • getTop();获取View到其父布局顶边的距离。
  • getLeft();获取View到其父布局左边的距离。
  • getBottom();获取View到其父布局顶边的距离。
  • getRight();获取View到其父布局左边的距离。

由此可计算宽高:

  • width = getRight() - getLeft();
  • height = getBottom() - getTop();

View的源码当中提供了getWidth()和getHeight()方法用来获取View的宽度和高度,其内部方法和上文所示是相同的,我们可以直接调用来获取View的宽高。

2.3 View的自定义属性 AttributeSet

每个控件都会有属性,既然是自定义控件怎么能少了自定义属性呢!

操作步骤:

Android自定义属性可分为以下几步:

  1. 自定义一个View
  2. 编写values/attrs.xml,在其中编写styleable和item等标签元素
  3. 在布局文件中View使用自定义的属性
  4. 在View的构造方法中通过TypedArray获取 

例如:

1.attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 这里会写什么呢!,当然是我们的自定义属性 -->
    <declare-styleable name="MyViewStyle" >
           <declare-styleable name="MyViewStyle" >
            <attr name="content" format="string" />
            <attr name="isShow" format="boolean"/>
            <attr name="background" format="color"/>
    </declare-styleable>
</resources>

 关于里面的format:即支持的自定义属性

  • reference:引用资源 传入资源ID 
  • string:字符串
  • Color:颜色
  • boolean:布尔值
  • dimension:尺寸值
  • float:浮点型
  • integer:整型
  • fraction:百分数
  • enum:枚举类型
  • flag:位或运算 

2.View代码

private String mContent;
private Boolean mIsShow;
private int mBackground;
private int mSelect;
public MyView(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MyViewStyle);
    if(typedArray != null){
        //这里要注意,String类型是没有默认值的,所以必须定义好,不然又是空指针大法
        mContent = typedArray.getString(R.styleable.MyViewStyle_content);
        mIsShow = typedArray.getBoolean(R.styleable.MyViewStyle_is_show, true);
        mBackground = typedArray.getColor(R.styleable.MyViewStyle_view_background, Color.RED);
        mSelect = typedArray.getInt(R.styleable.MyViewStyle_select, 0);
    }
  
}

3.当用于XML文件上时:记住哦,前面是app:

<com.xlhgo.viewdemo.MyView
    app:content="这是内容"
    app:is_show="false"
    app:view_background="@color/colorAccent"
    app:select="s1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

三、onMeasure函数

自定义View我们大部分时候只需重写两个函数(不包括构造函数):onMeasure()、onDraw()。onMeasure负责对当前View的尺寸进行测量,onDraw负责把当前这个View绘制出来。

需求:

xml布局文件中,我们的layout_width和layout_height参数可以不用写具体的尺寸,而是wrap_content或者是match_parent。其意思我们都知道,就是将尺寸设置为“包住内容”和“填充父布局给我们的所有空间”。这两个设置并没有指定真正的大小,可是我们绘制到屏幕上的View必须是要有具体的宽高的,正是因为这个原因,我们必须自己去处理和设置尺寸。当然了,View类给了默认的处理,但是如果View类的默认处理不满足我们的要求,我们就得重写onMeasure函数。

函数:

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

测量模式:

重写之前先了解MeasureSpec的specMode,一共三种类型:

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT,当前的尺寸就是当前View应该取的尺寸

AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT,当前尺寸是当前View能取的最大尺寸。

UNSPECIFIED:表示子布局想要多大就多大,父容器没有对当前View有任何限制,当前View可以任意取尺寸。

举例:假设我们要实现这样一个效果:将当前的View以正方形的形式显示,即要宽高相等,并且默认的宽高值为100像素。

private int getMySize(int defaultSize, int measureSpec) {
        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;
}

@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);
}

四、重写onDraw()

当我们可以去自定义尺寸后,我们就可以去通过onDraw方法绘制出来了。

画个圆:

其中的 Paint在自定义View时经常会用到,可以理解为是一个画笔,粗细,颜色,填充/描边等都可以通过它来设置。

其中的 Canvas是画布,我们通过自己的笔画在上面。当一个View较为复杂时,可以在这个View里定义多个画布和画笔。

  @Override
    protected void onDraw(Canvas canvas) {
        //调用父View的onDraw函数,因为View这个类帮我们实现了一些
        // 基本的而绘制功能,比如绘制背景颜色、背景图片等
        super.onDraw(canvas);
        int r = 40;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
        //圆心的横坐标为当前的View的左边起始位置+半径
        int centerX = getLeft() + r;
        //圆心的纵坐标为当前的View的顶部起始位置+半径
        int centerY = getTop() + r;
        //矩形左边离父布局 248 离顶部218,右边400,底部452
        new rectF = new RectF(248f,218f,400f,452f)
        Paint paint = new Paint();
        paint.setColor(Color.GREEN);
        //开始绘制
        canvas.drawCircle(centerX, centerY, r, paint);
//  画椭圆 canvas?.drawOval(rectF, paint)
//  画直线 canvas?.drawLine(400f,455f,400f,700f,piant)
    }

Android Canvasicon-default.png?t=LA92https://blog.csdn.net/iispring/article/details/49770651?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163825853716780261958851%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163825853716780261958851&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-49770651.pc_search_result_control_group&utm_term=android+canvas&spm=1018.2226.3001.4187Android Painticon-default.png?t=LA92https://blog.csdn.net/abcdef314159/article/details/51720686?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163825841716780261931477%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163825841716780261931477&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-4-51720686.pc_search_result_control_group&utm_term=android+paint&spm=1018.2226.3001.4187 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

&岁月不待人&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值