flutter 手写 TabBar

news2024/9/22 12:30:56

前言:

这几天在使用 flutter TabBar 的时候 我们的设计给我提了一个需求:

如下 Tabbar  第一个元素 左对齐试了下TabBar 的配置,无法实现这个需求,他的 配置是针对所有元素的。而且 这个 TabBar 下面的 滑块在移动的时候 上面的文字会相应的抖动。

看了下 TabBar 的源代码 他的实现是相对复杂的 下面的 滑块是 canvas 实现的。 有可能他要实现的功能比较丰富。

自定义Tabbar 的基本布局

下面是我页面的布局:这样实现起来 里面元素的 样式可以完全自己定义单个配置,想怎么显示都可以。这样就可以不用局限于 自带Tabbar的配置

SingleChildScrollView 解析

完成页面布局相对简单,主要实现底部 滑块的移动,以及 整 SingleChildScrollView 的居中移动是一个关键点

ScrollController 中的几个关键概念:
  • controller.position.viewportDimension:  SingleChildScrollView 视口 的大小
  • position.maxScrollExtent :                     SingleChildScrollView 可以移动的最大范围
  • position.minScrollExtent  :                     SingleChildScrollView 可以移动最小范围 一般是0
  • Row 的长度就是所有元素的长度之和:也就是 position.maxScrollExtent + position.viewportDimension 

Row 的长度之和 为什么是 SingleChildScrollView 最大可移动范围加 position.viewportDimension 的和

SingleChildScrollView 可见区域始终是他的视口大小,不可见的也就是 Row的长度减去视口大小 也就是 maxScrollExtent 可拖动的最大区域

实现 Tabbar

下面是我实现的大致效果:第一个元素左对齐,最后一个元素右对齐,我这边是直接写死的,你们封装一下 在外边直接用就好了。

代码如下:

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:game/const/app_textStyle.dart';
import 'package:game/utils/app_widget.dart';
import 'package:game/wrap/extension/extension.dart';

class PageTabBar extends StatefulWidget {
  const PageTabBar({Key? key}) : super(key: key);

  @override
  State<PageTabBar> createState() => _PageTabBarState();
}

class _PageTabBarState extends State<PageTabBar> {
  final ScrollController _controller = ScrollController();
  int _selectIndex = 0;
  double _width = 0;

  double _positionX = 0;
  final List<Map> _listMap = [
    {'width': 0, 'name': '一号', 'key': GlobalKey()},
    {'width': 0, 'name': '二二号技师', 'key': GlobalKey()},
    {'width': 0, 'name': '三三三号技师', 'key': GlobalKey()},
    {'width': 0, 'name': '四号技师', 'key': GlobalKey()},
    {'width': 0, 'name': '五五号技师', 'key': GlobalKey()},
    {'width': 0, 'name': '六六六号技师', 'key': GlobalKey()},
    {'width': 0, 'name': '七号技师', 'key': GlobalKey()},
    {'width': 0, 'name': '八八号技师', 'key': GlobalKey()},
    {'width': 0, 'name': '九', 'key': GlobalKey()},
    {'width': 0, 'name': '十号技师', 'key': GlobalKey()},
  ];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => _printSize());

    // _controller.addListener(() {
    //   print('_controller.offset:${_controller.offset}');
    // });
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _controller.dispose();
    super.dispose();
  }

  void _printSize() {
    for (Map element in _listMap) {
      final RenderBox box = element['key'].currentContext.findRenderObject();
      element['width'] = box.size.width;
    }
    _width = _listMap[0]['width'];
    _selectItem(0);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppWidget.appBar(title: 'TabBar 测试页面'),
      body: Center(
        child: Container(
          height: 220.cale,
          width: 710.cale,
          color: Colors.deepOrangeAccent,
          child: SingleChildScrollView(
            physics: const BouncingScrollPhysics(
              parent: AlwaysScrollableScrollPhysics(),
            ),
            controller: _controller,
            scrollDirection: Axis.horizontal,
            child: Stack(
              children: [
                Row(
                  children: _listMap
                      .asMap()
                      .map(
                        (key, value) => MapEntry(
                          key,
                          AppWidget.inkWellEffectNone(
                            onTap: () {
                              _selectItem(key);
                            },
                            child: Container(
                              padding: key == 0
                                  ? EdgeInsets.only(right: 25.cale)
                                  : key == _listMap.length - 1
                                      ? EdgeInsets.only(left: 25.cale)
                                      : EdgeInsets.symmetric(
                                          horizontal: 25.cale),
                              key: value['key'],
                              color: Colors.blue.withOpacity(0.1 * key),
                              height: 180.cale,
                              child: Center(
                                child: Text(
                                  value['name'],
                                  style: _selectIndex == key
                                      ? AppTextStyle.textStyle_34_FD3949_Bold
                                      : AppTextStyle.textStyle_30_1A1A1A,
                                ),
                              ),
                            ),
                          ),
                        ),
                      )
                      .values
                      .toList(),
                ),
                AnimatedPositioned(
                  bottom: 0.cale,
                  left: _positionX,
                  duration: const Duration(milliseconds: 250),
                  child: AnimatedContainer(
                    duration: const Duration(milliseconds: 250),
                    width: _width,
                    child: Container(
                      height: 20.cale,
                      margin: EdgeInsets.symmetric(horizontal: 25.cale),
                      width: double.infinity,
                      color: Colors.green,
                    ),
                  ),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }

  void _selectItem(int index) {
    print('index:$index');
    final ScrollPosition position = _controller.position;
    setState(() {
      _selectIndex = index;
      _width = _listMap[index]['width'];
    });

    _positionX = 0;
    List.generate(index, (itemIndex) {
      _positionX += _listMap[itemIndex]['width'];
    });
    //当前展示的元素位置 中心点位置,用户确定 滚动位置
    double viewPosition = _positionX + _listMap[index]['width'] / 2;
    double movePosition = viewPosition - position.viewportDimension / 2;
    movePosition = clampDouble(
        movePosition, position.minScrollExtent, position.maxScrollExtent);
    _controller.animateTo(
      movePosition,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeOut,
    );
  }
}

可以按需求封装下就能上手使用了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1928448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全开源TikTok跨境商城源码/TikTok内嵌商城/前端uniapp+后端+搭建教程

多语言跨境电商外贸商城 TikTok内嵌商城&#xff0c;商家入驻一键铺货一键提货 全开源完美运营 海外版抖音TikTok商城系统源码&#xff0c;TikToK内嵌商城&#xff0c;跨境商城系统源码 接在tiktok里面的商城。tiktok内嵌&#xff0c;也可单独分开出来当独立站运营 二十一种…

论文翻译:通过云计算对联网多智能体系统进行预测控制

通过云计算对联网多智能体系统进行预测控制 文章目录 通过云计算对联网多智能体系统进行预测控制摘要前言通过云计算实现联网的多智能体控制系统网络化多智能体系统的云预测控制器设计云预测控制系统的稳定性和一致性分析例子结论 摘要 本文研究了基于云计算的网络化多智能体预…

ubuntu虚拟机安装ssh时报错 正在等待缓存锁

问题&#xff1a; 连接vm ubuntu虚拟机安装ssh时报错 正在等待缓存锁。 sudo apt install openssh-server 处理办法 sudo rm /var/lib/dpkg/lock-frontend sudo rm /var/cache/apt/archives/lock sudo rm /var/lib/dpkg/lock

ipynb转换为pdf、Markdown(.md)

Jupyter Notebook 文件&#xff08;.ipynb&#xff09;可以转换成多种数据格式&#xff0c;以适应不同的使用场景和需求。以下是几种常见的转换格式及其简洁描述&#xff1a; HTML: Jupyter Notebook可以直接导出为静态的网页&#xff08;HTML&#xff09;格式&#xff0c;这样…

WPF 手撸插件 一

1、本文主要使不适用第三方工具&#xff0c;纯手工的WPF主项目加载另一个WPF的项目&#xff0c;这里我们加载的是*.exe。 2、项目结构如下图。AbstractionLayer用于创建插件的接口。WPFIPluginDemo是主程序。WpfPlugin3是要加载的插件程序。 3、 AbstractionLayer中添加接口IP…

昇思25天学习打卡营第22天 | 基于MobileNetv2的垃圾分类函数式自动微分

基于MobileNetV2的垃圾分类 本文档详细介绍了使用MobileNetV2模型进行垃圾分类的全过程&#xff0c;包括数据准备、模型搭建、模型训练、评估和推理等步骤。MobileNetV2是一种轻量级卷积神经网络&#xff0c;专为移动端和嵌入式设备设计&#xff0c;具有高效、低耗的特点。通过…

昇思25天学习打卡营第21天|RNN实现情感分类

这节课学习的是RNN实现情感分类&#xff0c;情感分类是自然语言处理中的经典任务&#xff0c;是典型的分类问题。本节使用MindSpore实现一个基于RNN网络的情感分类模型。 比如&#xff1a; 输入: This film is terrible 正确标签: Negative 预测标签: Negative 输入: This film…

Matlab进阶绘图第63期—带标记线的三维填充折线图

三维填充折线图是在三维折线图的基础上&#xff0c;对其与XOY平面之间的部分进行颜色填充&#xff0c;从而能够更好地刻画细节变化。 而带标记线的三维填充折线图是在其基础上&#xff0c;添加X相同的一条或多条标记线&#xff0c;以用于进一步讨论分析。 由于Matlab中未收录…

Mongodb复合索引

学习mongodb&#xff0c;体会mongodb的每一个使用细节&#xff0c;欢迎阅读威赞的文章。这是威赞发布的第90篇mongodb技术文章&#xff0c;欢迎浏览本专栏威赞发布的其他文章。如果您认为我的文章对您有帮助或者解决您的问题&#xff0c;欢迎在文章下面点个赞&#xff0c;或者关…

Windows上LabVIEW编译生成可执行程序

LabVIEW项目浏览器(Project Explorer)中的"Build Specifications"就是用来配置项目发布方法的。在"Build Specifications"右键菜单中选取"New"&#xff0c;可以看到程序有几种不同的发布方法&#xff1a;Application(EXE)、Installer、.Net Inte…

C# 基于共享内存实现跨进程队列

C# 进程通信系列 第一章 共享内存 第二章 共享队列&#xff08;本章&#xff09; 文章目录 C# 进程通信系列前言一、实现原理1、用到的主要对象2、创建共享内存3、头部信息4、入队5、出队6、释放资源 二、完整代码三、使用示例1、传输byte[]数据2、传输字符串3、传输对象 总结…

HarmonyOS 屏幕适配设计

1. armonyOS 屏幕适配设计 1.1. 像素单位 &#xff08;1&#xff09;px (Pixels)   px代表屏幕上的像素点&#xff0c;是手机屏幕分辨率的单位&#xff0c;即屏幕物理像素单位。 &#xff08;2&#xff09;vp (Viewport Percentage)   vp是视口百分比单位&#xff0c;基于…

Java学习之SPI、JDBC、SpringFactoriesLoader、Dubbo

概述 SPI&#xff0c;Service Provider Interface&#xff0c;一种服务发现机制&#xff0c;指一些提供给你继承、扩展&#xff0c;完成自定义功能的类、接口或方法。 在SPI机制中&#xff0c;服务提供者为某个接口实现具体的类&#xff0c;而在运行时通过SPI机制&#xff0c…

Facebook未来展望:数字社交平台的进化之路

在信息技术日新月异的时代&#xff0c;社交媒体平台不仅是人们交流沟通的重要工具&#xff0c;更是推动社会进步和变革的重要力量。作为全球最大的社交媒体平台之一&#xff0c;Facebook在过去十多年里&#xff0c;不断创新和发展&#xff0c;改变了数十亿用户的社交方式。展望…

构建企业多层社会传播网络:以AI智能名片S2B2C商城小程序为例

摘要&#xff1a;在数字化转型的浪潮中&#xff0c;企业如何有效构建并优化其社会传播网络&#xff0c;已成为提升市场竞争力、深化用户关系及实现价值转化的关键。本文以AI智能名片S2B2C商城小程序为例&#xff0c;深入探讨如何通过一系列精细化的策略与技术创新&#xff0c;构…

IP地址知识点

一、IP地址组成 把一个IP地址分成两部分&#xff1a;网络号&#xff08;标识了一个局域网&#xff09;主机号&#xff08;标识了一个局域网中的设备&#xff09; 下图是通过一个路由器连接的两个局域网&#xff08;两个相邻的局域网&#xff09;&#xff0c;网络号不相同&…

AI绘画入门实践|Midjourney 的模型版本

模型分类 Midjourney 的模型主要分为2大类&#xff1a; 默认模型&#xff1a;目前包括&#xff1a;V1, V2, V3, V4, V5.0, V5.1, V5.2, V6 NIJI模型&#xff1a;目前包括&#xff1a;NIJI V4, NIJI V5, NIJI V6 模型切换 你在服务器输入框中输入 /settings&#xff1a; 回车后…

Mac电脑清理软件有哪些 MacBooster和CleanMyMac哪个好用 苹果电脑清理垃圾软件推荐 cleanmymac和柠檬清理

对于苹果电脑用户来说&#xff0c;‌选择合适的清理软件可以帮助优化电脑性能&#xff0c;‌释放存储空间&#xff0c;‌并确保系统安全。一款好用的苹果电脑清理软件&#xff0c;能让Mac系统保持良好的运行状态&#xff0c;避免系统和应用程序卡顿的产生。有关Mac电脑清理软件…

什么是MOW,以bitget钱包为例

元描述&#xff1a;MOW凭借其富有创意的故事情节和广阔的潜力在Solana上脱颖而出。本文深入探讨了其独特的概念和光明的未来。 Mouse in a Cats World (MOW)是一个基于Solana区块链的创新meme项目&#xff0c;它重新构想了一个异想天开且赋予权力的故事。在这个奇幻的宇宙中&am…

JuiceFS、Ceph 和 MinIO 结合使用

1. 流程图 将 JuiceFS、Ceph 和 MinIO 结合使用&#xff0c;可以充分利用 Ceph 的分布式存储能力、JuiceFS 的高性能文件系统特性&#xff0c;以及 MinIO 提供的对象存储接口。以下是一个方案&#xff0c;介绍如何配置和部署 JuiceFS 使用 Ceph 作为其底层存储&#xff0c;并通…