You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android无第三方库实现环形ProgressBar内部填充椭圆(解决超出问题)

解决环形ProgressBar内部椭圆超出问题(无需第三方库)

我完全懂你的困扰——用layer-list做的内部椭圆比环形大,又不能加白色描边遮挡,因为要保持环形周围透明对吧?不用第三方库的话,咱们有两种靠谱的解决方法:

方法一:调整Layer-List的Inset

这是最快捷的方式,通过给内部椭圆的item添加inset属性,把椭圆缩小到环形的内部范围。

根据你现有的环形参数:innerRadiusRatio="3"(内部半径是总宽度的1/3)、thicknessRatio="15"(环形厚度是总宽度的1/15),咱们可以算出椭圆需要的缩进量:总宽度的1/6(因为椭圆直径要等于环形的内部直径,也就是总宽度的2/3,所以缩进量是(总宽度 - 2/3总宽度)/2 = 1/6总宽度)。

修改你的自定义Drawable文件:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/backgroundCenter">
        <!-- 添加inset让椭圆适配环形内部 -->
        <inset
            android:insetBottom="16.67%"
            android:insetLeft="16.67%"
            android:insetRight="16.67%"
            android:insetTop="16.67%">
            <shape android:shape="oval">
                <solid android:color="@color/orange_dark" />
            </shape>
        </inset>
    </item>
    <item android:id="@android:id/background">
        <!--Background progress-->
        <shape
            android:innerRadiusRatio="3"
            android:shape="ring"
            android:thicknessRatio="15"
            android:useLevel="false">
            <solid android:color="@color/light_grey" />
        </shape>
    </item>
    <item android:id="@android:id/secondaryProgress">
        <shape
            android:innerRadiusRatio="3"
            android:shape="ring"
            android:thicknessRatio="15"
            android:useLevel="true">
            <gradient
                android:centerColor="#999999"
                android:endColor="#999999"
                android:startColor="#999999"
                android:type="sweep" />
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <rotate
            android:fromDegrees="270"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toDegrees="270">
            <shape
                android:innerRadiusRatio="3"
                android:shape="ring"
                android:thicknessRatio="15"
                android:useLevel="true">
                <rotate
                    android:fromDegrees="0"
                    android:pivotX="50%"
                    android:pivotY="50%"
                    android:toDegrees="360" />
                <solid android:color="@color/cyan_light" />
            </shape>
        </rotate>
    </item>
</layer-list>

这里用百分比16.67%(约等于1/6),不管ProgressBar的尺寸是多少,椭圆都会自动适配环形内部,不会超出。

方法二:自定义Drawable精确绘制

如果需要更灵活的控制(比如后续要加动画或者调整样式),自定义Drawable是更好的选择。咱们可以直接在onDraw方法里依次绘制背景环形、次级进度、进度和内部椭圆,完全控制每个元素的大小。

步骤1:创建自定义Drawable类

public class CircularProgressDrawable extends Drawable {
    private final Paint mBackgroundPaint;
    private final Paint mProgressPaint;
    private final Paint mSecondaryProgressPaint;
    private final Paint mCenterPaint;

    private final RectF mDrawBounds = new RectF();
    private int mMax = 100;
    private int mProgress = 0;
    private int mSecondaryProgress = 0;
    // 和你原来的参数保持一致
    private final float mInnerRadiusRatio = 3f;
    private final float mThicknessRatio = 15f;

    public CircularProgressDrawable(Context context) {
        // 初始化画笔
        mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBackgroundPaint.setColor(ContextCompat.getColor(context, R.color.light_grey));
        mBackgroundPaint.setStyle(Paint.Style.STROKE);

        mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mProgressPaint.setColor(ContextCompat.getColor(context, R.color.cyan_light));
        mProgressPaint.setStyle(Paint.Style.STROKE);

        mSecondaryProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mSecondaryProgressPaint.setColor(Color.parseColor("#999999"));
        mSecondaryProgressPaint.setStyle(Paint.Style.STROKE);

        mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCenterPaint.setColor(ContextCompat.getColor(context, R.color.orange_dark));
        mCenterPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        mDrawBounds.set(bounds);
        // 更新环形的厚度
        float strokeWidth = bounds.width() / mThicknessRatio;
        mBackgroundPaint.setStrokeWidth(strokeWidth);
        mProgressPaint.setStrokeWidth(strokeWidth);
        mSecondaryProgressPaint.setStrokeWidth(strokeWidth);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        float centerX = mDrawBounds.centerX();
        float centerY = mDrawBounds.centerY();
        float innerRadius = mDrawBounds.width() / mInnerRadiusRatio;

        // 1. 绘制背景环形
        canvas.drawCircle(centerX, centerY, innerRadius + mBackgroundPaint.getStrokeWidth()/2, mBackgroundPaint);

        // 2. 绘制次级进度
        float secondarySweep = (float) mSecondaryProgress / mMax * 360f;
        canvas.drawArc(mDrawBounds, 270f, secondarySweep, false, mSecondaryProgressPaint);

        // 3. 绘制主进度
        float progressSweep = (float) mProgress / mMax * 360f;
        canvas.drawArc(mDrawBounds, 270f, progressSweep, false, mProgressPaint);

        // 4. 绘制内部椭圆(这里实际是正圆,和环形内部完全匹配)
        canvas.drawCircle(centerX, centerY, innerRadius, mCenterPaint);
    }

    // 实现Drawable的必要方法
    @Override
    public void setAlpha(int alpha) {
        mBackgroundPaint.setAlpha(alpha);
        mProgressPaint.setAlpha(alpha);
        mSecondaryProgressPaint.setAlpha(alpha);
        mCenterPaint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        mBackgroundPaint.setColorFilter(colorFilter);
        mProgressPaint.setColorFilter(colorFilter);
        mSecondaryProgressPaint.setColorFilter(colorFilter);
        mCenterPaint.setColorFilter(colorFilter);
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    // 提供设置进度的方法,和系统ProgressBar兼容
    public void setProgress(int progress) {
        mProgress = Math.max(0, Math.min(mMax, progress));
        invalidateSelf();
    }

    public void setSecondaryProgress(int secondaryProgress) {
        mSecondaryProgress = Math.max(0, Math.min(mMax, secondaryProgress));
        invalidateSelf();
    }

    public void setMax(int max) {
        mMax = Math.max(1, max);
        invalidateSelf();
    }
}

步骤2:在代码中设置Drawable

在你的Activity/Fragment里:

ProgressBar statusProgressBar = findViewById(R.id.statusProgressBar);
CircularProgressDrawable customDrawable = new CircularProgressDrawable(this);
statusProgressBar.setProgressDrawable(customDrawable);
// 设置进度
statusProgressBar.setMax(100);
statusProgressBar.setProgress(50);

这种方法完全避免了layer-list的布局冲突,所有元素都是精确绘制的,环形周围自然保持透明,不会有超出的问题。

内容的提问来源于stack exchange,提问作者Timo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:22:55