乐趣区

关于java:Android中的自定义View一

PS:本文系转载文章,浏览原文可读性会更好,文章开端有原文链接

ps:本文是基于 Android API 31 来剖析的,文章写的 demo 是用 AndroidStudio 工具来开发的。

目录

1、自定义 View 的分类

2、自定义 View 的注意事项

3、自定义 View 的实例

1、自定义 View 的分类

自定义 View 的分类到目前还没有对立的规范,以我对自定义 View 的学习积攒,我把自定义 View 布局为以下这 4 类。

(1)间接继承于 View

间接继承于 View 来绘制一些不规则的视图,须要通过绘制的形式来实现,也就是要重写 View 的 onDraw 办法;间接继承于 View 的子类都是须要本人去解决 wrap_content(看 Android 中 View 的工作流程之 measure 过程这篇文章,如果自定义的 View 间接继承 View 不解决 wrap_content,那么就和 match_parent 的成果是一样的)和 padding,否则做进去的成果和咱们实现的不一样。

(2)间接继承于 ViewGroup

这种办法用于实现自定义的布局,像 LinearLayout、RelativeLayout 等是属于零碎的布局,如果咱们须要从新定义一种新布局,能够采纳这种办法来实现,比如说布局能够左右(把 ViewPager 看作是一种自定义布局嘛)且还产生水波纹成果的时候,用这种形式有点简单,须要适当地解决 ViewGroup 的测量、布局这两个过程,与此同时解决子元素的测量和布局过程。

(3)间接继承于具体的 View(例如 TextView)

用于拓展某种已具备的 View 性能,比如说 TextView,它被 Button 继承着,Button 次要用来设置点击事件的;间接继承于具体的 View,它不须要本人去解决 wrap_content 和 padding。

(4)间接继承于具体的 ViewGroup(例如 FrameLayout)

如果某种成果看起来和具体的 ViewGroup 很像的时候,也就是和具体的 ViewGroup 布局子元素个性很像的时候,能够采纳这种办法来实现,采纳这种办法不须要本人解决 ViewGroup 的 measure 和 layout 这两个过程。

一种 View 的显示成果有很多种自定义 View 的形式去实现,喜爱用哪种最终还是看开发者本人的爱好,好,咱们当初说一下自定义 View 应该留神的一些事项。

2、自定义 View 的注意事项

(1)间接继承于 View 或者 ViewGroup,当用到 wrap_content 值时,必须反对 wrap_content;如果不在 onMeasure 中对 wrap_content 做非凡解决,那么当自定义 View 或者自定义 ViewGroup 在布局中应用 wrap_content 时就没法达到预期的成果,那么咱们看到的就是和 match_parent 一样的成果,能够看 Android 中 View 的工作流程之 measure 过程这篇文章,你们就能够看到和 match_parent 一样的成果了。

(2)如果间接继承 View 且应用到 padding 时,如果不在 draw 办法中解决 padding,那么 padding 属性就不会起作用;如果间接继承自 ViewGroup 并且应用到 wrap_content 时,须要在 onMeasure 和 onLayout 办法中解决 padding 和子 View 的 margin 对它造成的影响。

(3)不要在自定义 View 中创立一个 Handler,因为 View 提供了 post 一系列的办法,View 的一系列 post 办法其实还是调用了 Handler 的 post 一系列的办法。

(4)当 View 变得不可见时咱们须要进行线程和动画,如果不解决这种状况,很容易造成内存透露;如果有线程或者动画须要进行时,最好是在 View 的 onDetachedFromWindow 办法中进行调用,因为 Activity 退出或者 View 被删除的时候,View 的 onDetachedFromWindow 办法会被执行。

(5)如果 View 存在滑动抵触,该当解决好滑动抵触,否则会影响用户体验成果;无关滑动抵触的内容能够看 Android 中 View 的滑动抵触这篇文章。

3、自定义 View 的实例

咱们这里就写一个间接持续于 View 的案例,咱们就画一个椭圆,步骤如下所示;

(1)写一个 EllipseView 并继承于 View;

package com.xe.myapplication4;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;

/**

  • Created by 86188 on 2022/7/7.
    */

public class EllipseView extends View {

Paint p;
private int color;
public EllipseView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);
    init(context,attrs);
}

public EllipseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
    init(context,attrs);
}

private void init(Context context, @Nullable AttributeSet attrs) {p = new Paint();
    TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.EllipseView);

    //1、color = ta.getColor(R.styleable.EllipseView_ellipse_view_color,Color.GREEN);
    ta.recycle();}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    //2、if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(200,150);

        //3、} else if (widthSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(200,heightSpecSize);

        //4、} else if (heightSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSpecSize,150);
    }
}

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

    //5、int leftPadding = getPaddingLeft();
    int rightPadding = getPaddingRight();
    int topPadding = getPaddingTop();
    int buttomPadding = getPaddingBottom();
    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    int width2 = width  - rightPadding;
    int height2 = height  - buttomPadding;
    p.setColor(color);
    p.setAntiAlias(true);
    RectF rectF = new RectF();
    rectF.set(leftPadding,topPadding,width2,height2);
    canvas.drawOval(rectF, p);
}

}

(2)在 values 目录下创立一个 attrs.xml 文件,并自定义一个 ellipse_view_color 属性,格局为 color 类型的,其值就是色彩值;

图片

图片

(3)自定义 View 在 xml 中的布局;

图片

阐明:

1)看正文 1,它是解析自定义属性的 ellipse_view_color 值,如果在 xml 布局文件中没有应用该属性,那么就默认为绿色。

2)看正文 2、3、4,解决 wrap_content 模式,当宽在 wrap_content 模式时,就将宽设置为 200;当高在 wrap_content 模式时,就将高设置为 150。

3)看正文 5,获取上下左右的 padding,绘制自定义 View 时解决 padding。

4)看正文 6 的 ellipse_view_color 属性,是不是有 app 的前缀?这是自定义属性的标记,应用自定义属性时必须在 xml 布局文件中增加 schemas 申明:xmlns:app=”http://schemas.android.com/apk/res-auto”。

程序运行后果如下所示;

图片

退出移动版