Android自定义View实现圆形渐变多点的加载框效果
更新时间:2025年09月14日 14:35:23 作者:夏沫琅琊
自定义View是Android开发中的一项重要技术,允许开发者创建满足特定需求的图形用户界面,这篇文章主要介绍了Android自定义View实现圆形渐变多点的加载框效果,文中通过代码介绍的非常详细,需要的朋友可以参考下
本文主要记录创建一个 Android 自定义加载弹窗,实现指定个数且从小到大的实心圆周期性旋转的效果。
附上效果如下:

一: 自定义圆形loadingView
1.1 核心属性定义
//默认参数
private int circleCount = 8; // 圆圈数量
private int minCircleRadius = 5; // 最小圆圈半径
private int maxCircleRadius = 15; // 最大圆圈半径
private int circleColor = Color.parseColor("#3F51B5"); //默认颜色
这些属性控制了加载动画的外观:圆的数量、大小范围、颜色以及动画实例。
1.2 构造方法
public CircleLoadingView(Context context) {
super(context);
init(null);
}
public CircleLoadingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CircleLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
- 实现了 3 个构造方法,覆盖了代码创建和 XML 布局引用两种场景
- 所有构造方法最终都调用
init()方法完成初始化,符合 Android 自定义 View 的最佳实践
1.3 初始化方法init()
private void init(AttributeSet attrs) {
if (attrs != null){
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CircleLoadingView);
circleCount = ta.getInt(R.styleable.CircleLoadingView_circleCount, circleCount);
minCircleRadius = ta.getInt(R.styleable.CircleLoadingView_minCircleRadius, minCircleRadius);
maxCircleRadius = ta.getInt(R.styleable.CircleLoadingView_maxCircleRadius, maxCircleRadius);
circleColor = ta.getColor(R.styleable.CircleLoadingView_circleColor, circleColor);
ta.recycle();
}
paint = new Paint();
paint.setColor(circleColor);//画笔颜色
paint.setStyle(Paint.Style.FILL);//填充 绘制实心圆
paint.setAntiAlias(true);//抗锯齿
initAnimation();
}
- 自定义属性处理:通过
TypedArray读取 XML 中设置的自定义属性(如圆的数量、颜色等),如果没设置则使用默认值 - 画笔初始化:配置绘制圆形的画笔,设置为实心、抗锯齿
- 动画初始化:调用
initAnimation()方法创建旋转动画
1.4 动画实现
// 创建旋转动画,3秒完成一圈,无限循环
private void initAnimation() {
rotateAnimation = new RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
);
rotateAnimation.setDuration(3000);
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setInterpolator(new LinearInterpolator());
startAnimation(rotateAnimation);
}
1.5 绘制
这是自定义 View 的核心方法,负责绘制所有圆形:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取视图中心坐标
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
// 计算可用的半径(视图最小边的一半减去最大圆半径)
int availableRadius = Math.min(centerX, centerY) - maxCircleRadius;
// 绘制每个圆
for (int i = 0; i < circleCount; i++) {
// 计算每个圆的角度(等分圆周,从顶部开始)
// 减去90度(Math.PI/2)使第一个点位于顶部
float angle = (float) (2 * Math.PI * i / circleCount - Math.PI / 2);
// 计算当前圆的半径(从小到大渐变)
float radius = minCircleRadius;
if (circleCount > 1) {
radius = minCircleRadius + (maxCircleRadius - minCircleRadius) * (float) i / (circleCount - 1);
}
// 计算圆的中心点坐标(均匀分布在圆形轨迹上)
float x = centerX + (float) (availableRadius * Math.cos(angle));
float y = centerY + (float) (availableRadius * Math.sin(angle));
// 设置画笔颜色(可选:可以根据位置设置不同的透明度)
paint.setAlpha(calculateAlpha(i));
// 绘制圆
canvas.drawCircle(x, y, radius, paint);
}
}
/**
* 根据圆点位置计算透明度,创建渐变效果
* @param index 圆点索引
* @return 透明度值(0-255)
*/
private int calculateAlpha(int index) {
// 可以根据需要调整透明度计算逻辑
// 这里创建一个渐变效果,第一个点最暗,最后一个点较亮
if (circleCount <= 1) return 255;
// 计算透明度,
return 155 + (index * 100 / (circleCount - 1));
}
1.6: 尺寸测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置默认大小
int defaultSize = dp2px(80);
int width = measureSize(defaultSize, widthMeasureSpec);
int height = measureSize(defaultSize, heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureSize(int defaultSize, int measureSpec) {
int result = defaultSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(defaultSize, specSize);
}
return result;
}
// dp转px
private int dp2px(float dpValue) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
- 处理 View 的尺寸测量,支持
wrap_content、match_parent和固定尺寸 - 默认大小为 80dp,通过
dp2px()方法将 dp 转为 px,适配不同屏幕密度
1.7 : attrs自定义参数
<!-- 自定义加载视图的属性 -->
<declare-styleable name="CircleLoadingView">
<attr name="circleCount" format="integer" />
<attr name="minCircleRadius" format="integer" />
<attr name="maxCircleRadius" format="integer" />
<attr name="circleColor" format="color" />
</declare-styleable>
目前定义了四个属性包括圆点的个数,颜色,最大最小的半径. 有其他需求的我们可以继续补充.
下面是完整的自定义View的代码:
public class CircleLoadingView extends View {
private static final String TAG = "CircleLoadingView";
//默认参数
private int circleCount = 8; // 圆圈数量
private int minCircleRadius = 5; // 最小圆圈半径
private int maxCircleRadius = 15; // 最大圆圈半径
private int circleColor = Color.parseColor("#3F51B5"); //默认颜色
// 旋转动画
private RotateAnimation rotateAnimation;
// 画笔
private Paint paint;
public CircleLoadingView(Context context) {
super(context);
init(null);
}
public CircleLoadingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CircleLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
if (attrs != null){
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CircleLoadingView);
circleCount = ta.getInt(R.styleable.CircleLoadingView_circleCount, circleCount);
minCircleRadius = ta.getInt(R.styleable.CircleLoadingView_minCircleRadius, minCircleRadius);
maxCircleRadius = ta.getInt(R.styleable.CircleLoadingView_maxCircleRadius, maxCircleRadius);
circleColor = ta.getColor(R.styleable.CircleLoadingView_circleColor, circleColor);
ta.recycle();
}
paint = new Paint();
paint.setColor(circleColor);//画笔颜色
paint.setStyle(Paint.Style.FILL);//填充 绘制实心圆
paint.setAntiAlias(true);//抗锯齿
initAnimation();
}
// 创建旋转动画,3秒完成一圈,无限循环
private void initAnimation() {
rotateAnimation = new RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
);
rotateAnimation.setDuration(3000);
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setInterpolator(new LinearInterpolator());
startAnimation(rotateAnimation);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取视图中心坐标
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
// 计算可用的半径(视图最小边的一半减去最大圆半径)
int availableRadius = Math.min(centerX, centerY) - maxCircleRadius;
// 绘制每个圆
for (int i = 0; i < circleCount; i++) {
// 计算每个圆的角度(等分圆周,从顶部开始)
// 减去90度(Math.PI/2)使第一个点位于顶部
float angle = (float) (2 * Math.PI * i / circleCount - Math.PI / 2);
// 计算当前圆的半径(从小到大渐变)
float radius = minCircleRadius;
if (circleCount > 1) {
radius = minCircleRadius + (maxCircleRadius - minCircleRadius) * (float) i / (circleCount - 1);
}
// 计算圆的中心点坐标(均匀分布在圆形轨迹上)
float x = centerX + (float) (availableRadius * Math.cos(angle));
float y = centerY + (float) (availableRadius * Math.sin(angle));
// 设置画笔颜色(可选:可以根据位置设置不同的透明度)
paint.setAlpha(calculateAlpha(i));
// 绘制圆
canvas.drawCircle(x, y, radius, paint);
}
}
/**
* 根据圆点位置计算透明度,创建渐变效果
* @param index 圆点索引
* @return 透明度值(0-255)
*/
private int calculateAlpha(int index) {
// 可以根据需要调整透明度计算逻辑
// 这里创建一个渐变效果,第一个点最暗,最后一个点较亮
if (circleCount <= 1) return 255;
// 计算透明度,
return 155 + (index * 100 / (circleCount - 1));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置默认大小
int defaultSize = dp2px(80);
int width = measureSize(defaultSize, widthMeasureSpec);
int height = measureSize(defaultSize, heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureSize(int defaultSize, int measureSpec) {
int result = defaultSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(defaultSize, specSize);
}
return result;
}
// dp转px
private int dp2px(float dpValue) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
// 开始动画
public void startLoading() {
if (rotateAnimation != null && rotateAnimation.hasEnded()) {
startAnimation(rotateAnimation);
}
}
// 停止动画
public void stopLoading() {
if (rotateAnimation != null) {
clearAnimation();
}
}
// 更新圆点的颜色
public void setCircleColor(int color) {
this.circleColor = color;
paint.setColor(color);
invalidate();
}
}
二: 自定义Dialog.
2.1 代码
这里我写的很简单, 直接集成DIalog. 界面也只有CircleLoadingView.
public class CircleDialog extends Dialog {
private static final String TAG = "CircleDialog";
private CircleLoadingView loadingView;
public CircleDialog(@NonNull Context context) {
super(context, R.style.LoadingDialogStyle);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_circle_dialog);
loadingView = findViewById(R.id.loading_view);
}
private void initWindow() {
Window window = getWindow();
if (window != null) {
// 设置窗口属性
WindowManager.LayoutParams params = window.getAttributes();
// 设置窗口居中
params.gravity = Gravity.CENTER;
// 设置窗口宽高为包裹内容
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
// 设置窗口背景透明度
params.dimAmount = 0.3f;
window.setAttributes(params);
// 设置窗口背景为透明
window.setBackgroundDrawableResource(android.R.color.transparent);
// 设置窗口是否有遮罩层
window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
}
// 设置点击外部是否可取消
setCanceledOnTouchOutside(false);
}
// 设置加载圆圈的颜色
public void setCircleColor(int color) {
if (loadingView != null) {
loadingView.setCircleColor(color);
}
}
@Override
public void show() {
super.show();
if (loadingView != null) {
loadingView.startLoading();
}
}
@Override
public void dismiss() {
super.dismiss();
if (loadingView != null) {
loadingView.stopLoading();
}
}
}
2.2: layout布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.test.accessbilitytest.view.CircleLoadingView
android:layout_width="80dp"
android:layout_height="80dp"
android:id="@+id/loading_view"
android:layout_gravity="center_horizontal"
app:circleCount="8"
app:minCircleRadius="10"
app:maxCircleRadius="16"
app:circleColor="#fffdbe32"
/>
</LinearLayout>
2.3: dialog的样式
<!-- 加载弹窗样式 -->
<style name="LoadingDialogStyle" parent="@android:style/Theme.Dialog">
<!-- 不显示标题栏 -->
<item name="android:windowNoTitle">true</item>
<!-- 背景透明 -->
<item name="android:windowBackground">@android:color/transparent</item>
<!-- 窗口进入退出动画 -->
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>
三: 弹框的特点.
1. 核心是CircleLoadingView自定义视图,它负责绘制 8个从小到大的实心圆,并通过旋转动画实现周期性旋转效果。
2. 通过自定义属性,您可以轻松调整:
圆的数量(默认 8个)
最小圆半径
最大圆半径
圆的颜色
3. LoadingDialog封装了弹窗的显示逻辑,包括设置弹窗样式、背景透明度等。
4. 使用方法简单:
创建LoadingDialog实例
调用show()方法显示
调用dismiss()方法隐藏
总结
到此这篇关于Android自定义View实现圆形渐变多点的加载框效果的文章就介绍到这了,更多相关Android自定义加载弹窗内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Android 中View.onDraw(Canvas canvas)的使用方法
这篇文章主要介绍了Android 中View.onDraw(Canvas canvas)的使用方法的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下2017-09-09


最新评论