Android开发之自定义View_android 自定义view
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
一、View的简介
View类是Android中各种组件的基类如View是ViewGroup基类表现为显示在屏幕上的各种视图。Android中的UI组件都是由View和ViewGroup组成。
1.1 View的构造函数
//如果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与自定义属性系统自带的View可以在xml中配置属性对于已经写好的自定义的View同样可以在xml中配置属性为了使自定义View的属性可以在xml中配置需要一下四个步骤
- 通过<declare-styleable>为自定义View添加属性
- 在xml中为相应的属性生命属性值
- 在运行时获取属性值
- 将获取的属性值应用到View
1.2 View的绘制流程图
二、自定义View
自定义View的最基本的方法是:
onMeasure()测量决定View的大小
onLayout()布局决定View在ViewGroup中的位置
onDraw():绘制决定绘制这个View;
2.1 onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
}
首先看一下onMeasure函数的两个参数widthMeasureSpec和heightMeasureSpec。这两个int型的数据包含了测量模式和尺寸。
//获取测量模式
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//获取尺寸
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
什么是测量模式呢测量模式有三种分别是UNSPECIFIED/ EXCATLY/ AT_MOST。
测量模式 | 表示意思 |
---|---|
UNSPECIFIED | 父容器没有对当前View有任何限制当前View可以任意取尺寸 |
EXACTLY | 当前的尺寸就是当前View应该取的尺寸 |
AT_MOST | 当前尺寸是当前View能取的最大尺寸 |
现在重写一个onMeasure函数实现一个自定义的正方形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>
运行效果
Tips:在自定义View的时候需要两个构造函数。否则在编译的时候会报异常Binary XML file line Error inflating class. 原因是Android在根据xml文件夹创建View对象的时候会调用View的双参构造方法即public SquareView(Context context AttributeSetattrs)所以如果没有写全的话就会报错。
2.2 OnDraw()方法
在onMeasure方法中实现了自定义尺寸大小在onDraw方法中实现了自定义的绘制View。接下来做一个自定义的圆形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"
/>
运行效果
2.3 自定义布局属性
首先需要在res/values/styles.xml文件声明一个自定义属性
<resources>
<!--声明属性集合的名称-->
<declare-styleable name="CycloView">
<!--声明属性名称和取值类型-->
<attr name="default_size" format="dimension"/>
</declare-styleable>
</resources>
布局文件
<?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: 使用自定义属性需要在根标签添加命名空间命名空间的取值是
"http://schemas.android.com/apk/res-auto"
修改构造函数读取自定义属性的值
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);
}
}
参考文档