学习安卓笔记之自定义控件(四)
------使用SurfaceView进行简单的绘制
虽然没赶上移动开发最好的时候,也没赶上最好找工作的时候,但是赶上了学习移动开发最好的时候。资料很多,大部分常见的都能搜索出一大把,当然了“下雪花动画的效果 的示例也是非常的多。今天我也来仿造一个,既然是仿造,那就不能白写,不研究一下原理就没什么意义了。网上实现这个功能方法有很多种,比如在自定义控件中开启线程然后不停的绘制,或者直接SurfaceView去实现这种效果。虽然实现的方式不一样,但是实现的思路是一样的。先看一下将要实现的效果,这里没用到所谓的雪花(其实就是图片,很不幸我手上没有,我干脆就直接画圆圈)。
大致实现思路
- 准备数据模型:其实就是一个类,用于存放要绘制图形的位置、大小和下降偏移量的值。
- 创建类并继承SurfaceView:“雪花”的降落效果就是在这个类中实现的。
- 声明并初始化与绘制相关的值:就是初始化数据模型。
- 绘制图形:在线程中完成界面的刷新,达到不断降落的效果。
创建数据模型
就是创建一个类用于存放要绘制的圆的一些属性,代码如下:
package lyan.downsurfaceview.view2;
/**
* 圆圈的数据模型
* Author LYJ
* Created on 2016/9/20.
* Time 13:23
*/
public class MyCircle {
private float centerX;//坐标X
private float centerY;//坐标Y
private float radius;//半径
private float speed;//下落距离
public MyCircle setSpeed(float speed) {
this.speed = speed;
return this;
}
public MyCircle setCenterX(float centerX) {
this.centerX = centerX;
return this;
}
public MyCircle setCenterY(float centerY) {
this.centerY = centerY;
return this;
}
public MyCircle setRadius(float radius) {
this.radius = radius;
return this;
}
public float getCenterX() {
return centerX;
}
public float getCenterY() {
return centerY;
}
public float getRadius() {
return radius;
}
public float getSpeed() {
return speed;
}
}
创建MySurfaceView
我们想要的效果都会在这里去实现。这次我先不把整个类贴出来,我先贴出一些看起来比较干净一些的代码,看看我们创建的类都用到了什么,也就是实现功能的主体:
package lyan.downsurfaceview.view1;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* Author LYJ
* Created on 2016/9/20.
* Time 13:23
*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
/**
* 构造方法
* @param context
*/
public MySurfaceView(Context context) {
super(context);
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* Surface的回调
* @param surfaceHolder
*/
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
/**
* 线程
*/
@Override
public void run() {
}
}
继承SurfaceView之后的构造方法就不讲了,这是必须得有的。从实现的接口来看,我们这里继承了两个接口一个是Runalbe,一个是SurfaceHolder.Callback接口。
Runable很好理解,我先说明一下,为什么这里用到了线程,我们来看一下上面给出的效果图,图中可以看出这些圆圈是不断降落的,这种视觉效果就是通过线程去实现的。实现过程就是循环操作(canvas.drawCircle -> Thread.sleep -> 更改圆圈的纵坐标值)来达到降落动画的效果。
讲SurfaceHolder.Callback接口之前,我先说明一下我对SurfaceView,Surface,SurfaceHolder之间关系的理解,就是当创建SufaceView后SurfaceView内容同时创建了Surface(这是一个常量,它只被创建一次),在Surface第一次创建之后会调用SurfaceHolder.Callback回调接口中的surfaceCreated方法,在该方法中开启线程刷新界面来实现动画效果。(这里可能有误,如果有错的地方请提醒我,我会及时的改正,由于我的studio最近关联不到源码,所以参考了一些资料理解可能有误)
总结一下它们在该示例中的作用:Runable的run方法就是用来刷新界面的,我们将会使用SurfaceHolder.Callback的surfaceCreated来开启线程。
MySurfaceView的具体实现
该类的具体实现如下所示,完全按照上面给出的说明和骨架实现的:
package lyan.downsurfaceview.view2;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import java.util.ArrayList;
import java.util.Random;
/**
* Author LYJ
* Created on 2016/9/19.
* Time 15:48
*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private Paint paint;//画笔
private int counts = 5;//画圆的次数数
private float screenWidth, screenHeight;//屏幕宽高
private float[] raidusArray;
private Thread thread;
private boolean isRunning;//判断图片是否在进行移动
private static Random random = new Random();//获取随机数
private ArrayList<MyCircle> circle_xxl = new ArrayList<>();//存圆的集合
private ArrayList<MyCircle> circle_xl = new ArrayList<>();
private ArrayList<MyCircle> circle_l = new ArrayList<>();
private ArrayList<MyCircle> circle_m = new ArrayList<>();
private ArrayList<MyCircle> circle_s = new ArrayList<>();
private SurfaceHolder holder;
private Canvas canvas = null;
private String theWhite = "#45F8E58D";
/**
* 构造方法
*
* @param context
*/
public MySurfaceView(Context context) {
this(context, null);
}
public MySurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
raidusArray = new float[]{
dip2px(context, 2 * 2),
dip2px(context, 4 * 2),
dip2px(context, 6 * 2),
dip2px(context, 8 * 2),
dip2px(context, 10 * 2)};//圆的半径
holder = getHolder();
holder.addCallback(this);
//设置底层绘制的surfaceview透明
setZOrderOnTop(true);
holder.setFormat(PixelFormat.TRANSPARENT);
init(context);
addRandomCircle();
}
/**
* 初始化园
*/
private void addRandomCircle() {
for (int i = 0; i < counts; i++) {
circle_xxl.add(new MyCircle()
.setCenterX(random.nextFloat() * screenWidth)
.setCenterY(random.nextFloat() * screenHeight)
.setRadius(raidusArray[4])
.setSpeed(10));
circle_xl.add(new MyCircle()
.setCenterX(random.nextFloat() * screenWidth)
.setCenterY(random.nextFloat() * screenHeight)
.setRadius(raidusArray[3])
.setSpeed(8));
circle_l.add(new MyCircle()
.setCenterX(random.nextFloat() * screenWidth)
.setCenterY(random.nextFloat() * screenHeight)
.setRadius(raidusArray[2])
.setSpeed(6));
circle_m.add(new MyCircle()
.setCenterX(random.nextFloat() * screenWidth)
.setCenterY(random.nextFloat() * screenHeight)
.setRadius(raidusArray[1])
.setSpeed(4));
circle_s.add(new MyCircle()
.setCenterX(random.nextFloat() * screenWidth)
.setCenterY(random.nextFloat() * screenHeight)
.setRadius(raidusArray[0])
.setSpeed(2));
}
}
/**
* 初始化
*
* @param context
*/
private void init(Context context) {
screenWidth = context.getResources().getDisplayMetrics().widthPixels;
screenHeight = context.getResources().getDisplayMetrics().heightPixels;
//Log.e("屏幕的宽和高","w-->" +screenWidth + "h-->" +screenHeight);
paint = new Paint();
paint.setColor(Color.parseColor(theWhite));
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
}
/**
* run(线程)
*/
@Override
public void run() {
while (isRunning) {
try {
canvas = holder.lockCanvas();//锁定画布
canvas.drawColor(Color.RED, PorterDuff.Mode.CLEAR);
if (canvas != null) {
//画圆
drawMyCircle(canvas);
//改变位置
for (int i = 0; i < counts; i++) {
CircleDrop(circle_xxl.get(i), 10);
CircleDrop(circle_xl.get(i), 8);
CircleDrop(circle_l.get(i), 6);
CircleDrop(circle_m.get(i), 4);
CircleDrop(circle_s.get(i), 2);
}
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 降落
*
* @param myCircle
*/
private void CircleDrop(MyCircle myCircle, int speed) {
if (myCircle.getCenterY() > screenHeight) {
myCircle.setCenterY(0);
}
myCircle.setCenterY(myCircle.getCenterY() + speed);
}
/**
* 画圆
*
* @param canvas
*/
private void drawMyCircle(Canvas canvas) {
for (int i = 0; i < counts; i++) {
canvas.drawCircle(circle_s.get(i).getCenterX(),
circle_s.get(i).getCenterY(), circle_s.get(i).getRadius(), paint);
canvas.drawCircle(circle_m.get(i).getCenterX(),
circle_m.get(i).getCenterY(), circle_m.get(i).getRadius(), paint);
canvas.drawCircle(circle_l.get(i).getCenterX(),
circle_l.get(i).getCenterY(), circle_l.get(i).getRadius(), paint);
canvas.drawCircle(circle_xl.get(i).getCenterX(),
circle_xl.get(i).getCenterY(), circle_xl.get(i).getRadius(), paint);
canvas.drawCircle(circle_xxl.get(i).getCenterX(),
circle_xxl.get(i).getCenterY(), circle_xxl.get(i).getRadius(), paint);
}
}
/**
* callback(回调)
*
* @param holder
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new Thread(this);
thread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
isRunning = (visibility == VISIBLE);
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
检验下成果
先来看一下布局,布局很简单:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0c244e"
tools:context="lyan.downsurfaceview.MainActivity">
<lyan.downsurfaceview.view2.MySurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
【运行一下,看看效果】:
增加背景渐变效果
看着上面的图有点单调,接下来再来用属性动画加个背景渐变的效果,MainActivity中的代码示例如下:
package lyan.downsurfaceview;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = getLayoutInflater().inflate(R.layout.activity_main,null);
setContentView(view);
//属性动画
ObjectAnimator parentAnimator = ObjectAnimator.ofInt(
view,"backgroundColor",
Color.parseColor("#ffb300"),
Color.parseColor("#0000ff"));
parentAnimator.setEvaluator(new ArgbEvaluator());
parentAnimator.setRepeatCount(-1);//播放次数:循环
parentAnimator.setRepeatMode(2);//播放模式:循环播放时不是从头开始,从结尾开始
parentAnimator.setDuration(5000);//播放时间5000ms
parentAnimator.start();//播放动画
}
}
【点击运行,看一下运行效果】:
总结
我们不是要模拟下雪吗?怎嘛一个雪花都没有。很简单将圆圈换成雪花图片就可以了。
参考资料
- http://www.tuicool.com/articles/VbeAbuJ
- http://blog.csdn.net/luoshengyang/article/details/8661317/
- http://www.360doc.com/content/13/0927/09/7044580_317380131.shtml
- http://blog.csdn.net/zhaoyw2008/article/details/45825069
- http://www.cnblogs.com/bausch/archive/2011/10/20/2219068.html
- http://blog.csdn.net/jackie03/article/details/37922409