『Android基础入门』自定义view控件画一个五彩斑斓的黑圈圈

1,774 阅读5分钟

我正在参与掘金创作者训练营第5期,点击了解活动详情

👨‍🎓作者简介:一位喜欢写作,计科专业大二菜鸟

🏡个人主页:starry陆离

🕒首发日期:2022年8月3日星期三

🌌上期文章:『Android基础入门』dataBinding的简单使用

📚订阅专栏:『Android基础入门』

如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦


『Android基础入门』自定义view控件画一个五彩斑斓的黑圈圈

1.前言

今天我们来实现一个好玩的小功能,自定义一个view控件在随机位置画一个随机颜色的圆圈,并实现点击事件监听移除与清空功能

首先来康康效果吧

ad2

2.xml布局设计

首先我们创建一个空项目,简单的来实现这个xml文件布局

布局主要分为两个部分,上大半部分是我们用来画图的view布局,我们的图案最终会渲染到这个部分。

下半部分是一个嵌套的约束布局,布局中放置三个button,分别来实现在view布局中添加一个圆,移除一个圆以及清空所有的圆

image-20220706094151831

activity_main.xml源代码

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">
 ​
     <View
     android:id="@+id/view"
     android:background="@color/black"
     android:layout_width="match_parent"
     android:layout_height="500dp"
     app:layout_constraintEnd_toEndOf="parent"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintTop_toTopOf="parent" />
 ​
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_marginTop="10dp"
         android:layout_marginBottom="10dp"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/view">
 ​
         <Button
             android:id="@+id/moveBtn"
             android:layout_width="300dp"
             android:layout_height="60dp"
             android:text="move"
             app:layout_constraintBottom_toTopOf="@+id/clearBtn"
             app:layout_constraintStart_toStartOf="@+id/addBtn"
             app:layout_constraintTop_toBottomOf="@+id/addBtn" />
 ​
         <Button
             android:id="@+id/addBtn"
             android:layout_width="300dp"
             android:layout_height="60dp"
             android:text="add"
             app:layout_constraintBottom_toTopOf="@+id/moveBtn"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
 ​
         <Button
             android:id="@+id/clearBtn"
             android:layout_width="300dp"
             android:layout_height="60dp"
             android:text="clear"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="@+id/moveBtn"
             app:layout_constraintTop_toBottomOf="@+id/moveBtn" />
     </androidx.constraintlayout.widget.ConstraintLayout>
 ​
 </androidx.constraintlayout.widget.ConstraintLayout>

3.Activity点击事件

image-20220706100914999

 public class MainActivity extends AppCompatActivity implements View.OnClickListener {
     @Override
     protected void onCreate(Bundle savedInstanceState){
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         findViewById(R.id.addBtn).setOnClickListener(this);
         findViewById(R.id.moveBtn).setOnClickListener(this);
         findViewById(R.id.clearBtn).setOnClickListener(this);
     }
     @Override
     public void onClick(View view) {
         if(view.getId()==R.id.addBtn){
             Toast.makeText(MainActivity.this,"add",Toast.LENGTH_SHORT).show();
         }else if(view.getId()==R.id.clearBtn){
             Toast.makeText(MainActivity.this,"clear",Toast.LENGTH_SHORT).show();
         }else if(view.getId()==R.id.moveBtn){
             Toast.makeText(MainActivity.this,"Move",Toast.LENGTH_SHORT).show();
         }
     }
 }

4.自定义view画布

我们需要自定义一个画布类

image-20220706101345994

用自定义的CircleView继承View父类替换View控件

image-20220706101318959

View类中有onDraw()方法,我们通过重写此方式实现在view布局上作画

image-20220706101605897

image-20220706101746831

有了画布自然要有画布,创建一支红色的画笔,调用画圆的方法drawCircle()

 //有了画布canvas,我们还需要一支画笔
 Paint paint=new Paint();
 paint.setColor(Color.RED);

image-20220706102037972

 public class CircleView extends View {
     public CircleView(Context context) {
         super(context);
     }
 ​
     public CircleView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
     }
 ​
     @Override
     protected void onDraw(Canvas canvas){
         super.onDraw(canvas);
         //设置画布的背景色为黑色
         this.setBackgroundColor(Color.BLACK);
 ​
         //有了画布canvas,我们还需要一支画笔
         Paint paint=new Paint();
         paint.setColor(Color.RED);
         //用画笔paint画一个圆心坐标为(400,500)的半径为100的圆
         canvas.drawCircle(400,500,100,paint);
     }
 }
 ​

image-20220706102126176

为什么运行加载,画布上就出现了一个红色的圆呢?因为在加载CircleView的时候会自动执行onDraw()方法且默认只执行一次;

为了不让一运行就出现圆,而是通过我们的点击事件出现圆,我们定义一个布尔值变量,在MainActivity中通过按钮的点击事件添加圆

ad1

5.实现画多个圆

画多个圆就需要将圆保存在一个列表List或者是数组中,然后再到onDraw()方法中依次渲染这些圆到view布局上

除此之外,我们生成的圆对象的颜色和位置应该是随机的,而且圆的位置不应该超出画布的范围

因此我们创建多个类,分别来实现这些功能

5.1圆对象

 static class Circle{
         float x,y;
         final float RADIUS=120;
         final int color;
 ​
         public Circle(float x, float y,int color) {
             this.x = x;
             this.y = y;
             this.color=color;
         }
     }

5.2随机颜色

Color.argb(200,r,g,b)是返回是一个代表颜色的整数值,argb()里的四个参数就是a(透明度),r,g,b(红,绿,蓝三原色)的值

 public static int getRandomColor(){
         Random random=new Random();
         int r=random.nextInt(256);
         int g=random.nextInt(256);
         int b=random.nextInt(256);
         return Color.argb(200,r,g,b);
     }

5.3随机位置添加一个圆

 /*
     * 添加一个圆
     * */
     public void addCircle(){
         //在view的宽高范围内,随机生成一个坐标
         float x=(float) (Math.random()*viewWidth);
         float y=(float) (Math.random()*viewHeight);
         //约束条件,不让圆超出画布范围
         //宽度约束
         if(x<120f){
             x=120f;
         }else if(x>viewWidth-120f){
             x=viewWidth-120f;
         }
         //高度约束
         if(y<120f){
             y=120f;
         }else if(y>viewHeight-120f){
             y=viewHeight-120f;
         }
         //将圆添加到列表中
         CircleView.Circle circle=new CircleView.Circle(x,y,CircleView.getRandomColor());
         CircleView.circleList.add(circle);
     }

5.4移除与清空

     public void removeCircle(){
         if(!circleList.isEmpty()){
             circleList.remove(circleList.size()-1);
         }
     }
     public void clearCircle(){
         circleList.clear();
     }

5.5在Activity中监听点击事件

注意最后一行代码:在子线程中重新执行一次 circleView.postInvalidate();因为我们之前说过自定义的的CircleView类中的onDraw()方法在加载这个控件时只会执行一次,而我们每一次点击都要重写渲染view,所以需要在每一次用户点击操作后在子线程中重新渲染view控件,这样才能让用户看到实时的效果

 public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 ​
     private CircleView circleView;
     @Override
     protected void onCreate(Bundle savedInstanceState){
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         findViewById(R.id.addBtn).setOnClickListener(this);
         findViewById(R.id.moveBtn).setOnClickListener(this);
         findViewById(R.id.clearBtn).setOnClickListener(this);
 ​
         circleView=findViewById(R.id.view);
     }
     @Override
     public void onClick(View view) {
         if(view.getId()==R.id.addBtn){
             Toast.makeText(MainActivity.this,"add",Toast.LENGTH_SHORT).show();
             circleView.addCircle();
         }else if(view.getId()==R.id.clearBtn){
             Toast.makeText(MainActivity.this,"clear",Toast.LENGTH_SHORT).show();
             circleView.clearCircle();
         }else if(view.getId()==R.id.moveBtn){
             Toast.makeText(MainActivity.this,"Move",Toast.LENGTH_SHORT).show();
             circleView.removeCircle();
         }
         //在子线程中重新执行一次
         circleView.postInvalidate();
     }
 }

6.效果展示

ad2

7.完整代码

7.1MainActivity

 package com.hnucm.ad0301;
 ​
 import androidx.appcompat.app.AppCompatActivity;
 ​
 import android.os.Bundle;
 import android.view.View;
 import android.widget.Toast;
 ​
 ​
 public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 ​
     private CircleView circleView;
     @Override
     protected void onCreate(Bundle savedInstanceState){
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         findViewById(R.id.addBtn).setOnClickListener(this);
         findViewById(R.id.moveBtn).setOnClickListener(this);
         findViewById(R.id.clearBtn).setOnClickListener(this);
 ​
         circleView=findViewById(R.id.view);
     }
     @Override
     public void onClick(View view) {
         if(view.getId()==R.id.addBtn){
             Toast.makeText(MainActivity.this,"add",Toast.LENGTH_SHORT).show();
             circleView.addCircle();
         }else if(view.getId()==R.id.clearBtn){
             Toast.makeText(MainActivity.this,"clear",Toast.LENGTH_SHORT).show();
             circleView.clearCircle();
         }else if(view.getId()==R.id.moveBtn){
             Toast.makeText(MainActivity.this,"Move",Toast.LENGTH_SHORT).show();
             circleView.removeCircle();
         }
         //在子线程中重新执行一次
         circleView.postInvalidate();
     }
 }

7.2CircleView

 package com.hnucm.ad0301;
 ​
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.util.AttributeSet;
 import android.view.View;
 ​
 import androidx.annotation.Nullable;
 ​
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 ​
 public class CircleView extends View {
 ​
     public static List<Circle>circleList=new ArrayList<>();
     public float viewWidth;
     public float viewHeight;
 ​
     public CircleView(Context context) {
         super(context);
     }
 ​
     public CircleView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
     }
 ​
     @Override
     protected void onDraw(Canvas canvas){
         super.onDraw(canvas);
 ​
         //获取画布的宽高
         viewWidth=canvas.getWidth();
         viewHeight=canvas.getHeight();
         //设置画布的背景色为黑色
         this.setBackgroundColor(Color.BLACK);
         //有了画布canvas,我们还需要一支画笔
         Paint paint=new Paint();
 ​
         //遍历列表画圆
         for(int i=0;i<circleList.size();++i){
 ​
             Circle circle=circleList.get(i);
             paint.setColor(circle.color);
             canvas.drawCircle(circle.x,circle.y,circle.RADIUS,paint);
         }
     }
 ​
     /*
     * 添加一个圆
     * */
     public void addCircle(){
         //在view的宽高范围内,随机生成一个坐标
         float x=(float) (Math.random()*viewWidth);
         float y=(float) (Math.random()*viewHeight);
         //约束条件,不让圆超出画布范围
         //宽度约束
         if(x<120f){
             x=120f;
         }else if(x>viewWidth-120f){
             x=viewWidth-120f;
         }
         //高度约束
         if(y<120f){
             y=120f;
         }else if(y>viewHeight-120f){
             y=viewHeight-120f;
         }
         //将圆添加到列表中
         CircleView.Circle circle=new CircleView.Circle(x,y,CircleView.getRandomColor());
         CircleView.circleList.add(circle);
     }
 ​
     public void removeCircle(){
         if(!circleList.isEmpty()){
             circleList.remove(circleList.size()-1);
         }
     }
     public void clearCircle(){
         circleList.clear();
     }
     static class Circle{
         float x,y;
         final float RADIUS=120;
         final int color;
 ​
         public Circle(float x, float y,int color) {
             this.x = x;
             this.y = y;
             this.color=color;
         }
     }
 ​
     public static int getRandomColor(){
         Random random=new Random();
         int r=random.nextInt(256);
         int g=random.nextInt(256);
         int b=random.nextInt(256);
         return Color.argb(200,r,g,b);
     }
 }
 ​

7.3activity_main.xml

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">
 ​
     <com.hnucm.ad0301.CircleView
     android:id="@+id/view"
     android:layout_width="match_parent"
     android:layout_height="500dp"
     app:layout_constraintEnd_toEndOf="parent"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintTop_toTopOf="parent" />
 ​
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_marginTop="10dp"
         android:layout_marginBottom="10dp"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/view">
 ​
         <Button
             android:id="@+id/moveBtn"
             android:layout_width="301dp"
             android:layout_height="59dp"
             android:text="move"
             app:layout_constraintBottom_toTopOf="@+id/clearBtn"
             app:layout_constraintStart_toStartOf="@+id/addBtn"
             app:layout_constraintTop_toBottomOf="@+id/addBtn" />
 ​
         <Button
             android:id="@+id/addBtn"
             android:layout_width="300dp"
             android:layout_height="60dp"
             android:text="add"
             app:layout_constraintBottom_toTopOf="@+id/moveBtn"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
 ​
         <Button
             android:id="@+id/clearBtn"
             android:layout_width="300dp"
             android:layout_height="60dp"
             android:text="clear"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="@+id/moveBtn"
             app:layout_constraintTop_toBottomOf="@+id/moveBtn" />
     </androidx.constraintlayout.widget.ConstraintLayout>
 ​
 </androidx.constraintlayout.widget.ConstraintLayout>