Android开发之自定义View
目录
一、View的简介
1.1 View的构造函数
1.2 View的绘制流程图
二、自定义View
2.1 onMeasure()方法
2.2 OnDraw()方法
一、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); } }参考文档:
自定义View(一) - 简书 (jianshu.com)
(3条消息) 自定义View,有这一篇就够了_走召大爷的博客-CSDN博客_自定义view