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

Flutter中如何实现如图所示的自定义形状PageView指示器

实现Flutter引导页自定义PageView指示器

针对你需要的这种特殊曲线形状的PageView指示器,最灵活精准的实现方式是通过CustomPainter自定义绘制,下面是具体的实现思路和代码:

一、自定义绘制指示器

1. 核心思路

通过CustomPainter绘制选中状态的特殊曲线容器,未选中状态用常规圆角矩形(或对应设计的样式),结合PageController的页面监听实时更新指示器的位置和状态,完全匹配设计稿的形状。

2. 代码示例

import 'package:flutter/material.dart';

class CustomPageIndicator extends StatefulWidget {
  final PageController pageController;
  final int itemCount;
  final Color selectedColor;
  final Color unselectedColor;
  final double indicatorWidth;
  final double indicatorHeight;
  final double gap;

  const CustomPageIndicator({
    super.key,
    required this.pageController,
    required this.itemCount,
    this.selectedColor = Colors.blue,
    this.unselectedColor = Colors.grey,
    this.indicatorWidth = 40,
    this.indicatorHeight = 8,
    this.gap = 8,
  });

  @override
  State<CustomPageIndicator> createState() => _CustomPageIndicatorState();
}

class _CustomPageIndicatorState extends State<CustomPageIndicator> {
  late double _currentPage;

  @override
  void initState() {
    super.initState();
    _currentPage = widget.pageController.page ?? 0;
    widget.pageController.addListener(_updatePage);
  }

  void _updatePage() {
    setState(() {
      _currentPage = widget.pageController.page ?? 0;
    });
  }

  @override
  void dispose() {
    widget.pageController.removeListener(_updatePage);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.indicatorHeight + 6,
      child: Stack(
        children: [
          // 未选中指示器
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: List.generate(widget.itemCount, (index) {
              return Container(
                width: widget.indicatorWidth,
                height: widget.indicatorHeight,
                margin: EdgeInsets.symmetric(horizontal: widget.gap / 2),
                decoration: BoxDecoration(
                  color: widget.unselectedColor,
                  borderRadius: BorderRadius.circular(widget.indicatorHeight / 2),
                ),
              );
            }),
          ),
          // 选中状态的曲线指示器
          Positioned(
            left: (_currentPage * (widget.indicatorWidth + widget.gap)) + widget.gap/2,
            child: CustomPaint(
              size: Size(widget.indicatorWidth, widget.indicatorHeight),
              painter: _SelectedIndicatorPainter(
                color: widget.selectedColor,
                height: widget.indicatorHeight,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _SelectedIndicatorPainter extends CustomPainter {
  final Color color;
  final double height;

  _SelectedIndicatorPainter({required this.color, required this.height});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = color;
    final path = Path();
    // 这里根据设计稿调整曲线控制点,示例为中间下凹的曲线
    path.moveTo(0, 0);
    path.lineTo(size.width, 0);
    path.lineTo(size.width, height);
    // 贝塞尔曲线调整:修改控制点坐标可匹配不同曲线形状
    path.quadraticBezierTo(
      size.width / 2,
      height + 5, // 曲线突出/凹陷的深度
      0,
      height,
    );
    path.close();
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant _SelectedIndicatorPainter oldDelegate) {
    return oldDelegate.color != color || oldDelegate.height != height;
  }
}

// 使用示例
class OnboardingScreen extends StatelessWidget {
  final PageController _pageController = PageController();
  final List<String> pages = const ["引导页1", "引导页2", "引导页3"];

  OnboardingScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Expanded(
            child: PageView.builder(
              controller: _pageController,
              itemCount: pages.length,
              itemBuilder: (context, index) {
                return Center(child: Text(pages[index], style: const TextStyle(fontSize: 24)));
              },
            ),
          ),
          CustomPageIndicator(
            pageController: _pageController,
            itemCount: pages.length,
          ),
          const SizedBox(height: 40),
        ],
      ),
    );
  }
}

3. 匹配设计稿调整

你只需要修改_SelectedIndicatorPainter中的Path绘制逻辑,比如调整贝塞尔曲线的控制点坐标,或者添加多段曲线,就能完全复刻设计稿的特殊形状。

二、可选方案:使用自定义样式指示器包

如果不想自己编写绘制逻辑,也可以选择支持自定义路径的Flutter指示器包,这类包通常允许传入自定义Painter或形状路径,但自行实现CustomPainter能更精准地匹配设计,避免第三方包的功能限制。


内容的提问来源于stack exchange,提问作者Sreerekha K V

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:43:15