bibu
bibu

Reputation: 1269

Android - Rounded square line progress bar

I'm trying to create a rounded square line progress bar to draw a progress around an image.

enter image description here

So far, I have the following XML which defines my rounded square line:

<shape xmlns:android="http://schemas.android.com/apk/res/android" >

    <stroke
        android:width="6dp"
        android:color="@android:color/holo_green_light" />

    <padding
        android:left="5dp"
        android:right="5dp"
        android:bottom="5dp"
        android:top="5dp" />

    <corners android:radius="50dp" />

</shape>

I'm aware of this solution: https://github.com/mrwonderman/android-square-progressbar but I'm not interested in as the effect is not the one I want.

I've tried to create a plain circle on top of the rounded square line and tried to merge the two with PorterDuff, but so far I was also not able to create the progress bar effect. Drawing a pie of that circle to draw the progress.

I've also tried to create the rounded square programaticaly in case the XML inflating was considered as a plain image and all pixels were taken into account during the PorterDuff merge. But same result.

Upvotes: 6

Views: 3881

Answers (2)

Robin
Robin

Reputation: 943

I converted @pskink's answer to kotlin:

internal class V(context: Context?) : View(context) {
    var path: Path = Path()
    var paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
    var length: Float = 0f
    var intervals: FloatArray = floatArrayOf(0f, 0f)

    init {
        paint.color = Color.GREEN
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 20f
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        path.reset()
        val rect = RectF(0f, 0f, w.toFloat(), h.toFloat())
        val inset = paint.strokeWidth
        rect.inset(inset, inset)

        path.addRoundRect(rect, 100f, 100f, Path.Direction.CW)
        length = PathMeasure(path, false).length
        intervals[1] = length
        intervals[0] = intervals[1]
        val effect: PathEffect = DashPathEffect(intervals, length)
        paint.setPathEffect(effect)
    }

    fun setProgress(progress: Int) {
        val effect: PathEffect = DashPathEffect(intervals, length - length * progress / 100)
        paint.setPathEffect(effect)
        invalidate()
    }

    override fun onDraw(canvas: Canvas) {
        canvas.drawPath(path, paint)
    }
}

test code in Activity#onCreate

val ll = LinearLayout(this)
ll.orientation = LinearLayout.VERTICAL
val sb = SeekBar(this)
ll.addView(sb)
val v = V(this)
ll.addView(v)
setContentView(ll)

val sbcl: SeekBar.OnSeekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
        v.setProgress(progress)
    }

    override fun onStartTrackingTouch(seekBar: SeekBar) {}
    override fun onStopTrackingTouch(seekBar: SeekBar) {}
}
sb.setOnSeekBarChangeListener(sbcl)

in case you want to use this view in your xml instead you could also use this constructor on your V class.

internal class V(
    context: Context,
    attributeSet: AttributeSet?,
) : View(
    context,
    attributeSet
) {

Upvotes: 0

pskink
pskink

Reputation: 24720

try this simple custom class:

class V extends View {
    Path path = new Path();
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    float length;
    float[] intervals = {0, 0};

    public V(Context context) {
        super(context);
        paint.setColor(Color.GREEN);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(20);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        path.reset();
        RectF rect = new RectF(0, 0, w, h);
        float inset = paint.getStrokeWidth();
        rect.inset(inset, inset);

        path.addRoundRect(rect, 100, 100, Path.Direction.CW);
        length = new PathMeasure(path, false).getLength();
        intervals[0] = intervals[1] = length;
        PathEffect effect = new DashPathEffect(intervals, length);
        paint.setPathEffect(effect);
    }

    public void setProgress(int progress) {
        PathEffect effect = new DashPathEffect(intervals, length - length * progress / 100);
        paint.setPathEffect(effect);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(path, paint);
    }
}

test code (put in inside Activity#onCreate):

    LinearLayout ll = new LinearLayout(this);
    ll.setOrientation(LinearLayout.VERTICAL);
    SeekBar sb = new SeekBar(this);
    ll.addView(sb);
    final V v = new V(this);
    ll.addView(v);
    setContentView(ll);

    SeekBar.OnSeekBarChangeListener sbcl = new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            v.setProgress(progress);
        }
        @Override public void onStartTrackingTouch(SeekBar seekBar) {}
        @Override public void onStopTrackingTouch(SeekBar seekBar) {}
    };
    sb.setOnSeekBarChangeListener(sbcl);

Upvotes: 13

Related Questions