貌似很久没有更新博客了。最近一直忙着实习、毕业设计。总之好多事,不过还好,毕业设计也差不多写完了。但是论文还没写。。。
首先附上代码链接:
这是我毕设里面的一个自定义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;
}
}
}