今天来实现一个自定义气泡背景:先来张图(那些泡泡会跑的。。。)
看起来很高端?你要是这么想那你就输了,接下来就看看是怎么实现的。
这个就是一个自定义View,其实自定义View也就是重写那么几个方法,onDraw(),onMeasure()。还是直接来看代码吧,注视里面都有的
package com.tianzhao.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import java.util.Random;
/**
* Created by tianzhao on 2015/10/25.
* Date 21:38
*/
public class CircleView extends View {
private int circleSum;//圆的数量
private int circleRadio;//圆的半径
private int circleColor;//圆的颜色
private int backColor;//背景颜色
private Paint backPaint;//背景画笔
private Paint circlePaint;//圆的画笔
private Circle[] circles;
private int[] direction = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
private Random random;
private float width;
private float height;
private int[] location = new int[2];
private MyThread mMyThread;
private boolean running = true;
public CircleView(Context context) {
super(context);
backPaint = new Paint();
init();
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
circleSum = ta.getInteger(R.styleable.CircleView_circleSum, 10);
circleColor = ta.getColor(R.styleable.CircleView_circleColor, Color.parseColor("#7756ffff"));
backColor = ta.getColor(R.styleable.CircleView_backColor, Color.parseColor("#21efef"));
circleRadio = ta.getInteger(R.styleable.CircleView_circleRadio, 60);
ta.recycle();
init();
}
/**
* 初始化画笔
*/
private void init() {
backPaint = new Paint();
backPaint.setColor(backColor);
circlePaint = new Paint();
circlePaint.setColor(circleColor);
}
/**
* 比onDraw先执行
* <p/>
* 一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。
* 一个MeasureSpec由大小和模式组成
* 它有三种模式:UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
* EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
* AT_MOST(至多),子元素至多达到指定大小的值。
* <p/>
* 它常用的三个函数:
* 1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)
* 2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)
* 3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取控件宽度
width = getMeasuredWidth();
//获取控件高度
height = getMeasuredHeight();
//获取控件相对于父控件的位置坐标
this.getLocationInWindow(location);
//初始化圆
initCircle();
}
/**
* 初始化圆
*/
private void initCircle() {
circles = new Circle[circleSum];
random = new Random();
for (int i = 0; i < circleSum; i++) {
int d = random.nextInt(direction.length);
int x = random.nextInt((int) width);
int y = random.nextInt((int) height);
circles[i] = new Circle(x, y, d);
}
}
@Override
protected void onDraw(Canvas canvas) {
//绘制背景
canvas.drawRect(0, 0, location[0] + width, location[1] + height, backPaint);
//遍历绘制每一个圆
for (Circle c : circles) {
canvas.drawCircle(c.getX(), c.getY(), circleRadio, circlePaint);
}
if (mMyThread == null) {
mMyThread = new MyThread();
mMyThread.start();
}
}
/**
* 不在窗口显示的时候停止线程
*/
@Override
protected void onDetachedFromWindow() {
running = false;
super.onDetachedFromWindow();
}
class MyThread extends Thread {
@Override
public void run() {
while (running) {
for (int i = 0; i < circleSum; i++) {
Circle c = circles[i];
changeDirection(c);
//超出边界后重置方向
if (c.getX() > width || c.getX() < 0 || c.getY() > height || c.getY() < 0) {
int d;
while (true) {
d = random.nextInt(direction.length);
int lastD = c.getDirection();
if ((lastD == 0 || lastD == 4 || lastD == 6 || lastD == 8 || lastD == 10) && (d == 0 || d == 4 || d == 6 || d == 8 || d == 10)) {//上
continue;
} else if ((lastD == 1 || lastD == 5 || lastD == 7 || lastD == 9) && (d == 1 || d == 5 || d == 7 || d == 9)) {//下
continue;
} else if ((lastD == 2 || lastD == 4 || lastD == 5 || lastD == 8 || lastD == 9) && (d == 2 || d == 4 || d == 5 || d == 8 || d == 9)) {//左
continue;
} else if ((lastD == 3 || lastD == 6 || lastD == 7 || lastD == 10) && (d == 3 || d == 6 || d == 7 || d == 10)) {//右
continue;
}
break;
}
circles[i].setDirection(d);
}
}
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
postInvalidate();
}
}
private void changeDirection(Circle c) {
float dx = 0.3f;
switch (c.getDirection()) {
case 0://上
c.setY(c.getY() - dx);
break;
case 1://下
c.setY(c.getY() + dx);
break;
case 2://左
c.setX(c.getX() - dx);
break;
case 3://右
c.setX(c.getX() + dx);
break;
case 4://左上
c.setX(c.getX() - dx);
c.setY(c.getY() - dx);
break;
case 5://左下
c.setX(c.getX() - dx);
c.setY(c.getY() + dx);
break;
case 6://右上
c.setX(c.getX() + dx);
c.setY(c.getY() - dx);
break;
case 7://右下
c.setX(c.getX() + dx);
c.setY(c.getY() + dx);
break;
case 8://左上
c.setX(c.getX() - dx);
c.setY(c.getY() - dx * 2);
break;
case 9://左下
c.setX(c.getX() - dx * 2);
c.setY(c.getY() + dx);
break;
case 10://右上
c.setX(c.getX() + dx);
c.setY(c.getY() - dx * 2);
break;
}
}
}
class Circle {
private float x;
private float y;
private int direction;
public Circle(float x, float y, int d) {
this.x = x;
this.y = y;
this.direction = d;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public void setY(float y) {
this.y = y;
}
public float getY() {
return y;
}
public void setDirection(int direction) {
this.direction = direction;
}
public int getDirection() {
return direction;
}
}
}
来看看在xml文件中怎么使用
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.tianzhao.myapplication.CircleView
xmlns:cv="http://schemas.android.com/apk/res/com.tianzhao.myapplication" //自己的命名空间
android:layout_width="fill_parent"
android:layout_height="200dp"
cv:circleSum="10" //圆的数量
cv:circleColor="#7756ffff" //圆的颜色
cv:backColor="#21efef"/> //背景颜色
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="200dp">
<ImageView
android:id="@+id/iv"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:background="@drawable/user" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/iv"
android:layout_centerHorizontal="true"
android:layout_marginTop="5dp"
android:text="登录"
android:textSize="20dp" />
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="230dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="用户名:"
android:textSize="20dp" />
<EditText
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="280dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="密 码:"
android:textSize="20dp" />
<EditText
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:gravity="center_vertical" />
</LinearLayout>
</RelativeLayout>
这里会涉及到一个attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circleSum" format="integer"></attr>
<attr name="circleColor" format="color"></attr>
<attr name="backColor" format="color"></attr>
<attr name="circleRadio" format="integer"></attr>
</declare-styleable>
</resources>
当你看到这时,是不是感觉到不是很难,其实在我刚开始动手做的时候也很难的,但是当你开始做的时候,你就会觉得其实并没有多难,当然,这个CircleView还是有不少Bug的,我写这个就是为了了解一下View的绘制流程的,要是那路大神发现bug的话,给小弟提提。