使用SurfaceView模拟下雪花的动画

来源:互联网 发布:2017中国经济数据 编辑:程序博客网 时间:2024/05/20 09:11

学习安卓笔记之自定义控件(四)

——使用SurfaceView进行简单的绘制


  虽然没赶上移动开发最好的时候,也没赶上最好找工作的时候,但是赶上了学习移动开发最好的时候。资料很多,大部分常见的都能搜索出一大把,当然了“下雪花动画的效果 的示例也是非常的多。今天我也来仿造一个,既然是仿造,那就不能白写,不研究一下原理就没什么意义了。网上实现这个功能方法有很多种,比如在自定义控件中开启线程然后不停的绘制,或者直接SurfaceView去实现这种效果。虽然实现的方式不一样,但是实现的思路是一样的。先看一下将要实现的效果,这里没用到所谓的雪花(其实就是图片,很不幸我手上没有,我干脆就直接画圆圈)。

想要实现的效果


大致实现思路

  1. 准备数据模型:其实就是一个类,用于存放要绘制图形的位置、大小和下降偏移量的值。
  2. 创建类并继承SurfaceView:“雪花”的降落效果就是在这个类中实现的。
  3. 声明并初始化与绘制相关的值:就是初始化数据模型。
  4. 绘制图形:在线程中完成界面的刷新,达到不断降落的效果。

创建数据模型

  就是创建一个类用于存放要绘制的圆的一些属性,代码如下:

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
2 0
原创粉丝点击