基本概念
View类是android提供的控件基类,其常用的子类有Button、TextView...
View的绘制包含三个方面:宽高、位置、样式(形状、颜色...),分别对应measure()、layout()、draw()三个函数,在View中实现了这三方面的基本功能,和基本的属性
xml布局的控件属性,基本都是围绕这三方面的
当android提供的控件,不能满足需要时。我们可以自定义View,即创建View的子类,主要考虑宽高、样式(形状、颜色...)两个方面(一般自定义ViewGroup时要考虑到位置)
体现在代码上就是重写onMeasure()和onDraw()
有时候还需要自定义View的属性,需要在构造方法中获取属性值
Android中,自定义View是一个大的技术点,可深、可广
基本实现步骤,如下
继承View
package com.clc.diyview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class View1 extends View {
//自定义属性,对应 R.styleable.view1(xml),记得getter/setter
private String attributeStr;
private int attributeInteger;
//必须有一个构造方法
public View1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//获取属性集资源(自定义属性时才需要)
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.view1);
//获取xml布局中View1相应的属性值
attributeStr = typedArray.getString(R.styleable.view1_attribute_str);
attributeInteger = typedArray.getInteger(R.styleable.view1_attribute_integer, -1);
//最后,回收typedArray
typedArray.recycle();
}
//重写onMeasure(),设置控件宽高;不重写,则表示采用父类View默认的宽高处理逻辑
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//调用父类View基本的宽高处理功能
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*
xxxMeasureSpec是xml布局中View1宽高属性的int值,这个int值(32bit)包含两部分,前2bit为尺寸模式(UNSPECIFIED/EXACTLY/AT_MOST),剩余30bit为尺寸值
宽高值对应模式:match_parent-->EXACTLY
固定尺寸(如10dp)-->EXACTLY
warp_content--> AT_MOST
*/
//获取width的尺寸模式
int wmode = MeasureSpec.getMode(widthMeasureSpec);
//获取width的尺寸值
int width = MeasureSpec.getSize(widthMeasureSpec);
int hmode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//处理宽高...
//设置最终宽高
setMeasuredDimension(width,height);
}
//重写onDraw(),绘制控件;不重写,则表示采用父类View默认的绘制功能
@Override
protected void onDraw(Canvas canvas) {
//调用父类View基本的绘制功能
super.onDraw(canvas);
//...
//canvas.xxx();//绘制
}
}
在xml布局中应用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<com.clc.diyview.View1
android:layout_width="100dp"
android:layout_height="50dp"
android:background="#F44336"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:attribute_str="sssss"
app:attribute_integer="11111"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
自定义属性
新建 res/values/attrs.xml,自定义属性详细介绍
<resources>
<!--声明属性集合,name为“属性集合”的资源名-->
<declare-styleable name="view1">
<!--声明单个属性,name为属性名,format指定属性值类型-->
<attr name="attribute_str" format="string" />
<attr name="attribute_integer" format="integer" />
</declare-styleable>
</resources>
把自定义属性绑定到自定义View中
public class View1 extends View {
//自定义属性,对应 R.styleable.view1(xml),记得getter/setter
private String attributeStr;
private int attributeInteger;
//必须有一个构造方法
public View1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//获取属性集资源
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.view1);
//获取xml布局中View1相应的属性值,getXxx(index) index为属性id:属性集资源id_属性名
attributeStr = typedArray.getString(R.styleable.view1_attribute_str);
attributeInteger = typedArray.getInteger(R.styleable.view1_attribute_integer, -1);
//最后,回收typedArray
typedArray.recycle();
}
//......
xml布局中使用自定义属性
<com.clc.diyview.View1 ... app:attribute_str="sssss" app:attribute_integer="11111" ... />