1.前言
今天我们来实现一个好玩的小功能,自定义一个view控件在随机位置画一个随机颜色的圆圈,并实现点击事件监听移除与清空功能 首先来康康效果吧
2.xml布局设计
首先我们创建一个空项目,简单的来实现这个xml文件布局 布局主要分为两个部分,上大半部分是我们用来画图的view布局,我们的图案最终会渲染到这个部分。 下半部分是一个嵌套的约束布局,布局中放置三个button,分别来实现在view布局中添加一个圆,移除一个圆以及清空所有的圆
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点击事件
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()
//有了画布canvas,我们还需要一支画笔
Paint paint=new Paint();
paint.setColor(Color.RED);
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圆对象
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.效果展示
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>