炫酷安卓垃圾回收动画

貌似很久没有更新博客了。最近一直忙着实习、毕业设计。总之好多事,不过还好,毕业设计也差不多写完了。但是论文还没写。。。

首先附上代码链接:

https://github.com/RuijiePan/FileManager.git

这是我毕设里面的一个自定义view。欢迎大家star,fork。谢谢。

还是进入正题吧。因为毕设做的是文件管理器,一开始做完文件管理器之后,加了进程清理功能,还加了垃圾扫描功能。然后在垃圾完成页,自己就想添加一下结束页面垃圾回收的效果。因为现在在公司参与的项目是ace cleaner和go speed。发现ace cleaner垃圾完成页的效果还不错,便想着自己也模仿一下。先上下效果图吧。

这里写图片描述
从图我们可以看到,动画可以拆分为4部分:
1.垃圾箱渐变显示
2.icon从左右两边向中间回收。轨迹为曲线
3.垃圾桶摇晃
4.垃圾桶渐变消失

在这里。我主要讲解第二个动画、简单讲解一下第三个动画。第一和第四个动画我就不概述了。看了代码都知道是渐变加位移两个组合动画而已。

第二个动画主要用到了二阶贝塞尔曲线。
首先获取屏幕宽高mWidth,mHeight;
首先给icon设置一个初始化位置,还有结束的位置。如果icon从左边进入,那么初始化位置就是(0,0).如果从右边进入,那么就是屏幕宽度(mWidth,0)。结束点在屏幕中间(mWidth/2+mIcon.getWidth/2, mHeight)。那么,剩下的,就是我们的控制点了。由图我们可以看出,每个icon的轨迹都是不一样的。那么,我们需要给每个icon一个不一样的control点。我的取值范围是controlX = mWidth /2 ,controlY = [0, mHeight / 2]。有人就会问了。control的点从哪里计算的。这就需要你对二阶贝塞尔曲线比较熟悉了。还要一定的数学功底。我后续再附上我的一份手稿图,帮助大家理清control点变化的时候轨迹的变化图。

通过贝塞尔曲线,只是模拟出了icon的轨迹,我们可以看到,icon在移动的过程中,有渐变,还有旋转。那要做到这点,我们首先得获取icon在指定一帧的位置和旋转角度是吧。
那么这个就需要用到 PathMeasure了。
由贝塞尔曲线我们可以获取icon移动的路径path。那么通过 PathMeasure,我们可以获取path的长度,然后可以通过传入当前指定长度的值,获取当前点的所在位置。还有切角。
举个例子,还是刚才icon的移动轨迹。假如从初始点到结束点,它们的path路径长度是2。那么当我传入参数1的时候,我可以获取它中间点的坐标,还有中间点的切角大小。

由源码可知道:我们只需传入一个distance。

/**
     * Pins distance to 0 <= distance <= getLength(), and then computes the
     * corresponding position and tangent. Returns false if there is no path,
     * or a zero-length path was specified, in which case position and tangent
     * are unchanged.
     *
     * @param distance The distance along the current contour to sample
     * @param pos If not null, eturns the sampled position (x==[0], y==[1])
     * @param tan If not null, returns the sampled tangent (x==[0], y==[1])
     * @return false if there was no path associated with this measure object
    */
    public boolean getPosTan(float distance, float pos[], float tan[]) {
        if (pos != null && pos.length < 2 ||
            tan != null && tan.length < 2) {
            throw new ArrayIndexOutOfBoundsException();
        }
        return native_getPosTan(native_instance, distance, pos, tan);
    }

获取到切角之后,要转化成角度。还要经过数学计算
首先设置获取到的切角为tan。对应的分别为tan[0]、tan[1]
那么当前轨迹的弧度为Math.atan2(tan[1], tan[0])。
然后再将弧度转为角度,就是:
Math.atan2(tan[1], tan[0]) * 180 / Math.PI。
不熟悉弧度转角度公式的,可以自己查一下。
有了这些参数,我们可以很容易地将icon画出来了。

第三个动画:垃圾桶晃动。
这个其实没啥,首先我们通过
ValueAnimator shakeAnimator = ObjectAnimator.ofFloat(0, -1f, 0, 1f, 0);获取动画进行的如何了。
0->-1f。代表垃圾桶向左。-1f ->0代表垃圾桶向右。同理。。。由图片我们可以看出,垃圾桶摇晃的时候是按着垃圾桶的中下部摇曳的。

那么如果是对matrix的普通旋转,我们都知道是对着(0,0)旋转。这显然不是我们想要的。所以先把矩阵移动。然后再旋转。这样,就可以对着我们指定的点进行旋转了。还有,旋转完之后,记得要将位置复原。

mMatrix.postTranslate(-mBitmapWidth / 2, -mBitmapHeight / 3);
mMatrix.postRotate(mThirdAnimatorProcess * mShakeDrgree);
mMatrix.postTranslate(mBitmapWidth / 2, mBitmapHeight / 3);

分析到这里,基本就完成了。下面附上代码:

/**
 * Created by panruijie on 2017/3/6.
 * Email : zquprj@gmail.com
 * junk清理完成之后显示的垃圾桶view
 */

public class DustbinView extends View {

    private static final int DEFAULT_FADE_TIME = 1000;
    private static final int DEFAULT_RECYCLER_TIME = 1000;
    private static final int DEFAULT_SHAKE_TIME = 300;
    private static final int DEFAULT_SHAKE_DEGREE = 10;
    private static final int DEFAULT_SHAKE_COUNT = 2;
    private int mFadeTime = DEFAULT_FADE_TIME;
    private int mRecyclerTime = DEFAULT_RECYCLER_TIME;
    private int mShakeDrgree = DEFAULT_SHAKE_DEGREE;
    private int mShakeTime = DEFAULT_SHAKE_TIME;
    private int mShakeCount = DEFAULT_SHAKE_COUNT;
    private int mWidth;
    private int mHeight;
    private int mBitmapWidth;
    private int mBitmapHeight;
    private int mDy;
    private int mAlpha;
    private float mFirstAnimatorProcess;
    private float mSecondAnimatorProcess;
    private float mThirdAnimatorProcess;
    private float mFourAnimatorProcess;
    private boolean mIsFisrtAnimationFinish;
    private boolean mIsSecondAnimationFinish;
    private boolean mIsThirdAnimationFinish;
    private boolean mIsFourAnimationFinish;
    private long mAnimationStartTime;
    private Bitmap mDustbinBitmap;
    private IconInfo[] mIconInfos;
    private Matrix mMatrix;
    private Matrix mIconMatrix;
    private Paint mBitmapPaint;
    private Paint[] mProcessPaint;
    private AnimatorSet mAnimatorSetFirst;
    private AnimatorSet mAnimatorSetFour;
    private IAnimationListener mListener;
    private int[] mOtherDrawable = {
            R.drawable.type_unknown, R.drawable.type_note,
            R.drawable.type_config, R.drawable.type_html,
            R.drawable.type_xml, R.drawable.type_note,
            R.drawable.type_pdf, R.drawable.type_package,
            R.drawable.type_pic, R.drawable.type_music,
            R.drawable.type_video, R.drawable.type_apk
    };

    public DustbinView(Context context) {
        this(context, null);
    }

    public DustbinView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DustbinView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {

        //获取垃圾桶参数
        mDustbinBitmap = BitmapUtil.getBitmapFromRes(context, R.drawable.icon_junk_dustbin);
        mBitmapWidth = mDustbinBitmap.getWidth();
        mBitmapHeight = mDustbinBitmap.getHeight();
        mMatrix = new Matrix();
        mIconMatrix = new Matrix();

        //初始化进程图标初始化位置
        List<AppProcessInfo> mProcessList = ProcessManager.getInstance().getRunningProcessList();
        mIconInfos = new IconInfo[mProcessList.size() + mOtherDrawable.length];
        for (int i = 0; i < mProcessList.size(); i++) {
            mIconInfos[i] = new IconInfo(BitmapUtil.getBitmapFromDrawable(
                    BitmapUtil.zoomDrawable(mProcessList.get(i).getIcon(), 100, 100)));
        }
        for (int i = 0; i < mOtherDrawable.length; i++) {
            mIconInfos[i + mProcessList.size()] = new IconInfo(BitmapUtil.getBitmapFromDrawable(
                    BitmapUtil.zoomDrawable(getResources().getDrawable(mOtherDrawable[i]), 100, 100)));
        }

        //画笔
        mBitmapPaint = new Paint();
        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setDither(true);
        mBitmapPaint.setFilterBitmap(true);
        mBitmapPaint.setColor(Color.WHITE);

        mProcessPaint = new Paint[mProcessList.size() + mOtherDrawable.length];
        for (int i = 0; i < mProcessPaint.length; i++) {
            mProcessPaint[i] = new Paint();
            mProcessPaint[i].setAntiAlias(true);
            mProcessPaint[i].setDither(true);
            mProcessPaint[i].setFilterBitmap(true);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        //画垃圾桶
        canvas.translate(mWidth / 2 - mBitmapWidth / 2, mHeight / 2 - mBitmapHeight / 2);
        drawDustbin(canvas);
        canvas.restore();

        drawIcon(canvas);
        if (!mIsFourAnimationFinish) {
            invalidate();
        }
    }

    private void drawDustbin(Canvas canvas) {
        calculatePointAndFade();
        canvas.drawBitmap(mDustbinBitmap, mMatrix, mBitmapPaint);
        mMatrix.reset();
    }

    private void calculatePointAndFade() {

        if (!mIsFisrtAnimationFinish) {
            mDy = (int) ((1.0f - mFirstAnimatorProcess) * mHeight / 2);
            mAlpha = (int) (mFirstAnimatorProcess * 255);
        } else if (!mIsSecondAnimationFinish) {
            mDy = 0;
            mAlpha = 255;
        } else if (!mIsThirdAnimationFinish) {
            mDy = 0;
            mAlpha = 255;
            mMatrix.postTranslate(-mBitmapWidth / 2, -mBitmapHeight / 3);
            mMatrix.postRotate(mThirdAnimatorProcess * mShakeDrgree);
            mMatrix.postTranslate(mBitmapWidth / 2, mBitmapHeight / 3);
        } else if (!mIsFourAnimationFinish) {
            mDy = (int) ((1.0f - mFourAnimatorProcess) * mHeight / 2);
            mAlpha = (int) (mFourAnimatorProcess * 255);
        }
        mBitmapPaint.setAlpha(mAlpha);
        mMatrix.postTranslate(0, mDy);
    }

    private void drawIcon(Canvas canvas) {

        if (mIsFisrtAnimationFinish && !mIsSecondAnimationFinish) {
            long currentTime = System.currentTimeMillis();
            int i = -1;
            for (IconInfo info : mIconInfos) {
                i++;
                long time = currentTime - mFadeTime - mAnimationStartTime - info.getDelayTime();
                if (time > 0 && time < mRecyclerTime) {
                    Path path = new Path();
                    path.moveTo(info.startX, info.startY);
                    path.quadTo(info.getControlPoint().x, info.getControlPoint().y, mWidth / 2 + info.getBitmap().getWidth() / 2, mHeight / 2);
                    PathMeasure pathMeasure = new PathMeasure(path, false);

                    info.alpha = 255 - (int) (time * 255 / mRecyclerTime);
                    float[] pos = new float[2];
                    float[] tan = new float[2];
                    pathMeasure.getPosTan(time * pathMeasure.getLength() / mRecyclerTime, pos, tan);
                    info.setX((int) pos[0]);
                    info.setY((int) pos[1]);
                    info.setRotateAngle(Math.atan2(tan[1], tan[0]) * 180 / Math.PI);
                    mProcessPaint[i].setAlpha(info.getAlpha());

                    //先旋转再移动
                    mIconMatrix.postRotate((float) info.getRotateAngle());
                    mIconMatrix.postTranslate(info.getX(), info.getY());
                    canvas.drawBitmap(info.getBitmap(), mIconMatrix, mProcessPaint[i]);
                    //canvas.drawPath(path, mProcessPaint);
                    mIconMatrix.reset();
                }
            }
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    public void startAnimation() {
        mIsFisrtAnimationFinish = false;
        mIsSecondAnimationFinish = false;
        mIsThirdAnimationFinish = false;
        mIsFourAnimationFinish = false;
        mAnimationStartTime = System.currentTimeMillis();

        ValueAnimator fadeInAnimator = ObjectAnimator.ofFloat(0, 1f);
        ValueAnimator fadeOutAnimator = ObjectAnimator.ofFloat(1f, 0);
        ValueAnimator translateInAnimator = ObjectAnimator.ofFloat(1f, 0);
        ValueAnimator translateOutAnimator = ObjectAnimator.ofFloat(0, 1f);
        ValueAnimator recyclerAnimator = ObjectAnimator.ofFloat(0, 1f);
        ValueAnimator shakeAnimator = ObjectAnimator.ofFloat(0, -1f, 0, 1f, 0);

        fadeInAnimator.setDuration(mFadeTime);
        fadeOutAnimator.setDuration(mFadeTime);
        translateInAnimator.setDuration(mFadeTime);
        translateOutAnimator.setDuration(mFadeTime);
        recyclerAnimator.setDuration(2 * mRecyclerTime);
        shakeAnimator.setDuration(mShakeTime);

        //垃圾桶显示
        mAnimatorSetFirst = new AnimatorSet();
        mAnimatorSetFirst.play(fadeInAnimator)
                .with(translateInAnimator);

        mAnimatorSetFirst.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (mListener != null) {
                    mListener.onDustbinFadeIn();
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mIsFisrtAnimationFinish = true;
                if (mListener != null) {
                    mListener.onProcessRecycler();
                }
                recyclerAnimator.start();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        fadeInAnimator.addUpdateListener(animation -> {
            mFirstAnimatorProcess = (float) animation.getAnimatedValue();
        });

        //icon回收动画
        recyclerAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (mListener != null) {
                    mListener.onProcessRecycler();
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mIsSecondAnimationFinish = true;
                shakeAnimator.start();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        recyclerAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mSecondAnimatorProcess = (float) animation.getAnimatedValue();
            }
        });

        //垃圾桶摇晃动画
        shakeAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (mListener != null) {
                    mListener.onShakeDustbin();
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mIsThirdAnimationFinish = true;
                mAnimatorSetFour.start();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        shakeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mThirdAnimatorProcess = (float) animation.getAnimatedValue();
            }
        });
        shakeAnimator.setRepeatCount(mShakeCount);

        //垃圾桶淡出
        mAnimatorSetFour = new AnimatorSet();
        mAnimatorSetFour.play(fadeOutAnimator)
                .with(translateOutAnimator);

        mAnimatorSetFour.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (mListener != null) {
                    mListener.onDustbinFadeOut();
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (mListener != null) {
                    mListener.onAnimationFinish();
                }
                mIsFourAnimationFinish = true;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        fadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mFourAnimatorProcess = (float) animation.getAnimatedValue();
            }
        });

        mAnimatorSetFirst.start();
    }

    public DustbinView setFadeTime(int fadeTime) {
        this.mFadeTime = fadeTime;
        return this;
    }

    public DustbinView setRecyclerTime(int recyclerTime) {
        this.mRecyclerTime = recyclerTime;
        return this;
    }

    public DustbinView setShakeTime(int mShakeTime) {
        this.mShakeTime = mShakeTime;
        return this;
    }

    public DustbinView setShakeCount(int mShakeCount) {
        this.mShakeCount = mShakeCount;
        return this;
    }

    public DustbinView setShakeDegree(int shakeDegree) {
        this.mShakeDrgree = shakeDegree;
        return this;
    }

    public void setAnimationListener(IAnimationListener mListener) {
        this.mListener = mListener;
    }

    public interface IAnimationListener {

        void onDustbinFadeIn();

        void onProcessRecycler();

        void onShakeDustbin();

        void onDustbinFadeOut();

        void onAnimationFinish();
    }

    private class IconInfo {

        private int x;
        private int y;
        private int startX;
        private int startY;
        private Bitmap bitmap;
        private int alpha;
        private int delayTime;
        private int direction; //0代表从左到右
        private double rotateAngle;
        private Point controlPoint;
        private PathMeasure pathMeasure;

        public IconInfo(Bitmap bitmap) {
            this.bitmap = bitmap;
            Random random = new Random();
            int nextRandom = random.nextInt(mRecyclerTime);
            this.startY = 0;
            this.alpha = 255;
            this.rotateAngle = 0;
            this.delayTime = nextRandom;
            nextRandom = random.nextInt(2);
            if (nextRandom == 0) {
                direction = 0;
                startX = 0;
            } else {
                direction = 1;
                startX = App.sWidth;
            }
            controlPoint = new Point();
            controlPoint.x = App.sWidth / 2;
            controlPoint.y = (int) (random.nextFloat() * 0.5 * App.sHeight);
        }

        public Point getControlPoint() {
            return controlPoint;
        }

        public void setControlPoint(Point controlPoint) {
            this.controlPoint = controlPoint;
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public Bitmap getBitmap() {
            return bitmap;
        }

        public void setBitmap(Bitmap bitmap) {
            this.bitmap = bitmap;
        }

        public int getAlpha() {
            return alpha;
        }

        public void setAlpha(int alpha) {
            this.alpha = alpha;
        }

        public int getDelayTime() {
            return delayTime;
        }

        public void setDelayTime(int delayTime) {
            this.delayTime = delayTime;
        }

        public int getDirection() {
            return direction;
        }

        public void setDirection(int direction) {
            this.direction = direction;
        }

        public double getRotateAngle() {
            return rotateAngle;
        }

        public void setRotateAngle(double rotateAngle) {
            this.rotateAngle = rotateAngle;
        }

    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值