【转载请注明出处】
笔者:DrkCore (http://blog.csdn.net/DrkCore)
原文链接:(http://blog.csdn.net/drkcore/article/details/52664088)
守望先锋作为当下最有火爆的射击游戏之一为我们带了一波网络用语的节奏,你要是不会一句 “溜金哇开呀酷裂” 都不好意思说自己玩过屁股。作为 Android 狗的笔者最近想写个 Loading 界面掌握一些新姿势,索性实现一下守望先锋游戏界面的加载图吧。
原版效果如下:
该截图具有治疗颈椎病的效果,所以请不要在意这5毛画质。最后实现的效果则如下图:
主要用到的都是自定义 View、ValueAnimator 等基本的 Android 知识,稍微花点时间都能实现出来的。
从网络上找一张清晰度高一点的守望先锋 ICON 配合 PS 我们能够很容易地得到图标上的点和角度数据,之后只需要用 Canvas绘制出来即可:
private void initIcon() {
//ICON半径
iconRadius = dpToPx(DEFAULT_ICON_RADIUS_DP);
iconWidth = iconRadius * (87 / 300F);
iconGapWidth = iconRadius * (20 / 300F);
//按比例算出图标内部图案的每个点的坐标
iconCornerPoints = new float[10];
iconCornerPoints[0] = iconRadius * ((300 - 71) / 300F);
iconCornerPoints[1] = iconRadius * (450 / 300F);
iconCornerPoints[2] = iconRadius * ((300 - 233) / 300F);
iconCornerPoints[3] = iconRadius * (295 / 300F);
iconCornerPoints[4] = iconRadius * ((300 - 287) / 300F);
iconCornerPoints[5] = iconRadius * (165 / 300F);
iconCornerPoints[6] = iconRadius * ((300 - 287) / 300F);
iconCornerPoints[7] = iconRadius * (361 / 300F);
iconCornerPoints[8] = iconRadius * ((300 - 136) / 300F);
iconCornerPoints[9] = iconRadius * (505 / 300F);
}
private Bitmap iconBmp;
private void drawIcon(Canvas canvas) {
float left = centerX - iconRadius;
float top = centerY - iconRadius;
float right = centerX + iconRadius;
float bottom = centerY + iconRadius;
iconRectF.set(left, top, right, bottom);
//缓存绘制结果
if (iconBmp == null) {
iconBmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas bmpCanvas = new Canvas(iconBmp);
doDrawIcon(bmpCanvas);
}
canvas.drawBitmap(iconBmp, 0, 0, null);
}
private void doDrawIcon(Canvas canvas) {
//标记图层
int saveCount = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
//绘制灰圈
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(iconWidth);
paint.setColor(iconColor);
canvas.drawArc(iconRectF, 0, 360, false, paint);
//抹去交界线
paint.setXfermode(dstOutMode);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(iconGapWidth);
paint.setColor(Color.RED);
canvas.rotate(40, centerX, centerY);
canvas.drawLine(centerX, centerY, centerX, 0, paint);
canvas.rotate(-80, centerX, centerY);
canvas.drawLine(centerX, centerY, centerX, 0, paint);
paint.setXfermode(null);
//叠加图层
canvas.restoreToCount(saveCount);
//绘制中心的两个角
iconCornerLeftPath.rewind();
iconCornerRightPath.rewind();
float tmpX, tmpY;
for (int i = 0, len = iconCornerPoints.length; i < len; i += 2) {
tmpX = iconCornerPoints[i];
tmpY = iconCornerPoints[i + 1];
tmpY += centerY - iconRadius;
if (iconCornerLeftPath.isEmpty()) {
iconCornerLeftPath.moveTo(centerX - tmpX, tmpY);
iconCornerRightPath.moveTo(centerX + tmpX, tmpY);
} else {
iconCornerLeftPath.lineTo(centerX - tmpX, tmpY);
iconCornerRightPath.lineTo(centerX + tmpX, tmpY);
}
}
iconCornerLeftPath.close();
iconCornerRightPath.close();
paint.setColor(iconColor);
canvas.drawPath(iconCornerLeftPath, paint);
canvas.drawPath(iconCornerRightPath, paint);
}
因为图标的部分是静态的我们可以将之用 Bitmap 缓存起来,这样可以避免在多次绘制中造成的内存抖动。
我们看到的 Loading 效果都是动态的,这部分主要用成员变量和 ValueAnimator 来实现。
private float middleWidth;
private float middleRadius;
private RectF middleRectF = new RectF();
private int middleBgColor = Color.argb(128, 227, 165, 4);
private int middleColor = Color.rgb(227, 165, 4);
private float middleAngel_1 = 116;
private float middleAngel_2 = -74;
private float middleAngel_3 = 45;
private float middleStartAngel = 0;
private float middleSwipeAngel = 300;
private void initMiddleStroke() {
middleWidth = iconWidth * 2 / 3;
middleRadius = iconRadius + iconWidth / 2 + middleWidth;
}
private Animator[] prepareMiddleStrokeAnimator() {
ValueAnimator[] anim = new ValueAnimator[5];
anim[0] = ValueAnimator.ofFloat(0, 360);
anim[0].setRepeatCount(ValueAnimator.INFINITE);
anim[0].setDuration(1500);
anim[0].setInterpolator(new LinearInterpolator());
anim[0].addUpdateListener(animation -> {
middleStartAngel = (float) animation.getAnimatedValue();
invalidate();
});
anim[1] = ValueAnimator.ofFloat(0, 320);
anim[1].setRepeatMode(ValueAnimator.REVERSE);
anim[1].setRepeatCount(ValueAnimator.INFINITE);
anim[1].setDuration(1500);
anim[1].setInterpolator(new AccelerateDecelerateInterpolator());
anim[1].addUpdateListener(animation -> {
middleSwipeAngel = (float) animation.getAnimatedValue();
invalidate();
});
anim[2] = ValueAnimator.ofFloat(middleAngel_1, middleAngel_1 + 360);
anim[2].setRepeatCount(ValueAnimator.INFINITE);
anim[2].setDuration(1500);
anim[2].setInterpolator(new LinearInterpolator());
anim[2].addUpdateListener(animation -> {
middleAngel_1 = -(float) animation.getAnimatedValue();
invalidate();
});
anim[3] = ValueAnimator.ofFloat(middleAngel_2, middleAngel_2 + 360);
anim[3].setRepeatCount(ValueAnimator.INFINITE);
anim[3].setDuration(1500);
anim[3].setInterpolator(new LinearInterpolator());
anim[3].addUpdateListener(animation -> {
middleAngel_2 = (float) animation.getAnimatedValue();
invalidate();
});
anim[4] = ValueAnimator.ofFloat(middleAngel_3, middleAngel_3 + 360);
anim[4].setRepeatCount(ValueAnimator.INFINITE);
anim[4].setDuration(1500);
anim[4].setInterpolator(new LinearInterpolator());
anim[4].addUpdateListener(animation -> {
middleAngel_3 = -(float) animation.getAnimatedValue();
invalidate();
});
return anim;
}
private void drawMiddleStroke(Canvas canvas) {
float left = centerX - middleRadius;
float top = centerY - middleRadius;
float right = centerX + middleRadius;
float bottom = centerY + middleRadius;
middleRectF.set(left, top, right, bottom);
paint.setColor(middleBgColor);
paint.setStrokeWidth(middleWidth);
paint.setStyle(Paint.Style.STROKE);
canvas.drawArc(middleRectF, middleStartAngel, middleSwipeAngel, false, paint);
paint.setColor(middleColor);
canvas.drawArc(middleRectF, middleAngel_1, 10, false, paint);
canvas.drawArc(middleRectF, middleAngel_2, 54, false, paint);
canvas.drawArc(middleRectF, middleAngel_3, 40, false, paint);
}
ValueAnimator 能够动态改变属性值并且我们在之中调用 invalidate(),最后就能得到动画的效果了。
源码请参见我的 GitHub:
DrkCore的GitHub