{"id":13645675,"url":"https://github.com/JadynAi/Particle","last_synced_at":"2025-04-21T14:32:01.410Z","repository":{"id":186940490,"uuid":"89694779","full_name":"JadynAi/Particle","owner":"JadynAi","description":"一个android萤火虫飞舞的粒子效果","archived":false,"fork":false,"pushed_at":"2019-04-25T02:25:02.000Z","size":153,"stargazers_count":139,"open_issues_count":2,"forks_count":15,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-09T18:43:21.302Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JadynAi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-04-28T10:15:06.000Z","updated_at":"2024-09-29T03:01:03.000Z","dependencies_parsed_at":"2023-08-08T10:41:55.444Z","dependency_job_id":null,"html_url":"https://github.com/JadynAi/Particle","commit_stats":null,"previous_names":["jadynai/particle"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JadynAi%2FParticle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JadynAi%2FParticle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JadynAi%2FParticle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JadynAi%2FParticle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JadynAi","download_url":"https://codeload.github.com/JadynAi/Particle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250070250,"owners_count":21369842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T01:02:39.489Z","updated_at":"2025-04-21T14:32:01.026Z","avatar_url":"https://github.com/JadynAi.png","language":"Java","readme":"\u003e 原创文章，转载请注明出处\n*我的博客地址---\u003e* [这里！这里！](http://ailoli.me/)\n\n## 新增解决方案，不再继承SurfaceView，而是继承自View。文章地址：[新的粒子动画](https://ailoli.me/2017/06/28/android_partical/)\n\n## 萤火虫飞舞粒子效果\n\u003e本项目中我提供了两种方案，最终呈现的效果如下：\n\n![](https://raw.githubusercontent.com/JadynAi/oldpage.io/master/img/20170428-blog-particle.gif)\n\n## 实现原理\n\nAndroid的粒子效果、粒子动画，已经有很多开源的轮子了。作为一个坚定的轮子主义者，我google了大半天，却没有找到这种类似于萤火虫飞舞的效果。只好自己来实现这种效果。\n\n---\n\n相比较普通的View，SurfaceView更加适合这种不断变化的画面，所以选择SurfaceView来实现。现在把思路再重新梳理一下：\n\n- 大小不同的粒子在区域内随机分布\n- 粒子做无规则运动，然后消失\n\n##### 粒子区域内随机分布#####\n\n这个简单，我们在callBack的方法内直接循环生成一个粒子的数组即可。方位的话使用Random即可。\n\n```\nif (mCircles.size() == 0) {\n    for (int i = 0; i \u003c MAX_NUM; i++) {\n        FloatParticleLine f = new FloatParticleLine(getF() * mMeasuredWidth, getF() * mMeasuredHeight, mMeasuredWidth, mMeasuredHeight);\n        f.setRadius(mRandom.nextInt(2) + 1.2f);\n        mCircles.add(f);\n    }\n}\nprivate float getF() {\n        float v = mRandom.nextFloat();\n        if (v \u003c 0.2f) {\n            return v + 0.2f;\n        } else if (v \u003e= 0.85f) {\n            return v - 0.2f;\n        } else {\n            return v;\n        }\n    }\n```\n\n\u003e getF（）方法是限制在区域内取值，mMeasuredWidth、mMeasuredHeight为SurfaceView的宽和高。\n\u003e\n\u003e 这里的宽和高在粒子对象FloatParticleLine，内会用到。\n\n然后我们在创建一个线程，在run（）方法内做无线循环的绘制即可，为了避免无意义的绘制，可以使用Thread.sleep方法来控制帧数。\n\n```\nwhile (isRun) {\n    try {\n        mCanvas = mHolder.lockCanvas(null);\n        if (mCanvas != null) {\n            synchronized (mHolder) {\n                // 清屏\n                mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);\n\n                for (FloatParticleLine circle : mCircles) {\n                    circle.drawItem(mCanvas);\n                }\n                // 控制帧数\n                Thread.sleep(25);\n            }\n        }\n    } catch (Exception e) {\n        e.printStackTrace();\n    } finally {\n        if (mCanvas != null) {\n            mHolder.unlockCanvasAndPost(mCanvas);\n        }\n    }\n}\n```\n\nisRun的变量我们会在SurfaceView内callBack的surfaceDestroyed方法中置为false\n\n##### 粒子做无规则运动\n\n- 方案一\n\n其实看到这种粒子效果，首先应该想到的就是Canvas了。\n\n在SurfaceView里就是通过不断地循环调用FloatParticleLine类的drawItem（）方法来实现粒子的运动。我第一种方案的实现，就是每一个粒子在被创建出来的时候，就随机选择一个方向开始运动，滑过一定的轨迹之后让其消失就好了。\n\n至于怎么选择随机方向，我这里的做法是，分别随机生成一个x和y轴上的递增或者递减的数值，然后每次在前一次绘制的基础上，x和y分别递增递减，直到运动到屏幕边缘或者是规定的运动距离满足了再消失即可。\n\n```\n//随机生成参数\nprivate void setRandomParm() {\n    // 2017/5/2-上午10:47 x和y的方向\n    mIsAddX = mRandom.nextBoolean();\n    mIsAddY = mRandom.nextBoolean();\n\n    // 2017/5/2-上午10:47 x和y的取值\n    mDisX = mRandom.nextInt(2) + 0.2f;\n    mDisY = mRandom.nextInt(2) + 0.3f;\n\n    // 2017/5/2-上午10:47 内部区域的运动最远距离\n    mDistance = mRandom.nextInt((int) (0.25f * mWidth)) + (0.125f * mWidth);\n}\n```\n\n绘制图形：\n\n```\npublic void drawItem(Canvas canvas) {\n    if (mX == mStartX) {\n        mPaint.setAlpha(ALPHA_MAX);\n    }\n    //绘制\n    canvas.drawCircle(mX += getPNValue(mIsAddX, mDisX), mY += getPNValue(mIsAddY, mDisY), mRadius, mPaint);\n    //内部区域运动到一定距离消失\n    if (judgeInner()) {\n        float gapX = Math.abs(mX - mStartX);\n        float ratio = 1 - (gapX / mDistance);\n        mPaint.setAlpha((int) (255 * ratio));\n        mRadius = mStartRadius * ratio;\n        if (gapX \u003e= mDistance || mY - mStartY \u003e= mDistance) {\n            resetDisXY();\n            return;\n        }\n        return;\n    }\n    //外部区域运动到屏幕边缘消失\n    if (judgeOutline()) {\n        resetDisXY();\n    }\n}\n\nprivate void resetDisXY() {\n        setRandomParm();\n\n        mPaint.setAlpha(0);\n        mX = mStartX;\n        mY = mStartY;\n        mRadius = mStartRadius;\n    }\n```\n\n\u003e judgeInner()和judgeOutline()是判断区域的方法，内部区域的点和外部区域的店消失时机不同\n\n在透明度为0也就是粒子消失时，让粒子回到原点，再重新选择一个方向，进行下一步运动轨迹。\n\n- 方案二\n\n方案二粒子做的运动是贝塞尔曲线，函数实在网上找到的一个函数。每当粒子做完一次曲线运动后，再随机生成一段新的贝塞尔曲线即可。\n\n思路和方案一的思路都是一样的，无非就是运动的轨迹不同而已。\n\n## 总结\n\n做完之后回头再看，发现这个项目的原理其实并不难，可以说是简单了。但刚开始起步的时候真的还是比较懵的，原因就是没有思路。\n\n所以做任何效果，思路最重要。\n\n\n","funding_links":[],"categories":["其他"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJadynAi%2FParticle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FJadynAi%2FParticle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJadynAi%2FParticle/lists"}