You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Kotlin实现带指示点的自定义进度条(多步注册场景)

分段式步骤进度条实现方案

针对你的多步注册表单需求,原生ProgressBar无法直接实现这种分段带节点的进度效果(参考图为分段式结构,每个步骤对应节点,已完成段填充高亮色),必须通过自定义View来实现。以下是适合Android新手的完整实现步骤:


一、定义自定义属性

在res/values/attrs.xml中添加自定义属性,方便在布局中配置进度条样式:

<resources>
    <declare-styleable name="StepProgressBar">
        <!-- 总步骤数 -->
        <attr name="totalSteps" format="integer" />
        <!-- 当前步骤(从0开始计数) -->
        <attr name="currentStep" format="integer" />
        <!-- 已完成状态颜色 -->
        <attr name="completedColor" format="color" />
        <!-- 未完成状态颜色 -->
        <attr name="uncompletedColor" format="color" />
        <!-- 节点尺寸 -->
        <attr name="nodeSize" format="dimension" />
        <!-- 进度条高度 -->
        <attr name="barHeight" format="dimension" />
    </declare-styleable>
</resources>

二、创建自定义View类

新建StepProgressBar.java(Kotlin版本逻辑一致,可自行转译),实现测量与绘制逻辑:

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

public class StepProgressBar extends View {
    // 默认配置参数
    private int totalSteps = 3;
    private int currentStep = 0;
    private int completedColor = 0xFF2196F3;
    private int uncompletedColor = 0xFFE0E0E0;
    private float nodeSize = dp2px(16);
    private float barHeight = dp2px(4);

    private Paint paint;
    private float stepSpacing; // 步骤间的间距

    public StepProgressBar(Context context) {
        this(context, null);
    }

    public StepProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public StepProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 读取自定义属性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.StepProgressBar);
        totalSteps = ta.getInt(R.styleable.StepProgressBar_totalSteps, totalSteps);
        currentStep = ta.getInt(R.styleable.StepProgressBar_currentStep, currentStep);
        completedColor = ta.getColor(R.styleable.StepProgressBar_completedColor, completedColor);
        uncompletedColor = ta.getColor(R.styleable.StepProgressBar_uncompletedColor, uncompletedColor);
        nodeSize = ta.getDimension(R.styleable.StepProgressBar_nodeSize, nodeSize);
        barHeight = ta.getDimension(R.styleable.StepProgressBar_barHeight, barHeight);
        ta.recycle();

        // 初始化画笔
        paint = new Paint();
        paint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 计算View高度:节点尺寸 + 上下内边距
        int height = (int) (nodeSize + getPaddingTop() + getPaddingBottom());
        // 宽度跟随父布局
        int width = MeasureSpec.getSize(widthMeasureSpec);
        setMeasuredDimension(width, height);
        // 计算步骤间距:(总宽度 - 左右内边距 - 所有节点总宽度) / (总步骤数-1)
        stepSpacing = (width - getPaddingLeft() - getPaddingRight() - totalSteps * nodeSize) / (totalSteps - 1);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float startX = getPaddingLeft() + nodeSize / 2;
        float centerY = getPaddingTop() + nodeSize / 2;

        // 1. 绘制进度条背景线
        paint.setColor(uncompletedColor);
        paint.setStrokeWidth(barHeight);
        float endX = getWidth() - getPaddingRight() - nodeSize / 2;
        canvas.drawLine(startX, centerY, endX, centerY, paint);

        // 2. 绘制已完成的进度段
        if (currentStep > 0) {
            paint.setColor(completedColor);
            float completedEndX = startX + currentStep * (stepSpacing + nodeSize);
            canvas.drawLine(startX, centerY, completedEndX, centerY, paint);
        }

        // 3. 绘制所有步骤节点
        for (int i = 0; i < totalSteps; i++) {
            float nodeX = startX + i * (stepSpacing + nodeSize);
            // 设置节点颜色:已完成/当前步骤用高亮色,其余用默认色
            if (i <= currentStep) {
                paint.setColor(completedColor);
                // 绘制实心节点
                canvas.drawCircle(nodeX, centerY, nodeSize / 2, paint);
            } else {
                paint.setColor(uncompletedColor);
                // 绘制空心节点
                paint.setStyle(Paint.Style.STROKE);
                paint.setStrokeWidth(dp2px(2));
                canvas.drawCircle(nodeX, centerY, nodeSize / 2 - dp2px(1), paint);
                paint.setStyle(Paint.Style.FILL);
            }
        }
    }

    // 对外提供更新当前步骤的方法
    public void setCurrentStep(int currentStep) {
        if (currentStep < 0 || currentStep >= totalSteps) return;
        this.currentStep = currentStep;
        invalidate(); // 触发重绘
    }

    // dp转px工具方法
    private float dp2px(float dp) {
        return getContext().getResources().getDisplayMetrics().density * dp;
    }
}

三、在布局中使用自定义View

在Activity布局(如activity_register.xml)中,将进度条放在ViewPager2上方:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <!-- 自定义步骤进度条 -->
    <com.yourpackage.StepProgressBar
        android:id="@+id/stepProgressBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingVertical="8dp"
        app:totalSteps="3"
        app:currentStep="0"
        app:completedColor="#2196F3"
        app:uncompletedColor="#E0E0E0"
        app:nodeSize="16dp"
        app:barHeight="4dp" />

    <!-- ViewPager2 -->
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <!-- 上/下一步按钮 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gap="16dp">

        <Button
            android:id="@+id/btnPrev"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="上一步"
            android:visibility="gone" />

        <Button
            android:id="@+id/btnNext"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="下一步" />
    </LinearLayout>

</LinearLayout>

四、与ViewPager2联动

在Activity中监听页面切换,同步更新进度条与按钮状态:

import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class RegisterActivity extends AppCompatActivity {
    private ViewPager2 viewPager;
    private StepProgressBar stepProgressBar;
    private Button btnPrev, btnNext;
    private final int totalSteps = 3;

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

        viewPager = findViewById(R.id.viewPager);
        stepProgressBar = findViewById(R.id.stepProgressBar);
        btnPrev = findViewById(R.id.btnPrev);
        btnNext = findViewById(R.id.btnNext);

        // 设置ViewPager2适配器(替换为你自己的适配器类)
        viewPager.setAdapter(new RegisterPagerAdapter(this));

        // 监听页面切换
        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                stepProgressBar.setCurrentStep(position);
                // 更新按钮显示状态
                btnPrev.setVisibility(position == 0 ? View.GONE : View.VISIBLE);
                btnNext.setText(position == totalSteps - 1 ? "完成" : "下一步");
            }
        });

        // 上一步按钮点击事件
        btnPrev.setOnClickListener(v -> {
            if (viewPager.getCurrentItem() > 0) {
                viewPager.setCurrentItem(viewPager.getCurrentItem() - 1);
            }
        });

        // 下一步按钮点击事件
        btnNext.setOnClickListener(v -> {
            if (viewPager.getCurrentItem() < totalSteps - 1) {
                viewPager.setCurrentItem(viewPager.getCurrentItem() + 1);
            } else {
                // 执行注册完成逻辑
                finish();
            }
        });
    }
}

注意事项

  • 替换代码中的com.yourpackage为你项目的实际包名;
  • 若需修改节点形状(如改为方形)、进度条圆角等,只需调整onDraw方法中的绘制逻辑;
  • 可根据需求扩展自定义属性,比如添加节点文字、调整节点与进度条的位置关系等。

内容的提问来源于stack exchange,提问作者Syfur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 02:00:40