效果展示

源码分析
第一步:创建自定义组件类
第二步:自定义属性文件编写
<declare-styleable name="myLoadingBottomTop">
<attr name="circleNormalColor" format="color"/>
<attr name="circleChangeColor" format="color"/>
<attr name="circleRadius" format="dimension"/>
<attr name="loadingText" format="string"/>
<attr name="loadingTextSize" format="dimension"/>
<attr name="loadingTextNormalColor" format="color"/>
<attr name="loadingTextChangeColor" format="color"/>
</declare-styleable>
第三步:在布局文件中的引用
<com.wustyq.senseone.selfView.myLoadingBottomTop
android:id="@+id/lb_myLoadingBottomTop"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="10dp"
app:circleNormalColor="#ccdefc"
app:circleChangeColor="#4a8af4"
app:circleRadius="20dp"
app:loadingText="壹"
app:loadingTextSize="16sp"
app:loadingTextNormalColor="#4a8af4"
app:loadingTextChangeColor="#ccdefc"/>
<EditText
android:id="@+id/et_upDateText"
android:layout_width="match_parent"
android:layout_height="50dp"/>
<Button
android:id="@+id/bt_test_three"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="第三个组件"/>
第四步:在自定义组件类里获取属性并编写相关代码
package com.wustyq.senseone.selfView;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
import com.wustyq.senseone.R;
public class myLoadingBottomTop extends View {
private int mCircleNormalColor;
private int mCircleChangeColor;
private int mCircleRadius; //这里是dp2px
private String mLoadingText;
private int mLoadingTextSize; //这里sp2px
private int mLoadingTextNormalColor;
private int mLoadingTextChangeColor;
private Paint mCircleNormalPaint;
private Paint mCircleChangePaint;
private Paint mLoadingTextNormalPaint;
private Paint mLoadingTextChangePaint;
private float mCurrentProgress = 0.0f;
public myLoadingBottomTop(Context context) {
this(context,null);
}
public myLoadingBottomTop(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public myLoadingBottomTop(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myLoadingBottomTop);
mCircleNormalColor = typedArray.getColor(R.styleable.myLoadingBottomTop_circleNormalColor,mCircleNormalColor);
mCircleChangeColor = typedArray.getColor(R.styleable.myLoadingBottomTop_circleChangeColor,mCircleChangeColor);
mCircleRadius = (int) typedArray.getDimension(R.styleable.myLoadingBottomTop_circleRadius, dp2px(mCircleRadius));
mLoadingText = typedArray.getString(R.styleable.myLoadingBottomTop_loadingText);
mLoadingTextSize = typedArray.getDimensionPixelSize(R.styleable.myLoadingBottomTop_loadingTextSize, (int) sp2px(mLoadingTextSize));
mLoadingTextNormalColor = typedArray.getColor(R.styleable.myLoadingBottomTop_loadingTextNormalColor,mLoadingTextNormalColor);
mLoadingTextChangeColor = typedArray.getColor(R.styleable.myLoadingBottomTop_loadingTextChangeColor,mLoadingTextChangeColor);
//初始化画笔
mCircleNormalPaint = initCirclePaintByColor(mCircleNormalColor);
mCircleChangePaint = initCirclePaintByColor(mCircleChangeColor);
mLoadingTextNormalPaint = initTextPaintByColor(mLoadingTextNormalColor);
mLoadingTextChangePaint = initTextPaintByColor(mLoadingTextChangeColor);
typedArray.recycle();
}
private Paint initCirclePaintByColor(int color) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColor(color);
return paint;
}
private Paint initTextPaintByColor(int color) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColor(color);
paint.setTextSize(mLoadingTextSize);
return paint;
}
private float dp2px(int dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
private float sp2px(int sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(Math.min(width,height),Math.min(width,height));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircleText(canvas,mCircleNormalPaint,mLoadingTextNormalPaint,0,(int) (getHeight()*(1-mCurrentProgress)));
drawCircleText(canvas,mCircleChangePaint,mLoadingTextChangePaint,getHeight(),(int) (getHeight()*(1-mCurrentProgress)));
}
private void drawCircleText(Canvas canvas, Paint circlePaint, Paint textPaint, int start, int end) {
canvas.save();
Rect rect = new Rect(0,start,getWidth(), end);
canvas.clipRect(rect);
canvas.drawCircle(getWidth()/2,getHeight()/2,mCircleRadius,circlePaint);
//获取文字基线
int baseLine = getTextBaseLine();
//获取文字的宽度/高度
int textWidth = getTextWidth(mLoadingText);
textPaint.setTextSize(mLoadingTextSize);
canvas.drawText(mLoadingText,getWidth()/2 - textWidth/2,baseLine,textPaint);
canvas.restore();
}
private int getTextWidth(String str) {
Rect bounds = new Rect();
mLoadingTextNormalPaint.getTextBounds(str,0,str.length(),bounds);
return bounds.width();
}
private int getTextHeight(String str) {
Rect bounds = new Rect();
mLoadingTextNormalPaint.getTextBounds(str,0,str.length(),bounds);
return bounds.height();
}
private int getTextBaseLine() {
Paint.FontMetricsInt fontMetricsInt = mLoadingTextChangePaint.getFontMetricsInt();
int dy = (fontMetricsInt.bottom - fontMetricsInt.top)/2 - fontMetricsInt.bottom;
return getHeight()/2 + dy;
}
//对外暴露一些方法
public void setCurrentProgress(float currentProgress) {
this.mCurrentProgress = currentProgress;
invalidate();
}
public void setLoadingText(String str){
if (TextUtils.isEmpty(str)) return;
this.mLoadingText = str;
}
}
第五步:在外面调用
package com.wustyq.senseone.app;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Gravity;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.wustyq.senseone.R;
import com.wustyq.senseone.selfView.myLoading;
import com.wustyq.senseone.selfView.myLoadingBottomTop;
import com.wustyq.senseone.selfView.myProgress;
public class MainActivity extends Activity implements View.OnClickListener {
private myProgress mp_myProgress;
private myLoading ml_myLoading;
private Button bt_test_one;
private Button bt_test_two;
private int clickNum = 0;
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
ml_myLoading.exChange();
handler.sendEmptyMessageDelayed(0, 1000);
}
};
;
private Handler handler2 = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0.0f, 1.0f);
valueAnimator.setDuration(4000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
lb_myLoadingBottomTop.setCurrentProgress(value);
}
});
valueAnimator.start();
handler2.sendEmptyMessageDelayed(0, 4000);
}
};
private myLoadingBottomTop lb_myLoadingBottomTop;
private Button bt_test_three;
private EditText et_upDateText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initWelcome();
initView();
initData();
}
private void initView() {
mp_myProgress = findViewById(R.id.mp_myProgress);
ml_myLoading = findViewById(R.id.ml_myLoading);
bt_test_one = findViewById(R.id.bt_test_one);
bt_test_two = findViewById(R.id.bt_test_two);
lb_myLoadingBottomTop = findViewById(R.id.lb_myLoadingBottomTop);
bt_test_three = findViewById(R.id.bt_test_three);
et_upDateText = findViewById(R.id.et_upDateText);
}
private void initData() {
bt_test_one.setOnClickListener(this);
bt_test_two.setOnClickListener(this);
bt_test_three.setOnClickListener(this);
}
private void initWelcome() {
Toast toast = Toast.makeText(this, null, Toast.LENGTH_SHORT);
toast.setText("欢迎来到第一感知,很高兴为您服务");
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_test_one: {
System.out.println(clickNum + "clickNum");
if (clickNum == 0) {
setValueObject(mp_myProgress, 0.0f, 0.25f);
clickNum++;
} else if (clickNum == 1) {
setValueObject(mp_myProgress, 0.25f, 0.5f);
clickNum++;
} else if (clickNum == 2) {
setValueObject(mp_myProgress, 0.5f, 0.75f);
clickNum++;
} else if (clickNum == 3) {
setValueObject(mp_myProgress, 0.75f, 1.0f);
clickNum = 0;
}
}
break;
case R.id.bt_test_two: {
handler.removeCallbacksAndMessages(null);
handler.sendEmptyMessage(0);
}
break;
case R.id.bt_test_three: {
lb_myLoadingBottomTop.setLoadingText(et_upDateText.getText().toString());
handler2.removeCallbacksAndMessages(null);
handler2.sendEmptyMessage(0);
}
break;
}
}
private void setValueObject(final myProgress view, float start, float end) {
ValueAnimator valueAnimator = ObjectAnimator.ofFloat(start, end);
valueAnimator.setDuration(2000);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
view.setCurrentProgress(animatedValue);
}
});
valueAnimator.start();
}
}
总结
大家发现,越往后面的文章,写的都比较简介,因为前面我们每个方法都详细讲过了,相信大家都有基础了,再讲哪些就是凑字数了,没必要。基础不行的同志请自行复习。
注意
在获取文字 宽高 和基线的时候,如果发现获取的值不准确,文字出现了偏移,大部分原因是 代码顺序有问题,详细说明请看 获取文字基线和文字宽度不准确原因分析