方式一
效果图:
simpleButton类代码:
package com.oneway.demo.navcontroller.view;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.oneway.demo.navcontroller.R;
public class SimpleLoadingButton extends View {
private int textColor;
private int backgroundNormal;
private float textSize;
private String content;
private Bitmap bitmap;
private int default_padding;
private int paddingBottom;
private int paddingLeft;
private int paddingTop;
private int paddingRight;
private Paint paintRect;
private Paint paintTxt;
private RectF rect;
private float txtHeight;
private Matrix matrix;
private ObjectAnimator animator;
private int mViewWidth;
private int mViewHeight;
private int STATE_NORMAL = 0;
private int STATE_LOADING = 1;
private int STATE_COMPLETED = 2;
private int state = STATE_NORMAL;
private String loadingTxt;
private String contentNormal;
private float corners;
private int backgroundPressed;
public SimpleLoadingButton(Context context) {
this(context, null, 0);
}
public SimpleLoadingButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SimpleLoadingButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initData(context);
}
/**
* 方法描述:初始化属性值
*
* @param context 上下文
* @param attrs 属性集合
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.SimpleLoadingButton);
backgroundNormal = typeArray.getColor(R.styleable.SimpleLoadingButton_button_background_color_normal,
Color.parseColor("#3A96FF"));
backgroundPressed = typeArray.getColor(R.styleable.SimpleLoadingButton_button_background_color_pressed,
Color.parseColor("#1E90FF"));
// Log.e("Custom", "background===" + background);
// if (background >= TypedValue.TYPE_FIRST_COLOR_INT &&
// background <= TypedValue.TYPE_LAST_COLOR_INT){
textColor = typeArray.getColor(R.styleable.SimpleLoadingButton_button_text_color, Color.WHITE);
textSize = typeArray.getDimension(R.styleable.SimpleLoadingButton_button_text_size, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
contentNormal = typeArray.getString(R.styleable.SimpleLoadingButton_button_text);
loadingTxt = typeArray.getString(R.styleable.SimpleLoadingButton_button_loading_text);
corners = typeArray.getDimension(R.styleable.SimpleLoadingButton_corners, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 6, getResources().getDisplayMetrics()));
typeArray.recycle();
}
private void initData(Context context) {
if (contentNormal != null) {
content = contentNormal;
} else {
content = "";
}
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.loading);
default_padding = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 22, getResources().getDisplayMetrics());
paddingBottom = getPaddingBottom();
paddingLeft = getPaddingLeft();
paddingTop = getPaddingTop();
paddingRight = getPaddingRight();
//矩形画笔
paintRect = new Paint();
paintRect.setColor(backgroundNormal);
paintRect.setAntiAlias(true);
paintRect.setStyle(Paint.Style.FILL);
//矩形对象
rect = new RectF();
//文字画笔
paintTxt = new Paint();
paintTxt.setColor(textColor);
paintRect.setAntiAlias(true);
paintTxt.setTextAlign(Paint.Align.CENTER);
paintTxt.setTextSize(textSize);
txtHeight = (float) getTxtHeight(paintTxt);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
initRect(measureWidth, measureHeight);
setMeasuredDimension(measureWidth, measureHeight);
}
/**
* 方法描述:矩形对象初始化
*/
private void initRect(int measureWidth, int measureHeight) {
rect.left = 0;
rect.top = 0;
rect.right = measureWidth;
rect.bottom = measureHeight;
}
private int measureWidth(int measureSpec) {
int width;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
width = specSize;
} else {
width = (int) paintTxt.measureText(content) + paddingLeft + paddingRight;
if (specMode == MeasureSpec.AT_MOST) {
width = Math.min(width, specSize);
}
}
return width;
}
private int measureHeight(int measureSpec) {
int height = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
height = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
height = (int) txtHeight + paddingTop + paddingBottom + default_padding;
}
return height;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRoundRect(rect, corners, corners, paintRect);
if (state == STATE_NORMAL) {
canvas.drawText(content, paddingLeft + rect.width() / 2 - paddingRight,
rect.height() / 2 + txtHeight / 3, paintTxt);
}
if (state == STATE_LOADING) {
canvas.translate(mViewWidth / 2, mViewHeight / 2);// 旋转的位置
matrix.preTranslate(-bitmap.getWidth() / 2, -bitmap.getHeight() / 2);//旋转中心点
canvas.drawBitmap(bitmap, matrix, null);
}
if (state == STATE_COMPLETED) {
canvas.drawText(content, paddingLeft + rect.width() / 2 - paddingRight,
rect.height() / 2 + txtHeight / 3, paintTxt);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (listener == null) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (loadingTxt == null) {
content = "";
} else {
content = loadingTxt;
}
paintRect.setColor(backgroundPressed);
state = STATE_NORMAL;
postInvalidate();
break;
case MotionEvent.ACTION_UP:
paintRect.setColor(backgroundNormal);
startAnim();
state = STATE_LOADING;
postInvalidate();
listener.onLoadingClick(this);
break;
}
return true;
}
/**
* 方法描述:加载完成以后让SimpleLoadingButton复位
*/
public void setCompleted() {
content = contentNormal;
state = STATE_COMPLETED;
cancelAnim();
postInvalidate();
}
/**
* 方法描述:获取文本字符的高度
*
* @param mPaint 画文本字符的画笔
* @return 文本字符的高度
*/
public double getTxtHeight(Paint mPaint) {
Paint.FontMetrics fm = mPaint.getFontMetrics();
return Math.ceil(fm.descent - fm.ascent);
}
/**
* 方法描述:开启动画
*/
private void startAnim() {
if (animator == null) {
initAnimator();
animator.start();
return;
}
if (!animator.isRunning()) {
animator.start();
}
}
/**
* 方法描述:取消动画
*/
private void cancelAnim() {
if (animator != null && animator.isRunning()) {
animator.cancel();
}
}
@SuppressLint("ObjectAnimatorBinding")
private void initAnimator() {
matrix = new Matrix();
animator = ObjectAnimator.ofFloat(matrix, "rotation", 0, 360);
animator.setDuration(1000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
matrix.setRotate((float) animation.getAnimatedValue());
invalidate();
}
});
}
private LoadingListener listener;
public void setLoadingListener(LoadingListener loginClickListener) {
this.listener = loginClickListener;
}
public interface LoadingListener {
void onLoadingClick(SimpleLoadingButton view);
}
}
value文件夹里面的attrs.xml代码为:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SimpleLoadingButton">
<attr name="button_background_color_normal" format="color" />
<attr name="button_background_color_pressed" format="color" />
<attr name="button_text" format="string" />
<attr name="button_text_color" format="color" />
<attr name="button_text_size" format="dimension" />
<attr name="button_loading_text" format="string" />
<attr name="corners" format="dimension" />
</declare-styleable>
</resources>
loading.png图标为:
在activity的xml里面添加代码:
<com.oneway.demo.navcontroller.view.SimpleLoadingButton
android:id="@+id/simple_loading_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:button_background_color_normal="#3A96FF"
app:button_background_color_pressed="#1E90FF"
app:button_loading_text="登录中..."
app:button_text="登录"
app:button_text_color="#ffffff"
app:button_text_size="18sp"
app:corners="8dp"
tools:ignore="MissingConstraints" />
最后,开始调用findviewbyid拿到SimpleLoadingButton控件,调用setLoadingListener方法
加载结束调用setCompleted方法复位
方式二
点击前效果:
点击后效果:
LoadingButton代码:
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
/**
*
*/
public class LoadingButton extends androidx.appcompat.widget.AppCompatButton {
private Context context;
// 开始Loading时的回调
private OnStartListener startListener;
// 结束Loading时的回调
private OnFinishListener finishListener;
// 开始和结束Loading时的回调
private OnLoadingListener listener;
// Loading动画旋转周期
private int rotateDuration = 1000;
// 按钮缩成Loading动画的时间
private int reduceDuration = 350;
// Loading旋转动画控制器
private Interpolator rotateInterpolator;
// 按钮缩成Loading动画的控制器
private Interpolator reduceInterpolator;
private int width;
private int height;
private String text;
// 是否在Loading中
private boolean isLoading = false;
public LoadingButton(Context context) {
this(context, null);
}
public LoadingButton(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public LoadingButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
this.context = context;
setGravity(Gravity.CENTER);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (width == 0) width = getMeasuredWidth();
if (height == 0) height = getMeasuredHeight();
}
/**
* 播放按钮缩成Loading的动画
*/
private void showStartLoadAnimation() {
ValueAnimator animator = new ValueAnimator().ofInt(width, height);
animator.setDuration(reduceDuration);
if (reduceInterpolator != null) animator.setInterpolator(reduceInterpolator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
getLayoutParams().width = (int) animation.getAnimatedValue();
requestLayout();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setBackgroundDrawable(context.getResources().getDrawable(R.drawable.button_main_color_selector));
setEnabled(false);
text = getText().toString();
setText("");
}
@Override
public void onAnimationEnd(Animator animation) {
showLoadingAnimation();
}
});
animator.start();
}
/**
* 播放Loading动画
*/
private void showLoadingAnimation() {
RotateAnimation animation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(rotateDuration);
animation.setInterpolator(rotateInterpolator != null ? rotateInterpolator : new LinearInterpolator());
animation.setRepeatCount(-1);
setBackgroundDrawable(context.getResources().getDrawable(R.drawable.circle_loading));
if (startListener != null) {
startListener.onStart();
} else if (listener != null) {
listener.onStart();
}
startAnimation(animation);
isLoading = true;
}
/**
* 播放Loading拉伸成按钮的动画
*/
private void showFinishLoadAnimation() {
ValueAnimator animator = new ValueAnimator().ofInt(height, width);
animator.setDuration(reduceDuration);
if (reduceInterpolator != null) animator.setInterpolator(reduceInterpolator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
getLayoutParams().width = (int) animation.getAnimatedValue();
requestLayout();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setBackgroundDrawable(context.getResources().getDrawable(R.drawable.button_main_color_selector));
setEnabled(false);
}
@Override
public void onAnimationEnd(Animator animation) {
setText(text);
setEnabled(true);
if (finishListener != null) {
finishListener.onFinish();
} else if (listener != null) {
listener.onFinish();
}
}
});
animator.start();
isLoading = false;
}
/**
* 开始Loading
*/
public void startLoading() {
if (!isLoading) {
clearAnimation();
showStartLoadAnimation();
}
}
/**
* 开始Loading
*
* @param listener Loading开始时的回调
*/
public void startLoading(OnStartListener listener) {
if (!isLoading) {
this.startListener = listener;
clearAnimation();
showStartLoadAnimation();
}
}
/**
* 结束Loading
*/
public void finishLoading() {
if (isLoading) {
clearAnimation();
showFinishLoadAnimation();
}
}
/**
* 结束Loading
*
* @param listener Loading结束时的回调
*/
public void finishLoading(OnFinishListener listener) {
if (isLoading) {
this.finishListener = listener;
clearAnimation();
showFinishLoadAnimation();
}
}
/**
* 设置Loading开始和结束时的回调接口
*
* @param listener
*/
public void setOnLoadingListener(OnLoadingListener listener) {
this.listener = listener;
}
/**
* 设置按钮缩成Loading动画的时间
*
* @param reduceDuration 时间,单位毫秒
*/
public void setReduceDuration(int reduceDuration) {
this.reduceDuration = reduceDuration;
}
/**
* 设置Loading动画旋转周期
*
* @param rotateDuration 旋转周期,单位毫秒
*/
public void setRotateDuration(int rotateDuration) {
this.rotateDuration = rotateDuration;
}
/**
* 获取是否正在Loading
*
* @return
*/
public boolean isLoading() {
return isLoading;
}
/**
* 设置Loading旋转动画控制器
*
* @param rotateInterpolator
*/
public void setRotateInterpolator(Interpolator rotateInterpolator) {
this.rotateInterpolator = rotateInterpolator;
}
/**
* 按钮缩成Loading动画的控制器
*
* @param reduceInterpolator
*/
public void setReduceInterpolator(Interpolator reduceInterpolator) {
this.reduceInterpolator = reduceInterpolator;
}
/**
* Loading开始时的回调接口
*/
public interface OnStartListener {
void onStart();
}
/**
* Loading结束时的回调接口
*/
public interface OnFinishListener {
void onFinish();
}
/**
* Loading开始和结束时的回调接口
*/
public interface OnLoadingListener {
void onStart();
void onFinish();
}
}
button_main_color_up.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp" />
<solid android:color="#45a8ca" />
<gradient
android:endColor="#45a8ca"
android:startColor="#6fd1e7" />
</shape>
button_main_color_down.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp" />
<solid android:color="#45a8aa" />
<gradient
android:endColor="#45a8aa"
android:startColor="#6fd1c7" />
</shape>
button_main_color_selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_main_color_up" android:state_pressed="false"></item>
<item android:drawable="@drawable/button_main_color_down" android:state_pressed="true"></item>
</selector>
circle_loading.xml代码
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="18dp"
android:shape="ring"
android:thickness="2dp"
android:useLevel="false">
<solid android:color="#45a8ca" />
<gradient
android:endColor="#45a8ca"
android:startColor="#0000"
android:type="sweep" />
</shape>
在activity.xml里面的代码为:
<com.oneway.demo.loadingbutton.LoadingButton
android:id="@+id/load_btn"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/button_main_color_selector"
android:text="CLICK"
android:textColor="#ffffff"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在activity里的代码为(开始加载):
findViewById<LoadingButton>(R.id.load_btn).setOnClickListener {
(it as LoadingButton).startLoading()
}
结束加载:finishLoading