0

I have this ProgressBar xml:

<ProgressBar
  android:id="@+id/progressBar"
  style="?android:progressBarStyleHorizontal"
  android:layout_width="80dp"
  android:layout_height="80dp"
  android:layout_marginBottom="32dp"
  android:progress="0"
  android:progressDrawable="@drawable/bg_circular_progress_bar"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toTopOf="parent"
  android:elevation="11dp"/>

As can be seen, the style="?android:progressBarStyleHorizontal" specifies that it is a horizontal bar. However, with progressDrawable property, I can somehow change its shape to circular with the following drawable file:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="ring"
    android:innerRadiusRatio="2.5"
    android:thickness="4dp"
    android:useLevel="true">

    <solid android:color="@color/colorPrimary" />
</shape>
  • How does that drawable file change a horizontal thing into a circular thing and how does it decide how to animate the progress bar from 0degrees to 360degress forming a full ring when the progress is 100%? Also, is there a way to manipulate the start of the animation (like from 90degrees from the positive x-axis)?
  • How do I make another custom shape, like a rectangle being filled from bottom to top with a background color (to signify the rectangle shape before being filled) that doesn't change?
Richard
  • 7,037
  • 2
  • 23
  • 76
  • some of the questions are already answered such as: (to manipulate the start of the animation) https://stackoverflow.com/questions/23237564/android-start-the-circular-progress-bar-from-top-270 and more... – Rifat Dec 15 '19 at 19:47
  • @Rifat What about my other questions (like how it decides how to draw the ProgressBar to a ring)? – Richard Dec 16 '19 at 04:12
  • I found other search results that is someway gives solution. ProgressBar shapes are dependent on xml attributes as well. For a rectangle type progress bar you can use horizontal orientation and if you want only to show it is filling you can just make the background invisible and progressdrawable to something visible. – Rifat Dec 16 '19 at 05:33
  • @Rifat Would you be kind enough to provide the full-descriptive answer to all my questions? – Richard Dec 16 '19 at 05:40

1 Answers1

1
  • How does that drawable file change a horizontal thing into a circular thing and how does it decide how to animate the progress bar from 0degrees to 360degress forming a full ring when the progress is 100%?

When you specify style="?android:progressBarStyleHorizontal" what you are really saying is that the ProgressBar will be deteminate. From the documentation for ProgressBar:

To indicate determinate progress, you set the style of the progress bar to R.style.Widget_ProgressBar_Horizontal and set the amount of progress.

By default, the ProgressBar will be horizontal but, as you noted, it can be changed to circular with your drawable file.

<shape 
    android:shape="ring"
    android:innerRadiusRatio="2.5"
    android:thickness="4dp"
    android:useLevel="true">
    <solid android:color="@color/colorPrimary" />
</shape>

Notice the line android:useLevel="true". This specifies that the drawable can accept a level that can range from 0 to 100. The drawable knows how much of itself to draw based upon the level set: 0 is to draw nothing and 100 is to draw 100%. By changing the progress, you are changing the level set for the drawable. Try setting android:useLevel="false" to see what happens.

  • Also, is there a way to manipulate the start of the animation (like from 90degrees from the positive x-axis)?

In the XML, set android:rotation="90" to have the ring start at the bottom.

  • How do I make another custom shape, like a rectangle being filled from bottom to top with a background color (to signify the rectangle shape before being filled) that doesn't change?

There are a couple (maybe more) ways to do this. One way is to define a layer-list drawable that defines the background rectangle and the progress rectangle that is a scale drawable. The ids must be as specified since ProgressBar relies upon them.

<layer-list>
<item
    android:id="@android:id/background">
    <shape android:shape="rectangle">
        <solid android:color="@android:color/darker_gray" />
    </shape>
</item>
<item
    android:id="@android:id/progress">
    <scale
        android:scaleHeight="100%"
        android:scaleGravity="bottom">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/holo_red_light" />
        </shape>
    </scale>
</item>

You could also use a clip drawable in a layer list:

<layer-list>
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/darker_gray" />
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <clip
            android:clipOrientation="vertical"
            android:gravity="bottom">
            <shape android:shape="rectangle">
                <solid android:color="@android:color/holo_red_light" />
            </shape>
        </clip>
    </item>
</layer-list>

Here is a sample layout that puts this all together:

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:progressBarStyleHorizontal"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginTop="16dp"
        android:progress="0"
        android:progressDrawable="@drawable/bg_circular_progress_bar"
        android:rotation="90"
        app:layout_constraintVertical_chainStyle="packed"
        app:layout_constraintBottom_toTopOf="@+id/progressBar2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ProgressBar
        android:id="@+id/progressBar2"
        style="?android:progressBarStyleHorizontal"
        android:layout_width="20dp"
        android:layout_height="80dp"
        android:layout_marginTop="16dp"
        android:progress="0"
        android:progressDrawable="@drawable/rectangular_progress_with_scale_drawable"
        app:layout_constraintBottom_toTopOf="@+id/progressBar3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressBar" />

    <ProgressBar
        android:id="@+id/progressBar3"
        style="?android:progressBarStyleHorizontal"
        android:layout_width="20dp"
        android:layout_height="80dp"
        android:layout_marginTop="16dp"
        android:progress="0"
        android:progressDrawable="@drawable/rectangular_progress_with_clip_drawable"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressBar2" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="0/ 100"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressBar3" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:onClick="onClick"
        android:text="Start"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun onClick(view: View) {
        view.isEnabled = false
        var progress = 0
        val handler = Handler()

        Thread(Runnable {
            while (progress < 100) {
                progress += 5
                handler.post {
                    progressBar.progress = progress
                    progressBar2.progress = progress
                    progressBar3.progress = progress
                    textView.text = "$progress/ ${progressBar.max}"
                }
                try {
                    Thread.sleep(100)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }
            runOnUiThread { view.isEnabled = true }
        }).start()
    }
}

enter image description here

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • Perfect, so it was the `useLevel` that tells the XML how to draw the progressBar. Very in-depth explanation! However, is there a more in-depth explanation that explains how `useLevel` work other than this one here https://developer.android.com/guide/topics/resources/drawable-resource? – Richard Dec 17 '19 at 07:30
  • @Richard Not sure if there is an in-depth discussion online or not. If you want to learn the details, try starting with the `onLevelChange()` method in _Drawable.java_ and work from there. – Cheticamp Dec 17 '19 at 12:02
  • Okay, is there a documentation that explains how progress bar relies on the `id` of the `drawable`s items? I didn't know that until you mentioned it. Also, does the `id` have to be exactly `@android:id/progress` for the drawable that can be animated depending on its `level`? If it doesn't, is there a possibility that I can animate two `progress`es at once with a single progress bar? – Richard Dec 18 '19 at 04:12