【问题标题】:Android round edges on ring shaped progressbar环形进度条上的 Android 圆形边缘
【发布时间】:2015-09-22 01:00:28
【问题描述】:

我正在尝试在 android 上制作一个圆形进度条,这似乎是一项非常简单的任务,但我正在努力解决进度和次要进度的边缘。

有没有办法在不制作自定义视图的情况下做到这一点?使用拐角半径?还是九个可绘制的补丁?

对于这个视图(见附件),我使用的是一个简单的 xml 文件

<item android:id="@android:id/progress">


    <shape
        android:useLevel="true"
        android:innerRadius="@dimen/sixty_dp"
        android:shape="ring"
        android:thickness="@dimen/seven_dp">

        <solid android:color="#477C5B"/>

        <stroke android:width="1dip"
            android:color="#FFFF"/>
    </shape>






</item>

【问题讨论】:

  • 我认为你应该使用自定义视图...因为我也面临同样的问题...stackoverflow.com/q/29516123/3544839 我已经使用自定义视图完成了
  • @Moinkhan 你能在 Github 上提供你的代码吗?提前谢谢你。

标签: android widget progress-bar drawable cornerradius


【解决方案1】:

只需在您的包中创建名为MyProgress 的类.. 并粘贴以下代码..

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;

public class MyProgress extends View {

    private Paint mPrimaryPaint;
    private Paint mSecondaryPaint;
    private RectF mRectF;
    private TextPaint mTextPaint;
    private Paint mBackgroundPaint;

    private boolean mDrawText = false;

    private int mSecondaryProgressColor;
    private int mPrimaryProgressColor;
    private int mBackgroundColor;

    private int mStrokeWidth;

    private int mProgress;
    private int mSecodaryProgress;

    private int mTextColor;

    private int mPrimaryCapSize;
    private int mSecondaryCapSize;
    private boolean mIsPrimaryCapVisible;
    private boolean mIsSecondaryCapVisible;

    private int x;
    private int y;
    private int mWidth = 0, mHeight = 0;


    public MyProgress(Context context) {
        super(context);
        init(context, null);
    }

    public MyProgress(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public MyProgress(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    void init(Context context, AttributeSet attrs) {
        TypedArray a;
        if (attrs != null) {
            a = context.getTheme().obtainStyledAttributes(
                    attrs,
                    R.styleable.MyProgress,
                    0, 0);
        } else {
            throw new IllegalArgumentException("Must have to pass the attributes");
        }

        try {
            mDrawText = a.getBoolean(R.styleable.MyProgress_showProgressText, false);

            mBackgroundColor = a.getColor(R.styleable.MyProgress_backgroundColor, android.R.color.darker_gray);
            mPrimaryProgressColor = a.getColor(R.styleable.MyProgress_progressColor, android.R.color.darker_gray);
            mSecondaryProgressColor = a.getColor(R.styleable.MyProgress_secondaryProgressColor, android.R.color.black);

            mProgress = a.getInt(R.styleable.MyProgress_progress, 0);
            mSecodaryProgress = a.getInt(R.styleable.MyProgress_secondaryProgress, 0);

            mStrokeWidth = a.getDimensionPixelSize(R.styleable.MyProgress_strokeWidth, 20);
            mTextColor = a.getColor(R.styleable.MyProgress_textColor, android.R.color.black);

            mPrimaryCapSize = a.getInt(R.styleable.MyProgress_primaryCapSize, 20);
            mSecondaryCapSize = a.getInt(R.styleable.MyProgress_secodaryCapSize, 20);

            mIsPrimaryCapVisible = a.getBoolean(R.styleable.MyProgress_primaryCapVisibility, true);
            mIsSecondaryCapVisible = a.getBoolean(R.styleable.MyProgress_secodaryCapVisibility, true);
        } finally {
            a.recycle();
        }

        mBackgroundPaint = new Paint();
        mBackgroundPaint.setAntiAlias(true);
        mBackgroundPaint.setStyle(Paint.Style.STROKE);
        mBackgroundPaint.setStrokeWidth(mStrokeWidth);
        mBackgroundPaint.setColor(mBackgroundColor);

        mPrimaryPaint = new Paint();
        mPrimaryPaint.setAntiAlias(true);
        mPrimaryPaint.setStyle(Paint.Style.STROKE);
        mPrimaryPaint.setStrokeWidth(mStrokeWidth);
        mPrimaryPaint.setColor(mPrimaryProgressColor);

        mSecondaryPaint = new Paint();
        mSecondaryPaint.setAntiAlias(true);
        mSecondaryPaint.setStyle(Paint.Style.STROKE);
        mSecondaryPaint.setStrokeWidth(mStrokeWidth - 2);
        mSecondaryPaint.setColor(mSecondaryProgressColor);

        mTextPaint = new TextPaint();
        mTextPaint.setColor(mTextColor);

        mRectF = new RectF();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRectF.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - getPaddingBottom());
        mTextPaint.setTextSize(w / 5);
        x = (w / 2) - ((int) (mTextPaint.measureText(mProgress + "%") / 2));
        y = (int) ((h / 2) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
        mWidth = w;
        mHeight = h;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPrimaryPaint.setStyle(Paint.Style.STROKE);
        mSecondaryPaint.setStyle(Paint.Style.STROKE);

        // for drawing a full progress .. The background circle
        canvas.drawArc(mRectF, 0, 360, false, mBackgroundPaint);

        // for drawing a secondary progress circle
        int secondarySwipeangle = (mSecodaryProgress * 360) / 100;
        canvas.drawArc(mRectF, 270, secondarySwipeangle, false, mSecondaryPaint);

        // for drawing a main progress circle
        int primarySwipeangle = (mProgress * 360) / 100;
        canvas.drawArc(mRectF, 270, primarySwipeangle, false, mPrimaryPaint);

        // for cap of secondary progress
        int r = (getHeight() - getPaddingLeft() * 2) / 2;      // Calculated from canvas width
        double trad = (secondarySwipeangle - 90) * (Math.PI / 180d); // = 5.1051
        int x = (int) (r * Math.cos(trad));
        int y = (int) (r * Math.sin(trad));
        mSecondaryPaint.setStyle(Paint.Style.FILL);
        if (mIsSecondaryCapVisible)
            canvas.drawCircle(x + (mWidth / 2), y + (mHeight / 2), mSecondaryCapSize, mSecondaryPaint);

        // for cap of primary progress
        trad = (primarySwipeangle - 90) * (Math.PI / 180d); // = 5.1051
        x = (int) (r * Math.cos(trad));
        y = (int) (r * Math.sin(trad));
        mPrimaryPaint.setStyle(Paint.Style.FILL);
        if (mIsPrimaryCapVisible)
            canvas.drawCircle(x + (mWidth / 2), y + (mHeight / 2), mPrimaryCapSize, mPrimaryPaint);


        if (mDrawText)
            canvas.drawText(mProgress + "%", x, y, mTextPaint);
    }

    public void setDrawText(boolean mDrawText) {
        this.mDrawText = mDrawText;
        invalidate();
    }

    public void setBackgroundColor(int mBackgroundColor) {
        this.mBackgroundColor = mBackgroundColor;
        invalidate();
    }

    public void setSecondaryProgressColor(int mSecondaryProgressColor) {
        this.mSecondaryProgressColor = mSecondaryProgressColor;
        invalidate();
    }

    public void setPrimaryProgressColor(int mPrimaryProgressColor) {
        this.mPrimaryProgressColor = mPrimaryProgressColor;
        invalidate();
    }

    public void setStrokeWidth(int mStrokeWidth) {
        this.mStrokeWidth = mStrokeWidth;
        invalidate();
    }

    public void setProgress(int mProgress) {
        this.mProgress = mProgress;
        invalidate();
    }

    public void setSecondaryProgress(int mSecondaryProgress) {
        this.mSecodaryProgress = mSecondaryProgress;
        invalidate();
    }

    public void setTextColor(int mTextColor) {
        this.mTextColor = mTextColor;
        invalidate();
    }

    public void setPrimaryCapSize(int mPrimaryCapSize) {
        this.mPrimaryCapSize = mPrimaryCapSize;
        invalidate();
    }

    public void setSecondaryCapSize(int mSecondaryCapSize) {
        this.mSecondaryCapSize = mSecondaryCapSize;
        invalidate();
    }

    public boolean isPrimaryCapVisible() {
        return mIsPrimaryCapVisible;
    }

    public void setIsPrimaryCapVisible(boolean mIsPrimaryCapVisible) {
        this.mIsPrimaryCapVisible = mIsPrimaryCapVisible;
    }

    public boolean isSecondaryCapVisible() {
        return mIsSecondaryCapVisible;
    }

    public void setIsSecondaryCapVisible(boolean mIsSecondaryCapVisible) {
        this.mIsSecondaryCapVisible = mIsSecondaryCapVisible;
    }


    public int getSecondaryProgressColor() {
        return mSecondaryProgressColor;
    }

    public int getPrimaryProgressColor() {
        return mPrimaryProgressColor;
    }

    public int getProgress() {
        return mProgress;
    }

    public int getBackgroundColor() {
        return mBackgroundColor;
    }

    public int getSecodaryProgress() {
        return mSecodaryProgress;
    }

    public int getPrimaryCapSize() {
        return mPrimaryCapSize;
    }

    public int getSecondaryCapSize() {
        return mSecondaryCapSize;
    }
}

并在标签下的res->values->attr.xml中添加以下行并构建它

<declare-styleable name="MyProgress">
    <attr name="showProgressText" format="boolean" />
    <attr name="progress" format="integer" />
    <attr name="secondaryProgress" format="integer" />
    <attr name="progressColor" format="color" />
    <attr name="secondaryProgressColor" format="color" />
    <attr name="backgroundColor" format="color" />
    <attr name="primaryCapSize" format="integer" />
    <attr name="secodaryCapSize" format="integer" />
    <attr name="primaryCapVisibility" format="boolean" />
    <attr name="secodaryCapVisibility" format="boolean" />
    <attr name="strokeWidth" format="dimension" />
    <attr name="textColor" format="color" />
</declare-styleable>

就是这样...... 并在您的布局中使用..

<Your_Package_Name.MyProgress
    android:padding="20dp"
    android:id="@+id/timer1"
    app:strokeWidth="10dp"
    app:progress="30"
    app:secondaryProgress="50"
    app:backgroundColor="@android:color/black"
    app:progressColor="@android:color/holo_blue_bright"
    app:secondaryProgressColor="@android:color/holo_blue_dark"
    app:primaryCapSize="30"
    app:secodaryCapSize="40"
    app:primaryCapVisibility="true"
    app:secodaryCapVisibility="true"
    android:layout_width="200dp"
    android:layout_height="200dp" />

您还可以使用 setMethods() 以编程方式更改所有属性...

随便问什么.. 祝你好运

[2016 年 1 月 23 日更新]

最后我在 github 上上传了代码。 你可以从这里参考 https://github.com/msquare097/MProgressBar

现在您可以通过在您的应用程序 build.gradle 文件中编写以下行来使用此 ProgressBar。 您不必复制以上代码。

compile 'com.msquare.widget.mprogressbar:mprogressbar:1.0.0'

【讨论】:

  • 谢谢,这正是我所需要的。接受你的回答。在 github 上提供它会很棒。
  • @Moinkhan 我如何设置 Cap 不仅用于进度的结束,也用于第一个? TnX
  • 好的...对于起始上限,我没有这样做。但是您可以通过将次要进度设置为 0 并根据需要应用上限来实现这一点...希望您理解...
  • @Moinkhan Tnx 为您解答,我以另一种方式做到了,我添加了 mPrimaryPaint.setStrokeCap(Paint.Cap.ROUND);到你实例化 mPrimaryPaint 的地方结束了,这解决了我的问题
【解决方案2】:

我能够使用图层列表来实现这一点,并在线条的每一侧添加一个点。第一个将卡在顶部,而第二个将跟随进度,因为在旋转元素中添加了插图。不过,您必须根据进度条布局的大小对其进行调整。我的是 250dp x 250dp。

progress_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <rotate android:fromDegrees="270" android:toDegrees="270">
        <shape
            android:innerRadiusRatio="2.55"
            android:shape="ring"
            android:thickness="15dp"
            android:useLevel="true">
            <solid android:color="@color/main_color" />
        </shape>
        </rotate>
    </item>
    <item android:bottom="211dp">
        <shape
            android:innerRadiusRatio="1000"
            android:shape="ring"
            android:thickness="7dp"
            android:useLevel="false">
            <solid android:color="@color/main_color" />
        </shape>
    </item>
    <item>
        <rotate>
            <inset android:insetBottom="211dp">
                <shape
                    android:innerRadiusRatio="1000"
                    android:shape="ring"
                    android:thickness="7dp"
                    android:useLevel="false">
                    <solid android:color="@color/main_color" />
                </shape>
            </inset>
        </rotate>
    </item>
</layer-list>

我这里没有辅助流程,但您应该也可以针对它进行调整。

【讨论】:

  • 布局中的ProgressBar 元素是什么样的?我试过将此drawable设置为android:progressDrawable属性无效
  • 它有效,很棒。唯一的缺点是,如果尺寸不同于 250dp,您必须更改值
  • 喜欢这个!干的很好。但是,我确实需要进展的开始是方正的。此外,如果 progress=0 点仍然显示。当progress = 0时如何没有点?
  • 它对我有用。这就是我要找的。只需要稍微调整一下'bottom'和'insetBottom'。我实际上不明白它为什么以及如何工作。我的进度条大小是 92dp,厚度是 6dp,不知道为什么我必须将bottominsetBottom 设置为 67.5。如果你知道答案,请解释一下。
【解决方案3】:

一个简单高效的类扩展 View 以绘制圆形进度,圆角作为选项。进度颜色、背景颜色、笔划宽度也可自定义。如my other answer 所示。

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import androidx.annotation.FloatRange

class CircularProgressView : View {
  constructor(context: Context) : super(context)
  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

  private val progressPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    style = Paint.Style.STROKE
  }
  private val backgroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    style = Paint.Style.STROKE
  }

  private val rect = RectF()
  private val startAngle = -90f
  private val maxAngle = 360f
  private val maxProgress = 100

  private var diameter = 0f
  private var angle = 0f

  override fun onDraw(canvas: Canvas) {
    drawCircle(maxAngle, canvas, backgroundPaint)
    drawCircle(angle, canvas, progressPaint)
  }

  override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
    diameter = Math.min(width, height).toFloat()
    updateRect()
  }

  private fun updateRect() {
    val strokeWidth = backgroundPaint.strokeWidth
    rect.set(strokeWidth, strokeWidth, diameter - strokeWidth, diameter - strokeWidth)
  }

  private fun drawCircle(angle: Float, canvas: Canvas, paint: Paint) {
    canvas.drawArc(rect, startAngle, angle, false, paint)
  }

  private fun calculateAngle(progress: Float) = maxAngle / maxProgress * progress

  fun setProgress(@FloatRange(from = 0.0, to = 100.0) progress: Float) {
    angle = calculateAngle(progress)
    invalidate()
  }

  fun setProgressColor(color: Int) {
    progressPaint.color = color
    invalidate()
  }

  fun setProgressBackgroundColor(color: Int) {
    backgroundPaint.color = color
    invalidate()
  }

  fun setProgressWidth(width: Float) {
    progressPaint.strokeWidth = width
    backgroundPaint.strokeWidth = width
    updateRect()
    invalidate()
  }

  fun setRounded(rounded: Boolean) {
    progressPaint.strokeCap = if (rounded) Paint.Cap.ROUND else Paint.Cap.BUTT
    invalidate()
  }
}

【讨论】:

    【解决方案4】:

    您可以在环的末端和开始侧添加一个圆形/椭圆形,就像下面的代码一样

    一个图层列表drawable包含两个圆形/椭圆形drawable和一个环形drawable

    round_progress_drawable.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:bottom="294px"
            android:left="540px"
            android:right="48px"
            android:top="294px">
            <shape
                android:innerRadius="6px"
                android:shape="oval">
                <solid android:color="#FFFFFF"/>
            </shape>
        </item>
        <item>
            <shape
                android:innerRadius="240px"
                android:shape="ring"
                android:thickness="12px">
                <size
                    android:width="600px"
                    android:height="600px"/>
                <solid android:color="#FFFFFF"/>
            </shape>
        </item>
        <item>
            <rotate>
                <layer-list>
                    <item
                        android:bottom="294px"
                        android:left="540px"
                        android:right="48px"
                        android:top="294px">
                        <shape
                            android:innerRadius="6px"
                            android:shape="oval">
                            <solid android:color="#FFFFFF"/>
                        </shape>
                    </item>
                </layer-list>
            </rotate>
        </item>
    </layer-list>
    

    round_progress_animation_selector.xml

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_window_focused="true">
            <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="5000"
                android:propertyName="ImageLevel"
                android:repeatCount="infinite"
                android:valueFrom="0"
                android:valueTo="10000"
                android:valueType="intType">
    
            </objectAnimator>
        </item>
        <item>
            <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="0"
                android:propertyName="ImageLevel"
                android:valueFrom="0"
                android:valueTo="0"
                android:valueType="intType">
    
            </objectAnimator>
        </item>
    </selector>
    

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimaryDark"
        tools:context=".MainActivity">
    
    
        <ImageView
            android:stateListAnimator="@animator/round_progress_animation_selector"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/round_progress_drawable" />
    
    </FrameLayout>
    

    【讨论】:

      【解决方案5】:

      我尝试为任意大小的进度条解决这个问题,如果您可以确保进度条是正方形并且您不使用边距,这应该可以工作,但它不会。 Android 10 有时会正确呈现它,有时却不会。尤其是进度值 > 50% 往往会严重崩溃。这不是一个可行的解决方案,但可能会给某人一个想法。渐变填充仍然存在问题。最终颜色与它不匹配。但如果你使用纯色,它应该可以正常工作。此外,圆形帽使条形图显示更大的值范围。我的想法是将圆环替换为背景颜色为 5dp x 2.5dp 的矩形,并剪裁出半个圆形。您需要为开始和结束使用不同的旋转:

      <?xml version="1.0" encoding="utf-8"?>
      <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
          <item>
      <rotate
          android:fromDegrees="270"
          android:toDegrees="270">
          <shape
              android:innerRadiusRatio="2.4"
              android:shape="ring"
              android:thickness="5dp"
              android:useLevel="true"><!-- this line fixes the issue for lollipop api 21 -->
      
              <gradient
                  android:angle="0"
                  android:endColor="@color/yellow"
                  android:startColor="@color/orange"
                  android:type="sweep"
                  android:useLevel="false" />
      
          </shape>
      </rotate>
          </item>
          <item
              android:gravity="top">
              <shape
                  android:innerRadiusRatio="1000"
                  android:shape="ring"
                  android:thickness="2.5dp"
                  android:useLevel="false">
                  <solid android:color="@color/orange" />
                  <size android:height="5dp"/>
              </shape>
          </item>
          <item>
              <rotate>
                  <scale android:scaleGravity="top|center_horizontal"
                      android:scaleHeight="150%"
                      android:scaleWidth="150%">
                      <shape
                          android:innerRadiusRatio="1000"
                          android:shape="ring"
                          android:thickness="2.5dp"
                          android:useLevel="false">
                          <solid android:color="@color/yellow" />
                      </shape>
                  </scale>
              </rotate>
          </item>
      </layer-list>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多