Android - Rounded square line progress bar
Asked Answered
L

2

6

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.

Lipid answered 29/5, 2016 at 8:47 Comment(18)
create your custom Drawable class that will be used when calling setProgressDrawableMeredi
@Meredi , yes thanks but how to draw the correct progress effect on the line ?Lipid
did you create a custom Drawable class? What problens do you have when drawing round rect?Meredi
For now, I have only a View class which is converting the XML drawable into bitmap and then I try to draw on that bitmap to create the desired effect (infinite post loop incrementing again and again the progress). The problem is that I didn't find so far the correct way to draw the progress effect. The solution in the mrwonderman library seems for me not applicable there as I have rounded corner and seems also not very convenient to implement. ThxLipid
no cusom View, you have to create custom Drawable classMeredi
How this will help me to draw on top of the existing drawable ? Not sure to understand the path you want me to take. Thx.Lipid
first read full android.graphics.drawable.Drawable documentation, then try this simple custom DrawableMeredi
Sorry for late reply pskink. Thx for your response, but the piece of code you shared is to draw a plain rounded square. I have no difficulties to draw a line rounded square as shown on the first picture. Either by XML or Programmaticaly. What I want to acheive is the progress effect on that line.Lipid
Yes it draws me a half plain rounded rectangle.Lipid
ok, so define "progress effect" is it calling setProgress again and again wuth different progress parameter?Meredi
If I'm understanding correctly, you are thinking to have a standard ProgressBar with a Custom drawable: setProgressDrawable. And changes of progress call a setLevel from the Drawable ?Lipid
so what is "progress effect"? animating the progress, e. g. from 0 to max value? if so, just use ObjectAnimator for thatMeredi
as simple as thisMeredi
This is the kind of effect I want to acheive: jsfiddle.net/m1erickson/P2qTq not an horrizontal progressbar.Lipid
so you have the JS sources, what stops you from porting it to android Canvas API?Meredi
I just fint it, I will try to convert it and post it as a solution if it is working.Lipid
here you have simple solution to start with: pastebin.com/p3LNt4JgMeredi
Man that's awesome. Thx. That will be def a very good point to start with.Lipid
M
13

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);
Meredi answered 5/6, 2016 at 9:27 Comment(0)
S
0

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
) {
Specialist answered 1/5, 2024 at 9:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.