java实现物体下落效果_手撸一个物体下落的控件,实现雪花飘落效果
效果图:
圣诞登录页.gif
参考文章:
Android自定义View——从零开始实现雪花飘落效果
感谢原文作者,不仅实现了效果,并且写得非常详细,还做了优化。笔者参考原文作者的源码,做了一点修改,实现了效果并加入了项目中。不过都大同小异,下面笔者会将学习和制作中的难点和注意点分享给大家。
提炼与分享:
1. 如何实现简单的物体下落:@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();
}
}; public void drawObject(Canvas canvas){
moveObject();
canvas.drawBitmap(bitmap,presentX,presentY,null);
} private void moveObject(){
moveX();
moveY(); if(presentY>parentHeight || presentXparentWidth+bitmap.getWidth()){
reset();
}
} private void moveY(){
presentY += presentSpeed;
} private void moveX(){
presentX += defaultWindSpeed * Math.sin(angle); if(isAngleChange){
angle += (float) (random.nextBoolean()?-1:1) * Math.random() * 0.0025;
}
} private void reset(){
presentY = -objectHeight;
randomSpeed();
randomWind();//记得重置一下初始角度,不然雪花会越下越少(因为角度累加会让雪花越下越偏)
}
首先是Y轴控制竖直下落,初始的Y轴坐标是通过屏幕高度取随机值-屏幕高度来确定的。这样物体会从不同的位置下落,在相同速度的情况下,也能在不同的时间进入屏幕。
然后是X轴,正常的雪花肯定不是竖直下落,也不是折线下落,而是弧形,View中采用的sin函数的-Pi到Pi之间的值绘制弧形。x轴的初始位置通过对屏幕宽度做随机值确定。
最后在物体到底屏幕底部,或者超过屏幕左右边界时,重置物体(reset方法)。需要重置的是y轴的点,以及物体的速度,当然还有我们模拟的风力,后面会单独说。
2. 为什么要使用Builder建造者模式
其实原文已经讲得很仔细了,我们物体会有大量的参数和对应的行为方法,为了提高代码的可读性,我们将物体提取出来,作为一个单独的类。而大量的参数采用普通的构造方法去构造,实在是不知道,传入的参数究竟代表什么。而建造者模式能够解决这个问题。FallObject.Builder builder = new FallObject.Builder(getResources().getDrawable(R.drawable.snowflake));
FallObject fallObject = builder
.setSpeed(8,true)
.setSize(80,80,true)
.setWind(5,true)
.build(); //初始化一个雪球样式的fallObject
((FallingView)findViewById(R.id.fallingView)).addFallObject(fallObject,60);//添加60个雪球对象
在这个项目中,我们将所有与下落物体相关的方法和属性全部封装在FallObject中,并且提供Builder内部类实例化。而我们的View则仅仅需要作为一个画布,提供添加下落对象的方法,重复的绘制物体即可。至于绘制的对象是要下落还是要旋转,都与View没有关系了。
3. 绘制图片并且控制其大小
绘制图片在View中是有提供方法的:canvas.drawBitmap(bitmap,presentX,presentY,null);从方法中可以看到,我们需要的是bitmap的图片,那么,我们在修改图片大小之前,还需要先将drawable转化为bitmap。/**
* 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转化为bitmap的代码,简单来讲就是Bitmap的采用的是工厂模式创建一个bitmap空对象,然后通过drawable将图片图像画在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;
}
上面是改变图片显示大小的方法。就是直接对bitmap的缩放操作返回新的bitmao对象。需要注意的是为了保证不失帧,新的宽高度需要按原大小等比例缩放。
4. 如何引入风的概念/**
* 随机风的风向和风力大小比例,即随机物体初始下落角度
*/
private void randomWind(){ if(isAngleChange){
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;
}
}
正常情况下,我们的雪花不会是直线下落的,而是有轻微的弧度飘落,我们通过改变X轴的方式来实现水平位移,但是为了保证位移的平滑,我们采用了sin正弦函数计算x轴的值,采用-π/2到π/2的弧线值作为函数的角度。这个曲线值是[-1,1],可以实现雪花自由的左右弧线移动。initWindLevel是我们模拟的风力,风力值越大,雪花飘落的弧度就越大。
源码:
画布View:package com.wusy.wusylibrary.view.FallingView;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.view.View;import android.view.ViewTreeObserver;import java.util.ArrayList;import java.util.List;/**
* Created by XIAO RONG on 2018/12/24.
*/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 = 10;//重绘间隔时间
private List fallObjects; 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 = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == 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.get(i).drawObject(canvas);
} // 隔一段时间重绘一次, 动画效果
getHandler().postDelayed(runnable, intervalTime);
}
} // 重绘线程
private Runnable runnable = new Runnable() { @Override
public void run() {
invalidate();
}
}; /**
* 向View添加下落物体对象
* @param fallObject 下落物体对象
* @param num
* view.getViewTreeObserver().addOnPreDrawListener(opdl)
* 此方法在视图绘制前会被调用,测量结束,客户获取到一些数据。再计算一些动态宽高时可以使用。
* 调用一次后需要注销这个监听,否则会阻塞ui线程。
*/
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
FallObject newFallObject = new FallObject(fallObject.builder,viewWidth,viewHeight);
fallObjects.add(newFallObject);
}
invalidate(); return true;
}
});
}
}
下落物体类:package com.wusy.wusylibrary.view.FallingView;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 initWindLevel;//初始风力等级
private float angle;//下落物体角度
private boolean isAngleChange;//下落物体角度是否改变
private static final int defaultWindLevel = 0;//默认风力等级
private static final int defaultWindSpeed = 10;//默认单位风速
private static final float HALF_PI = (float) Math.PI / 2;//π/2
public float initSpeed;//初始下降速度
public float presentSpeed;//当前下降速度
public float presentX;//当前位置X坐标
public float presentY;//当前位置Y坐标
private boolean isSpeedRandom;//物体初始下降速度比例是否随机
private boolean isSizeRandom;//物体初始大小比例是否随机
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; this.builder = builder;
isSpeedRandom = builder.isSpeedRandom;
isSizeRandom = builder.isSizeRandom;
isAngleChange=builder.isAngleChange;
initWindLevel=builder.initWindLevel;
randomSpeed();
randomSize();
} private FallObject(Builder builder) { this.builder = builder;
initSpeed = builder.initSpeed;
bitmap = builder.bitmap;
isSpeedRandom = builder.isSpeedRandom;
isSizeRandom = builder.isSizeRandom;
isAngleChange=builder.isAngleChange;
initWindLevel=builder.initWindLevel;
} /**
* 绘制物体对象
* @param canvas
*/
public void drawObject(Canvas canvas){
moveObject();
canvas.drawBitmap(bitmap,presentX,presentY,null);
} /**
* 移动物体对象
*/
private void moveObject(){
moveX();
moveY(); if(presentY>parentHeight || presentXparentWidth+bitmap.getWidth()){
reset();
}
} /**
* Y轴上的移动逻辑
*/
private void moveY(){
presentY += presentSpeed;
} private void moveX(){
presentX += defaultWindSpeed * Math.sin(angle); if(isAngleChange){
angle += (float) (random.nextBoolean()?-1:1) * Math.random() * 0.0025;
}
} /**
* 重置object位置
*/
private void reset(){
presentY = -objectHeight;
randomSpeed();
randomWind();//记得重置一下初始角度,不然雪花会越下越少(因为角度累加会让雪花越下越偏)
} /**
* 随机风的风向和风力大小比例,即随机物体初始下落角度
*/
private void randomWind(){ if(isAngleChange){
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;
}
} /**
* 随机物体初始下落速度
*/
private void randomSpeed(){ if(isSpeedRandom){
initSpeed = (float)((random.nextInt(3)+1)*0.1+1)* builder.initSpeed;
}else {
initSpeed = builder.initSpeed;
}
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();
} /**
* 改变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;
} /**
* 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;
} public static final class Builder { private float initSpeed; private Bitmap bitmap; private boolean isSpeedRandom; private boolean isSizeRandom; private int initWindLevel;//下落物体角度
private boolean isAngleChange;//下落物体角度是否改变
public Builder(Bitmap bitmap) { this.initSpeed = defaultSpeed; this.bitmap = bitmap;
} public Builder(Drawable drawable) { this.initSpeed = defaultSpeed; this.bitmap = drawableToBitmap(drawable);
} /**
* 设置物体的初始下落速度
* @param level
* @param isAngleChange 物体初始下降速度比例是否随机
* @return
*/
public Builder setWind(int level,boolean isAngleChange) { this.initWindLevel = level; this.isAngleChange = isAngleChange; return this;
} /**
* 设置物体的初始下落速度
* @param speed
* @return
*/
public Builder setSpeed(float speed) { this.initSpeed = speed; return this;
} /**
* 设置物体的初始下落速度
* @param speed
* @param isRandomSpeed 物体初始下降速度比例是否随机
* @return
*/
public Builder setSpeed(float 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;
} /**
* 构建FallObject
* @return
*/
public FallObject build() { return new FallObject(this);
}
}
}
在用户在xml中使用,Activity中实例化:FallObject.Builder builder = new FallObject.Builder(getResources().getDrawable(R.drawable.snowflake));
FallObject fallObject = builder
.setSpeed(8,true)
.setSize(80,80,true)
.setWind(5,true)
.build(); //初始化一个雪球样式的fallObject
((FallingView)findViewById(R.id.fallingView)).addFallObject(fallObject,60);//添加50个雪球对象
android:id="@+id/fallingView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
作者:饮水思源为名
链接:https://www.jianshu.com/p/1c2964957197
java实现物体下落效果_手撸一个物体下落的控件,实现雪花飘落效果相关推荐
- javascript实现图片轮播_手撸一个简易版轮播图(上)
手撸一个简易版轮播图 实现原理,通过控制 swiper-warpper 容器的定位来达到切换图片的效果. 页面布局 简易版轮播图 < > 页面样式 .container{width: 60 ...
- java飘落的雪花_[Java教程]实现雪花飘落效果
[Java教程]实现雪花飘落效果 0 2016-11-02 21:00:17 雪花飘落 body{background:#000;background: url(http://www.wallcoo. ...
- 手撸一个动态数据源的Starter 完整编写一个Starter及融合项目的过程 保姆级教程
手撸一个动态数据源的Starter! 文章目录 手撸一个动态数据源的Starter! 前言 一.准备工作 1,演示 2,项目目录结构 3,POM文件 二.思路 三.编写代码 1,定义核心注解 Ds 2 ...
- 使用Node.js手撸一个建静态Web服务器,内部CV指南
文章里有全部代码,也可以积分下载 操作步骤如上图 文章结束 话说这个键盘真漂亮~~ 文章目录 使用Node.js手撸一个建静态Web服务器 一.动静态服务器的概念 1.1 静态Web服务器概念 1.2 ...
- Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池
文章目录 1 前言 2 Goroutine & Scheduler 2.1 线程那些事儿 2.1.1 用户级线程模型 2.1.2 内核级线程模型 2.1.3 两级线程模型 2.2 G-P-M ...
- 很多小伙伴不太了解ORM框架的底层原理,这不,冰河带你10分钟手撸一个极简版ORM框架(赶快收藏吧)
大家好,我是冰河~~ 最近很多小伙伴对ORM框架的实现很感兴趣,不少读者在冰河的微信上问:冰河,你知道ORM框架是如何实现的吗?比如像MyBatis和Hibernate这种ORM框架,它们是如何实现的 ...
- .Net Core手撸一个基于Token的权限认证
说明 权限认证是确定用户身份的过程.可确定用户是否有访问资源的权力 今天给大家分享一下类似JWT这种基于token的鉴权机制 基于token的鉴权机制,它不需要在服务端去保留用户的认证信息或者会话信息 ...
- 五分钟,手撸一个Spring容器!
Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌. 这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开Spring神秘的面 ...
- 呆呆带你手撸一个思维导图-基础篇
希沃ENOW大前端 公司官网:CVTE(广州视源股份) 团队:CVTE旗下未来教育希沃软件平台中心enow团队 「本文作者:」 前言 你盼世界,我盼望你无bug.Hello 大家好,我是霖呆呆! 哈哈 ...
最新文章
- python主要学哪些课程_Python学习课程大纲自学Python参考
- TCP/IP 总结一
- SQL -- 数据字典生成工具
- matlab调用C程序
- 【3】C++语法与数据结构之MFC_CList学生管理系统_链表外排序_函数指针
- Jmeter之性能测试类型
- 编程 态度目标_对目标持开放态度,从而推动事业发展
- 重用之前应仔细分析问题---------用错轮子有感
- 专题三:MATLAB程序流程控制
- 实践:VB创建Com组件 在Asp以及.Net中调用
- VS2005编译Openssl-1.1.0f
- DTcms二次开发心得
- 【你好,windows】Windows 10 X64 21H1 19043.964 纯净版2021.5.2
- css写字体浮雕效果,使用CSS3的text-shadow制作浮雕文字阴影和多重文字阴影效果-网站程序网...
- ecshop常见漏洞
- 上海市计算机三级 是什么水平,上海计算机等级考试跟全国计算机等级考试有什么区别吗?...
- Angular读取文件内容并显示
- 树莓派3B+安装Android 系统
- uniapp设置整个页面背景颜色渐变,设置单个页面背景颜色
- JBoss 中间件漏洞