preface
Android
Development,Load the wait requirementA very common- This article will teach you how to do itA lovely & petty bourgeoisie styleWait for loading
Android
The customView
Control, hope you like it.
Open source on Github: Kawaii_LoadingView, welcome Star!
directory
1. Introduction
A cute, fresh & xiaozi style Android custom View control
Open source on Github: Kawaii_LoadingView, welcome Star!
2. Application scenarios
When the App is waiting for loading for a long time, it is used to remind users of the progress & relieve users’ emotions
Characteristics of 3.
Kawaii_LoadingView is a Kawaii_LoadingView control.
3.1 Fresh style
- Compare the market in a variety of cool, dazzling load waiting custom controls, the
Kawaii_LoadingView
的 Fresh & xiaozi styleIt’s a clean stream - Also available according to your
App
Position & main color color adjustment to make the control more consistentApp
In the image. Details are as follows:
3.2 Easy To Use
Only 3 steps & simple configuration.
See article: Android Open Source Controls: a load waiting custom View that you can’t miss with cute & petty style
3.3 Low cost of secondary development
- This project is already in
Github
The open source:Kawaii_LoadingView - Detailed source code analysis documentation: See section 6 of this article for details
Therefore, it is very cheap to do secondary development & customization on it.
4. Specific use
See article: Android Open Source Controls: a load waiting custom View that you can’t miss with cute & petty style
5. Full Demo address
Carson_Ho Github address: Kawaii_LoadingView_TestDemo
6. Source code analysis
Below, I will teach you how to implement this cute & xiaozi style load wait Android custom View control
6.1 Preparations
- Grid specification
- Block Type Description
6.2 Principles of Animation
- Hides fixed 2 squares & moves the position of 1 of them
Note: Only external blocks move
- Animate properties (Pan + Rotate = combination animation) to change the position of the moving block & rotation Angle
- By calling the
invalidate()
Redraw to achieve dynamic animation effects - The specific schematic diagram is as follows:
6.3 Implementation Procedure
I’ll go through each step in detail below:
Step 1: Initialize the animation properties
- Attribute Description:
- Specific property Settings
- Add properties file
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>
Copy the code
- Specific source code analysis
private void initAttrs(Context context, AttributeSet attrs) {/ / controls resource name TypedArray TypedArray = context. ObtainStyledAttributes (attrs. R.styleable.Kawaii_LoadingView); // Number of lines (minimum 3 lines) lineNumber = typeDarray. getInteger(r.tyleable. Kawaii_LoadingView_lineNumber, 3);if(lineNumber < 3) { lineNumber = 3; } // The width of half a block (dp) half_BlockWidth = typeDarray.getDimension (r.style.kawaii_loadingView_half_BlockWidth, 30); Kawaii_LoadingView_blockInterval = typeDarray.getDimension (r.tyleable.kawaii_loadingView_blockInterval, 10); MoveBlock_Angle = typeDarray.getFloat (r.style.kawaii_loadingView_moveBlock_angle, 10); Kawaii_LoadingView_fixBlock_Angle = typeDarray.getFloat (r.style.kawaii_loadingView_fixBlock_angle, 30); // Better animation can be achieved by setting the radius of the two squares so that they are different // Square color (using hexadecimal code, e.g# 333, # 8 e8e8e)int defaultColor = context.getResources().getColor(R.color.colorAccent); // defaultColor blockColor = typedarray.getcolor (r.tyleable. Kawaii_LoadingView_blockColor, defaultColor); InitPosition = typeDarray.getInteger (r.tyleable.kawaii_loadingView_initPosition, 0); // Since the moving blocks can only be external blocks, we need to determine whether the blocks are external blocks --> attention 1if(isInsideTheRect(initPosition, lineNumber)) { initPosition = 0; IsClock_Wise = typeDarray.getBoolean (r.style.kawaii_loadingView_ISClock_wise,true); // Move the block speed // note: It is not recommended that users set the speed too fast // as this will cause ValueAnimator objects to be repeatedly created. MoveSpeed = typeDarray. getInteger(r.tyleable. Kawaii_LoadingView_moveSpeed, 250); Int move_InterpolatorResId = typeDarray.getResourceId (r.stringable. kawaii_loadingView_interpolator, android.R.anim.linear_interpolator); move_Interpolator = AnimationUtils.loadInterpolator(context, move_InterpolatorResId); MCurrEmptyPosition = initPosition; // Release the resource typedarray.recycle (); } private Boolean isInsideTheRect(int pos, int lineCount) {// Check whether the block is in line 1if (pos < lineCount) {
return false; // is the last line}else if (pos > (lineCount * lineCount - 1 - lineCount)) {
return false; // is the last line}else if ((pos + 1) % lineCount == 0) {
return false; // if in line 1}else if (pos % lineCount == 0) {
return false; } // If not on the 4 side, insidereturn true; } // return to the original positionCopy the code
Step 2: Initialize the relationship between the square object &
private void init() {// Initialize the paintbrush mPaint = new Paint(paint.anti_alias_flag); mPaint.setColor(blockColor); // Initialize block objects & relationships ->> Focus on 1 initBlocks(initPosition); } private void initBlocks(int initPosition) {// 1. LineNumber = lineNumber * lineNumber // lineNumber = lineNumber of blocks // fixedBlock = fixedBlock class ->> attention 2 mfixedBlocks = new fixedBlock[lineNumber * lineNumber]; // 2. Create a blockfor(int i = 0; i < mfixedBlocks.length; MfixedBlocks [I] = new fixedBlock(); MfixedBlocks [I].index = I; mfixedBlocks[I].index = I; // If the position of the block = move the initial position of the block, then hide; MfixedBlocks [I]. IsShow = initPosition == I?false : true; mfixedBlocks[i].rectF = new RectF(); } // 3. Create MoveBlock(1) ->> attention 3 mMoveBlock = new MoveBlock(); mMoveBlock.rectF = new RectF(); mMoveBlock.isShow =false; // Because the external block serial number ≠ 0, 1, 2... Relate_OuterBlock (mfixedBlocks, isClock_Wise); relate_OuterBlock(mfixedBlocks, isClock_Wise); } private class fixedBlock {private class fixedBlock {// RectF RectF; // int index; // flag bit: determine whether to draw Boolean isShow; // Point to the next position to move fixedBlock next; // External block serial number ≠ 0, 1, 2... // Return to the original position /** * focus on 3 * : MoveBlock (inner class) */ private class MoveBlock {// Store the coordinate position parameter of the block RectF RectF; // int index; // flag bit: determine whether to draw Boolean isShow; // Rotation center coordinates // rotation center when moving (X, Y)float cx;
floatcy; ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** * IsClockwise (clockwise Or counterclockwise) */ private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClockwise) { int lineCount = (int) Math.sqrt(fixedBlocks.length); // Case 1: associate line 1for(int i = 0; i < lineCount; I++) {// is at the far leftif(i % lineCount == 0) { fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i + 1]; // at the far right}else if((i + 1) % lineCount == 0) { fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + lineCount]; / / intermediate}else{ fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + 1]; }} // Case 2: associate the last 1 linefor(int i = (lineCount - 1) * lineCount; i < lineCount * lineCount; I++) {// is at the far leftif(i % lineCount == 0) { fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount]; // at the far right}else if((i + 1) % lineCount == 0) { fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1]; / / intermediate}else{ fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - 1]; }} // Case 3: associate column 1for(int i = 1 * lineCount; i <= (lineCount - 1) * lineCount; I += lineCount) {// if the first column is last 1if (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]; } // Case 4: associate the last columnfor(int i = 2 * lineCount - 1; i <= lineCount * lineCount - 1; I += lineCount) {// If the last column is the last oneif (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]; }} // Please go back to the original placeCopy the code
Step 3: Set the initial position of the square
// This step is written in onSizeChanged() @override protected void onSizeChanged(int w, int h, int oldw, int oldh) { OnCreate after onDraw before onDraw; Change the size of the view will call the method / / usage scenarios: used to screen the size of the change, need according to the high screen width to determine other variables can be initialized here. Super onSizeChanged (w, h, oldw, oldh); int measuredWidth = getMeasuredWidth(); int measuredHeight = getMeasuredHeight(); MeasuredWidth = measuredWidth / 2; // measuredWidth = measuredWidth / 2; int cy = measuredHeight / 2; 1 fixedBlockPosition(mfixedBlocks, Cx, cy, blockInterval, half_BlockWidth); 2 MoveBlockPosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise); } private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int cy,float dividerWidth, floatHalfSquareWidth) {// if the number of rows is even/odd // if the number of rows is even/odd //float squareWidth = halfSquareWidth * 2;
int lineCount = (int) Math.sqrt(fixedBlocks.length);
float firstRectLeft = 0;
floatfirstRectTop = 0; // Case 1: when the number of rows = evenif (lineCount % 2 == 0) {
int squareCountInAline = lineCount / 2;
int diviCountInAline = squareCountInAline - 1;
floatfirstRectLeftTopFromCenter = squareCountInAline * squareWidth + diviCountInAline * dividerWidth + dividerWidth / 2; firstRectLeft = cx - firstRectLeftTopFromCenter; firstRectTop = cy - firstRectLeftTopFromCenter; // Case 2: when the number of rows is odd}else {
int squareCountInAline = lineCount / 2;
int diviCountInAline = squareCountInAline;
floatfirstRectLeftTopFromCenter = squareCountInAline * squareWidth + diviCountInAline * dividerWidth + halfSquareWidth; firstRectLeft = cx - firstRectLeftTopFromCenter; firstRectTop = cy - firstRectLeftTopFromCenter; firstRectLeft = cx - firstRectLeftTopFromCenter; firstRectTop = cy - firstRectLeftTopFromCenter; } // 2. Determine the position of the remaining square // thought: move the position of the first row of square down // passforLoop determination: firstforLoop = row, second = columnfor(int i = 0; i < lineCount; I++) {/ / linefor(int j = 0; j < lineCount; J++) {/ / columnif (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); }}}} // Back to square one /** * Attention 2: */ private void MoveBlockPosition(fixedBlock[] fixedBlocks, MoveBlock, int initPosition, Boolean isClockwise) {// Move block position = set the next position of the initial vacant position (next) // Next position is determined by the position of the attached external block fixedBlock = fixedBlocks[initPosition]; moveBlock.rectF.set(fixedBlock.next.rectF); } // return to the original positionCopy the code
Step 4: Draw squares
@override protected void onDraw(Canvas Canvas) {// 1. Draw inner block (fixed)for(int i = 0; i < mfixedBlocks.length; I++) {// determine whether to draw according to the flag bitif(mfixedBlocks[I].isshow) {canvas. DrawRoundRect (mfixedBlocks[I].rectf, fixBlock_Angle, fixBlock_Angle, mPaint); }} // 2. Draw the moving blockif(mMoveBlock.isShow) { canvas.rotate(isClock_Wise ? mRotateDegree : -mRotateDegree, mMoveBlock.cx, mMoveBlock.cy); canvas.drawRoundRect(mMoveBlock.rectF, moveBlock_Angle, moveBlock_Angle, mPaint); }}Copy the code
Step 5: Set up the animation
The steps to achieve this animation include: set up pan animation, rotate animation & composite animation.
1. Set the pan animation
private ValueAnimator createTranslateValueAnimator(fixedBlock currEmptyfixedBlock,
fixedBlock moveBlock) {
float startAnimValue = 0;
floatendAnimValue = 0; PropertyValuesHolder left = null; PropertyValuesHolder top = null; ValueAnimator ValueAnimator = new ValueAnimator().setDuration(moveSpeed); // 2. Set the direction of movement // the situation can be divided into 4 kinds, respectively, move the block left, right and up and down // Note: we need to consider the direction of rotation (isClock_Wise), namely clockwise ->> attention 1if(isNextRollLeftOrRight(currEmptyfixedBlock, moveBlock)) {// Case 1: clockwise and on the first line/counterclockwise and on the last line, move the block to the rightif(isClock_Wise && currEmptyfixedBlock.index > moveBlock.index || ! isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) { startAnimValue = moveBlock.rectF.left; endAnimValue = moveBlock.rectF.left + blockInterval; // Case 2: clockwise and in the last row/counterclockwise and in the first row, move the block to the left}else if(isClock_Wise && currEmptyfixedBlock.index < moveBlock.index || ! isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) { startAnimValue = moveBlock.rectF.left; endAnimValue = moveBlock.rectF.left - blockInterval; } / / set the attribute value left = PropertyValuesHolder. OfFloat ("left", startAnimValue, endAnimValue);
valueAnimator.setValues(left);
} else{// Case 3: Clockwise and in the left-most column/counterclockwise and in the right-most column, move the block upif(isClock_Wise && currEmptyfixedBlock.index < moveBlock.index || ! isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) { startAnimValue = moveBlock.rectF.top; endAnimValue = moveBlock.rectF.top - blockInterval; // Case 4: clockwise and in the rightmost column/counterclockwise and in the leftmost column, move the block down}else if(isClock_Wise && currEmptyfixedBlock.index > moveBlock.index || ! isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) { startAnimValue = moveBlock.rectF.top; endAnimValue = moveBlock.rectF.top + blockInterval; } / top/set attribute value = PropertyValuesHolder ofFloat ("top", startAnimValue, endAnimValue); valueAnimator.setValues(top); } / / 3. ValueAnimator through update listener attribute value. 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); } // Update rotation center in real time ->> Focus 2setMoveBlockRotateCenter(mMoveBlock, isClock_Wise); // Update drawing invalidate(); }});returnvalueAnimator; } // This step is completed /** * / private Boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, fixedBlock rollSquare) {if (currEmptyfixedBlock.rectF.left - rollSquare.rectF.left == 0) {
return false;
} else {
return true; }} // return to the original position /** ** focus 2: Update the center of rotation of MoveBlock */ private void */ cx,cysetMoveBlockRotateCenter(MoveBlock MoveBlock, Boolean isClockwise) {// Case 1: Move the upper left corner of the block as the rotation centerif(moveBlock.index == 0) { moveBlock.cx = moveBlock.rectF.right; moveBlock.cy = moveBlock.rectF.bottom; // Case 2: Move the lower right corner of the box as the rotation center}else if(moveBlock.index == lineNumber * lineNumber - 1) { moveBlock.cx = moveBlock.rectF.left; moveBlock.cy = moveBlock.rectF.top; // Case 3: Move the lower left corner of the box as the rotation center}else if(moveBlock.index == lineNumber * (lineNumber - 1)) { moveBlock.cx = moveBlock.rectF.right; moveBlock.cy = moveBlock.rectF.top; // Case 4: Move the upper right corner of the box as the rotation center}else if(moveBlock.index == lineNumber - 1) { moveBlock.cx = moveBlock.rectF.left; moveBlock.cy = moveBlock.rectF.bottom; } // The following judgments depend on the direction of rotation: clockwise or clockwise // Case 1: leftelse if(moveBlock.index % lineNumber == 0) { moveBlock.cx = moveBlock.rectF.right; moveBlock.cy = isClockwise ? moveBlock.rectF.top : moveBlock.rectF.bottom; // Case 2: top}else if(moveBlock.index < lineNumber) { moveBlock.cx = isClockwise ? moveBlock.rectF.right : moveBlock.rectF.left; moveBlock.cy = moveBlock.rectF.bottom; }}else if((moveBlock.index + 1) % lineNumber == 0) { moveBlock.cx = moveBlock.rectF.left; moveBlock.cy = isClockwise ? moveBlock.rectF.bottom : moveBlock.rectF.top; // Case 4: below}else if(moveBlock.index > (lineNumber - 1) * lineNumber) { moveBlock.cx = isClockwise ? moveBlock.rectF.left : moveBlock.rectF.right; moveBlock.cy = moveBlock.rectF.top; }} // Go backCopy the code
2. Set the rotation animation
private ValueAnimator createMoveValueAnimatorValueAnimator moveAnim = ValueAnimator.offloat (0, 90).setDuration(moveSpeed); moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Object animatedValue = animation.getAnimatedValue(); // assign mRotateDegree = (float) animatedValue; // Update view invalidate(); }});returnmoveAnim; } // This step is completeCopy the code
3. Set up the composite animation
private void setAnimation () {// 1. Get the empty position of the fixedBlock, that is, move the current position of the block. FixedBlock movedBlock = curremptyFixedBlock. next; / / 3. Set the animation of the interpolation mAnimatorSet change setInterpolator (move_Interpolator); mAnimatorSet.playTogether(translateConrtroller, moveConrtroller); mAnimatorSet.addListener(newAnimatorListenerAdapter@override public void onAnimationStart(Animator animation) {// The position of the moving block needs to be updated before each animation starts ->> Attention 1 updateMoveBlock(); MfixedBlocks [mCurrEmptyPosition].next. IsShow = mfixedBlocks[mCurrEmptyPositionfalse; // Display the moving block with the flag bit mmoveBlock. isShow =true; @override public void onAnimationEnd(Animator animation) {isMoving =false;
mfixedBlocks[mCurrEmptyPosition].isShow = true; mCurrEmptyPosition = mfixedBlocks[mCurrEmptyPosition].next.index; // Hide the moving block mmoveBlock. isShow =false; // Use the flag bit to determine whether the animation should be loopedif(mAllowRoll) { startMoving(); }}}); /** * note 1: Update the position of the moving block */ private voidupdateMoveBlock() {
mMoveBlock.rectF.set(mfixedBlocks[mCurrEmptyPosition].next.rectF);
mMoveBlock.index = mfixedBlocks[mCurrEmptyPosition].next.index;
setMoveBlockRotateCenter(mMoveBlock, isClock_Wise); } // return to the original positionCopy the code
Step 6: Start the animation
public void startMoving() {// 1. Determine whether animation needs to be started according to whether flag bit & view is visible // Set here to facilitate manual & automatic animation stopif(isMoving || getVisibility() ! = View.VISIBLE ) {return; } // 2. Set the flag bit to whether to stop the animation isMoving =true;
mAllowRoll = true; // 3. Start animation manimatorset.start (); // Stop animation public voidstopMoving() {// Use the flag bit to set mAllowRoll =false;
}
Copy the code
- At this point, the small fresh load waiting for the custom control source code analysis is finished
- Complete source address: https://github.com/Carson-Ho/Kawaii_LoadingView
7. Contribute code
- I hope you can work with me to improve this fresh & xiaozi style of custom controls, see: contribute code notes
- Comments & suggestions about the open source project can be made on Issue. welcome
Star
!
Github Kawaii_LoadingView
8. To summarize
- I’m sure you’ll love this refreshing little load wait custom control
Open source on Github: Kawaii_LoadingView, welcome Star!
- In addition, I have some interesting customizations
View
Examples explained, interested can continue to pay attention toCarson_Ho android Development Notes
A. Hand in hand to teach you to achieve a simple and easy to use search box (including historical search records)
B. You need a simple and useful SuperEditText(Delete & Custom Styles)
C. Android custom View Combat Series: Timeline
Thumb up, please! Because your encouragement is the biggest power that I write!
- Refer to the article
http://www.jianshu.com/p/9a6cbb7aa54f http://www.jianshu.com/p/2412d00a0ce4 http://www.jianshu.com/p/733532041f46 http://halohoop.com/2017/06/04/roll_loading/ http://www.jianshu.com/p/e9d8420b1b9c http://www.jianshu.com/p/762b490403c3 http://www.jianshu.com/p/1dab927b2f36 http://www.jianshu.com/p/158736a2549d http://www.jianshu.com/p/146e5cec4863