自定义加载等待控件
MainActivity.java
package com.loaderman.rollloaderingdemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; public class MainActivity extends AppCompatActivity { private RollSquareView rollSquareView1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); rollSquareView1 = (RollSquareView) findViewById(R.id.rollSquareView1); } //开始 public void startRoll(View view) { rollSquareView1.setVisibility(View.VISIBLE); rollSquareView1.startRoll(); } //停止 public void stopRoll(View view) { rollSquareView1.stopRoll(); rollSquareView1.setVisibility(View.VISIBLE); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.loaderman.rollloaderingdemo.MainActivity"> <com.loaderman.rollloaderingdemo.RollSquareView android:id="@+id/rollSquareView1" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@drawable/default_bg" android:visibility="invisible" app:fix_round_cornor="5" app:half_rect_width="15dp" app:is_clockwise="false" app:line_count="3" app:rect_divier_width="8dp" app:roll_interpolator="@android:anim/bounce_interpolator" app:roll_round_cornor="5" app:roll_speed="500" app:square_color="#9e40fb" app:start_empty_position="3" /> <Button android:layout_width="match_parent" android:layout_height="100dp" android:onClick="startRoll" android:text="Roll开始滚动" /> <Button android:layout_width="match_parent" android:layout_height="100dp" android:onClick="stopRoll" android:text="Roll结束滚动" /> </LinearLayout>
default_bg.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:bottomLeftRadius="10dp" android:bottomRightRadius="10dp" android:radius="10dp" android:topLeftRadius="10dp" android:topRightRadius="10dp" /> <solid android:color="#ff0" /> </shape>
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RollSquareView"> <attr name="half_rect_width" format="dimension" /> <attr name="rect_divier_width" format="dimension" /> <attr name="start_empty_position" format="integer" /> <attr name="is_clockwise" format="boolean" /> <attr name="line_count" format="integer" /> <attr name="roll_speed" format="integer" /> <attr name="square_color" format="color" /> <attr name="roll_round_cornor" format="float" /> <attr name="fix_round_cornor" format="float" /> <attr name="roll_interpolator" format="reference" /> </declare-styleable> </resources>
RollSquareView.java
package com.loaderman.rollloaderingdemo; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; public class RollSquareView extends View { private FixSquare[] mFixSquares; private RollSquare mRollSquare; private float mHalfSquareWidth; private float mDividerWidth; private Paint mPaint; private boolean mIsClockwise; private int mStartEmptyPosition; private int mCurrEmptyPosition; private int mLineCount; /** * 方框的圆角半径 */ private float mRollRoundCornor; private float mFixRoundCornor; private float mRotateDegree; private boolean mAllowRoll = false; private boolean mIsRolling = false; private int mSpeed = 250; /** * 一个方块的动画结束的后是否需要重置(再从startEmpty开始) */ private boolean mIsReset = false; private int mSquareColor; /** * 动画插值器的全局变量 * 默认为线性 */ private Interpolator mRollInterpolator; private AnimatorSet mAnimatorSet; private Rect mDirtyRect; public RollSquareView(Context context) { this(context, null); } public RollSquareView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public RollSquareView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initAttrs(context, attrs); init(); } private void initAttrs(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RollSquareView); mLineCount = typedArray.getInteger(R.styleable.RollSquareView_line_count, 3); mRollRoundCornor = typedArray.getFloat(R.styleable.RollSquareView_roll_round_cornor, 10); mFixRoundCornor = typedArray.getFloat(R.styleable.RollSquareView_fix_round_cornor, 10); int rollInterpolatorResId = typedArray.getResourceId(R.styleable.RollSquareView_roll_interpolator, android.R.anim.linear_interpolator); mRollInterpolator = AnimationUtils.loadInterpolator(context, rollInterpolatorResId); int defaultColor = context.getResources().getColor(R.color.default_color); mSquareColor = typedArray.getColor(R.styleable.RollSquareView_square_color, defaultColor); mSpeed = typedArray.getInteger(R.styleable.RollSquareView_roll_speed, 250); if (mLineCount < 2) { mLineCount = 2;//至少要四个方块 } mHalfSquareWidth = typedArray.getDimension(R.styleable.RollSquareView_half_rect_width, 30); mDividerWidth = typedArray.getDimension(R.styleable.RollSquareView_rect_divier_width, 10); mIsClockwise = typedArray.getBoolean(R.styleable.RollSquareView_is_clockwise, true); mStartEmptyPosition = typedArray.getInteger(R.styleable.RollSquareView_start_empty_position, 0); if (isInsideTheRect(mStartEmptyPosition, mLineCount)) { mStartEmptyPosition = 0; } mCurrEmptyPosition = mStartEmptyPosition; typedArray.recycle(); } 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; } private void init() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(mSquareColor); initSquares(mStartEmptyPosition); } private void initSquares(int startEmptyPosition) { //创建mLineCount * mLineCount个方块 mFixSquares = new FixSquare[mLineCount * mLineCount]; for (int i = 0; i < mFixSquares.length; i++) { mFixSquares[i] = new FixSquare(); mFixSquares[i].index = i; mFixSquares[i].isShow = startEmptyPosition == i ? false : true; mFixSquares[i].rectF = new RectF(); } //外圈链接起来 linkTheOuterSquare(mFixSquares, mIsClockwise); //创建1个滚动方块 mRollSquare = new RollSquare(); mRollSquare.rectF = new RectF(); mRollSquare.isShow = false; } private void linkTheOuterSquare(FixSquare[] fixSquares, boolean isClockwise) { int lineCount = (int) Math.sqrt(fixSquares.length); //连接第一行 for (int i = 0; i < lineCount; i++) { if (i % lineCount == 0) {//位于最左边 fixSquares[i].next = isClockwise ? fixSquares[i + lineCount] : fixSquares[i + 1]; } else if ((i + 1) % lineCount == 0) {//位于最右边 fixSquares[i].next = isClockwise ? fixSquares[i - 1] : fixSquares[i + lineCount]; } else {//中间 fixSquares[i].next = isClockwise ? fixSquares[i - 1] : fixSquares[i + 1]; } } //连接最后一行 for (int i = (lineCount - 1) * lineCount; i < lineCount * lineCount; i++) { if (i % lineCount == 0) {//位于最左边 fixSquares[i].next = isClockwise ? fixSquares[i + 1] : fixSquares[i - lineCount]; } else if ((i + 1) % lineCount == 0) {//位于最右边 fixSquares[i].next = isClockwise ? fixSquares[i - lineCount] : fixSquares[i - 1]; } else {//中间 fixSquares[i].next = isClockwise ? fixSquares[i + 1] : fixSquares[i - 1]; } } //连接左边 for (int i = 1 * lineCount; i <= (lineCount - 1) * lineCount; i += lineCount) { if (i == (lineCount - 1) * lineCount) {//如果是左下角的一个 fixSquares[i].next = isClockwise ? fixSquares[i + 1] : fixSquares[i - lineCount]; continue; } fixSquares[i].next = isClockwise ? fixSquares[i + lineCount] : fixSquares[i - lineCount]; } //连接右边 for (int i = 2 * lineCount - 1; i <= lineCount * lineCount - 1; i += lineCount) { if (i == lineCount * lineCount - 1) {//如果是右下角的一个 fixSquares[i].next = isClockwise ? fixSquares[i - lineCount] : fixSquares[i - 1]; continue; } fixSquares[i].next = isClockwise ? fixSquares[i - lineCount] : fixSquares[i + lineCount]; } } // RectF testRectF; // public void testRectInvalidate() { // testRectF = new RectF(mDirtyRect); // } private class FixSquare { RectF rectF; int index; boolean isShow; FixSquare next; } private class RollSquare { RectF rectF; int index; boolean isShow; /** * 旋转中心坐标 */ float cx; float cy; } @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; fixFixSquarePosition(mFixSquares, cx, cy, mDividerWidth, mHalfSquareWidth); fixRollSquarePosition(mFixSquares, mRollSquare, mStartEmptyPosition, mIsClockwise); mDirtyRect = getDirtyRect(mFixSquares[0].rectF, mFixSquares[mFixSquares.length - 1].rectF); } private Rect getDirtyRect(RectF leftTopRectF, RectF rightBottomRectF) { if (leftTopRectF != null && rightBottomRectF != null) { float width = leftTopRectF.width(); float height = leftTopRectF.height(); float sqrt = (float) Math.sqrt(width * width + height * height); float extra = sqrt - width; Rect dirtyRectF = new Rect((int) (leftTopRectF.left - extra), (int) (leftTopRectF.top - extra), (int) (rightBottomRectF.right + extra), (int) (rightBottomRectF.bottom + extra)); return dirtyRectF; } return null; } private void fixRollSquarePosition(FixSquare[] fixSquares, RollSquare rollSquare, int startEmptyPosition, boolean isClockwise) { FixSquare fixSquare = fixSquares[startEmptyPosition]; rollSquare.rectF.set(fixSquare.next.rectF); } /** * 固定这些图形的rect位置 * @param fixSquares * @param cx * @param cy * @param dividerWidth * @param halfSquareWidth */ private void fixFixSquarePosition(FixSquare[] fixSquares, int cx, int cy, float dividerWidth, float halfSquareWidth) { //确定第一个rect的位置 float squareWidth = halfSquareWidth * 2; int lineCount = (int) Math.sqrt(fixSquares.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; } for (int i = 0; i < lineCount; i++) {//行 for (int j = 0; j < lineCount; j++) {//列 if (i == 0) { if (j == 0) { fixSquares[0].rectF.set(firstRectLeft, firstRectTop, firstRectLeft + squareWidth, firstRectTop + squareWidth); } else { int currIndex = i * lineCount + j; fixSquares[currIndex].rectF.set(fixSquares[currIndex - 1].rectF); fixSquares[currIndex].rectF.offset(dividerWidth + squareWidth, 0); } } else { // Log.i(TAG, "fixFixSquarePosition: i:" + i + " j:" + j + " i*lineCount + j="+(i*lineCount + j)); //i * lineCount + j ==> currentIndex int currIndex = i * lineCount + j; fixSquares[currIndex].rectF.set(fixSquares[currIndex - lineCount].rectF); fixSquares[currIndex].rectF.offset(0, dividerWidth + squareWidth); } } } } public void startRoll() { if (mIsRolling || getVisibility() != View.VISIBLE || getWindowVisibility() != VISIBLE) { return; } mIsRolling = true; mAllowRoll = true; FixSquare currEmptyFixSquare = mFixSquares[mCurrEmptyPosition]; FixSquare rollSquare = currEmptyFixSquare.next; mAnimatorSet = new AnimatorSet(); ValueAnimator translateConrtroller = createTranslateValueAnimator(currEmptyFixSquare, rollSquare); ValueAnimator rollConrtroller = createRollValueAnimator(); mAnimatorSet.setInterpolator(mRollInterpolator); mAnimatorSet.playTogether(translateConrtroller, rollConrtroller); mAnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { updateRollSquare(); //让空square的next隐藏,现在FixSquares中那就是有两个隐藏了 mFixSquares[mCurrEmptyPosition].next.isShow = false; //然后滚动的suqare需要显示出来 mRollSquare.isShow = true; } @Override public void onAnimationEnd(Animator animation) { mIsRolling = false; mFixSquares[mCurrEmptyPosition].isShow = true; mCurrEmptyPosition = mFixSquares[mCurrEmptyPosition].next.index; //然后滚动的suqare隐藏 mRollSquare.isShow = false; if (mAllowRoll) { startRoll(); } if (mIsReset) { mCurrEmptyPosition = mStartEmptyPosition; //重置所有的 for (int i = 0; i < mFixSquares.length; i++) { mFixSquares[i].isShow = true; } mFixSquares[mCurrEmptyPosition].isShow = false; updateRollSquare(); if (!isHardwareAccelerated()) { invalidate(mDirtyRect); } else { invalidate(); } startRoll(); mIsReset = false; } } }); mAnimatorSet.start(); } private void updateRollSquare() { mRollSquare.rectF.set(mFixSquares[mCurrEmptyPosition].next.rectF); mRollSquare.index = mFixSquares[mCurrEmptyPosition].next.index; setRollSquareRotateCenter(mRollSquare, mIsClockwise); } public void stopRoll() { mAllowRoll = false; } public void resetRoll() { stopRoll(); mIsReset = true; } private void setRollSquareRotateCenter(RollSquare rollSquare, boolean isClockwise) { if (rollSquare.index == 0) {//左上角 rollSquare.cx = rollSquare.rectF.right; rollSquare.cy = rollSquare.rectF.bottom; } else if (rollSquare.index == mLineCount * mLineCount - 1) {//右下角 rollSquare.cx = rollSquare.rectF.left; rollSquare.cy = rollSquare.rectF.top; } else if (rollSquare.index == mLineCount * (mLineCount - 1)) {//左下角 rollSquare.cx = rollSquare.rectF.right; rollSquare.cy = rollSquare.rectF.top; } else if (rollSquare.index == mLineCount - 1) {//右上角 rollSquare.cx = rollSquare.rectF.left; rollSquare.cy = rollSquare.rectF.bottom; } //以下和顺不顺时针有关,其中判断条件还包含了角落的,但是无关紧要,上边的判断已经过滤掉角落的了 //走到下面的判断的都不可能是角落的index else if (rollSquare.index % mLineCount == 0) {//左边 rollSquare.cx = rollSquare.rectF.right; rollSquare.cy = isClockwise ? rollSquare.rectF.top : rollSquare.rectF.bottom; } else if (rollSquare.index < mLineCount) {//上边 rollSquare.cx = isClockwise ? rollSquare.rectF.right : rollSquare.rectF.left; rollSquare.cy = rollSquare.rectF.bottom; } else if ((rollSquare.index + 1) % mLineCount == 0) {//右边 rollSquare.cx = rollSquare.rectF.left; rollSquare.cy = isClockwise ? rollSquare.rectF.bottom : rollSquare.rectF.top; } else if (rollSquare.index > (mLineCount - 1) * mLineCount) {//下边 rollSquare.cx = isClockwise ? rollSquare.rectF.left : rollSquare.rectF.right; rollSquare.cy = rollSquare.rectF.top; } } private ValueAnimator createRollValueAnimator() { ValueAnimator rollAnim = ValueAnimator.ofFloat(0, 90).setDuration(mSpeed); // rollAnim.setInterpolator(mRollInterpolator); rollAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Object animatedValue = animation.getAnimatedValue(); mRotateDegree = (float) animatedValue; if (!isHardwareAccelerated()) { invalidate(mDirtyRect); } else { invalidate(); } } }); return rollAnim; } private ValueAnimator createTranslateValueAnimator(FixSquare currEmptyFixSquare, FixSquare rollSquare) { float startAnimValue = 0; float endAnimValue = 0; PropertyValuesHolder left = null; PropertyValuesHolder top = null; ValueAnimator valueAnimator = new ValueAnimator().setDuration(mSpeed); // valueAnimator.setInterpolator(mRollInterpolator); if (isNextRollLeftOrRight(currEmptyFixSquare, rollSquare)) { if (mIsClockwise && currEmptyFixSquare.index > rollSquare.index//顺时针且在第一行 || !mIsClockwise && currEmptyFixSquare.index > rollSquare.index) {//逆时针且在最后一行 //向右滚 startAnimValue = rollSquare.rectF.left; endAnimValue = rollSquare.rectF.left + mDividerWidth; } else if (mIsClockwise && currEmptyFixSquare.index < rollSquare.index//顺时针且在最后一行 || !mIsClockwise && currEmptyFixSquare.index < rollSquare.index) {//逆时针且在第一行 //向左滚 startAnimValue = rollSquare.rectF.left; endAnimValue = rollSquare.rectF.left - mDividerWidth; } left = PropertyValuesHolder.ofFloat("left", startAnimValue, endAnimValue); valueAnimator.setValues(left); } else { if (mIsClockwise && currEmptyFixSquare.index < rollSquare.index//顺时针且在最左列 || !mIsClockwise && currEmptyFixSquare.index < rollSquare.index) {//逆时针且在最右列 //向上滚 startAnimValue = rollSquare.rectF.top; endAnimValue = rollSquare.rectF.top - mDividerWidth; } else if (mIsClockwise && currEmptyFixSquare.index > rollSquare.index//顺时针且在最右列 || !mIsClockwise && currEmptyFixSquare.index > rollSquare.index) {//逆时针且在最左列 //向下滚 startAnimValue = rollSquare.rectF.top; endAnimValue = rollSquare.rectF.top + mDividerWidth; } top = PropertyValuesHolder.ofFloat("top", startAnimValue, endAnimValue); valueAnimator.setValues(top); } valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Object left = animation.getAnimatedValue("left"); Object top = animation.getAnimatedValue("top"); if (left != null) { mRollSquare.rectF.offsetTo((Float) left, mRollSquare.rectF.top); } if (top != null) { mRollSquare.rectF.offsetTo(mRollSquare.rectF.left, (Float) top); } setRollSquareRotateCenter(mRollSquare, mIsClockwise); if (!isHardwareAccelerated()) { invalidate(mDirtyRect); } else { invalidate(); } } }); return valueAnimator; } /** * 如果不是左右运动那就是上下运动 * @param currEmptyFixSquare * @param rollSquare * @return */ private boolean isNextRollLeftOrRight(FixSquare currEmptyFixSquare, FixSquare rollSquare) { if (currEmptyFixSquare.rectF.left - rollSquare.rectF.left == 0) { return false; } else { return true; } } @Override protected void onDraw(Canvas canvas) { //for test the rect invalidate // if (testRectF != null) { // mPaint.setColor(Color.RED); // canvas.drawCircle(testRectF.centerX(),testRectF.centerY(),testRectF.width(),mPaint); // mPaint.setColor(mSquareColor); // } //for test the rect invalidate for (int i = 0; i < mFixSquares.length; i++) { if (mFixSquares[i].isShow) { canvas.drawRoundRect(mFixSquares[i].rectF, mFixRoundCornor, mFixRoundCornor, mPaint); } } if (mRollSquare.isShow) { canvas.rotate(mIsClockwise ? mRotateDegree : -mRotateDegree, mRollSquare.cx, mRollSquare.cy); canvas.drawRoundRect(mRollSquare.rectF, mRollRoundCornor, mRollRoundCornor, mPaint); } } @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (changedView == this && visibility == VISIBLE) { startRoll(); } else if (changedView == this && visibility != VISIBLE) { stopRoll(); } } @Override protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); if (visibility == VISIBLE && getVisibility() == VISIBLE) { startRoll(); } else { stopRoll(); } } }
color.xml
<color name="default_color">#ff820e</color>
效果图
学习来源:https://github.com/halohoop/RollSquareView
最后,关注【码上加油站】微信公众号后,有疑惑有问题想加油的小伙伴可以码上加入社群,让我们一起码上加油吧!!!