【安卓基础重点知识11】自定义View实现方式

2 阅读5分钟

Android允许自定义控件,来弥补原生控件的不足。实现自定义View的方式,分为三种:组合控件,继承控件,自绘控件。然后根据需要来添加自定义的属性。

1. 组合控件

就是将系统原有的控件进行组合,构成一个新的控件。这种方式下,不需要开发者自己去绘制图上显示的内容,也不需要开发者重写onMeasure,onLayout,onDraw方法来实现测量、布局以及draw流程。实际开发中,标题栏是一个比较常见的例子。

  • 编写布局文件,写入组合到一起的控件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/tv_custom_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Title"
        android:textSize="35dp"
        android:gravity="center"
        android:background="@color/black"
        android:textColor="@color/white"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <Button
        android:id="@+id/btn_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="返回"
        android:textSize="20dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
  • 根据给定布局,实现自定义View
public class CustomTitleView extends FrameLayout implements View.OnClickListener {

    private TextView tv_custom_title;
    private Button btn_back;
    private View.OnClickListener backOnClickListener;

    //这里使用其他参数的构造器会闪退,我也不清楚为啥
    public CustomTitleView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //初始化
        LayoutInflater.from(context).inflate(R.layout.custom_title_view, this);
        tv_custom_title = findViewById(R.id.tv_custom_title);
        btn_back = findViewById(R.id.btn_back);
        btn_back.setOnClickListener(this);
    }
    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.btn_back){
            if (backOnClickListener != null){
                backOnClickListener.onClick(view);
            }
        }
    }
    //给定两个接口,可以对控件进行修改
    public void setTitle(String str){
        tv_custom_title.setText(str);
    }

    public void setBackOnClickListener(View.OnClickListener backOnClickListener){
        this.backOnClickListener = backOnClickListener;
    }
}
  • 在MainActivity中的布局文件中引入
<com.example.day8_view.CustomTitleView
    android:id="@+id/custom_title_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>
  • 在MainActivity中使用
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        CustomTitleView custom_title_view = findViewById(R.id.custom_title_view);
        custom_title_view.setTitle("这是标题栏");
        custom_title_view.setBackOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Toast.makeText(this, "您点击了返回", Toast.LENGTH_SHORT).show();
    }
}

2. 继承控件

通过继承系统控件(View子类控件或ViewGroup子类控件)来完成自定义View,一般是希望在原有系统控件基础上做一些修饰性的修改,而不会做大幅度的改动,如在TextView的文字下方添加下划线,在LinearLayout布局中加一个蒙板等。这种方式往往都会复用系统控件的onMeasure和onLayout方法,而只需要重写onDraw方法,在其中绘制一些需要的内容。下面会分别继承View类控件和ViewGroup类控件来举例说明。

2.1 继承View类系统控件

示例:在TextView文字下方显示红色下划线

  • 继承TextView实现UnderlineTextView,重写onDraw方法
@SuppressLint("AppCompatCustomView")
public class UnderlineTextView extends TextView {
    public UnderlineTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        //设置画笔宽度
        paint.setStrokeWidth(5);
        int width = getWidth();
        //确定文本基线位置
        int height = getBaseline();
        //起点的X坐标、起点的Y坐标、终点的X坐标、终点的Y坐标、绘制样式
        canvas.drawLine(0, height, width, height, paint);
    }
}
  • 在MainActivity的布局文件中引入
<com.example.day8_view.UnderlineTextView
    android:id="@+id/underline_textview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="hello world!"
    android:textSize="35dp"
    android:gravity="center"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent"/>

2.2 继承ViewGroup类系统控件

示例:在layout布局上添加一个浅红色的半透明蒙板

  • 继承LinearLayout重写diapatchDraw方法
public class ForegroundLinearLayout extends LinearLayout {
    public ForegroundLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void dispatchDraw(@NonNull Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.drawColor(Color.parseColor("#50FF0000"));
    }
}
  • 在布局文件中使用
<com.example.day8_view.ForegroundLinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="parent">

    <com.example.day8_view.UnderlineTextView
        android:id="@+id/underline_textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="hello world!"
        android:textSize="35dp"
        android:gravity="center"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>
</com.example.day8_view.ForegroundLinearLayout>

3. 自绘控件

采用自绘控件这种方式时,如果自定义View为最终的叶子控件,那么需要直接继承View;当自定义View为容器类控件,则需要直接继承ViewGroup

3.1 继承View

示例:绘制直方图

  • 建立自定义View的类HistogramView,继承View,由于自定义View是叶子节点,所以没有子控件,只重写onDraw方法
public class HistogramView extends View {
    private Paint mPaint;
    private Path mPath;

    public HistogramView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制坐标轴
        mPaint.reset();
        mPath.reset();
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        mPath.moveTo(100,100);
        mPath.rLineTo(0,402);
        mPath.rLineTo(800,0);
        canvas.drawPath(mPath,mPaint);
        //绘制文字
        mPaint.reset();
        mPaint.setTextSize(30);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawText("Froyo",160,540,mPaint);
        canvas.drawText("CB",280,540,mPaint);
        canvas.drawText("ICS",380,540,mPaint);
        canvas.drawText("J",480,540,mPaint);
        canvas.drawText("KitKat",560,540,mPaint);
        canvas.drawText("L",690,540,mPaint);
        canvas.drawText("M",790,540,mPaint);
        //绘制直方图,柱形图是用较粗的直线来实现的
        mPaint.reset();
        mPaint.setColor(Color.GREEN);
        mPaint.setStrokeWidth(80);
        float[] lines3={
                200,500,200,495,
                300,500,300,480,
                400,500,400,480,
                500,500,500,300,
                600,500,600,200,
                700,500,700,150,
                800,500,800,350,
        };
        canvas.drawLines(lines3,mPaint);
    }
}
  • 在布局文件中引用即可
<com.example.day8_view.HistogramView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="parent"/>

3.2 继承ViewGroup

这里通过自定义一个父布局控件,并添加一个子view来作为例子讲解该方法的实现。

4. 在自定义View中使用自定义属性

Android --- 自定义View的三种实现方式及自定义属性使用介绍_android 自定义view-CSDN博客

本系列【安卓基础重点知识】是刚开始学习android的时候记录的,其中部分内容来自网页,忘记记录来源了,如需添加引用,联系我即可