效果图:

圣诞登录页.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实现物体下落效果_手撸一个物体下落的控件,实现雪花飘落效果相关推荐

  1. javascript实现图片轮播_手撸一个简易版轮播图(上)

    手撸一个简易版轮播图 实现原理,通过控制 swiper-warpper 容器的定位来达到切换图片的效果. 页面布局 简易版轮播图 < > 页面样式 .container{width: 60 ...

  2. java飘落的雪花_[Java教程]实现雪花飘落效果

    [Java教程]实现雪花飘落效果 0 2016-11-02 21:00:17 雪花飘落 body{background:#000;background: url(http://www.wallcoo. ...

  3. 手撸一个动态数据源的Starter 完整编写一个Starter及融合项目的过程 保姆级教程

    手撸一个动态数据源的Starter! 文章目录 手撸一个动态数据源的Starter! 前言 一.准备工作 1,演示 2,项目目录结构 3,POM文件 二.思路 三.编写代码 1,定义核心注解 Ds 2 ...

  4. 使用Node.js手撸一个建静态Web服务器,内部CV指南

    文章里有全部代码,也可以积分下载 操作步骤如上图 文章结束 话说这个键盘真漂亮~~ 文章目录 使用Node.js手撸一个建静态Web服务器 一.动静态服务器的概念 1.1 静态Web服务器概念 1.2 ...

  5. Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池

    文章目录 1 前言 2 Goroutine & Scheduler 2.1 线程那些事儿 2.1.1 用户级线程模型 2.1.2 内核级线程模型 2.1.3 两级线程模型 2.2 G-P-M ...

  6. 很多小伙伴不太了解ORM框架的底层原理,这不,冰河带你10分钟手撸一个极简版ORM框架(赶快收藏吧)

    大家好,我是冰河~~ 最近很多小伙伴对ORM框架的实现很感兴趣,不少读者在冰河的微信上问:冰河,你知道ORM框架是如何实现的吗?比如像MyBatis和Hibernate这种ORM框架,它们是如何实现的 ...

  7. .Net Core手撸一个基于Token的权限认证

    说明 权限认证是确定用户身份的过程.可确定用户是否有访问资源的权力 今天给大家分享一下类似JWT这种基于token的鉴权机制 基于token的鉴权机制,它不需要在服务端去保留用户的认证信息或者会话信息 ...

  8. 五分钟,手撸一个Spring容器!

    Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌. 这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开Spring神秘的面 ...

  9. 呆呆带你手撸一个思维导图-基础篇

    希沃ENOW大前端 公司官网:CVTE(广州视源股份) 团队:CVTE旗下未来教育希沃软件平台中心enow团队 「本文作者:」 前言 你盼世界,我盼望你无bug.Hello 大家好,我是霖呆呆! 哈哈 ...

最新文章

  1. python主要学哪些课程_Python学习课程大纲自学Python参考
  2. TCP/IP 总结一
  3. SQL -- 数据字典生成工具
  4. matlab调用C程序
  5. 【3】C++语法与数据结构之MFC_CList学生管理系统_链表外排序_函数指针
  6. Jmeter之性能测试类型
  7. 编程 态度目标_对目标持开放态度,从而推动事业发展
  8. 重用之前应仔细分析问题---------用错轮子有感
  9. 专题三:MATLAB程序流程控制
  10. 实践:VB创建Com组件 在Asp以及.Net中调用
  11. VS2005编译Openssl-1.1.0f
  12. DTcms二次开发心得
  13. 【你好,windows】Windows 10 X64 21H1 19043.964 纯净版2021.5.2
  14. css写字体浮雕效果,使用CSS3的text-shadow制作浮雕文字阴影和多重文字阴影效果-网站程序网...
  15. ecshop常见漏洞
  16. 上海市计算机三级 是什么水平,上海计算机等级考试跟全国计算机等级考试有什么区别吗?...
  17. Angular读取文件内容并显示
  18. 树莓派3B+安装Android 系统
  19. uniapp设置整个页面背景颜色渐变,设置单个页面背景颜色
  20. JBoss 中间件漏洞

热门文章

  1. 对自定义SharePoint WebService的总结
  2. Linux 命令(28)—— tee 命令
  3. springboot的基础面试题
  4. Spring入门第十七课
  5. 判断字符串为空 为null
  6. Appium框架中Android下EditText内容清除
  7. 配置普通,长得丑,OPPO R11凸显OPPO创新乏力
  8. 第1章 网站与网站的建设过程
  9. 4-2 数据模型的生成
  10. 7-7 全量复制开销