前言
Android
开发中,加载等待的需求 非常常见- 本文将手把手教你做 一款 可爱 & 小资风格的加载等待
Android
自定义View
控件,希望你们会喜欢。
已在Github
开源:Kawaii_LoadingView,欢迎 Star
!
目录
1. 简介
一款 可爱 、清新 & 小资风格的 Android
自定义View
控件
已在Github
开源:Kawaii_LoadingView,欢迎 Star
!
2. 应用场景
App
长时间加载等待时,用于提示用户进度 & 缓解用户情绪
3. 特点
对比市面上的加载等待自定义控件,该控件Kawaii_LoadingView
的特点是:
3.1 样式清新
- 对比市面上 各种酷炫、眼花缭乱的加载等待自定义控件,该款
Kawaii_LoadingView
的 清新 & 小资风格 简直是一股清流 - 同时,可根据您的
App
定位 & 主色进行颜色调整,使得控件更加符合App
的形象。具体如下:
3.2 使用简单
仅需要3步骤 & 配置简单。
具体请看文章:Android开源控件:一款你不可错过的可爱 & 小资风格的加载等待自定义View
3.3 二次开发成本低
所以,在其上做二次开发 & 定制化成本非常低。
4. 具体使用
具体请看文章:http://blog.csdn.net/carson_ho/article/details/77711952
5. 完整Demo地址
Carson_Ho的Github地址:Kawaii_LoadingView_TestDemo
6. 源码分析
下面,我将手把手教你如何实现这款 可爱 & 小资风格的加载等待android
自定义View
控件
6.1 准备说明
6.2 动画原理
- 隐藏固定的2个方块 & 移动方块继承其中1个的位置
注:只有外部方块运动
- 通过 属性动画 (平移 + 旋转 = 组合动画)改变移动方块的位置 & 旋转角度
- 通过调用
invalidate()
重新绘制,从而实现动态的动画效果 - 具体原理图如下:
6.3 实现步骤
下面我将详细介绍每个步骤:
步骤1:初始化动画属性
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Kawaii_LoadingView">
<attr name="half_BlockWidth" format="dimension" />
<attr name="blockInterval" format="dimension" />
<attr name="initPosition" format="integer" />
<attr name="isClock_Wise" format="boolean" />
<attr name="lineNumber" format="integer" />
<attr name="moveSpeed" format="integer" />
<attr name="blockColor" format="color" />
<attr name="moveBlock_Angle" format="float" />
<attr name="fixBlock_Angle" format="float" />
<attr name="move_Interpolator" format="reference" />
</declare-styleable>
</resources>
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Kawaii_LoadingView);
lineNumber = typedArray.getInteger(R.styleable.Kawaii_LoadingView_lineNumber, 3);
if (lineNumber < 3) {
lineNumber = 3;
}
half_BlockWidth = typedArray.getDimension(R.styleable.Kawaii_LoadingView_half_BlockWidth, 30);
blockInterval = typedArray.getDimension(R.styleable.Kawaii_LoadingView_blockInterval, 10);
moveBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_moveBlock_Angle, 10);
fixBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_fixBlock_Angle, 30);
int defaultColor = context.getResources().getColor(R.color.colorAccent);
blockColor = typedArray.getColor(R.styleable.Kawaii_LoadingView_blockColor, defaultColor);
initPosition = typedArray.getInteger(R.styleable.Kawaii_LoadingView_initPosition, 0);
if (isInsideTheRect(initPosition, lineNumber)) {
initPosition = 0;
}
isClock_Wise = typedArray.getBoolean(R.styleable.Kawaii_LoadingView_isClock_Wise, true);
moveSpeed = typedArray.getInteger(R.styleable.Kawaii_LoadingView_moveSpeed, 250);
int move_InterpolatorResId = typedArray.getResourceId(R.styleable.Kawaii_LoadingView_move_Interpolator,
android.R.anim.linear_interpolator);
move_Interpolator = AnimationUtils.loadInterpolator(context, move_InterpolatorResId);
mCurrEmptyPosition = initPosition;
typedArray.recycle();
}
/**
* 关注1:判断方块是否在内部
*/
private boolean isInsideTheRect(int pos, int lineCount) {
if (pos < lineCount) {
return false;
} else if (pos > (lineCount * lineCount - 1 - lineCount)) {
return false;
} else if ((pos + 1) % lineCount == 0) {
return false;
} else if (pos % lineCount == 0) {
return false;
}
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
步骤2:初始化方块对象 & 之间的关系
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(blockColor);
initBlocks(initPosition);
}
/**
* 关注1
* 初始化方块对象、之间的关系
* 参数说明:initPosition = 移动方块的初始位置
*/
private void initBlocks(int initPosition) {
mfixedBlocks = new fixedBlock[lineNumber * lineNumber];
for (int i = 0; i < mfixedBlocks.length; i++) {
mfixedBlocks[i] = new fixedBlock();
mfixedBlocks[i].index = i;
mfixedBlocks[i].isShow = initPosition == i ? false : true;
mfixedBlocks[i].rectF = new RectF();
}
mMoveBlock = new MoveBlock();
mMoveBlock.rectF = new RectF();
mMoveBlock.isShow = false;
relate_OuterBlock(mfixedBlocks, isClock_Wise);
}
/**
* 关注2:固定方块 类(内部类)
*/
private class fixedBlock {
RectF rectF;
int index;
boolean isShow;
fixedBlock next;
}
/**
* 关注3
*:移动方块类(内部类)
*/
private class MoveBlock {
RectF rectF;
int index;
boolean isShow;
float cx;
float cy;
}
/**
* 关注4:将外部方块的位置关联起来
* 算法思想: 按照第1行、最后1行、第1列 & 最后1列的顺序,分别让每个外部方块的next属性 == 下一个外部方块的位置,最终对整个外部方块的位置进行关联
* 注:需要考虑移动方向变量isClockwise( 顺 Or 逆时针)
*/
private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClockwise) {
int lineCount = (int) Math.sqrt(fixedBlocks.length);
for (int i = 0; i < lineCount; i++) {
if (i % lineCount == 0) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i + 1];
} else if ((i + 1) % lineCount == 0) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + lineCount];
} else {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + 1];
}
}
for (int i = (lineCount - 1) * lineCount; i < lineCount * lineCount; i++) {
if (i % lineCount == 0) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
} else if ((i + 1) % lineCount == 0) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
} else {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - 1];
}
}
for (int i = 1 * lineCount; i <= (lineCount - 1) * lineCount; i += lineCount) {
if (i == (lineCount - 1) * lineCount) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
continue;
}
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i - lineCount];
}
for (int i = 2 * lineCount - 1; i <= lineCount * lineCount - 1; i += lineCount) {
if (i == lineCount * lineCount - 1) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
continue;
}
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i + lineCount];
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
步骤3:设置方块初始位置
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
int cx = measuredWidth / 2;
int cy = measuredHeight / 2;
fixedBlockPosition(mfixedBlocks, cx, cy, blockInterval, half_BlockWidth);
MoveBlockPosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise);
}
/**
* 关注1:设置 固定方块位置
*/
private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int cy, float dividerWidth, float halfSquareWidth) {
float squareWidth = halfSquareWidth * 2;
int lineCount = (int) Math.sqrt(fixedBlocks.length);
float firstRectLeft = 0;
float firstRectTop = 0;
if (lineCount % 2 == 0) {
int squareCountInAline = lineCount / 2;
int diviCountInAline = squareCountInAline - 1;
float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
+ diviCountInAline * dividerWidth
+ dividerWidth / 2;
firstRectLeft = cx - firstRectLeftTopFromCenter;
firstRectTop = cy - firstRectLeftTopFromCenter;
} else {
int squareCountInAline = lineCount / 2;
int diviCountInAline = squareCountInAline;
float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
+ diviCountInAline * dividerWidth
+ halfSquareWidth;
firstRectLeft = cx - firstRectLeftTopFromCenter;
firstRectTop = cy - firstRectLeftTopFromCenter;
firstRectLeft = cx - firstRectLeftTopFromCenter;
firstRectTop = cy - firstRectLeftTopFromCenter;
}
for (int i = 0; i < lineCount; i++) {
for (int j = 0; j < lineCount; j++) {
if (i == 0) {
if (j == 0) {
fixedBlocks[0].rectF.set(firstRectLeft, firstRectTop,
firstRectLeft + squareWidth, firstRectTop + squareWidth);
} else {
int currIndex = i * lineCount + j;
fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - 1].rectF);
fixedBlocks[currIndex].rectF.offset(dividerWidth + squareWidth, 0);
}
} else {
int currIndex = i * lineCount + j;
fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - lineCount].rectF);
fixedBlocks[currIndex].rectF.offset(0, dividerWidth + squareWidth);
}
}
}
}
/**
* 关注2:设置移动方块的位置
*/
private void MoveBlockPosition(fixedBlock[] fixedBlocks,
MoveBlock moveBlock, int initPosition, boolean isClockwise) {
fixedBlock fixedBlock = fixedBlocks[initPosition];
moveBlock.rectF.set(fixedBlock.next.rectF);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
步骤4:绘制方块
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < mfixedBlocks.length; i++) {
if (mfixedBlocks[i].isShow) {
canvas.drawRoundRect(mfixedBlocks[i].rectF, fixBlock_Angle, fixBlock_Angle, mPaint);
}
}
if (mMoveBlock.isShow) {
canvas.rotate(isClock_Wise ? mRotateDegree : -mRotateDegree, mMoveBlock.cx, mMoveBlock.cy);
canvas.drawRoundRect(mMoveBlock.rectF, moveBlock_Angle, moveBlock_Angle, mPaint);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
步骤5:设置动画
实现该动画的步骤包括:设置平移动画、旋转动画 & 组合动画。
1.设置平移动画
private ValueAnimator createTranslateValueAnimator(fixedBlock currEmptyfixedBlock,
fixedBlock moveBlock) {
float startAnimValue = 0
float endAnimValue = 0
PropertyValuesHolder left = null
PropertyValuesHolder top = null
// 1. 设置移动速度
ValueAnimator valueAnimator = new ValueAnimator().setDuration(moveSpeed)
// 2. 设置移动方向
// 情况分为:4种,分别是移动方块向左、右移动 和 上、下移动
// 注:需考虑 旋转方向(isClock_Wise),即顺逆时针 ->>关注1
if (isNextRollLeftOrRight(currEmptyfixedBlock, moveBlock)) {
// 情况1:顺时针且在第一行 / 逆时针且在最后一行时,移动方块向右移动
if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
startAnimValue = moveBlock.rectF.left
endAnimValue = moveBlock.rectF.left + blockInterval
// 情况2:顺时针且在最后一行 / 逆时针且在第一行,移动方块向左移动
} else if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
|| !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {
startAnimValue = moveBlock.rectF.left
endAnimValue = moveBlock.rectF.left - blockInterval
}
// 设置属性值
left = PropertyValuesHolder.ofFloat("left", startAnimValue, endAnimValue)
valueAnimator.setValues(left)
} else {
// 情况3:顺时针且在最左列 / 逆时针且在最右列,移动方块向上移动
if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
|| !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {
startAnimValue = moveBlock.rectF.top
endAnimValue = moveBlock.rectF.top - blockInterval
// 情况4:顺时针且在最右列 / 逆时针且在最左列,移动方块向下移动
} else if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index
|| !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
startAnimValue = moveBlock.rectF.top
endAnimValue = moveBlock.rectF.top + blockInterval
}
// 设置属性值
top = PropertyValuesHolder.ofFloat("top", startAnimValue, endAnimValue)
valueAnimator.setValues(top)
}
// 3. 通过监听器更新属性值
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object left = animation.getAnimatedValue("left")
Object top = animation.getAnimatedValue("top")
if (left != null) {
mMoveBlock.rectF.offsetTo((Float) left, mMoveBlock.rectF.top)
}
if (top != null) {
mMoveBlock.rectF.offsetTo(mMoveBlock.rectF.left, (Float) top)
}
// 实时更新旋转中心 ->>关注2
setMoveBlockRotateCenter(mMoveBlock, isClock_Wise)
// 更新绘制
invalidate()
}
})
return valueAnimator
}
// 此步骤分析完毕
private boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, fixedBlock rollSquare) {
if (currEmptyfixedBlock.rectF.left - rollSquare.rectF.left == 0) {
return false
} else {
return true
}
}
// 回到原处
private void setMoveBlockRotateCenter(MoveBlock moveBlock, boolean isClockwise) {
// 情况1:以移动方块的左上角为旋转中心
if (moveBlock.index == 0) {
moveBlock.cx = moveBlock.rectF.right
moveBlock.cy = moveBlock.rectF.bottom
// 情况2:以移动方块的右下角为旋转中心
} else if (moveBlock.index == lineNumber * lineNumber - 1) {
moveBlock.cx = moveBlock.rectF.left
moveBlock.cy = moveBlock.rectF.top
// 情况3:以移动方块的左下角为旋转中心
} else if (moveBlock.index == lineNumber * (lineNumber - 1)) {
moveBlock.cx = moveBlock.rectF.right
moveBlock.cy = moveBlock.rectF.top
// 情况4:以移动方块的右上角为旋转中心
} else if (moveBlock.index == lineNumber - 1) {
moveBlock.cx = moveBlock.rectF.left
moveBlock.cy = moveBlock.rectF.bottom
}
//以下判断与旋转方向有关:即顺 or 逆顺时针
// 情况1:左边
else if (moveBlock.index % lineNumber == 0) {
moveBlock.cx = moveBlock.rectF.right
moveBlock.cy = isClockwise ? moveBlock.rectF.top : moveBlock.rectF.bottom
// 情况2:上边
} else if (moveBlock.index < lineNumber) {
moveBlock.cx = isClockwise ? moveBlock.rectF.right : moveBlock.rectF.left
moveBlock.cy = moveBlock.rectF.bottom
// 情况3:右边
} else if ((moveBlock.index + 1) % lineNumber == 0) {
moveBlock.cx = moveBlock.rectF.left
moveBlock.cy = isClockwise ? moveBlock.rectF.bottom : moveBlock.rectF.top
// 情况4:下边
} else if (moveBlock.index > (lineNumber - 1) * lineNumber) {
moveBlock.cx = isClockwise ? moveBlock.rectF.left : moveBlock.rectF.right
moveBlock.cy = moveBlock.rectF.top
}
}
// 回到原处
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
2. 设置旋转动画
private ValueAnimator createMoveValueAnimator() {
ValueAnimator moveAnim = ValueAnimator.ofFloat(0, 90).setDuration(moveSpeed);
moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object animatedValue = animation.getAnimatedValue();
mRotateDegree = (float) animatedValue;
invalidate();
}
});
return moveAnim;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
3. 设置组合动画
private void setAnimation() {
fixedBlock currEmptyfixedBlock = mfixedBlocks[mCurrEmptyPosition];
fixedBlock movedBlock = currEmptyfixedBlock.next;
mAnimatorSet.setInterpolator(move_Interpolator);
mAnimatorSet.playTogether(translateConrtroller, moveConrtroller);
mAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
updateMoveBlock();
mfixedBlocks[mCurrEmptyPosition].next.isShow = false;
mMoveBlock.isShow = true;
}
@Override
public void onAnimationEnd(Animator animation) {
isMoving = false;
mfixedBlocks[mCurrEmptyPosition].isShow = true;
mCurrEmptyPosition = mfixedBlocks[mCurrEmptyPosition].next.index;
mMoveBlock.isShow = false;
if (mAllowRoll) {
startMoving();
}
}
});
/**
* 关注1:更新移动方块的位置
*/
private void updateMoveBlock() {
mMoveBlock.rectF.set(mfixedBlocks[mCurrEmptyPosition].next.rectF);
mMoveBlock.index = mfixedBlocks[mCurrEmptyPosition].next.index;
setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
步骤6:启动动画
public void startMoving() {
if (isMoving || getVisibility() != View.VISIBLE ) {
return;
}
isMoving = true;
mAllowRoll = true;
mAnimatorSet.start();
public void stopMoving() {
mAllowRoll = false;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
7. 贡献代码
- 希望你们能和我一起完善这款清新 & 小资风格的自定义控件,具体请看:贡献代码说明
- 关于该开源项目的意见 & 建议可在Issue上提出。欢迎
Star
!
Github开源地址:Kawaii_LoadingView
8. 总结
- 相信你一定会喜欢上 这款可爱、清新 & 小资风格的加载等待自定义控件
已在Github
上开源:Kawaii_LoadingView,欢迎 Star
!
原文地址:http://blog.csdn.net/carson_ho/article/details/77712072 原文作者:Carson_Ho