简体   繁体   English

Flutter 如何制作自定义标签栏?

[英]Flutter How to make an custom Tab Bar?

I am build apps in Flutter.我在 Flutter 中构建应用程序。 Now I want to create custom Tab Bar.现在我想创建自定义标签栏。 I want a tab bar according to the picture.我想要一个根据图片的标签栏。

在此处输入图像描述

Here is a Solution using a CustomPainter .这是使用CustomPainter的解决方案。

在此处输入图像描述

TabPainter标签画家

Very basic Path painter to draw the background of the tabs.非常基本的路径画家来绘制选项卡的背景。

class TabPainter extends CustomPainter {
  final Color color;

  TabPainter({this.color});

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;

    var path = Path();

    path.moveTo(0, size.height);
    path.lineTo(0, .5 * size.height);
    path.quadraticBezierTo(0, 0, .1 * size.width, 0);
    path.lineTo(.48 * size.width, 0);
    path.quadraticBezierTo(
        .512 * size.width, 0, .52 * size.width, .1 * size.height);
    path.lineTo(.57 * size.width, .83 * size.height);
    path.quadraticBezierTo(
        .58 * size.width, .9 * size.height, .59 * size.width, .9 * size.height);
    path.lineTo(size.width, .9 * size.height);
    path.lineTo(size.width, size.height);
    path.lineTo(0, size.height);
    path.close();

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

MyTab我的标签

This class uses our TabPainter to draw the background of the Tab and a TextButton for the foreground with onPressed functionality.这个 class 使用我们的TabPainter来绘制 Tab 的背景,并使用具有onPressed功能的TextButton来绘制前景。

MyTab is defined by: MyTab定义为:

  • a set of colors: backgroundColor , foregroundColor , activeBackgroundColor , activeForegroundColor一组 colors: backgroundColor , foregroundColor , activeBackgroundColor , activeForegroundColor
  • a label and its fontSize一个label及其fontSize大小
  • whether it is active of not是否active
  • whether is should be reversed or not (for the right tab)是否应该reversed (对于右侧选项卡)

When the users tap on the tab, onTap is called.当用户点击选项卡时,会调用onTap

class MyTab extends StatelessWidget {
  final Color backgroundColor;
  final Color foregroundColor;
  final Color activeBackgroundColor;
  final Color activeForegroundColor;
  final double fontSize;
  final bool active;
  final bool reversed;
  final String label;
  final VoidCallback onTap;

  Color get bgColor =>
      active ? activeBackgroundColor ?? foregroundColor : backgroundColor;
  Color get fgColor =>
      active ? activeForegroundColor ?? backgroundColor : foregroundColor;

  const MyTab({
    Key key,
    this.active,
    this.reversed = false,
    this.label,
    this.backgroundColor,
    this.foregroundColor,
    this.activeBackgroundColor,
    this.activeForegroundColor,
    this.fontSize,
    this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned.fill(
          child: IgnorePointer(
            child: reversed
                ? Transform(
                    alignment: Alignment.center,
                    transform: Matrix4.rotationY(math.pi),
                    child: CustomPaint(painter: TabPainter(color: bgColor)),
                  )
                : CustomPaint(painter: TabPainter(color: bgColor)),
          ),
        ),
        Align(
          alignment: reversed ? Alignment.centerRight : Alignment.centerLeft,
          child: FractionallySizedBox(
            widthFactor: .5,
            heightFactor: 1,
            child: TextButton(
              onPressed: active ? null : onTap,
              child: Text(
                label,
                style: TextStyle(color: fgColor, fontSize: fontSize),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

MyTabBar我的标签栏

This Widget provides a TabBar of two Tabs.这个 Widget 提供了一个包含两个 Tabs 的 TabBar。 Besides the configuration of the tabs, it also takes a TabController to manage the tab transitions.除了选项卡的配置,它还需要一个 TabController 来管理选项卡的转换。

class MyTabBar extends HookWidget {
  final TabController controller;
  final List<String> labels;
  final Color backgroundColor;
  final Color foregroundColor;
  final Color activeBackgroundColor;
  final Color activeForegroundColor;
  final double fontSize;

  const MyTabBar({
    Key key,
    @required this.controller,
    @required this.labels,
    @required this.backgroundColor,
    @required this.foregroundColor,
    this.activeBackgroundColor,
    this.activeForegroundColor,
    this.fontSize,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final _ids = useState([1, 0]);
    return AspectRatio(
      aspectRatio: 100 / 18,
      child: ColoredBox(
        color: Theme.of(context).primaryColor,
        child: Stack(
          fit: StackFit.expand,
          children: _ids.value.map((id) {
            final active = controller.index == id;
            return MyTab(
                backgroundColor: backgroundColor,
                foregroundColor: foregroundColor,
                activeBackgroundColor: activeBackgroundColor,
                activeForegroundColor: activeForegroundColor,
                fontSize: fontSize,
                active: active,
                reversed: id == 1,
                label: labels[id],
                onTap: () {
                  controller.animateTo(id);
                  _ids.value = _ids.value.reversed.toList();
                });
          }).toList(),
        ),
      ),
    );
  }
}

SettingsPage设置页面

Basic page using our MyTabBar and a TabView :使用我们的MyTabBarTabView的基本页面:

class SettingsPage extends HookWidget {
  const SettingsPage({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final _controller = useTabController(initialLength: 2);
    return Scaffold(
      appBar: AppBar(
        title: Text(kPageTitle),
        elevation: 0,
      ),
      body: Column(
        children: [
          MyTabBar(
            controller: _controller,
            labels: kLabels,
            backgroundColor: kTabBgColor,
            foregroundColor: kTabFgColor,
          ),
          Expanded(
            child: TabBarView(
              controller: _controller,
              children: [
                Center(child: Text(kLabels[0])),
                Center(child: Text(kLabels[1])),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Full Source Code完整的源代码

For easy copy-paste, here is the full source code:为了方便复制粘贴,这里是完整的源代码:

import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

const kPageTitle = 'Settings';
const kLabels = ["Edit Profile", "Accounts"];
const kTabBgColor = Color(0xFF8F32A9);
const kTabFgColor = Colors.white;

void main() {
  runApp(TabBarDemo());
}

class TabBarDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primaryColor: Color(0xFF5046AF)),
      home: SettingsPage(),
    );
  }
}

class SettingsPage extends HookWidget {
  const SettingsPage({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final _controller = useTabController(initialLength: 2);
    return Scaffold(
      appBar: AppBar(
        title: Text(kPageTitle),
        elevation: 0,
      ),
      body: Column(
        children: [
          MyTabBar(
            controller: _controller,
            labels: kLabels,
            backgroundColor: kTabBgColor,
            foregroundColor: kTabFgColor,
          ),
          Expanded(
            child: TabBarView(
              controller: _controller,
              children: [
                Center(child: Text(kLabels[0])),
                Center(child: Text(kLabels[1])),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class MyTabBar extends HookWidget {
  final TabController controller;
  final List<String> labels;
  final Color backgroundColor;
  final Color foregroundColor;
  final Color activeBackgroundColor;
  final Color activeForegroundColor;
  final double fontSize;

  const MyTabBar({
    Key key,
    @required this.controller,
    @required this.labels,
    @required this.backgroundColor,
    @required this.foregroundColor,
    this.activeBackgroundColor,
    this.activeForegroundColor,
    this.fontSize,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final _ids = useState([1, 0]);
    return AspectRatio(
      aspectRatio: 100 / 18,
      child: ColoredBox(
        color: Theme.of(context).primaryColor,
        child: Stack(
          fit: StackFit.expand,
          children: _ids.value.map((id) {
            final active = controller.index == id;
            return MyTab(
                backgroundColor: backgroundColor,
                foregroundColor: foregroundColor,
                activeBackgroundColor: activeBackgroundColor,
                activeForegroundColor: activeForegroundColor,
                fontSize: fontSize,
                active: active,
                reversed: id == 1,
                label: labels[id],
                onTap: () {
                  controller.animateTo(id);
                  _ids.value = _ids.value.reversed.toList();
                });
          }).toList(),
        ),
      ),
    );
  }
}

class MyTab extends StatelessWidget {
  final Color backgroundColor;
  final Color foregroundColor;
  final Color activeBackgroundColor;
  final Color activeForegroundColor;
  final double fontSize;
  final bool active;
  final bool reversed;
  final String label;
  final VoidCallback onTap;

  Color get bgColor =>
      active ? activeBackgroundColor ?? foregroundColor : backgroundColor;
  Color get fgColor =>
      active ? activeForegroundColor ?? backgroundColor : foregroundColor;

  const MyTab({
    Key key,
    this.active,
    this.reversed = false,
    this.label,
    this.backgroundColor,
    this.foregroundColor,
    this.activeBackgroundColor,
    this.activeForegroundColor,
    this.fontSize,
    this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned.fill(
          child: IgnorePointer(
            child: reversed
                ? Transform(
                    alignment: Alignment.center,
                    transform: Matrix4.rotationY(math.pi),
                    child: CustomPaint(painter: TabPainter(color: bgColor)),
                  )
                : CustomPaint(painter: TabPainter(color: bgColor)),
          ),
        ),
        Align(
          alignment: reversed ? Alignment.centerRight : Alignment.centerLeft,
          child: FractionallySizedBox(
            widthFactor: .5,
            heightFactor: 1,
            child: TextButton(
              onPressed: active ? null : onTap,
              child: Text(
                label,
                style: TextStyle(color: fgColor, fontSize: fontSize),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

class TabPainter extends CustomPainter {
  final Color color;

  TabPainter({this.color});

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;

    var path = Path();

    path.moveTo(0, size.height);
    path.lineTo(0, .5 * size.height);
    path.quadraticBezierTo(0, 0, .1 * size.width, 0);
    path.lineTo(.48 * size.width, 0);
    path.quadraticBezierTo(
        .512 * size.width, 0, .52 * size.width, .1 * size.height);
    path.lineTo(.57 * size.width, .83 * size.height);
    path.quadraticBezierTo(
        .58 * size.width, .9 * size.height, .59 * size.width, .9 * size.height);
    path.lineTo(size.width, .9 * size.height);
    path.lineTo(size.width, size.height);
    path.lineTo(0, size.height);
    path.close();

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM