1. CircularProgressBar.java
package com.example.myapplication.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DrawFilter;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.example.myapplication.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class CircularProgressBar extends View {
private static final int WAVE_PAINT_COLOR = 0xC815BC83;
private static final int OUTER_RING_COLOR = 0xF800838F;
private static final float STRETCH_FACTOR_A = 16;
private static final int TRANSLATE_X_SPEED_ONE = 5;
private static final int TRANSLATE_X_SPEED_TWO = 3;
private static final int BALL_COLOR = Color.BLUE;
private static final float BALL_RADIUS = 7.0f;
protected Context mContext;
private String centerText;
private float centerTextSize;
private int centerTextColor, wavePaintColor, strokeColor, submergedTextColor;
private float strokeWidth;
private float waveHeight;
private int max, progress;
private @BindingText int bindingText;
private Paint mWavePaint, centerPaint, outerRingPaint, mBallPaint;
private DrawFilter mDrawFilter;
private Path cirPath;
private Rect textRect;
private int width, height;
private float[] mYPositions, mResetOneYPositions, mResetTwoYPositions;
private int mXOffsetSpeedOne, mXOffsetSpeedTwo;
private int mXOneOffset, mXTwoOffset;
private float ballAngle = 0;
@IntDef({NONE, TOP_TEXT, CENTER_TEXT, BOTTOM_TEXT})
@Retention(RetentionPolicy.SOURCE)
private @interface BindingText {
}
public static final int NONE = 0;
public static final int TOP_TEXT = 1;
public static final int CENTER_TEXT = 2;
public static final int BOTTOM_TEXT = 3;
public CircularProgressBar(Context context) {
super(context);
init(context, null);
}
public CircularProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs) {
this.mContext = context;
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.WaveProgressView);
bindingText = array.getInt(R.styleable.WaveProgressView_bindingText, NONE);
wavePaintColor = array.getColor(R.styleable.WaveProgressView_wave_color, WAVE_PAINT_COLOR);
submergedTextColor = array.getColor(R.styleable.WaveProgressView_submerged_textColor, 0xffffff0);
max = array.getInt(R.styleable.WaveProgressView_max, 100);
waveHeight = array.getDimension(R.styleable.WaveProgressView_wave_height, STRETCH_FACTOR_A);
strokeWidth = array.getDimension(R.styleable.WaveProgressView_stroke_width, getResources().getDimension(R.dimen.stroke_width));
strokeColor = array.getColor(R.styleable.WaveProgressView_stroke_color, OUTER_RING_COLOR);
centerText = array.getString(R.styleable.WaveProgressView_center_text);
centerTextColor = array.getColor(R.styleable.WaveProgressView_center_textColor, Color.BLACK);
centerTextSize = array.getDimension(R.styleable.WaveProgressView_center_textSize, getResources().getDimension(R.dimen.center_text_size));
if (centerText == null) centerText = "";
outerRingPaint = new Paint();
outerRingPaint.setAntiAlias(true);
outerRingPaint.setStrokeWidth(strokeWidth);
outerRingPaint.setColor(strokeColor);
outerRingPaint.setStyle(Paint.Style.STROKE);
mWavePaint = new Paint();
mWavePaint.setAntiAlias(true);
mWavePaint.setStyle(Paint.Style.FILL);
mWavePaint.setColor(wavePaintColor);
mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
centerPaint = new Paint();
centerPaint.setAntiAlias(true);
centerPaint.setTextSize(centerTextSize);
mBallPaint = new Paint();
mBallPaint.setAntiAlias(true);
mBallPaint.setColor(BALL_COLOR);
mBallPaint.setStyle(Paint.Style.FILL);
int progress = array.getInt(R.styleable.WaveProgressView_progress, 0);
setProgress(progress);
textRect = new Rect();
cirPath = new Path();
array.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
int size = dp2px(mContext, 150);
setMeasuredDimension(size, size);
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(dp2px(mContext, 150), MeasureSpec.getSize(heightMeasureSpec));
} else if (heightMode == MeasureSpec.AT_MOST) {
int size = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(size, size);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w < h)
width = height = w;
else
width = height = h;
mYPositions = new float[width];
mResetOneYPositions = new float[width];
mResetTwoYPositions = new float[width];
float mCycleFactorW = (float) (2 * Math.PI / width);
for (int i = 0; i < width; i++) {
mYPositions[i] = (float) (waveHeight * Math.sin(mCycleFactorW * i) - waveHeight);
}
cirPath.addCircle(width / 2.0f, height / 2.0f, height / 4.0f - strokeWidth + 0.3f, Path.Direction.CW);
mXOffsetSpeedOne = dp2px(mContext, TRANSLATE_X_SPEED_ONE) * width / dp2px(mContext, 330);
mXOffsetSpeedTwo = dp2px(mContext, TRANSLATE_X_SPEED_TWO) * width / dp2px(mContext, 330);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.setDrawFilter(mDrawFilter);
canvas.save();
canvas.clipPath(cirPath);
resetPositionY();
float proHeight = height - (float) progress / max * (height / 2.0f - strokeWidth) - height/4.0f -strokeWidth;
Log.d("Circle","proHeight: "+proHeight);
for (int i = 0; i < width; i++) {
canvas.drawLine(i, proHeight - mResetOneYPositions[i], i, height, mWavePaint);
canvas.drawLine(i, proHeight - mResetTwoYPositions[i], i, height, mWavePaint);
}
canvas.restore();
float centerTextY = height / 2.0f + textRect.height() / 2.0f;
centerPaint.setColor(centerTextY > proHeight + textRect.height() ? (submergedTextColor == 0xffffff0 ? centerTextColor : submergedTextColor) : centerTextColor);
centerPaint.getTextBounds(centerText, 0, centerText.length(), textRect);
canvas.drawText(centerText, width / 2.0f - textRect.width() / 2.0f, centerTextY, centerPaint);
if (strokeWidth > 0)
canvas.drawCircle(width / 2.0f, height / 2.0f, width / 4.0f - strokeWidth / 2, outerRingPaint);
float ballRadius = width / 2.6f - strokeWidth * 3 / 2;
float ballX = (float) (width / 2.0f + ballRadius * Math.cos(ballAngle));
float ballY = (float) (height / 2.0f + ballRadius * Math.sin(ballAngle));
canvas.drawCircle(ballX, ballY, BALL_RADIUS, mBallPaint);
mXOneOffset += mXOffsetSpeedOne;
mXTwoOffset += mXOffsetSpeedTwo;
if (mXOneOffset >= width)
mXOneOffset = 0;
if (mXTwoOffset > width)
mXTwoOffset = 0;
ballAngle += 0.05f;
if (ballAngle >= 2 * Math.PI) {
ballAngle -= 2 * Math.PI;
}
if (waveHeight > 0)
postInvalidate();
}
private void resetPositionY() {
int oneInterval = mYPositions.length - mXOneOffset;
System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, oneInterval);
System.arraycopy(mYPositions, 0, mResetOneYPositions, oneInterval, mXOneOffset);
int twoInterval = mYPositions.length - mXTwoOffset;
System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0, twoInterval);
System.arraycopy(mYPositions, 0, mResetTwoYPositions, twoInterval, mXTwoOffset);
}
private int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
invalidate();
}
public int getProgress() {
return progress;
}
@SuppressLint("DefaultLocale")
public void setProgress(int progress) {
this.progress = progress;
String content = String.format("%.2f", (float) progress / max * 100) + "%";
switch (bindingText) {
case CENTER_TEXT:
centerText = content;
break;
case NONE:
break;
}
postInvalidate();
}
public int getBindingText() {
return bindingText;
}
public void setBindingText(@BindingText int bindingText) {
this.bindingText = bindingText;
}
public String getCenterText() {
return centerText;
}
public void setCenterText(String centerText) {
this.centerText = centerText == null ? "" : centerText;
invalidate();
}
public float getCenterTextSize() {
return centerTextSize;
}
public void setCenterTextSize(float centerTextSize) {
centerPaint.setTextSize(centerTextSize);
invalidate();
}
public int getCenterTextColor() {
return centerTextColor;
}
public void setCenterTextColor(int centerTextColor) {
this.centerTextColor = centerTextColor;
invalidate();
}
public float getStrokeWidth() {
return strokeWidth;
}
public void setStrokeWidth(float strokeWidth) {
this.strokeWidth = strokeWidth;
invalidate();
}
public int getStrokeColor() {
return strokeColor;
}
public void setStrokeColor(int strokeColor) {
outerRingPaint.setColor(strokeColor);
invalidate();
}
public int getWaveColor() {
return wavePaintColor;
}
public void setWaveColor(int wavePaintColor) {
mWavePaint.setColor(wavePaintColor);
invalidate();
}
}
2. 推进度
package com.example.myapplication.view;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.SeekBar;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.R;
public class MainActivity extends AppCompatActivity {
private CircularProgressBar progressBar;
private Handler handler = new Handler();
private int progress = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.circularProgressBar);
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (progress <= 100) {
progressBar.setProgress(progress);
progress += 2;
handler.postDelayed(this, 200);
} else {
Intent intent = new Intent(MainActivity.this, NextActivity.class);
startActivity(intent);
finish();
}
}
}, 200);
}
}
3. attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="bindingText">
<enum name="none" value="0"/>
<enum name="top_text" value="1" />
<enum name="center_text" value="2" />
<enum name="bottom_text" value="3" />
</attr>
<declare-styleable name="WaveProgressView">
<attr name="top_text" format="string" />
<attr name="top_textSize" format="dimension" />
<attr name="top_textColor" format="color" />
<attr name="center_text" format="string" />
<attr name="center_textSize" format="dimension" />
<attr name="center_textColor" format="color" />
<attr name="bottom_text" format="string" />
<attr name="bottom_textSize" format="dimension" />
<attr name="bottom_textColor" format="color" />
<attr name="max" format="integer" />
<attr name="progress" format="integer" />
<attr name="bindingText" />
<attr name="wave_color" format="color" />
<attr name="wave_height" format="dimension" />
<attr name="stroke_color" format="color" />
<attr name="stroke_width" format="dimension" />
<attr name="submerged_textColor" format="color" />
</declare-styleable>
</resources>
4. dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="top_text_size">18sp</dimen>
<dimen name="center_text_size">22sp</dimen>
<dimen name="bottom_text_size">16sp</dimen>
<dimen name="stroke_width">4dp</dimen>
</resources>
5.colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="waveColor">#87CEFA</color>
<color name="backgroundColor">#FFFFFF</color>
</resources>
6. thems.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
7. layout.xml
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.myapplication.view.CircularProgressBar
android:id="@+id/circularProgressBar"
android:layout_width="381dp"
android:layout_height="384dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="15dp"
android:layout_marginTop="93dp"
android:layout_marginEnd="15dp"
android:layout_marginBottom="254dp"
app:bindingText="center_text"
app:stroke_color="#87CEFA"
app:wave_color="#87CEFA" />
</RelativeLayout>