preface

  • AndroidDevelopment,Load the wait requirementA very common
  • This article will teach you how to do itA lovely & petty bourgeoisie styleWait for loadingAndroidThe customViewControl, 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, theKawaii_LoadingViewFresh & xiaozi styleIt’s a clean stream
  • Also available according to yourAppPosition & main color color adjustment to make the control more consistentAppIn 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 inGithubThe 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 theinvalidate()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. welcomeStar

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 customizationsViewExamples 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