数据结构
public class ShimmerFrameLayout extends FrameLayout{
// enum specity(特殊) the shape(形状) of the highlight mask applied to the contained view
// 内容控件高光蒙版效果的枚举
public enum MaskShape{
LINEAR, // 线性效果
RADIAL // 辐射效果
}
// enum controlling the angle of the highlight mask animation
// 控制高光蒙版动画的角度
public enum MaskAngel{
CW_0, // left to right 水平向右划过
CW_90, // top to bottom 垂直向下落
CW_180, // right to left 水平向左划过
CW_270, // bottom to top 垂直向上冲
}
// struct storing(存储) various(各种) mask related(相关) parameters, which are used to construct the mask bitmap
// 存放各种蒙版参数的类结构,这些参数是用于创建蒙版位图
private static class Mask{
public MaskAngle angle; // 动画角度
public float tilt; // 动画角度偏移
public float dropoff; // 掉落??
public int fixedWidth; // 固定宽度
public int fixedHeight; // 固定高度
public float intensity; // 强度
public float relativeWidth; // 相对宽度
public float relativeHeight; // 相对高度
public MaskShape shape; // 动画形状
}
// struct for storing the mask translation animation values
// 存放蒙版移动动画的起始点信息
private staic class MaskTranslation{
public int fromX;
public int fromY;
public int toX;
public int toY;
}
}
流程步骤
- 在构造方法中初始化参数
- 启动动画(自动启动或主动调用)
- 在动画中刷新参数,并调用invalidate(),触发逻辑
- 在dispatchDraw中,使用一个Bitmap结合子控件内容绘制
构造方法
setWillNotDraw(false);
mAlphaPaint = new Paint();
mMaskPaint = new Paint();
// 初始化
useDefaults();
if (attrs != null) {
// 读取属性
}
动画
/**
* Start the shimmer animation. If the 'auto start' property is set, this method is called automatically when the
* layout is attached to the current window. Calling this method has no effect if the animation is already playing.
* 如果设置为自动开启动画(auto start),这个方法当绑定到窗口之后就会自动调用
*/
public void startShimmerAnimation() {
if (mAnimationStarted) {
return;
}
Animator animator = getShimmerAnimation();
animator.start();
mAnimationStarted = true;
}
/**
* Stop the shimmer animation. Calling this method has no effect if the animation hasn't been started yet.
*/
public void stopShimmerAnimation() {
if (mAnimator != null) {
mAnimator.end();
mAnimator.removeAllUpdateListeners();
mAnimator.cancel();
}
mAnimator = null;
mAnimationStarted = false;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mGlobalLayoutListener == null) {
mGlobalLayoutListener = getLayoutListener();
}
// 绑定到窗口后,等子视图都摆放好后,就会尝试启动动画
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
private ViewTreeObserver.OnGlobalLayoutListener getLayoutListener() {
return new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
boolean animationStarted = mAnimationStarted;
resetAll();
if (mAutoStart || animationStarted) {
startShimmerAnimation();
}
}
};
}
@Override
protected void onDetachedFromWindow() {
stopShimmerAnimation();
if (mGlobalLayoutListener != null) {
getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
mGlobalLayoutListener = null;
}
super.onDetachedFromWindow();
}
动画和绘制
动画改变数据
// Get the shimmer Animator object, which is responsible(负责的) for driving the highlight mask animation.
private Animator getShimmerAnimation() {
if (mAnimator != null) {
return mAnimator;
}
int width = getWidth();
int height = getHeight();
switch (mMask.shape) {
default:
case LINEAR:
switch (mMask.angle) {
default: case CW_0: mMaskTranslation.set(-width, 0, width, 0); break;
case CW_90: mMaskTranslation.set(0, -height, 0, height); break;
case CW_180: mMaskTranslation.set(width, 0, -width, 0); break;
case CW_270: mMaskTranslation.set(0, height, 0, -height); break;
}
}
mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f + (float) mRepeatDelay / mDuration);
mAnimator.setDuration(mDuration + mRepeatDelay);
mAnimator.setRepeatCount(mRepeatCount);
mAnimator.setRepeatMode(mRepeatMode);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = Math.max(0.0f, Math.min(1.0f, (Float) animation.getAnimatedValue()));
setMaskOffsetX((int) (mMaskTranslation.fromX * (1 - value) + mMaskTranslation.toX * value));
setMaskOffsetY((int) (mMaskTranslation.fromY * (1 - value) + mMaskTranslation.toY * value));
}
});
return mAnimator;
}
/**
* Translate the mask offset horizontally. Used by the animator.
*
* @param maskOffsetX Horizontal translation offset of the mask
*/
private void setMaskOffsetX(int maskOffsetX) {
if (mMaskOffsetX == maskOffsetX) {
return;
}
mMaskOffsetX = maskOffsetX;
invalidate(); // 使其重绘
}
/**
* Translate the mask offset vertically. Used by the animator.
*
* @param maskOffsetY Vertical translation offset of the mask
*/
private void setMaskOffsetY(int maskOffsetY) {
if (mMaskOffsetY == maskOffsetY) {
return;
}
mMaskOffsetY = maskOffsetY;
invalidate(); // 使其重绘
}
在dispatchDraw处理绘制工作
@Override
protected void dispatchDraw(Canvas canvas) {
if (!mAnimationStarted || getWidth() <= 0 || getHeight() <= 0) {
super.dispatchDraw(canvas);
return;
}
dispatchDrawUsingBitmap(canvas);
}
/**
* Draws and masks the children using a Bitmap.
*
* @param canvas Canvas that the masked children will end up being drawn to.
*/
private boolean dispatchDrawUsingBitmap(Canvas canvas) {
Bitmap unmaskBitmap = tryObtainRenderUnmaskBitmap();
Bitmap maskBitmap = tryObtainRenderMaskBitmap();
if (unmaskBitmap == null || maskBitmap == null) {
return false;
}
// First draw a desaturated version
drawUnmasked(new Canvas(unmaskBitmap));
canvas.drawBitmap(unmaskBitmap, 0, 0, mAlphaPaint);
// Then draw the masked version
drawMasked(new Canvas(maskBitmap));
canvas.drawBitmap(maskBitmap, 0, 0, null);
return true;
}
最主要的是对Bitmap的处理
/**
* Draws and masks the children using a Bitmap.
*
* @param canvas Canvas that the masked children will end up being drawn to.
*/
private boolean dispatchDrawUsingBitmap(Canvas canvas) {
Bitmap unmaskBitmap = tryObtainRenderUnmaskBitmap(); // 获取一个Bitmap,
Bitmap maskBitmap = tryObtainRenderMaskBitmap(); // 获取一个Bitmap,
if (unmaskBitmap == null || maskBitmap == null) {
return false;
}
// First draw a desaturated version
drawUnmasked(new Canvas(unmaskBitmap)); // 将子控件绘制到临时的画布上
canvas.drawBitmap(unmaskBitmap, 0, 0, mAlphaPaint);// 将子控件绘制到屏幕上
// Then draw the masked version
drawMasked(new Canvas(maskBitmap));
canvas.drawBitmap(maskBitmap, 0, 0, null);
return true;
}
// 试图获取一个Bitmap
private Bitmap tryObtainRenderUnmaskBitmap() {
if (mRenderUnmaskBitmap == null) {
mRenderUnmaskBitmap = tryCreateRenderBitmap();
}
return mRenderUnmaskBitmap;
}
// 试图获取一个Bitmap
private Bitmap tryObtainRenderMaskBitmap() {
if (mRenderMaskBitmap == null) {
mRenderMaskBitmap = tryCreateRenderBitmap();
}
return mRenderMaskBitmap;
}
// 其实就是创建一个和自己一样大的Bitmap
private Bitmap tryCreateRenderBitmap() {
int width = getWidth();
int height = getHeight();
try {
return createBitmapAndGcIfNecessary(width, height);
} catch (OutOfMemoryError e) {
// 打印log
String logMessage = "ShimmerFrameLayout failed to create working bitmap";
StringBuilder logMessageStringBuilder = new StringBuilder(logMessage);
logMessageStringBuilder.append(" (width = ");
logMessageStringBuilder.append(width);
logMessageStringBuilder.append(", height = ");
logMessageStringBuilder.append(height);
logMessageStringBuilder.append(")\n\n");
for (StackTraceElement stackTraceElement :
Thread.currentThread().getStackTrace()) {
logMessageStringBuilder.append(stackTraceElement.toString());
logMessageStringBuilder.append("\n");
}
logMessage = logMessageStringBuilder.toString();
Log.d(TAG, logMessage);
}
return null;
}
// Draws the children without any mask.
// 将子控件绘制到画布上
private void drawUnmasked(Canvas renderCanvas) {
renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
super.dispatchDraw(renderCanvas);
}
// Draws the children and masks them on the given Canvas.
private void drawMasked(Canvas renderCanvas) {
Bitmap maskBitmap = getMaskBitmap();
if (maskBitmap == null) {
return;
}
renderCanvas.clipRect(
mMaskOffsetX,
mMaskOffsetY,
mMaskOffsetX + maskBitmap.getWidth(),
mMaskOffsetY + maskBitmap.getHeight());
renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
super.dispatchDraw(renderCanvas);
renderCanvas.drawBitmap(maskBitmap, mMaskOffsetX, mMaskOffsetY, mMaskPaint);
}
// Return the mask bitmap, creating it if necessary.
private Bitmap getMaskBitmap() {
if (mMaskBitmap != null) {
return mMaskBitmap;
}
int width = mMask.maskWidth(getWidth());
int height = mMask.maskHeight(getHeight());
mMaskBitmap = createBitmapAndGcIfNecessary(width, height);
Canvas canvas = new Canvas(mMaskBitmap);
Shader gradient;
switch (mMask.shape) {
default:
case LINEAR: {
int x1, y1;
int x2, y2;
switch (mMask.angle) {
default:
case CW_0:
x1 = 0;
y1 = 0;
x2 = width;
y2 = 0;
break;
case CW_90:
x1 = 0;
y1 = 0;
x2 = 0;
y2 = height;
break;
case CW_180:
x1 = width;
y1 = 0;
x2 = 0;
y2 = 0;
break;
case CW_270:
x1 = 0;
y1 = height;
x2 = 0;
y2 = 0;
break;
}
gradient =
new LinearGradient(
x1, y1,
x2, y2,
mMask.getGradientColors(),
mMask.getGradientPositions(),
Shader.TileMode.REPEAT);
break;
}
case RADIAL: {
int x = width / 2;
int y = height / 2;
gradient =
new RadialGradient(
x,
y,
(float) (Math.max(width, height) / Math.sqrt(2)),
mMask.getGradientColors(),
mMask.getGradientPositions(),
Shader.TileMode.REPEAT);
break;
}
}
canvas.rotate(mMask.tilt, width / 2, height / 2);
Paint paint = new Paint();
paint.setShader(gradient);
// We need to increase the rect size to account for the tilt
int padding = (int) (Math.sqrt(2) * Math.max(width, height)) / 2;
canvas.drawRect(-padding, -padding, width + padding, height + padding, paint);
return mMaskBitmap;
}