IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android自定义View高级动效之---流星雨动效|流星雨专辑封面 -> 正文阅读

[移动开发]Android自定义View高级动效之---流星雨动效|流星雨专辑封面

篇章目标要点

最近看到酷我音乐App出了一则《穹顶流星》动效,看完之后决定自己尝试一下实现,本文将围绕通过自定义View实现流星雨效果,可以看到流星雨环绕专辑图的高级动效。通过完成这项开发,能够更深的理解自定义View。

实现效果

先上图看下效果,中间设置的是外部传入的图片,四周是通过自定义View实现的流星效果
在这里插入图片描述

核心设计思路

1.裁剪ImageView原图获得圆形图片

这部分的基本思路是先将原图缩放至与目标大小合适的尺寸,然后进行裁剪。
在这里插入图片描述
其关键步骤是需要设置相交取背景,这部分有较多人员以及整理了相关资料,详情如下。其中SRC表示背景图,DST表示前景图。
在这里插入图片描述

2.旋转圆形图片

在onDraw中处理图片都是基于Bitmap类型对象进行操作的,而Java提供了Matrix用法处理Bitmap旋转,在这里虽然我们需要的是处理后的圆形效果的图片,但是图片的画布并非是圆形,而是矩形,只是其背景是透明色而已,因此旋转过程中,会旋转最合适的大小来容纳原图,故处理之后的图片的宽高是变化的,如下图所示,旋转过程中会产生补充区。
在这里插入图片描述

3.绘制流星

一个静态的流星的基本构成包含流星本体是一个圆形,流星的运动轨迹是弧线两个部分构成
在这里插入图片描述

这个只是静态的流星,如果要实现动态流星的效果,则需要通过以下3个参数保证

序号参数参数要求
1圆弧终点角圆弧的终点角 = 起点角 + 扫过角度,其值应当呈现顺时针方向变化,才能呈现旋转效果
2圆弧扫过角度要实现流星拖尾和消亡两个阶段,则扫过角度应当线性增加或减少,减少至0后回收
3圆弧颜色圆弧颜色应当有渐变效果,能够体现拖尾的光减弱效果

关键代码说明

1.首先创建一个内部类实现流星对象 ,其内部定义了起点角度/扫过角度/圆弧半径/流星头部的圆心横坐标/流星头部的纵坐标 等几个主要的要素信息

    /**
     * 流星对象,主要由起点角度/扫过角度/圆弧半径/流星头部的圆心横坐标/流星头部的纵坐标 5个要素构成
     */
    private class FallingStar{
        //流星规则的渐变色
        private final int[] SWEEP_COLORS = new int[]{Color.TRANSPARENT, 0xFFE0E0E0};
        private final float[] POSITIONS = new float[]{0.2f, 0.8f};
        private int startAngle;
        private int sweepAngle;
        private int radius;
        //流星头部圆形的横纵坐标
        private int starCenterX;
        private int starCenterY;
        private SweepGradient shader;
        private RectF rect;
        //true表示流星轨迹,处于增长期;反正为衰减期
        private boolean rise;
        //流星旋转速度
        private int velocity;

        public int getStartAngle() {
            return startAngle;
        }

        public FallingStar setStartAngle(int startAngle) {
            this.startAngle = startAngle;
            return this;
        }

        public int getSweepAngle() {
            return sweepAngle;
        }

        public FallingStar setSweepAngle(int sweepAngle) {
            this.sweepAngle = sweepAngle;
            return this;
        }

        public int getRadius() {
            return radius;
        }

        public FallingStar setRadius(int radius) {
            this.radius = radius;
            return this;
        }

        public int getStarCenterX() {
            return starCenterX;
        }

        public int getStarCenterY() {
            return starCenterY;
        }

        public SweepGradient getShader() {
            return shader;
        }

        public RectF getRect() {
            return rect;
        }

        public FallingStar build(){
            //计算流星头部的圆心坐标
            double endAngle = 2 *Math.PI*(getStartAngle()+getSweepAngle()) / 360 ;
            starCenterX = (int)(radius*Math.cos(endAngle)) + width/2;
            starCenterY = (int)(radius*Math.sin(endAngle)) + height/2;
            shader = new SweepGradient(width/2, height/2, SWEEP_COLORS, POSITIONS);
            rect = new RectF(width/2 - getRadius(), height/2 - getRadius(), width/2 + getRadius(), height/2 + getRadius());
            rise = true;
            velocity = mRandomInt.nextInt(2) + 2;
            return this;
        }

        /**
         * 调整流星起点角度和扫过角度,起点角度的算法是逆时针匀速调整,扫过角度的算法是阈值及以上匀速减少至0,阈值以下匀速增加至阈值
         */
        public void changeAngle(){
            startAngle +=velocity;
            startAngle %= 360;
            if(rise){
                sweepAngle ++;
                if(sweepAngle > MAX_SWEEP_ANGLE){
                    rise = false;
                    sweepAngle = MAX_SWEEP_ANGLE;
                }
            }else{
                sweepAngle --;
                if(sweepAngle <= 0){
                    rise = true;
                    sweepAngle = 0;
                    mFallingStarList.remove(this);
                }
            }
            //相应的调整圆头的位置
            double endAngle = 2 *Math.PI*(getStartAngle()+getSweepAngle()) / 360 ;
            starCenterX = (int)(getRadius()*Math.cos(endAngle)) + width/2;
            starCenterY = (int)(getRadius()*Math.sin(endAngle)) + height/2;
        }
    }

2.然后熟悉一下单个流星的绘制,主要就是绘制一个圆形和一个弧线

    /**
    * 绘制单个流星包括绘制星星头部的圆形和绘制流星轨迹的渐变线
    * @param canvas
    * @param fallingStar
    */
   private void drawFallingStar(Canvas canvas, FallingStar fallingStar){
       //绘制流星头部圆形
       canvas.drawCircle(fallingStar.getStarCenterX(), fallingStar.getStarCenterY(), 2 , starPaint);
       //绘制轨迹线圆弧,设置弧线为渐变色
       fallingLinePaint.setShader(fallingStar.getShader());
       canvas.drawArc(fallingStar.getRect(), fallingStar.getStartAngle(), fallingStar.getSweepAngle(), false, fallingLinePaint);
   }

3.绘制流星群组和补充流星的过程

    /**
    * 绘制流星群组
    */
   private void drawFallingStarGroup(Canvas canvas){
       //剩余流星不及原来总数50%时补充流星
       if(mFallingStarList.size() <= 0){
           initFallingStarAngle();
       }else if(mFallingStarList.size() <= (FALLING_STAR_GROUP_SIZE/2)){
           addFallingStar();
       }
       //绘制流星群组
       for(int i = 0; i < mFallingStarList.size(); i++){
           drawFallingStar(canvas, mFallingStarList.get(i));
       }
   }

   /**
    * 调整流星群组的起点角度和扫过角度,起点角度的算法是逆时针匀速调整,扫过角度的算法是阈值及以上匀速减少至0,阈值以下匀速增加至阈值
    */
   private void changeFallingStarAngle(){
       for(int i = 0; i < mFallingStarList.size(); i++){
           mFallingStarList.get(i).changeAngle();
       }
       //调整中心图片的旋转角度,设置进行逆时针旋转
       mRotateAngle -= 2;
       mRotateAngle = (mRotateAngle + 360) % 360;
       invalidate();
   }

补充流星的详细代码

    /**
    * 在流星数量减少并处于衰竭状况下,补充流星个数,以确保整体的可观性
    */
   private void addFallingStar(){
       int additionSize = FALLING_STAR_GROUP_SIZE - mFallingStarList.size();
       int starPathNo = 1;
       int beginRandomAngle = mRandomInt.nextInt(360);
       //计算原有的轨道总层数,将待补充的流星分配至原各层级轨道
       int starPathCount = FALLING_STAR_GROUP_SIZE / (360 / MAX_SWEEP_ANGLE);
       if((FALLING_STAR_GROUP_SIZE % (360 / MAX_SWEEP_ANGLE)) > 0){
           starPathCount++;
       }
       //每一层待分配的流星个数
       int starCountPerPath = additionSize / starPathCount;
       for(int i = 0; i < additionSize; i++){
           starPathNo = i / starCountPerPath + 1;
           FallingStar star = new FallingStar().setRadius(mInsideImageRadius + starPathNo * 10)
                   .setStartAngle(i % (360 / MAX_SWEEP_ANGLE) * MAX_SWEEP_ANGLE + (starPathNo - 1) * 360 / PATH_COUNT_MAX + beginRandomAngle).setSweepAngle(mRandomInt.nextInt(MAX_SWEEP_ANGLE/6) + 5).build();
           mFallingStarList.add(star);
       }
   }

4.裁剪获得圆形图片的过程,首先需要对原图进行适当的缩放,然后创建画图准备绘制输出后的图片,绘制圆形与缩放后的图片相交,取相交的背景即获得了圆形的图片

    private Bitmap createRoundBitmap(Bitmap inBitmap){
       Bitmap tempBitmap;
       //判断是否需要进行缩放
       if(inBitmap.getWidth() == (2 * mInsideImageRadius) && inBitmap.getHeight() == inBitmap.getWidth()){
           tempBitmap = inBitmap;
       }else {
           tempBitmap = Bitmap.createScaledBitmap(inBitmap, 2*mInsideImageRadius, 2*mInsideImageRadius, false);
       }
       //创建待输出图片的画布
       Bitmap result = Bitmap.createBitmap(tempBitmap.getWidth(), tempBitmap.getHeight(), Bitmap.Config.ARGB_8888);
       Canvas canvas = new Canvas(result);
       //设置画布透明
       canvas.drawColor(0x00FFFFFF);
       Paint paint = new Paint();
       paint.setAntiAlias(true);
       //绘制要裁剪的圆形
       canvas.drawCircle(tempBitmap.getWidth()/2, tempBitmap.getHeight()/2, mInsideImageRadius, paint);
       //设置相交模式
       paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
       Rect rect = new Rect(0, 0, tempBitmap.getWidth(), tempBitmap.getHeight());
       canvas.drawBitmap(tempBitmap, rect, rect, paint);
       canvas.setBitmap(null);
       tempBitmap.recycle();
       return result;
   }

5.对圆形图片设置旋转和初始化流星群组

    @Override
   protected void onDraw(Canvas canvas) {
       //设置画布透明
       canvas.drawARGB(0,0,0,0);
       //绘制中间的圆形图片
       Drawable drawable = getDrawable();
       if(null == drawable){
           return;
       }
       //将ImageView的原图裁剪成圆形图片
       Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
       Bitmap roundBitmap = createRoundBitmap(bitmap);
       //通过Matrix设置圆形Bitmap旋转
       mMatrix.reset();
       mMatrix.setRotate(mRotateAngle);
       //获取旋转后的Bitmap
       Bitmap rotateBitmap = Bitmap.createBitmap(roundBitmap, 0, 0, 2*mInsideImageRadius, 2*mInsideImageRadius, mMatrix, false);
       //在画布上绘制旋转后的Bitmap,注意基于Matrix旋转后的Bitmap与原图的大小并不相等,故计算中心位置时应以转换后的Bitmap进行计算
       canvas.drawBitmap(rotateBitmap, width / 2 - rotateBitmap.getWidth()/2 , height / 2 - rotateBitmap.getHeight()/2, null);
       //绘制流星
       drawFallingStarGroup(canvas);
       //33ms后更新流星位置
       postDelayed(new Runnable() {
           @Override
           public void run() {
               changeFallingStarAngle();
           }
       }, 33);
       //回收过程中Bitmap
       roundBitmap.recycle();
   }

6.初始化流星群组
这步骤的主要目标是使流星群组相对均匀的分布在轨道上,间距始终,避免产生混乱感

    @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);
       width = getMeasuredWidth();
       height = getMeasuredHeight();
       //计算流星最外轨道半径
       mOutSideStarRadius = Math.min(width, height) / 2 * 9 / 10;
       //计算中心原图的半径
       mInsideImageRadius = mOutSideStarRadius * 2 / 3;
       //计算可设置的流星雨最大个数
       PATH_COUNT_MAX = (mOutSideStarRadius - mInsideImageRadius) / PATH_INTERVAL_FALLING_STAR + 1;
       FALLING_STAR_GROUP_SIZE = PATH_COUNT_MAX * 360 / MAX_SWEEP_ANGLE;
       //初始化流星群组
       initFallingStarAngle();
   }
    /**
    * 初始化流星群组的角度和半径参数
    */
   private void initFallingStarAngle(){
       mFallingStarList.clear();
       int starPathNo = 1;
       int beginRandomAngle = mRandomInt.nextInt(360);
       for(int i = 0; i < FALLING_STAR_GROUP_SIZE; i++){
           starPathNo = i / (360 / MAX_SWEEP_ANGLE) + 1;
           FallingStar star = new FallingStar().setRadius(mInsideImageRadius + starPathNo * PATH_INTERVAL_FALLING_STAR)
                   .setStartAngle(i % (360 / MAX_SWEEP_ANGLE) * MAX_SWEEP_ANGLE + (starPathNo - 1) * 360 / PATH_COUNT_MAX + beginRandomAngle).setSweepAngle(mRandomInt.nextInt(MAX_SWEEP_ANGLE/6) + 5).build();
           mFallingStarList.add(star);
       }
   }

学习心得

以上是初步实现的流星雨动效过程,并且实现了流星雨环绕圆形专辑图封面旋转。过程效果还存在部分改进空间,后续会进一步完善。如需要源码,请提供邮箱留言

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-04 12:58:23  更:2021-10-04 12:58:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 21:54:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码