Skip to content

1.前言

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

2.xml布局设计

首先我们创建一个空项目,简单的来实现这个xml文件布局 布局主要分为两个部分,上大半部分是我们用来画图的view布局,我们的图案最终会渲染到这个部分。 下半部分是一个嵌套的约束布局,布局中放置三个button,分别来实现在view布局中添加一个圆,移除一个圆以及清空所有的圆

activity_main.xml源代码

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点击事件

java
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画布

我们需要自定义一个画布类 用自定义的CircleView继承View父类替换View控件 View类中有onDraw()方法,我们通过重写此方式实现在view布局上作画

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

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

java
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);
    }
}

为什么运行加载,画布上就出现了一个红色的圆呢?因为在加载CircleView的时候会自动执行onDraw()方法且默认只执行一次; 为了不让一运行就出现圆,而是通过我们的点击事件出现圆,我们定义一个布尔值变量,在MainActivity中通过按钮的点击事件添加圆

5.实现画多个圆

画多个圆就需要将圆保存在一个列表List或者是数组中,然后再到onDraw()方法中依次渲染这些圆到view布局上 除此之外,我们生成的圆对象的颜色和位置应该是随机的,而且圆的位置不应该超出画布的范围 因此我们创建多个类,分别来实现这些功能

5.1圆对象

java
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(红,绿,蓝三原色)的值

java
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随机位置添加一个圆

java
/*
    * 添加一个圆
    * */
    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移除与清空

java
	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控件,这样才能让用户看到实时的效果

java
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.效果展示

7.完整代码

7.1MainActivity

java
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

java
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
<?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>