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);//添加下落物体对象
}
}