0


Android开发之自定义View

一、View的简介

View类是Android中各种组件的基类,如View是ViewGroup基类,表现为显示在屏幕上的各种视图。Android中的UI组件都是由View和ViewGroup组成。

1.1 View的构造函数

  1. //如果View在Java代码中是new出来的,就会调用第一个构造函数
  2. public View(Context context) {
  3. throw new RuntimeException("Stub!");
  4. }
  5. //如果View是在.xml里面声明的就会调用第二个构造函数
  6. public View(Context context, @Nullable AttributeSet attrs) {
  7. throw new RuntimeException("Stub!");
  8. }
  9. public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  10. throw new RuntimeException("Stub!");
  11. }
  12. public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  13. throw new RuntimeException("Stub!");
  14. }

AttributeSet与自定义属性:系统自带的View可以在xml中配置属性,对于已经写好的自定义的View同样可以在xml中配置属性,为了使自定义View的属性可以在xml中配置,需要一下四个步骤:

  1. 通过<declare-styleable>为自定义View添加属性
  2. 在xml中为相应的属性生命属性值
  3. 在运行时获取属性值
  4. 将获取的属性值应用到View

1.2 View的绘制流程图

二、自定义View

自定义View的最基本的方法是:

onMeasure():测量,决定View的大小;

onLayout():布局,决定View在ViewGroup中的位置

onDraw():绘制,决定绘制这个View;

2.1 onMeasure()方法

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. ...
  4. }

首先看一下onMeasure函数的两个参数,widthMeasureSpec和heightMeasureSpec。这两个int型的数据包含了测量模式和尺寸。

  1. //获取测量模式
  2. int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
  3. int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
  4. //获取尺寸
  5. int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
  6. int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

什么是测量模式呢?测量模式有三种,分别是UNSPECIFIED/ EXCATLY/ AT_MOST。
测量模式表示意思UNSPECIFIED父容器没有对当前View有任何限制,当前View可以任意取尺寸EXACTLY当前的尺寸就是当前View应该取的尺寸AT_MOST当前尺寸是当前View能取的最大尺寸
现在重写一个onMeasure函数,实现一个自定义的正方形View。

  1. //继承View
  2. public class SquareView extends View {
  3. //1.只有Context的构造函数
  4. public SquareView(Context context) {
  5. super(context);
  6. }
  7. //2.含有Context和AttributeSet的构造函数
  8. public SquareView(Context context, @Nullable AttributeSet attrs) {
  9. super(context, attrs);
  10. }
  11. //3.重写onMesure方法
  12. @Override
  13. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  14. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  15. int width = getMySize(100,widthMeasureSpec);
  16. int height = getMySize(100,heightMeasureSpec);
  17. if (width<height){
  18. height = width;
  19. }else {
  20. width = height;
  21. }
  22. setMeasuredDimension(width,height);
  23. }
  24. //根据测量模式
  25. private int getMySize(int defaultSize, int measureSpec) {
  26. int mSize = defaultSize;
  27. int mode = MeasureSpec.getMode(measureSpec);
  28. int size = MeasureSpec.getSize(measureSpec);
  29. switch (mode){
  30. //父容器没有对当前View有任何限制,当前View可以任意取尺寸
  31. case MeasureSpec.UNSPECIFIED:
  32. mSize = defaultSize;
  33. break;
  34. //View能取得的最大尺寸
  35. case MeasureSpec.AT_MOST:
  36. mSize = size;
  37. break;
  38. //当前的尺寸就是当前View应该取的尺寸
  39. case MeasureSpec.EXACTLY:
  40. mSize = size;
  41. break;
  42. }
  43. return mSize;
  44. }
  45. }
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical"
  7. tools:context=".MainActivity">
  8. <TextView
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_margin="15sp"
  12. android:text="自定义View_SquareView"
  13. android:textSize="30sp" />
  14. <com.example.appb.SquareView
  15. android:layout_width="match_parent"
  16. android:layout_height="100dp"
  17. android:layout_margin="20dp"
  18. android:background="@color/purple_200" />
  19. <com.example.appb.SquareView
  20. android:layout_width="wrap_content"
  21. android:layout_height="wrap_content"
  22. android:layout_margin="20dp"
  23. android:background="@color/teal_200" />
  24. </LinearLayout>

运行效果:

  1. 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。

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. //调用父类的onDraw函数,因为View这个类实现了一些基本的绘制功能,比如绘制背景颜色和背景图片
  4. super.onDraw(canvas);
  5. //半径
  6. int r = getMeasuredWidth()/2;
  7. //以圆心的横坐标为当前View的左起始位置+半径
  8. int centerX = getLeft() + r;
  9. //以圆心的横坐标为当前View的顶部起始位置+半径
  10. int centerY = getTop() + r;
  11. Paint paint = new Paint();
  12. paint.setColor(Color.YELLOW);
  13. canvas.drawCircle(centerX,centerY,r,paint);
  14. }
  1. <com.example.appb.CycloView
  2. android:layout_width="100dp"
  3. android:layout_height="wrap_content"
  4. />

运行效果:

2.3 自定义布局属性

首先需要在res/values/styles.xml文件声明一个自定义属性:

  1. <resources>
  2. <!--声明属性集合的名称-->
  3. <declare-styleable name="CycloView">
  4. <!--声明属性名称,和取值类型-->
  5. <attr name="default_size" format="dimension"/>
  6. </declare-styleable>
  7. </resources>

布局文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:hc="http://schemas.android.com/apk/res-auto"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical">
  7. <com.example.appb.CycloView
  8. android:layout_width="100dp"
  9. android:layout_height="wrap_content"
  10. hc:default_size="100dp"
  11. />
  12. </LinearLayout>

Tips: 使用自定义属性需要在根标签添加命名空间,命名空间的取值是

"http://schemas.android.com/apk/res-auto"

修改构造函数,读取自定义属性的值:

  1. import android.content.Context;
  2. import android.content.res.TypedArray;
  3. import android.graphics.Canvas;
  4. import android.graphics.Color;
  5. import android.graphics.Paint;
  6. import android.util.AttributeSet;
  7. import android.view.View;
  8. import androidx.annotation.Nullable;
  9. public class CycloView extends View {
  10. private int defaultSize;
  11. public CycloView(Context context) {
  12. super(context);
  13. }
  14. public CycloView(Context context, @Nullable AttributeSet attrs) {
  15. super(context, attrs);
  16. //获取属性集合
  17. TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CycloView);
  18. //获取default_size属性
  19. defaultSize = typedArray.getDimensionPixelSize(R.styleable.CycloView_default_size,100);
  20. //将TypedArray回收
  21. typedArray.recycle();
  22. }
  23. @Override
  24. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  25. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  26. int width = getMySize(defaultSize,widthMeasureSpec);
  27. int height = getMySize(defaultSize,heightMeasureSpec);
  28. if (width<height){
  29. height = width;
  30. }else {
  31. width = height;
  32. }
  33. setMeasuredDimension(width,height);
  34. }
  35. private int getMySize(int defaultSize, int measureSpec) {
  36. int mSize = defaultSize;
  37. int mode = MeasureSpec.getMode(measureSpec);
  38. int size = MeasureSpec.getSize(measureSpec);
  39. switch (mode){
  40. case MeasureSpec.UNSPECIFIED:
  41. mSize = defaultSize;
  42. break;
  43. case MeasureSpec.AT_MOST:
  44. mSize = size;
  45. break;
  46. case MeasureSpec.EXACTLY:
  47. mSize = size;
  48. break;
  49. }
  50. return mSize;
  51. }
  52. @Override
  53. protected void onDraw(Canvas canvas) {
  54. //调用父类的onDraw函数,因为View这个类实现了一些基本的绘制功能,比如绘制背景颜色和背景图片
  55. super.onDraw(canvas);
  56. //半径
  57. int r = getMeasuredWidth()/2;
  58. //以圆心的横坐标为当前View的左起始位置+半径
  59. int centerX = getLeft() + r;
  60. //以圆心的横坐标为当前View的顶部起始位置+半径
  61. int centerY = getTop() + r;
  62. Paint paint = new Paint();
  63. paint.setColor(Color.YELLOW);
  64. canvas.drawCircle(centerX,centerY,r,paint);
  65. }
  66. }

参考文档:

自定义View(一) - 简书 (jianshu.com)

(3条消息) 自定义View,有这一篇就够了_走召大爷的博客-CSDN博客_自定义view


本文转载自: https://blog.csdn.net/weixin_43858011/article/details/125102763
版权归原作者 小树熊 所有, 如有侵权,请联系我们删除。

“Android开发之自定义View”的评论:

还没有评论