Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Tạo ProgressBar tròn tùy chỉnh (Circle Custom ProgressBar) trên Android

Giới thiệu

Ví dụ này minh họa cách tạo ProgressBar hình tròn (vòng tròn) tùy chỉnh màu sắc, độ dày, góc xoay bằng layer-list drawable XML, sau đó animate tiến độ từ 0-100% bằng thread.

Bước 1: Tạo dự án mới

Tạo project Android Studio: File → New Project.

Bước 2: Layout (res/layout/activity_main.xml)

ProgressBar style horizontal (để dùng drawable ring) + TextView overlay hiển thị % ở giữa.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="8dp"
    tools:context=".MainActivity">

    <ProgressBar
        android:id="@+id/circularProgressbar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:layout_centerInParent="true"
        android:indeterminate="false"
        android:max="100"
        android:progress="50"
        android:secondaryProgress="100" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:gravity="center"
        android:text="25%"
        android:layout_centerInParent="true"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="24sp" />

</RelativeLayout>

Bước 3: Drawable tùy chỉnh (res/drawable/circularprogressbar.xml)

Sử dụng layer-list với 2 item: background (secondaryProgress) và progress chính (rotate + sweep gradient).

<layer-list xmlns:android="https://schemas.android.com/apk/res/android">
    <!-- Background ring (secondaryProgress) -->
    <item android:id="@android:id/secondaryProgress">
        <shape
            android:innerRadiusRatio="6"
            android:shape="ring"
            android:thicknessRatio="20.0"
            android:useLevel="true">
            <gradient
                android:centerColor="#999999"
                android:endColor="#999999"
                android:startColor="#999999"
                android:type="sweep" />
        </shape>
    </item>

    <!-- Progress ring (progress) - xoay -90đ để bắt đầu từ 12h -->
    <item android:id="@android:id/progress">
        <rotate
            android:fromDegrees="270"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toDegrees="270">
            <shape
                android:innerRadiusRatio="6"
                android:shape="ring"
                android:thicknessRatio="20.0"
                android:useLevel="true">
                <gradient
                    android:centerColor="#00FF00"
                    android:endColor="#00FF00"
                    android:startColor="#00FF00"
                    android:type="sweep" />
                <rotate
                    android:fromDegrees="0"
                    android:pivotX="50%"
                    android:pivotY="50%"
                    android:toDegrees="360" />
            </shape>
        </rotate>
    </item>
</layer-list>

Bước 4: MainActivity - Animate progress

Thread nền tăng progress mỗi 16ms (~60fps), cập nhật UI qua Handler.

import androidx.appcompat.app.AppCompatActivity;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    int status = 0;
    private Handler handler = new Handler();
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Resources resources = getResources();
        Drawable drawable = resources.getDrawable(R.drawable.circularprogressbar);
        ProgressBar progressBar = findViewById(R.id.circularProgressbar);
        progressBar.setProgress(0);
        progressBar.setSecondaryProgress(100);
        progressBar.setMax(100);
        progressBar.setProgressDrawable(drawable);

        textView = findViewById(R.id.textView);

        new Thread(() -> {
            while (status < 100) {
                status += 1;
                handler.post(() -> {
                    progressBar.setProgress(status);
                    textView.setText(String.format("%d%%", status));
                });
                try { Thread.sleep(16); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }).start();
    }
}

Bước 5: Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android" package="app.com.sample">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Chạy ứng dụng

Chạy app, ProgressBar tròn sẽ chạy từ 0% đến 100% trong ~1.6 giây, TextView ở giữa cập nhật số % đồng bộ.

Cách hiện đại: CircularProgressIndicator (Material Components)

Thư viện com.google.android.material:material:1.6.0+ cung cấp CircularProgressIndicator hỗ trợ indeterminate/determinate, màu, size, animation sẵn có:

<com.google.android.material.progressindicator.CircularProgressIndicator
    android:id="@+id/circularProgressIndicator"
    android:layout_width="250dp"
    android:layout_height="250dp"
    android:indeterminate="false"
    android:progress="0"
    android:max="100"
    app:indicatorColor="@color/colorPrimary"
    app:trackColor="@color/gray_light"
    app:trackThickness="12dp"/>

// Animate:
circularProgressIndicator.setProgressCompat(100, true); // true = animate