Table of contents
1. Introduction to View
The View class is the base class of various components in Android. For example, View is the base class of ViewGroup, which is represented as various views displayed on the screen. UI components in Android are composed of View and ViewGroup.
1.1 View's constructor
//如果View在Java代码中是new出来的,就会调用第一个构造函数
public View(Context context) {
throw new RuntimeException("Stub!");
}
//如果View是在.xml里面声明的就会调用第二个构造函数
public View(Context context, @Nullable AttributeSet attrs) {
throw new RuntimeException("Stub!");
}
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
throw new RuntimeException("Stub!");
}
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
throw new RuntimeException("Stub!");
}
AttributeSet and custom attributes: The View that comes with the system can configure attributes in xml. For the custom View that has been written, attributes can also be configured in xml. In order to make the attributes of custom Views configurable in xml, you need to Four steps:
- Add attributes to custom View via <declare-styleable>
- In the xml for the corresponding attribute life attribute value
- Get property value at runtime
- Apply the obtained attribute value to the View
1.2 View drawing flow chart
Two, custom View
The most basic way to customize View is:
onMeasure(): Measure to determine the size of the View;
onLayout(): Layout, determines the position of the View in the ViewGroup
onDraw(): Draw, decide to draw this View;
2.1 onMeasure() method
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
}
First look at the two parameters of the onMeasure function, widthMeasureSpec and heightMeasureSpec. The two int data contains measurement mode and size.
//获取测量模式
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//获取尺寸
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
What is measurement mode? There are three measurement modes, namely UNSPECIFIED/ EXCATLY/ AT_MOST.
measurement mode | express meaning |
---|---|
UNSPECIFIED | The parent container does not have any restrictions on the current View, and the current View can be of any size |
EXACTLY | The current size is the size that the current View should take |
AT_MOST | The current size is the maximum size that the current View can take |
Now rewrite an onMeasure function to implement a custom square View.
//继承View
public class SquareView extends View {
//1.只有Context的构造函数
public SquareView(Context context) {
super(context);
}
//2.含有Context和AttributeSet的构造函数
public SquareView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
//3.重写onMesure方法
@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);
}
//根据测量模式
private int getMySize(int defaultSize, int measureSpec) {
int mSize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode){
//父容器没有对当前View有任何限制,当前View可以任意取尺寸
case MeasureSpec.UNSPECIFIED:
mSize = defaultSize;
break;
//View能取得的最大尺寸
case MeasureSpec.AT_MOST:
mSize = size;
break;
//当前的尺寸就是当前View应该取的尺寸
case MeasureSpec.EXACTLY:
mSize = size;
break;
}
return mSize;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15sp"
android:text="自定义View_SquareView"
android:textSize="30sp" />
<com.example.appb.SquareView
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_margin="20dp"
android:background="@color/purple_200" />
<com.example.appb.SquareView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:background="@color/teal_200" />
</LinearLayout>
running result:
Tips: When customizing View, two constructors are required. Otherwise, an exception will be reported during compilation: Binary XML file line Error inflating class. The reason is: Android will call the double-parameter construction method of View when creating a View object based on the xml folder, that is, public SquareView(Context context, AttributeSetattrs), So if it is not written in full, an error will be reported.
2.2 OnDraw() method
The custom size is implemented in the onMeasure method, and the custom drawing View is implemented in the onDraw method. Next make a custom circular View.
@Override
protected void onDraw(Canvas canvas) {
//调用父类的onDraw函数,因为View这个类实现了一些基本的绘制功能,比如绘制背景颜色和背景图片
super.onDraw(canvas);
//半径
int r = getMeasuredWidth()/2;
//以圆心的横坐标为当前View的左起始位置+半径
int centerX = getLeft() + r;
//以圆心的横坐标为当前View的顶部起始位置+半径
int centerY = getTop() + r;
Paint paint = new Paint();
paint.setColor(Color.YELLOW);
canvas.drawCircle(centerX,centerY,r,paint);
}
<com.example.appb.CycloView
android:layout_width="100dp"
android:layout_height="wrap_content"
/>
running result:
2.3 Custom layout properties
First, you need to declare a custom attribute in the res/values/styles.xml file:
<resources>
<!--声明属性集合的名称-->
<declare-styleable name="CycloView">
<!--声明属性名称,和取值类型-->
<attr name="default_size" format="dimension"/>
</declare-styleable>
</resources>
layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:hc="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.appb.CycloView
android:layout_width="100dp"
android:layout_height="wrap_content"
hc:default_size="100dp"
/>
</LinearLayout>
Tips : To use custom attributes, you need to add a namespace to the root tag. The value of the namespace is
"http://schemas.android.com/apk/res-auto"
Modify the constructor to read the value of the custom property:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class CycloView extends View {
private int defaultSize;
public CycloView(Context context) {
super(context);
}
public CycloView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//获取属性集合
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CycloView);
//获取default_size属性
defaultSize = typedArray.getDimensionPixelSize(R.styleable.CycloView_default_size,100);
//将TypedArray回收
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMySize(defaultSize,widthMeasureSpec);
int height = getMySize(defaultSize,heightMeasureSpec);
if (width<height){
height = width;
}else {
width = height;
}
setMeasuredDimension(width,height);
}
private int getMySize(int defaultSize, int measureSpec) {
int mSize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode){
case MeasureSpec.UNSPECIFIED:
mSize = defaultSize;
break;
case MeasureSpec.AT_MOST:
mSize = size;
break;
case MeasureSpec.EXACTLY:
mSize = size;
break;
}
return mSize;
}
@Override
protected void onDraw(Canvas canvas) {
//调用父类的onDraw函数,因为View这个类实现了一些基本的绘制功能,比如绘制背景颜色和背景图片
super.onDraw(canvas);
//半径
int r = getMeasuredWidth()/2;
//以圆心的横坐标为当前View的左起始位置+半径
int centerX = getLeft() + r;
//以圆心的横坐标为当前View的顶部起始位置+半径
int centerY = getTop() + r;
Paint paint = new Paint();
paint.setColor(Color.YELLOW);
canvas.drawCircle(centerX,centerY,r,paint);
}
}
Reference documents: