使用SurfaceView模拟下雪花的动画

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

------使用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();//播放动画
    }
}

  【点击运行,看一下运行效果】:
背景渐变


总结

  我们不是要模拟下雪吗?怎嘛一个雪花都没有。很简单将圆圈换成雪花图片就可以了。


参考资料

发布了41 篇原创文章 · 获赞 18 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/baidu_32377671/article/details/52593878