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<>();}@Overrideprotected 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;}@Overrideprotected 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() {@Overridepublic void run() {invalidate();}};/*** 向View添加下落物体对象** @param fallObject 下落物体对象* @param num*/public void addFallObject(final FallObject fallObject, final int num) {getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {@Overridepublic 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;//π/2public 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.FallingViewandroid:id="@+id/fallingView"android:layout_width="match_parent"android:layout_height="match_parent" />

代码设置动画属性并显示

//初始化一个雪花样式的fallObjectFallObject.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网页

<WebViewandroid: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.htmlwv.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"><WebViewandroid:id="@+id/wv"android:layout_width="match_parent"android:layout_height="240dp" /><com.shanjing.snowflake.FallingViewandroid:id="@+id/fallingView"android:layout_width="match_parent"android:layout_height="match_parent" /><androidx.coordinatorlayout.widget.CoordinatorLayoutandroid: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;@Overrideprotected 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.htmlwv.loadUrl("file:///android_asset/index.html");//初始化一个雪花样式的fallObjectFallObject.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

Android下雪动画 VS JS下雪动画相关推荐

  1. 【译】CSS动画 vs JS动画

    原文地址 目前有两个主流的方法在web上创建动画:使用CSS或JS.到底选择哪种方法来实现动画,完全取决于你的项目以及你想要达到的效果. tips: 对于简单的只出现一次的过渡效果,可以采用CSS动画 ...

  2. c android显示gif动画,MFC显示GIF动画图片

    本帖则将讨论如何在MFC的对话框里显示GIF动画图片.一些关于传统控件的美化方法正在研究当中会陆续发帖的. 这是本帖用到的一个VS2008例程. 附件  GifPicture.rar (138.1 K ...

  3. Android动画之帧动画和补间动画

    Android系统提供三种动画:帧动画.补间动画和属性动画.这里先分析总结帧动画和补间动画. FrameAnimation 帧动画,通俗来说就是按照图片动作顺序依次播放来形成动画,创建帧动画可以用 x ...

  4. 【Android动画】之Tween动画 (渐变、缩放、位移、旋转)

    Android 平台提供了两类动画. 一类是Tween动画,就是对场景里的对象不断的进行图像变化来产生动画效果(旋转.平移.放缩和渐变). 第二类就是 Frame动画,即顺序的播放事先做好的图像,与g ...

  5. Android Property Animation属性动画:scale缩放动画(4)

     Android Property Animation属性动画:scale缩放动画(4) 和之前我写的附录文章1,2,3相似,本文将接着使用Android Property Animation属性 ...

  6. Android源码解析(一)动画篇-- Animator属性动画系统

    Android源码解析-动画篇 Android源码解析(一)动画篇-- Animator属性动画系统 Android源码解析(二)动画篇-- ObjectAnimator Android在3.0版本中 ...

  7. Android之帧动画与补间动画的使用

    前言 在日常开发中,我们有时候需要一些好看的动画效果,这时可以充分利用Android提供的这几种动画来实现, Android提供了3种类型的动画: 补间动画:补间动画可以应用于View,让你可以定义一 ...

  8. js运动动画的八个知识点

    今天简单的学了一下js运动动画,记录一下自己的心得体会,分享给大家. 下面是我整理出来的结果. 知识点一:速度动画. 1.首先第一步实现速度运动动画,封装一个函数,用到的知识是setInterval( ...

  9. Android自定义控件:动画类---逐帧动画AnimationDrawable

    1:概述 Android动画包括View Animation(视图动画)和Property Animator(属性动画),而View Animation包括Tween Animation(补间动画)和 ...

最新文章

  1. 开启Mac充电提示音
  2. 软件开发心得点滴记录
  3. dart - 如何制作新数组嵌套排序映射
  4. c语言realloc函数中写啥,求大神解惑realloc函数,谢谢!
  5. 正文获取摘要 去除html标记
  6. MyEclipse移动开发教程:迁移HTML5移动项目到PhoneGap(二)
  7. VBA之六--EXCEL VBA两则
  8. OpenCV学习笔记(八):形态学morpholgy(2):开/闭运算,形态学梯度、顶帽/黑帽morphologyEx()
  9. 平板电脑有什么用_除了盖泡面,平板电脑没什么用了
  10. Github | TensorFlow Extended (TFX) 面向机器学习工具
  11. 【C++】判断两个vector是否相等~直接用“==”呀~
  12. 如何查询Opencv的版本
  13. taxi计费器c语言程序,基于单片机的出租车计费器的设计(附实物图,原理图,程序)...
  14. 数据时代,大数据未来的发展趋势主要有哪些?
  15. Android相对布局简单案例(附完整源码)
  16. excel如何把多张表合并成一个表_excel如何快速把多个工作表合并到一个excel表
  17. Juniper SRX密码恢复
  18. 【Matplotlib设置】Python绘图全局字体改为 Times New Roman
  19. python温度转换代码
  20. 屠蛟之路_重伤的屠蛟俊_ThirdDay

热门文章

  1. 信用评分模型开发(FICO评分)
  2. 数字化转型:玫琳凯推出沉浸式虚拟体验平台Suite 13TM
  3. Oracle 关于Pooled connection request timed out
  4. hdu6525——Subway Chasing(差分约束系统)
  5. Mysql explain 中的extra字段 解读
  6. 《京韵大鼓——贺新春(1995年文化部春晚)》(骆玉笙)(唱词文本)
  7. 电汇Swift Code汇总
  8. meshgrid用法
  9. 跨境电商的斋月狂欢即将带来,卖家如何疯狂飚单?
  10. solrJ6.5.1 使用