大家要是看到有错误的地方或者有啥好的建议,欢迎留言评论

前言:转眼已是十一月下旬了,天气慢慢转冷,不知道北方是不是已经开始下雪了呢?本期教程我们就顺应季节主题,一起来实现 雪花飘落的效果吧。本篇效果思路参考自国外大神的Android实现雪花飞舞效果,并在此基础上实现进一步的封装和功能扩展

本篇只着重于思路和实现步骤,里面用到的一些知识原理不会非常细地拿来讲,如果有不清楚的api或方法可以在网上搜下相应的资料,肯定有大神讲得非常清楚的,我这就不献丑了。本着认真负责的精神我会把相关知识的博文链接也贴出来(其实就是懒不想写那么多哈哈),大家可以自行传送。为了照顾第一次阅读系列博客的小伙伴,本篇会出现一些在之前系列博客就讲过的内容,看过的童鞋自行跳过该段即可

国际惯例,先上效果图

目录

绘制一个循环下落的“雪球”

封装下落物体对象

扩展一:增加导入Drawable资源的构造方法和设置物体大小的接口

扩展一:扩展二:实现雪花“大小不一”、“快慢有别”的效果

扩展三:引入“风”的概念

绘制一个循环下落的“雪球”

我们先从最简单的部分做起,自定义View中实现循环动画的方法有很多,最简单直接的当然是用Animation类去实现,但考虑到无论是雪花、雪球亦或是雨滴什么的,每个独立的个体都有自己的起点、速度和方向等等,其下落的过程会出现很多随机的因素,实现这种非规律的动画Animation类就不怎么适用了,因此我们这次要利用线程通信实现一个简单的定时器,达到周期性绘制View的效果。这里我们简单绘制一个“雪球”(其实就是个白色背景的圆形哈哈)来看看定时器的效果,新建一个FallingView

public class FallingView extends View {

private Context mContext;

private AttributeSet mAttrs;

private int viewWidth;

private int viewHeight;

private static final int defaultWidth = 600;//默认宽度

private static final int defaultHeight = 1000;//默认高度

private static final int intervalTime = 5;//重绘间隔时间

private Paint testPaint;

private int snowY;

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(){

testPaint = new Paint();

testPaint.setColor(Color.WHITE);

testPaint.setStyle(Paint.Style.FILL);

snowY = 0;

}

@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);

canvas.drawCircle(100,snowY,25,testPaint);

getHandler().postDelayed(runnable, intervalTime);//间隔一段时间再进行重绘

}

// 重绘线程

private Runnable runnable = new Runnable() {

@Override

public void run() {

snowY += 15;

if(snowY>viewHeight){//超出屏幕则重置雪球位置

snowY = 0;

}

invalidate();

}

};

}

效果如图

在上述代码中View基本的框架我们已经搭好了,思路其实很简单,我们需要做仅仅是在每次重绘之前更新做下落运动的物体的位置即可

封装下落物体对象

要实现大雪纷飞的效果,很明显只有一个雪球是不够的,而且雪也不能只有雪球一个形状,我们希望可以自定义雪的样式,甚至不局限于下雪,还可以下雨、下金币等等,因此我们要对下落的物体进行封装。为了以后物体类对外方法代码的可读性,这里我们采用Builder设计模式来构建物体对象类,新建FallObject

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 float presentX;//当前位置X坐标

public float presentY;//当前位置Y坐标

public float presentSpeed;//当前下降速度

private Bitmap bitmap;

public Builder builder;

private static final int defaultSpeed = 10;//默认下降速度

public FallObject(Builder builder, int parentWidth, int parentHeight){

random = new Random();

this.parentWidth = parentWidth;

this.parentHeight = parentHeight;

initX = random.nextInt(parentWidth);//随机物体的X坐标

initY = random.nextInt(parentHeight)- parentHeight;//随机物体的Y坐标,并让物体一开始从屏幕顶部下落

presentX = initX;

presentY = initY;

initSpeed = builder.initSpeed;

presentSpeed = initSpeed;

bitmap = builder.bitmap;

objectWidth = bitmap.getWidth();

objectHeight = bitmap.getHeight();

}

private FallObject(Builder builder) {

this.builder = builder;

initSpeed = builder.initSpeed;

bitmap = builder.bitmap;

}

public static final class Builder {

private int initSpeed;

private Bitmap bitmap;

public Builder(Bitmap bitmap) {

this.initSpeed = defaultSpeed;

this.bitmap = bitmap;

}

/**

* 设置物体的初始下落速度

* @param speed

* @return

*/

public Builder setSpeed(int speed) {

this.initSpeed = speed;

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(){

moveY();

if(presentY>parentHeight){

reset();

}

}

/**

* Y轴上的移动逻辑

*/

private void moveY(){

presentY += presentSpeed;

}

/**

* 重置object位置

*/

private void reset(){

presentY = -objectHeight;

presentSpeed = initSpeed;

}

}

FallingView中相应地设置添加物体的方法

public class FallingView extends View {

//省略部分代码...

private List fallObjects;

private void init(){

fallObjects = new ArrayList<>();

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if(fallObjects.size()>0){

for (int i=0;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;

}

});

}

}

在Activity中向FallingView添加一些物体看看效果

//绘制雪球bitmap

snowPaint = new Paint();

snowPaint.setColor(Color.WHITE);

snowPaint.setStyle(Paint.Style.FILL);

bitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);

bitmapCanvas = new Canvas(bitmap);

bitmapCanvas.drawCircle(25,25,25,snowPaint);

//初始化一个雪球样式的fallObject

FallObject.Builder builder = new FallObject.Builder(bitmap);

FallObject fallObject = builder

.setSpeed(10)

.build();

fallingView = (FallingView) findViewById(R.id.fallingView);

fallingView.addFallObject(fallObject,50);//添加50个雪球对象

效果如图

到这里我们完成了一个最基础的下落物体类,下面开始扩展功能和效果

扩展一:增加导入Drawable资源的构造方法和设置物体大小的接口

我们之前的FallObject类中Builder只支持bitmap的导入,很多时候我们的图片样式都是从drawable资源文件夹中获取的,每次都要将drawable转成bitmap是件很麻烦的事,因此我们要在FallObject类中封装drawable资源导入的构造方法,修改FallObject

public static final class Builder {

//省略部分代码...

public Builder(Bitmap bitmap) {

this.initSpeed = defaultSpeed;

this.bitmap = bitmap;

}

public Builder(Drawable drawable) {

this.initSpeed = defaultSpeed;

this.bitmap = drawableToBitmap(drawable);

}

}

/**

* 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;

}

有了drawable资源导入的构造方法,肯定需要配套改变FallObject图片样式大小的接口,依然是在FallObject的Builder中扩展相应的接口

public static final class Builder {

//省略部分代码...

public Builder setSize(int w, int h){

this.bitmap = changeBitmapSize(this.bitmap,w,h);

return this;

}

}

/**

* 改变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;

}

在Activity中初始化下落物体样式时我们就可以导入drawable资源和设置物体大小了(图片资源我是在阿里图标库下载的)

FallObject.Builder builder = new FallObject.Builder(getResources().getDrawable(R.drawable.ic_snow));

FallObject fallObject = builder

.setSpeed(10)

.setSize(50,50)

.build();

来看下效果

扩展二:实现雪花“大小不一”、“快慢有别”的效果

之前我们通过导入drawable资源的方法让屏幕“下起了雪花”,但雪花个个都一样大小,下落速度也都完全一致,这显得十分的单调,看起来一点也不像现实中的下雪场景。因此我们需要利用随机数实现雪花“大小不一”、“快慢有别”的效果,修改FallObject

public class FallObject {

//省略部分代码...

private boolean isSpeedRandom;//物体初始下降速度比例是否随机

private boolean isSizeRandom;//物体初始大小比例是否随机

public FallObject(Builder builder, int parentWidth, int parentHeight){

//省略部分代码...

this.builder = builder;

isSpeedRandom = builder.isSpeedRandom;

isSizeRandom = builder.isSizeRandom;

initSpeed = builder.initSpeed;

randomSpeed();

randomSize();

}

private FallObject(Builder builder) {

//省略部分代码...

isSpeedRandom = builder.isSpeedRandom;

isSizeRandom = builder.isSizeRandom;

}

public static final class Builder {

//省略部分代码...

private boolean isSpeedRandom;

private boolean isSizeRandom;

public Builder(Bitmap bitmap) {

//省略部分代码...

this.isSpeedRandom = false;

this.isSizeRandom = false;

}

public Builder(Drawable drawable) {

//省略部分代码...

this.isSpeedRandom = false;

this.isSizeRandom = 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;

}

}

/**

* 重置object位置

*/

private void reset(){

presentY = -objectHeight;

randomSpeed();//记得重置时速度也一起重置,这样效果会好很多

}

/**

* 随机物体初始下落速度

*/

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();

}

}

在Activity中设置相应参数即可

FallObject.Builder builder = new FallObject.Builder(getResources().getDrawable(R.drawable.ic_snow));

FallObject fallObject = builder

.setSpeed(10,true)

.setSize(50,50,true)

.build();

效果如图,是不是看起来感觉好多了๑乛◡乛๑

扩展三:引入“风”的概念

“风”其实是一种比喻,实际上要做的是让雪花除了做下落运动外,还会横向移动,也就是说我们要模拟出雪花在风中乱舞的效果。为了让雪花在X轴上的位移不显得鬼畜(大家可以直接随机增减x坐标值就知道为什么是鬼畜了哈哈),我们采用正弦函数来获取X轴上的位移距离,如图所示

正弦函数曲线见下图

我们选取-π到π这段曲线,可以看出角的弧度在为π/2时正弦值最大(-π/2时最小),因此我们在计算角度时还需要考虑其极限值。同时,因为我们添加了横向的移动,所以判断边界时要记得判定最左和最右的边界,修改FallObject

public class FallObject {

//省略部分代码...

public int initSpeed;//初始下降速度

public int initWindLevel;//初始风力等级

private float angle;//物体下落角度

private boolean isWindRandom;//物体初始风向和风力大小比例是否随机

private boolean isWindChange;//物体下落过程中风向和风力是否产生随机变化

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){

//省略部分代码...

isWindRandom = builder.isWindRandom;

isWindChange = builder.isWindChange;

initSpeed = builder.initSpeed;

randomSpeed();

randomSize();

randomWind();

}

private FallObject(Builder builder) {

//省略部分代码...

isWindRandom = builder.isWindRandom;

isWindChange = builder.isWindChange;

}

public static final class Builder {

//省略部分代码...

private boolean isWindRandom;

private boolean isWindChange;

public Builder(Bitmap bitmap) {

//省略部分代码...

this.isWindRandom = false;

this.isWindChange = false;

}

public Builder(Drawable drawable) {

//省略部分代码...

this.isWindRandom = false;

this.isWindChange = false;

}

/**

* 设置风力等级、方向以及随机因素

* @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;

}

}

/**

* 移动物体对象

*/

private void moveObject(){

moveX();

moveY();

if(presentY>parentHeight || presentXparentWidth+bitmap.getWidth()){

reset();

}

}

/**

* X轴上的移动逻辑

*/

private void moveX(){

presentX += defaultWindSpeed * Math.sin(angle);

if(isWindChange){

angle += (float) (random.nextBoolean()?-1:1) * Math.random() * 0.0025;

}

}

/**

* 重置object位置

*/

private void reset(){

presentY = -objectHeight;

randomSpeed();//记得重置时速度也一起重置,这样效果会好很多

randomWind();//记得重置一下初始角度,不然雪花会越下越少(因为角度累加会让雪花越下越偏)

}

/**

* 随机风的风向和风力大小比例,即随机物体初始下落角度

*/

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

angle = -HALF_PI;

}

}

}

在Activity中调用新增加的接口

FallObject.Builder builder = new FallObject.Builder(getResources().getDrawable(R.drawable.ic_snow));

FallObject fallObject = builder

.setSpeed(7,true)

.setSize(50,50,true)

.setWind(5,true,true)

.build();

效果如图

至此本篇教程到此结束,如果大家看了感觉还不错麻烦点个赞,你们的支持是我最大的动力~

android雪花飘落动画,Android自定义View——从零开始实现雪花飘落效果相关推荐

  1. android录音波浪动画_Android自定义View实现波浪动画

    本文实例为大家分享了Android自定义View实现波浪动画的具体代码,供大家参考,具体内容如下 效果演示 代码调用与实现效果 xml中调用 android:layout_width="ma ...

  2. android录音波浪动画_Android 自定义 view 实现波浪动画进度条

    最近在做项目时需要实现这样一种动画,类似于波浪形的进度动画,粗略的看了一下,发现好像类似于正余弦曲线实现的,但是Android 没有相关的API,所以需要我们动手画出来,所以现在在此记录一下学习过程, ...

  3. android+清除循环动画,android自定义View之(4)-一键清除动画

    android自定义View之(四)------一键清除动画 1.前言: 自己也是参考别人的一些自定义view例子,学习了一些基本的自定义view的方法.今天,我参考了一些资料,再结合自已的一些理解, ...

  4. Android属性动画与自定义View——实现vivo x6更新系统的动画效果

    晚上好,现在是凌晨两点半,然后我还在写代码.电脑里播放着<凌晨两点半>,晚上写代码,脑子更清醒,思路更清晰. 今天聊聊属性动画和自定义View搭配使用,前面都讲到自定义View和属性动画, ...

  5. Android 气泡动画(自定义View类)

    Android 气泡动画(自定义View类) 一.前言 二.代码 1. 随机移动的气泡 2.热水气泡 一.前言 最近有需求制作一个水壶的气泡动画,首先在网上查找了一番,找到了一个文章. https:/ ...

  6. Android软件开发之盘点自定义View界面大合集(二)

    Android软件开发之盘点自定义View界面大合集(二) - 雨松MOMO的程序世界 - 51CTO技术博客 雨松MOMO带大家盘点Android 中的自定义View界面的绘制 今天我用自己写的一个 ...

  7. Carson带你学Android:源码解析自定义View Draw过程

    前言 自定义View是Android开发者必须了解的基础 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化 等 今天,我将全面总结自定义View ...

  8. Android中实现Bitmap在自定义View中的放大与拖动

    一基本实现思路: 基于View类实现自定义View –MyImageView类.在使用View的Activity类中完成OnTouchListener接口,实现对MotionEvent事件的监听与处理 ...

  9. android自定义拱形,Android自定义View实现圆弧进度的效果

    前言 Android开发中,常常自定义View实现自己想要的效果,当然自定义View也是Android开发中比较难的部分,涉及到的知识有Canvas(画布),Paint(画笔)等,自定义控件分为三种: ...

  10. android 仿360浮动,Android仿360悬浮小球自定义view实现示例

    Android仿360悬浮小球自定义view实现示例 效果图如下: 实现当前这种类似的效果 和360小球 悬浮桌面差不错类似.这种效果是如何实现的呢.废话不多说 ,直接上代码. 1.新建工程,添加悬浮 ...

最新文章

  1. PHP远程DoS漏洞深入分析及防护方案
  2. centos在线安装svn
  3. SD卡驱动分析(一)
  4. html怎么整体放大,html页面放大时不能铺满整个页面问题
  5. 一文讲清,MySQL中的二级索引
  6. 联想YOGA四款新品齐发 跨设备新技术Lenovo One惊艳亮相
  7. centos磁盘满了,查找大文件并清理
  8. 代理模式和php实现
  9. 软件安全测试培训大纲
  10. ESP8266 alios things 自带 linkkitapp OTA 更新失败
  11. 8.19! 今天我有18生日,点击阅读或顶部 尾随幸运的一天!生日知识!↓——【Badboy】...
  12. 语音芯片WT2003H4 B008单芯片实现智能门铃方案快捷设计
  13. 工科硕士毕业14年,谈谈我的经验教训
  14. Redis高级应(2)-事务以及LUA脚本
  15. break、continue、goto
  16. 幼儿园语言活动包括哪几类_幼儿园健康活动分为哪几类,幼儿园教育活动包括...
  17. Vmare horizon client 5.0安装过程中自动取消
  18. 实测windows 7兼容软件
  19. 字符串什么时候用单引号,什么时候用双引号?
  20. 任天堂Switch游戏机底座方案,支持按键一键切换TV模式

热门文章

  1. 发布工程到私有仓库maven
  2. so easy 的 Vue3.0自定义指令(也搜一贼)
  3. 如何创建一个最简单的Windows桌面应用程序 (C++)
  4. 联想拯救者笔记本加固态硬盘过程重点
  5. Android一步步实现无痕埋点(3)-------虎躯一震
  6. 单节点 Elasticsearch 健康状态为 yellow 问题的解决
  7. dns被劫持怎么办,DNS被劫持了有什么解决办法?
  8. 微信为什么不禁拼多多?诱导分享到底怎么判
  9. 程序员与颈椎病(三):颈椎病终极解决办法
  10. R语言解读多元线性回归模型