Android下雪动画 VS JS下雪动画

Android下雪动画的实现

自定义View

package com.shanjing.snowflake;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;

import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;

public class FallingView extends View {

    private Context mContext;
    private AttributeSet mAttrs;

    private List<FallObject> fallObjects;

    private int viewWidth;
    private int viewHeight;

    private static final int defaultWidth = 600;//默认宽度
    private static final int defaultHeight = 1000;//默认高度
    private static final int intervalTime = 5;//重绘间隔时间

    public FallingView(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public FallingView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mAttrs = attrs;
        init();
    }

    private void init() {
        fallObjects = new ArrayList<>();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = measureSize(defaultHeight, heightMeasureSpec);
        int width = measureSize(defaultWidth, widthMeasureSpec);
        setMeasuredDimension(width, height);

        viewWidth = width;
        viewHeight = height;
    }

    private int measureSize(int defaultSize, int measureSpec) {
        int result = defaultSize;
        int specMode = View.MeasureSpec.getMode(measureSpec);
        int specSize = View.MeasureSpec.getSize(measureSpec);

        if (specMode == View.MeasureSpec.EXACTLY) {
            result = specSize;
        } else if (specMode == View.MeasureSpec.AT_MOST) {
            result = Math.min(result, specSize);
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (fallObjects.size() > 0) {
            for (int i = 0; i < fallObjects.size(); i++) {
                //然后进行绘制
                fallObjects.get(i).drawObject(canvas);
            }
            // 隔一段时间重绘一次, 动画效果
            getHandler().postDelayed(runnable, intervalTime);
        }
    }

    // 重绘线程
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            invalidate();
        }
    };

    /**
     * 向View添加下落物体对象
     *
     * @param fallObject 下落物体对象
     * @param num
     */
    public void addFallObject(final FallObject fallObject, final int num) {
        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                getViewTreeObserver().removeOnPreDrawListener(this);
                for (int i = 0; i < num; i++) {
                    FallObject newFallObject = new FallObject(fallObject.builder, viewWidth, viewHeight);
                    fallObjects.add(newFallObject);
                }
                invalidate();
                return true;
            }
        });
    }
}
package com.shanjing.snowflake;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;

import java.util.Random;

public class FallObject {
    private int initX;
    private int initY;
    private Random random;
    private int parentWidth;//父容器宽度
    private int parentHeight;//父容器高度
    private float objectWidth;//下落物体宽度
    private float objectHeight;//下落物体高度

    public int initSpeed;//初始下降速度
    public int initWindLevel;//初始风力等级

    public float presentX;//当前位置X坐标
    public float presentY;//当前位置Y坐标
    public float presentSpeed;//当前下降速度
    private float angle;//物体下落角度

    private Bitmap bitmap;
    public Builder builder;

    private boolean isSpeedRandom;//物体初始下降速度比例是否随机
    private boolean isSizeRandom;//物体初始大小比例是否随机
    private boolean isWindRandom;//物体初始风向和风力大小比例是否随机
    private boolean isWindChange;//物体下落过程中风向和风力是否产生随机变化

    private static final int defaultSpeed = 10;//默认下降速度
    private static final int defaultWindLevel = 0;//默认风力等级
    private static final int defaultWindSpeed = 10;//默认单位风速
    private static final float HALF_PI = (float) Math.PI / 2;//π/2

    public FallObject(Builder builder, int parentWidth, int parentHeight) {
        random = new Random();
        this.parentWidth = parentWidth;
        this.parentHeight = parentHeight;
        initX = random.nextInt(parentWidth);
        initY = random.nextInt(parentHeight) - parentHeight;
        presentX = initX;
        presentY = initY;

        this.builder = builder;
        isSpeedRandom = builder.isSpeedRandom;
        isSizeRandom = builder.isSizeRandom;
        isWindRandom = builder.isWindRandom;
        isWindChange = builder.isWindChange;

        initSpeed = builder.initSpeed;
        randomSpeed();
        randomSize();
        randomWind();
    }

    private FallObject(Builder builder) {
        this.builder = builder;
        initSpeed = builder.initSpeed;
        bitmap = builder.bitmap;

        isSpeedRandom = builder.isSpeedRandom;
        isSizeRandom = builder.isSizeRandom;
        isWindRandom = builder.isWindRandom;
        isWindChange = builder.isWindChange;
    }

    public static final class Builder {
        private int initSpeed;
        private int initWindLevel;
        private Bitmap bitmap;

        private boolean isSpeedRandom;
        private boolean isSizeRandom;
        private boolean isWindRandom;
        private boolean isWindChange;

        public Builder(Bitmap bitmap) {
            this.initSpeed = defaultSpeed;
            this.initWindLevel = defaultWindLevel;
            this.bitmap = bitmap;

            this.isSpeedRandom = false;
            this.isSizeRandom = false;
            this.isWindRandom = false;
            this.isWindChange = false;
        }

        public Builder(Drawable drawable) {
            this.initSpeed = defaultSpeed;
            this.initWindLevel = defaultWindLevel;
            this.bitmap = drawableToBitmap(drawable);

            this.isSpeedRandom = false;
            this.isSizeRandom = false;
            this.isWindRandom = false;
            this.isWindChange = false;
        }

        /**
         * 设置物体的初始下落速度
         *
         * @param speed
         * @return
         */
        public Builder setSpeed(int speed) {
            this.initSpeed = speed;
            return this;
        }

        /**
         * 设置物体的初始下落速度
         *
         * @param speed
         * @param isRandomSpeed 物体初始下降速度比例是否随机
         * @return
         */
        public Builder setSpeed(int speed, boolean isRandomSpeed) {
            this.initSpeed = speed;
            this.isSpeedRandom = isRandomSpeed;
            return this;
        }

        /**
         * 设置物体大小
         *
         * @param w
         * @param h
         * @return
         */
        public Builder setSize(int w, int h) {
            this.bitmap = changeBitmapSize(this.bitmap, w, h);
            return this;
        }

        /**
         * 设置物体大小
         *
         * @param w
         * @param h
         * @param isRandomSize 物体初始大小比例是否随机
         * @return
         */
        public Builder setSize(int w, int h, boolean isRandomSize) {
            this.bitmap = changeBitmapSize(this.bitmap, w, h);
            this.isSizeRandom = isRandomSize;
            return this;
        }

        /**
         * 设置风力等级、方向以及随机因素
         *
         * @param level        风力等级(绝对值为 5 时效果会比较好),为正时风从左向右吹(物体向X轴正方向偏移),为负时则相反
         * @param isWindRandom 物体初始风向和风力大小比例是否随机
         * @param isWindChange 在物体下落过程中风的风向和风力是否会产生随机变化
         * @return
         */
        public Builder setWind(int level, boolean isWindRandom, boolean isWindChange) {
            this.initWindLevel = level;
            this.isWindRandom = isWindRandom;
            this.isWindChange = isWindChange;
            return this;
        }

        public FallObject build() {
            return new FallObject(this);
        }
    }

    /**
     * 绘制物体对象
     *
     * @param canvas
     */
    public void drawObject(Canvas canvas) {
        moveObject();
        canvas.drawBitmap(bitmap, presentX, presentY, null);
    }

    /**
     * 移动物体对象
     */
    private void moveObject() {
        moveX();
        moveY();
        if (presentY > parentHeight || presentX < -bitmap.getWidth() || presentX > parentWidth + bitmap.getWidth()) {
            reset();
        }
    }

    /**
     * X轴上的移动逻辑
     */
    private void moveX() {
        presentX += defaultWindSpeed * Math.sin(angle);
        if (isWindChange) {
            angle += (float) (random.nextBoolean() ? -1 : 1) * Math.random() * 0.0025;
        }
    }

    /**
     * Y轴上的移动逻辑
     */
    private void moveY() {
        presentY += presentSpeed;
    }

    /**
     * 重置object位置
     */
    private void reset() {
        presentY = -objectHeight;
        randomSpeed();//记得重置时速度也一起重置,这样效果会好很多
        randomWind();//记得重置一下初始角度,不然雪花会越下越少(因为角度累加会让雪花越下越偏)
    }

    /**
     * 随机物体初始下落速度
     */
    private void randomSpeed() {
        if (isSpeedRandom) {
            presentSpeed = (float) ((random.nextInt(3) + 1) * 0.1 + 1) * initSpeed;//这些随机数大家可以按自己的需要进行调整
        } else {
            presentSpeed = initSpeed;
        }
    }

    /**
     * 随机物体初始大小比例
     */
    private void randomSize() {
        if (isSizeRandom) {
            float r = (random.nextInt(10) + 1) * 0.1f;
            float rW = r * builder.bitmap.getWidth();
            float rH = r * builder.bitmap.getHeight();
            bitmap = changeBitmapSize(builder.bitmap, (int) rW, (int) rH);
        } else {
            bitmap = builder.bitmap;
        }
        objectWidth = bitmap.getWidth();
        objectHeight = bitmap.getHeight();
    }

    /**
     * 随机风的风向和风力大小比例,即随机物体初始下落角度
     */
    private void randomWind() {
        if (isWindRandom) {
            angle = (float) ((random.nextBoolean() ? -1 : 1) * Math.random() * initWindLevel / 50);
        } else {
            angle = (float) initWindLevel / 50;
        }

        //限制angle的最大最小值
        if (angle > HALF_PI) {
            angle = HALF_PI;
        } else if (angle < -HALF_PI) {
            angle = -HALF_PI;
        }
    }

    /**
     * drawable图片资源转bitmap
     *
     * @param drawable
     * @return
     */
    public static Bitmap drawableToBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(
                drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(),
                drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
                        : Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);
        return bitmap;
    }

    /**
     * 改变bitmap的大小
     *
     * @param bitmap 目标bitmap
     * @param newW   目标宽度
     * @param newH   目标高度
     * @return
     */
    public static Bitmap changeBitmapSize(Bitmap bitmap, int newW, int newH) {
        int oldW = bitmap.getWidth();
        int oldH = bitmap.getHeight();
        // 计算缩放比例
        float scaleWidth = ((float) newW) / oldW;
        float scaleHeight = ((float) newH) / oldH;
        // 取得想要缩放的matrix参数
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        // 得到新的图片
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, oldW, oldH, matrix, true);
        return bitmap;
    }
}

布局中引用自定义视图

<com.shanjing.snowflake.FallingView
        android:id="@+id/fallingView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

代码设置动画属性并显示

//初始化一个雪花样式的fallObject
        FallObject.Builder builder = new FallObject.Builder(getResources().getDrawable(R.drawable.snow_flake));
        FallObject fallObject = builder
                .setSpeed(6, true)
                .setSize(40, 40, true)
                .setWind(5, true, true)
                .build();

        fallingView = findViewById(R.id.fallingView);
        fallingView.addFallObject(fallObject, 100);//添加下落物体对象

原生JS实现(JS资源来自PHP中文网

assets/css/www.jsdaima.com.css

/*js代码(www.ph.cn)是IT资源下载与IT技能学习平台。我们拒绝滥竽充数,只提供精品IT资源!*/
:root {
  font-family: "Microsoft Yahei", sans-serif;
}

html,
body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  background: rgb(119, 13, 13);
  background: radial-gradient(
    circle,
    rgba(119, 13, 13, 0.92) 64%,
    rgba(0, 0, 0, 0.6) 100%
  );
}

canvas {
  width: 100%;
  height: 100%;
}


.label {
  font-size: 2.2rem;
  background: url("../img/6368077651977322227241996.png");
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  animation: moveBg 30s linear infinite;
}

@keyframes moveBg {
  0% {
    background-position: 0% 30%;
  }
  100% {
    background-position: 1000% 500%;
  }
}

.middle {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  user-select: none;
}

.time {
  color: #d99c3b;
  text-transform: uppercase;
  display: flex;
  justify-content: center;
}

.time span {
  padding: 0 14px;
  font-size: 0.8rem;
}

.time span div {
  font-size: 3rem;
}

@media (max-width: 740px) {
  .label {
    font-size: 1.7rem;
  }
  .time span {
    padding: 0 16px;
    font-size: 0.6rem;
  }
  .time span div {
    font-size: 2rem;
  }
}
/*Powered by www.php.cn*/

assets/index.html

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,user-scalable=no,minimal-ui">
    <title>原生js实现喜庆背景带炫酷雪花飘落动画特效代码</title>
    <meta name="keywords" content="原生js,喜庆背景,炫酷雪花,飘落动画,特效代码"/>
    <meta name="description"
          content="原生js实现喜庆背景带炫酷雪花飘落动画特效代码下载。基于原生JavaScript+CSS实现,不依靠任何第三方jQuery库,兼容手机移动端,新年倒计时自动获取,可循环使用,非常简单实用的一款新年倒计时js特效代码。"/>
    <meta name="author" content="js代码(www.jsdaima.com)"/>
    <meta name="copyright" content="js代码(www.jsdaima.com)"/>
    <link rel="stylesheet" type="text/css" href="css/www.jsdaima.com.css">
</head>
<body>
<div class="middle">
    <h1 class="label" id="newYear">距离新年倒计时</h1>
    <div class="time">
		<span>
		<div id="d">
			00
		</div>
		天 </span> <span>
		<div id="h">
			00
		</div>
		时 </span> <span>
		<div id="m">
			00
		</div>
		分 </span> <span>
		<div id="s">
			00
		</div>
		秒 </span>
    </div>
</div>

<script>
class Snowflake {
  constructor() {
    this.x = 0;
    this.y = 0;
    this.vx = 0;
    this.vy = 0;
    this.radius = 0;
    this.alpha = 0;

    this.reset();
  }

  reset() {
    this.x = this.randBetween(0, window.innerWidth);
    this.y = this.randBetween(0, -window.innerHeight);
    this.vx = this.randBetween(-3, 3);
    this.vy = this.randBetween(2, 5);
    this.radius = this.randBetween(1, 4);
    this.alpha = this.randBetween(0.1, 0.9);
  }

  randBetween(min, max) {
    return min + Math.random() * (max - min);
  }

  update() {
    this.x += this.vx;
    this.y += this.vy;

    if (this.y + this.radius > window.innerHeight) {
      this.reset();
    }
  }
}

class Snow {
  constructor() {
    this.canvas = document.createElement("canvas");
    this.ctx = this.canvas.getContext("2d");

    document.body.appendChild(this.canvas);

    window.addEventListener("resize", () => this.onResize());
    this.onResize();
    this.updateBound = this.update.bind(this);
    requestAnimationFrame(this.updateBound);

    this.createSnowflakes();
  }

  onResize() {
    this.width = window.innerWidth;
    this.height = window.innerHeight;
    this.canvas.width = this.width;
    this.canvas.height = this.height;
  }

  createSnowflakes() {
    const flakes = window.innerWidth / 4;

    this.snowflakes = [];

    for (let s = 0; s < flakes; s++) {
      this.snowflakes.push(new Snowflake());
    }
  }

  update() {
    this.ctx.clearRect(0, 0, this.width, this.height);

    for (let flake of this.snowflakes) {
      flake.update();

      this.ctx.save();
      this.ctx.fillStyle = "#FFF";
      this.ctx.beginPath();
      this.ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
      this.ctx.closePath();
      this.ctx.globalAlpha = flake.alpha;
      this.ctx.fill();
      this.ctx.restore();
    }
    requestAnimationFrame(this.updateBound);
  }
}

new Snow();




// Simple CountDown Clock
var date = new Date;
var year = date.getFullYear() + 1; 
var NY = document.getElementById("newYear");
NY.innerHTML = year+ " 年新年倒计时";
const comingdate = new Date("Jan 1, "+year+" 00:00:00");

const d = document.getElementById("d");
const h = document.getElementById("h");
const m = document.getElementById("m");
const s = document.getElementById("s");

const countdown = setInterval(() => {
  const now   = new Date();
  const des   = comingdate.getTime() - now.getTime();
  const days  = Math.floor(des / (1000 * 60 * 60 * 24));
  const hours = Math.floor((des % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  const mins  = Math.floor((des % (1000 * 60 * 60)) / (1000 * 60));
  const secs  = Math.floor((des % (1000 * 60)) / 1000);

  d.innerHTML = getTrueNumber(days);
  h.innerHTML = getTrueNumber(hours);
  m.innerHTML = getTrueNumber(mins);
  s.innerHTML = getTrueNumber(secs);

  if (x <= 0) clearInterval(x);
}, 1000);

const getTrueNumber = x => (x < 10 ? "0" + x : x);


</script>

</body>
</html>

布局中使用WebView展示html网页

<WebView
        android:id="@+id/wv"
        android:layout_width="match_parent"
        android:layout_height="240dp" />

java代码进行加载网页

wv = findViewById(R.id.wv);
        // 设置WebView属性,能够执行Javascript脚本
        wv.getSettings().setJavaScriptEnabled(true);
        //语言设置防止加载乱码
        wv.getSettings().setDefaultTextEncodingName("GBK");
        // 即asserts文件夹下有一个color2.html
        wv.loadUrl("file:///android_asset/index.html");

最后是沉浸状态栏

导入依赖库

//沉浸状态栏
    implementation 'com.jaeger.statusbarutil:library:1.5.1'

布局中需要使用CoordinatorLayout布局要不然沉浸不起作用,完整布局入下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:background="@drawable/bg"
    android:orientation="vertical">

    <WebView
        android:id="@+id/wv"
        android:layout_width="match_parent"
        android:layout_height="240dp" />

    <com.shanjing.snowflake.FallingView
        android:id="@+id/fallingView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/cl_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

完整Java代码如下:

package com.shanjing.snowflake;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;

import com.jaeger.library.StatusBarUtil;

public class MainActivity extends AppCompatActivity {

    private WebView wv;
    private View cl_view;
    private FallingView fallingView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        cl_view = findViewById(R.id.cl_view);
        StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, cl_view);

        wv = findViewById(R.id.wv);
        // 设置WebView属性,能够执行Javascript脚本
        wv.getSettings().setJavaScriptEnabled(true);
        //语言设置防止加载乱码
        wv.getSettings().setDefaultTextEncodingName("GBK");
        // 即asserts文件夹下有一个color2.html
        wv.loadUrl("file:///android_asset/index.html");


        //初始化一个雪花样式的fallObject
        FallObject.Builder builder = new FallObject.Builder(getResources().getDrawable(R.drawable.snow_flake));
        FallObject fallObject = builder
                .setSpeed(6, true)
                .setSize(40, 40, true)
                .setWind(5, true, true)
                .build();

        fallingView = findViewById(R.id.fallingView);
        fallingView.addFallObject(fallObject, 100);//添加下落物体对象
    }
}

Demo:https://github.com/cuiwenju2017/Snowflake

猜你喜欢

转载自blog.csdn.net/juer2017/article/details/101546213
今日推荐