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
相关产品推荐
相关产品推荐

