当前位置: 首页 > article >正文

Android 自定义View的详解

引言:自定义View‌是指在Android开发中,当现有的官方提供的View无法满足需求时,开发者需要自己定义一个新的View。自定义View可以通过继承现有的View类或ViewGroup类来实现,通常涉及到重写一些关键的方法,如onMeasure、onLayout和onDraw等‌

一、基本概念和用途

自定义View的主要用途是满足特定的布局需求或实现特定的视觉效果,当标准控件无法满足复杂布局或特殊视觉效果时,自定义View就显得尤为重要。例如,实现一个复杂的动画效果、一个独特的用户界面元素或一个需要特定交互逻辑的控件‌。

二、绘制流程

在这里插入图片描述

三、3种实现方式

  1. 继承系统控件‌:继承现有的系统控件如TextView、Button等,在其基础上进行扩展和修改。
  2. 直接继承View或ViewGroup‌:直接继承View或ViewGroup类,实现完全自定义的控件。
  3. 自定义组合View‌:通过组合多个现有控件来创建一个新的控件。

四、自定义控件分类

  • 自定义View:只需要重写onMeasure()和onDraw()
  • 自定义ViewGroup:只需要重写onMeasure()和onLayout()

五、自定义View的关键方法和步骤

自定义View的核心在于重写或实现以下方法:

  • ‌onMeasure()‌:用于测量View的大小(宽度和高度)。
  • ‌onLayout():确定View在ViewGroup中的位置;
  • onDraw(Canvas canvas)‌:用于绘制View的内容。

此外,自定义View还需要处理触摸事件,通常需要重写以下方法来实现交互逻辑‌:

  • onTouchEvent(),在里面写上你的触摸反馈算法,并返回 true(关键是 ACTION_DOWN 事件时返回 true)。
  • 如果是会发生触摸冲突的 ViewGroup,还需要重写 onInterceptTouchEvent(),在事件流开始时返回 false,并在确认接管事件流时返回一次 true,以实现对事件的拦截。
  • 当子 View 临时需要阻止父 View 拦截事件流时,可以调用父 View 的 requestDisallowInterceptTouchEvent() ,通知父 View 在当前事件流中不再尝试通过 onInterceptTouchEvent() 来拦截。

六、构造和常用方法简介

class CustomTextView : androidx.appcompat.widget.AppCompatTextView {

    private var alignOnlyOneLine = false

    // 如果view在 代码 里面实例化,则调用该构造函数 
    constructor(context: Context) : super(context) {
        initAttr(context, null)
    }

    // 如果 View是在 .xml里声明的,则调用第二个构造函数
    // 自定义属性是从 AttributeSet参数传进来的
    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
        initAttr(context, attributeSet)
    }

    // 在XML中声明自定义View时调用, 当属性中有style属性时调用
    constructor(
        context: Context,
        attributeSet: AttributeSet,
        defStyleAttr: Int
    ) : super(context, attributeSet, defStyleAttr) {
        initAttr(context, attributeSet)
    }

    // 自定义属性获取
    private fun initAttr(context: Context, attributeSet: AttributeSet?) {
        val typedArray: TypedArray =
            context.obtainStyledAttributes(attributeSet, R.styleable.AlignTextView2)
        alignOnlyOneLine = typedArray.getBoolean(R.styleable.AlignTextView2_alignOnlyOneLine, false)
        typedArray.recycle()
    }

    // 测量方法:确定View的大小
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    // 确定View中的子控件的大小和位置
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
    }

    // 绘制自定义View的方法,用于绘制View的内容
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
    }

    // 当View中在xml中加载完之后调用(含所有子控件)
    override fun onFinishInflate() {
        super.onFinishInflate()
    }

    // 当View尺寸大小发生变化时调用
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
    }

    // 当手指触发某个区域控件时触发
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        return super.onTouchEvent(event)
    }

    // invalidate使用的非常频繁,他会触发View的重新绘制,也就是绘制流程的draw()过程,但不会调用测量和布局过程
    override fun invalidate() {
        super.invalidate()
    }

}

七、自定义组合View

这种方法优点是不需要重写相关方法就可以直接使用,开发起来非常方便;缺点是只能够通过现有的View(系统View)进行组合,如果我们需要自定义的View是形状非常不规则,无法通过现有View直接组合得出的话,这种方法是无法满足要求的。

如下图,以实现一个自定义的TitleBar为例:
在这里插入图片描述

1. 自定义属性
在values文件夹下,新建一个attrs.xml文件,并且自定义相关属性。

<resources>
    <declare-styleable name="CusTitleBar">
        <attr name="bg_color" format="color"></attr>
        <attr name="text_color" format="color"></attr>
        <attr name="title_text" format="string"></attr>
    </declare-styleable>
</resources>

2. 自定义布局
然后在layout文件夹,新建一个布局文件layout_custom_titlebar,并根据需要进行自定义布局。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:id="@+id/layout_titlebar_root">
 
    <ImageView
        android:id="@+id/btn_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:src="@drawable/ico_return"
        android:paddingLeft="10dp" />
 
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:textSize="20sp" />
 
    <ImageView
        android:id="@+id/btn_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ico_title_right"
        android:paddingRight="10dp" />
</RelativeLayout>
  1. 实现自定义View
    通过继承一个系统Layout父布局,并且将自定义View的布局和属性进行关联。再根据需要,编写一些功能代码。
public class CustomTitleBar extends RelativeLayout {
    private int mBgColor = Color.BLUE;
    private int mTextColor = Color.WHITE;
    private String mTitleText = "";
 
    private ImageView btn_left;
    private ImageView btn_right;
    private TextView tvTitle;
    private RelativeLayout relativeLayout;
 
    public CustomTitleBar(Context context) {
        super(context);
        initView(context);
    }
 
    public CustomTitleBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        initTypeValue(context,attrs);
        initView(context);
    }
 
    public void initTypeValue(Context context ,AttributeSet attrs){
        TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CusTitleBar);
        mBgColor = a.getColor(R.styleable.CusTitleBar_bg_color, Color.YELLOW);
        mTitleText = a.getString(R.styleable.CusTitleBar_title_text);
        mTextColor = a.getColor(R.styleable.CusTitleBar_text_color,Color.RED);
        a.recycle();
    }
 
    public void initView(Context context){
        LayoutInflater.from(context).inflate(R.layout.layout_custom_titlebar,this,true);
 
        btn_left = findViewById(R.id.btn_left);
        btn_right = findViewById(R.id.btn_right);
        tvTitle = findViewById(R.id.tv_title);
        relativeLayout = findViewById(R.id.layout_titlebar_root);
 
        relativeLayout.setBackgroundColor(mBgColor);
        tvTitle.setTextColor(mTextColor);
        tvTitle.setText(mTitleText);
    }
 
    public void setBackClickListener(OnClickListener listener){
        btn_left.setOnClickListener(listener);
    }
 
    public void setRightClickListener(OnClickListener listener){
        btn_right.setOnClickListener(listener);
    }
 
    public void setTitleText(String str){
        if(!TextUtils.isEmpty(str)){
            tvTitle.setText(str);
        }
    }
 
}
  1. 使用自定义View
    用法非常简单,在需要使用的layout布局中,将自定义的View导入,并完善相关属性。最后在Java代码中进行调用即可。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:apps="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#eee"
    tools:context=".MainActivity">
 
    <software.baby.learncustomview.CustomTitleBar
        android:id="@+id/custom_title_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        apps:title_text="@string/app_name"
        apps:text_color="@color/colorWhite"
        apps:bg_color = "@color/colorPrimary" />
</LinearLayout>
public class MainActivity extends AppCompatActivity {
    private CustomTitleBar customTitleBar;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        customTitleBar = findViewById(R.id.custom_title_bar);
        customTitleBar.setTitleText("标题标题");
        customTitleBar.setBackClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "Click Back!", Toast.LENGTH_SHORT).show();
            }
        });
        customTitleBar.setRightClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "Click Right!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

八、自定义View的绘制onDraw()

绘制的关键是 Canvas 的使用

  1. Canvas 的绘制类方法: drawXXX() (关键参数:Paint)
  2. Canvas 的辅助类方法:范围裁切和几何变换

自定义绘制:提前创建好 Paint 对象,重写 onDraw(),把绘制代码写在 onDraw() 里面,就是自定义绘制最基本的实现。大概如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 绘制代码,例如画一个圆或矩形
    Paint paint = new Paint();
    paint.setColor(Color.BLUE);
    canvas.drawCircle(100, 100, 50, paint); // 绘制一个蓝色圆心在(100, 100),半径为50的圆
}

8.1 canvas可绘制的常见内容

填充

drawARGB(int a, int r, int g, int b)
drawColor(int color)
drawRGB(int r, int g, int b)
drawColor(int color, PorterDuff.Mode mode)

几何图形

canvas.drawArc (扇形)
canvas.drawCircle(圆)
canvas.drawOval(椭圆)
canvas.drawLine(线)
canvas.drawPoint(点)
canvas.drawRect(矩形)
canvas.drawRoundRect(圆角矩形)
canvas.drawVertices(顶点)
cnavas.drawPath(路径)

图片

canvas.drawBitmap (位图)
canvas.drawPicture (图片)

文本

canvas.drawText

drawPath:用于绘制自定义图形。drawPath(path) 这个方法是通过描述路径的方式来绘制图形的,它的 path 参数就是用来描述图形路径的对象。

drawPath(Path path, Paint paint)

8.2 Path用法详解

第一类:直接描述路径

这一类方法还可以细分为两组:添加子图形和画线(直线或曲线)
第一组: addXxx() ——添加子图形

  • addCircle(float x, float y, float radius, Direction dir) 添加圆

  • addOval(float left, float top, float right, float bottom, Direction dir) / addOval(RectF oval, Direction dir) 添加椭圆

  • addRect(float left, float top, float right, float bottom, Direction dir) / addRect(RectF rect, Direction dir) 添加矩形

  • addRoundRect(RectF rect, float rx, float ry, Direction dir) / addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Direction dir) /

  • addRoundRect(RectF rect, float[] radii, Direction dir) / addRoundRect(float left, float top, float right, float bottom, float[] radii, Direction dir) 添加圆角矩形

  • addPath(Path path) 添加另一个 Path

    Direction 指路径的方向。路径方向有两种:顺时针 (CW clockwise) 和逆时针 (CCW counter-clockwise) 。

    path.addCircle(300, 300, 200, Path.Direction.CW);

第二组:xxxTo() ——画线(直线或曲线)
和第一组 addXxx() 方法的区别在于,第一组是添加的完整封闭图形(除了 addPath() ),而这一组添加的只是一条线。

  • lineTo(float x, float y) / rLineTo(float x, float y) 画直线
    从当前位置向目标位置画一条直线, x 和 y 是目标位置的坐标。这两个方法的区别是,lineTo(x, y) 的参数是绝对坐标,而 rLineTo(x, y) 的参数是相对当前位置的相对坐标 (前缀 r 指的就是 relatively 「相对地」)。

  • quadTo(float x1, float y1, float x2, float y2) / rQuadTo(float dx1, float dy1, float dx2, float dy2) 画二次贝塞尔曲线

  • cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) / rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 画三次贝塞尔曲线

  • moveTo(float x, float y) / rMoveTo(float x, float y) 移动到目标位置

  • arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) /

  • arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(RectF oval, float startAngle, float sweepAngle) 画弧形

    close() 封闭当前子图形。

paint.setStyle(Style.STROKE);
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(150, 150);
path.close(); // 使用 close() 封闭子图形。等价于 path.lineTo(100, 100)

在这里插入图片描述
不是所有的子图形都需要使用 close() 来封闭。当需要填充图形时(即 Paint.Style 为 FILL 或 FILL_AND_STROKE),Path 会自动封闭子图形。

第二类:辅助的设置或计算

Path.setFillType(Path.FillType ft) 设置填充方式

8.3 Paint的详解

Paint 类的几个最常用的方法。具体是:

Paint.setStyle(Style style) 设置绘制模式

Style 具体来说有三种: FILL, STROKEFILL_AND_STROKE 。FILL 是填充模式,STROKE 是画线模式(即勾边模式),FILL_AND_STROKE 是两种模式一并使用:既画线又填充。它的默认值是 FILL,填充模式。

paint.setStyle(Paint.Style.STROKE); // Style 修改为画线模式
canvas.drawCircle(300, 300, 200, paint);

Paint.setColor(int color) 设置颜色
Paint.setColorFilter(ColorFilter colorFilter) 设置颜色过滤
Paint.setStrokeWidth(float width) 设置线条宽度
Paint.setTextSize(float textSize) 设置文字大小
Paint.setShader(Shader shader) 设置 Shader

Shader 中文叫做「着色器」,也是用于设置绘制颜色的。「着色器」不是 Android 独有的,它是图形领域里一个通用的概念,它和直接设置颜色的区别是,着色器设置的是一个颜色方案,或者说是一套着色规则。当设置了 Shader 之后,Paint 在绘制图形和文字时就不使用 setColor/ARGB() 设置的颜色了,而是使用 Shader 的方案中的颜色。

    Android 的绘制里使用 Shader ,并不直接用 Shader 这个类,而是用它的几个子类。具体来讲有 
  • LinearGradient 线性渐变
  • RadialGradient 辐射渐变
  • SweepGradient 扫描渐变
  • BitmapShader 用 Bitmap 的像素来作为图形或文字的填充
  • ComposeShader 混合着色器
Shader shader = new LinearGradient(100, 100, 500, 500, Color.parseColor("#E91E63"),
        Color.parseColor("#2196F3"), Shader.TileMode.CLAMP);
paint.setShader(shader);
     
...
     
canvas.drawCircle(300, 300, 200, paint);

Paint.setAntiAlias(boolean aa) 设置抗锯齿开关

在绘制的时候,往往需要开启抗锯齿来让图形和文字的边缘更加平滑。开启抗锯齿很简单,只要在 new Paint() 的时候加上一个 ANTI_ALIAS_FLAG 参数就行。
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

九、MeasureSpec讲解

9.1 MeasureSpec定义

MeasureSpec是View中的内部类,基本都是二进制运算。由于int是32位,用高2位表示mode(UNSPECIFIED、EXACTLY、AT_MOST),低30位表示size,MODE_SHIFT=30的作用是移位。
测量规格,封装了父容器对view的布局上的限制,内部提供了宽高的信息(SpecMode、SpecSize),SpecSize是指在某种SpecMode下的参考尺寸,其中SpecMode有如下三种:
在这里插入图片描述

9.2 MeasureSpecs的意义

通过将SpecMode和SpecSize打包成一个int值可以避免过多的对象内存分配,为了方便操作,其提供了打包/解包方法

9.3 MeasureSpec值的确定

/**
*
* 目标是将父控件的测量规格和child view的布局参数LayoutParams相结合,得到一个
* 最可能符合条件的child view的测量规格。
* @param spec 父控件的测量规格
* @param padding 父控件里已经占用的大小
* @param childDimension child view布局LayoutParams里的尺寸
* @return child view 的测量规格
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
	int specMode = MeasureSpec.getMode(spec); //父控件的测量模式
	int specSize = MeasureSpec.getSize(spec); //父控件的测量大小
	int size = Math.max(0, specSize - padding);
	int resultSize = 0;
	int resultMode = 0;
	switch (specMode) {
		// 当父控件的测量模式 是 精确模式,也就是有精确的尺寸了
		case MeasureSpec.EXACTLY:
			//如果child的布局参数有固定值,比如"layout_width" = "100dp"
			//那么显然child的测量规格也可以确定下来了,测量大小就是100dp,测量模式也是EXACTLY
			if (childDimension >= 0) {
				resultSize = childDimension;
				resultMode = MeasureSpec.EXACTLY;
			}
			//如果child的布局参数是"match_parent",也就是想要占满父控件

			//而此时父控件是精确模式,也就是能确定自己的尺寸了,那child也能确定自己大小了
			else if (childDimension == LayoutParams.MATCH_PARENT) {
				resultSize = size;
				resultMode = MeasureSpec.EXACTLY;
			}
			//如果child的布局参数是"wrap_content",也就是想要根据自己的逻辑决定自己大小,
			//比如TextView根据设置的字符串大小来决定自己的大小
			//那就自己决定呗,不过你的大小肯定不能大于父控件的大小嘛
			//所以测量模式就是AT_MOST,测量大小就是父控件的size
			else if (childDimension == LayoutParams.WRAP_CONTENT) {
				resultSize = size;
				resultMode = MeasureSpec.AT_MOST;
			}
			break;
		// 当父控件的测量模式 是 最大模式,也就是说父控件自己还不知道自己的尺寸,但是大小不超过size
		case MeasureSpec.AT_MOST:
		//同样的,既然child能确定自己大小,尽管父控件自己还不知道自己大小,也优先满足孩子的需求
			if (childDimension >= 0) {
				resultSize = childDimension;
				resultMode = MeasureSpec.EXACTLY;
			}
			//child想要和父控件一样大,但父控件自己也不确定自己大小,所以child也无法确定自己大小
			//但同样的,child的尺寸上限也是父控件的尺寸上限size
			else if (childDimension == LayoutParams.MATCH_PARENT) {
				resultSize = size;
				resultMode = MeasureSpec.AT_MOST;
			}
			//child想要根据自己逻辑决定大小,那就自己决定呗
			else if (childDimension == LayoutParams.WRAP_CONTENT) {
				resultSize = size;
				resultMode = MeasureSpec.AT_MOST;
			}
			break;
		// Parent asked to see how big we want to be
		case MeasureSpec.UNSPECIFIED:
			if (childDimension >= 0) {
				// Child wants a specific size... let him have it
				resultSize = childDimension;
				resultMode = MeasureSpec.EXACTLY;
			} else if (childDimension == LayoutParams.MATCH_PARENT) {
				// Child wants to be our size... find out how big it should
				// be
				resultSize = 0;
				resultMode = MeasureSpec.UNSPECIFIED;
			} else if (childDimension == LayoutParams.WRAP_CONTENT) {
				// Child wants to determine its own size.... find out how
				// big it should be
				resultSize = 0;
				resultMode = MeasureSpec.UNSPECIFIED;
			}
			break;
		}
	return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

9.4 getMeasureWidth和getWidth的区别

getMeasureWidth:

  • 在measure()过程结束后就可以获取到对应的值
  • 通过setMeasuredDimension()方法来进行设置的

getWidth:

  • 在layout()过程结束后才能获取到
  • 通过视图右边的坐标减去左边的坐标计算出来的

10、onMeasure()、onLayout()、onDraw() 详解

在Android开发中,自定义ViewViewGroup时,理解onMeasure()onLayout()和onDraw()这三个方法的调用时机和作用非常重要。它们是自定义视图的核心方法,负责视图的测量、布局和绘制。

1. onMeasure()
作用
onMeasure()方法用于测量视图及其子视图的大小。系统会调用该方法来确定视图的宽度和高度。

调用时机

  • 当视图首次被添加到视图树中时。
  • 当视图的父视图调用requestLayout()时。
  • 当视图的布局参数发生变化时。

关键点

  • onMeasure()方法接收两个参数:widthMeasureSpec和heightMeasureSpec,它们包含了父视图对当前视图的宽度和高度的约束信息。

  • MeasureSpec是一个32位的int值,高2位表示测量模式,低30位表示测量大小。

测量模式有三种

  • UNSPECIFIED:父视图没有对子视图施加任何约束,子视图可以是任意大小。
  • EXACTLY:父视图已经确定了子视图的确切大小,子视图必须使用这个大小。
  • AT_MOST:子视图的大小不能超过父视图指定的最大尺寸。

实现步骤

  1. 根据MeasureSpec计算视图的宽度和高度。
  2. 调用setMeasuredDimension(int width, int height)方法设置视图的最终大小。

示例:

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
 
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
 
        int width = 0;
        int height = 0;
 
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            width = Math.max(width, child.getMeasuredWidth());
            height += child.getMeasuredHeight();
        }
 
        if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(width, widthSize);
        }
 
        if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(height, heightSize);
        }
 
        setMeasuredDimension(width, height);
    }

2. onLayout()
作用
onLayout()方法用于确定子视图在父视图中的位置。对于ViewGroup来说,它需要调用每个子视图的layout()方法来设置子视图的位置。

调用时机
当视图的onMeasure()方法被调用后,系统会调用onLayout()来布局子视图。

关键点

  • onLayout()方法接收四个参数:left, top, right, bottom,它们表示当前视图相对于父视图的位置。
  • 对于ViewGroup,需要在onLayout()中调用每个子视图的layout()方法,传递子视图的位置参数。

实现步骤

  1. 遍历所有子视图。
  2. 计算每个子视图的位置。
  3. 调用子视图的layout(int l, int t, int r, int b)方法设置子视图的位置。

示例:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    int childCount = getChildCount();
    int currentTop = top;

    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        int childHeight = child.getMeasuredHeight();
        child.layout(left, currentTop, right, currentTop + childHeight);
        currentTop += childHeight;
    }
}

3. onDraw()
作用
onDraw()方法用于绘制视图的内容。开发者可以在这个方法中使用CanvasPaint等工具来绘制自定义的图形、文本等。

调用时机

  • 当视图首次被添加到视图树中时。
  • 当视图调用invalidate()或postInvalidate()方法时。
  • 当视图的内容发生变化时。

关键点

  • onDraw()方法接收一个Canvas对象,开发者可以使用Canvas的API来绘制内容。
  • Canvas提供了丰富的绘图方法,如drawLine()、drawRect()、drawText()等。
  • Paint对象用于设置绘图的样式,如颜色、字体、线条宽度等。

实现步骤

  1. 使用Canvas对象绘制内容。
  2. 可以通过Paint对象设置绘图的样式。

示例:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    Paint paint = new Paint();
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.FILL);

    // 绘制一个矩形
    canvas.drawRect(0, 0, getWidth(), getHeight(), paint);

    // 绘制文本
    paint.setColor(Color.WHITE);
    paint.setTextSize(40);
    canvas.drawText("Hello, World!", 50, 50, paint);
}

总结

  • onMeasure():负责测量视图的大小。
  • onLayout():负责确定子视图的位置。
  • onDraw():负责绘制视图的内容。

在自定义ViewViewGroup时,通常需要重写这三个方法来实现自定义的测量、布局和绘制逻辑。理解它们的调用时机和作用,能够更好地控制视图的显示和行为。

参考:
1、https://blog.csdn.net/qq_34519487/article/details/104076633
2、https://www.jianshu.com/p/9b190f232b27
3、https://blog.csdn.net/qq_45716076/article/details/120460575
4、https://www.cnblogs.com/ruiruizhou/p/18524792


http://www.kler.cn/a/534708.html

相关文章:

  • Linux中的基本指令(二)
  • 动态规划练习八(01背包问题)
  • 机器学习--2.多元线性回归
  • 每日Attention学习19——Convolutional Multi-Focal Attention
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.22 多项式运算:从求根到拟合的数值方法
  • 注解(Annotation)
  • html转PDF文件最完美的方案(wkhtmltopdf)
  • 【机器学习】训练(Training)、验证(Validation)和测试(Testing)
  • Linux内核链表
  • 从0开始达芬奇(3.8)
  • 【Spring Boot】解锁高效安全之门:登录令牌技术的实战应用与价值解析
  • Oracle 变更redo log文件位置
  • Java 大视界 -- Java 大数据在智能教育中的应用与个性化学习(75)
  • 【重生之学习C语言----杨辉三角篇】
  • AWS Copilot
  • 威联通NAS桌面图标消失后恢复术
  • k8s部署rabbitmq
  • PCL 最小包围圆(二维)
  • IEEE 802.3/802.2 | LLC / SNAP
  • 配置Apache本地服务支持PHP8--易错点
  • [创业之路-285]:《产品开发管理-方法.流程.工具 》-1- IPD的功能列表以及导入步骤
  • 【Elasticsearch】Global 聚合
  • 项目练习:SpringSecurity+OAuth2接入gitee的第三方登陆(授权码模式)
  • 二进制/源码编译安装httpd 2.4,提供系统服务管理脚本并测试
  • 简单说一下CAP理论和Base理论
  • 办理CE-notify-body资质流程详细讲解