Android下雪动画 VS JS下雪动画
16lz
2021-01-23
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 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; }}
布局中引用自定义视图
代码设置动画属性并显示
//初始化一个雪花样式的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
原生js实现喜庆背景带炫酷雪花飘落动画特效代码 距离新年倒计时
00天 00时 00分 00秒
布局中使用WebView展示html网页
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"?>
完整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