flutter 手写 TabBar

前言:

这几天在使用 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,
    );
  }
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

产品经理-产品经理会在项目中遇到的几个问题(16)

项目中遇到了需求变更怎么办&#xff1f; 首先要弄清楚需求变更的原因是什么。如果是因为在迭代的过程中更好地理解了用户需求 进而产生了更好的需求则完全是正常的。如果是因为老板的需求 那就需要和老板沟通清楚&#xff0c;并且确保自己能理解老板的需求&#xff0c;而且这个…

软件测试——测试用例

工作职责&#xff1a; 1.负责产品系统测试&#xff0c;包括功能测试、性能测试、稳定性测试、用户场景测试、可靠性测试等。 2.负责测试相关文档的编写&#xff0c;包括测试计划、测试用例、测试报告等。 3.负责自动化测试框架、用例的维护。 岗位要求&#xff1a; 1.熟练…

800块,我从淘宝上买AGV……

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》人俱乐部 从淘宝上打算够购买一台AGV小车&#xff0c;上去一搜&#xff0c;嘿&#xff0c;你别说&#xff0c;还真有。便宜的才200块钱。 很兴奋把…

17-8 向量数据库之野望8 - 7 个主流向量数据库

​​​​​​ 在快速发展的人工智能 (AI)、机器学习 (ML) 和数据工程领域,对高效数据存储和检索系统的需求至关重要。矢量数据库已成为管理这些技术通常依赖的复杂高维数据的关键解决方案。在这里,我们探讨了每个 AI/ML/数据工程师都应该熟悉的七个矢量数据库,重点介绍了它们…

【Linux】01.Linux 的常见指令

1. ls 指令 语法&#xff1a;ls [选项] [目录名或文件名] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息 常用选项&#xff1a; -a&#xff1a;列出当前目录下的所有文件&#xff0c;包含隐藏文件…

JavaSE学习笔记第三弹之异常抛出

今天我们继续来学习JavaSE相关的知识&#xff0c;希望与大家共同努力。 目录 异常 什么是异常 运行时异常 编译时异常 ​编辑 为什么需要异常处理机制 错误 异常的处理与抛出 异常处理 异常抛出 自定义异常 结语 异常 什么是异常 Java中异常是一种在程序运行时发…

Java二十三种设计模式-原型模式(5/23)

Java中的原型模式&#xff1a;深入解析与应用实践 引言 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它使用一个已有的对象作为原型&#xff0c;通过复制这个原型来创建新的实例。这种模式适用于对象的创建成本较高&#xff0c;或者对…

BL201分布式I/O耦合器连接Profinet网络

钡铼技术的BL201分布式I/O耦合器是一个用于Profinet网络的设备&#xff0c;用于连接远程输入/输出&#xff08;I/O&#xff09;设备到控制系统&#xff0c;如可编程逻辑控制器&#xff08;PLC&#xff09;&#xff0c;能够实现分布式的I/O连接和通信。 它支持标准Profinet IO …

Qt实现一个简单的视频播放器

目录 1 工程配置 1.1 创建新工程 1.2 ui界面配置 1.3 .pro配置 2 代码 2.1 main.c代码 2.2 widget.c 2.3 widget.h 本文主要记述了如何使用Qt编写一个简单的视频播放器&#xff0c;整个示例采用Qt自带组件就可以完成。可以实现视频的播放和暂停等功能。 1 工程配置 1.…

都说油车不行了,外资两款经典油车降价起量,与电动车展开决战

6月份的轿车销量数据出来了&#xff0c;数据显示电动汽车并没稳赢&#xff0c;燃油车终于发力了&#xff0c;反过来围剿电动汽车了&#xff0c;显示出消费者并非说不买油车&#xff0c;而是看价格&#xff0c;价格合适&#xff0c;消费者仍然会选择油车。 6月份的数据显示之前销…

AV1 编码标准帧间预测技术概述

AV1 编码标准帧间预测 AV1&#xff08;AOMedia Video1&#xff09;是一种开源的视频编码格式&#xff0c;它在帧间预测技术上做出了显著的改进和扩展&#xff0c;以提供比现有标准更高的压缩效率和更好的视频质量。以下是AV1帧间预测技术的几个关键点&#xff1a; 参考帧扩展&a…

VGMP(VRRP组管理协议)和HRP(华为冗余技术)

1、VGMP VGMP&#xff08;VRRP Group Management Protocol&#xff09;&#xff1a;VRRP组管理协议&#xff0c;是华为开发的一种私有协议&#xff0c;主要用于实现对多个VRRP组进行统一管理的功能 概述&#xff1a;VGMP协议是在VRRP协议的基础上开发的&#xff0c;其最主要的…

linux的学习(五):shell编程中的变量,运算符,条件判断

简介&#xff1a; shell编程的基本概念&#xff0c;定义变量&#xff0c;运算符&#xff0c;条件判断的基本使用 shell编程 把多个命令写到一个文件里&#xff0c;这个文件就是脚本&#xff0c;里面还有很多的流程控制 基本概念 脚本的后缀名是.sh 脚本的执行&#xff1a;…

Golang | Leetcode Golang题解之第235题二叉搜索树的最近公共祖先

题目&#xff1a; 题解&#xff1a; func lowestCommonAncestor(root, p, q *TreeNode) (ancestor *TreeNode) {ancestor rootfor {if p.Val < ancestor.Val && q.Val < ancestor.Val {ancestor ancestor.Left} else if p.Val > ancestor.Val && q…

【Docker】Docker 的数据管理与镜像创建

目录 一.数据管理 1.数据卷 2.数据卷容器 二.端口映射 三.容器互联 四.Docker 镜像的创建 1.基于现有镜像创建 1.1.首先启动一个镜像&#xff0c;基于镜像创建容器&#xff0c;更新容器内容 1.2.将修改后的容器提交为新的镜像&#xff0c;需要使用该容器的 ID 号创建新…

Studying-代码随想录训练营day40| 198.打家劫舍、213.打家劫舍II、337.打家劫舍III

第40天&#xff0c;动态规划part07&#xff0c;动态规划经典题型“打家劫舍”(ง •_•)ง&#xff0c;编程语言&#xff1a;C 目录 198.打家劫舍 213.打家劫舍II 337.打家劫舍III 总结 198.打家劫舍 文档讲解&#xff1a;代码随想录打家劫舍 视频讲解&#xff1a;手…

人工智能算法工程师(中级)课程13-神经网络的优化与设计之梯度问题及优化与代码详解

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(中级)课程13-神经网络的优化与设计之梯度问题及优化与代码详解。 文章目录 一、引言二、梯度问题1. 梯度爆炸梯度爆炸的概念梯度爆炸的原因梯度爆炸的解决方案 2. 梯度消失梯度消失的概念梯度…

按钮测试: 循环遍历 单据行明细 执行SQL

using Kingdee.BOS.Core.Bill.PlugIn; using Kingdee.BOS.Core.Metadata.EntityElement; using Kingdee.BOS.Orm.DataEntity; using Kingdee.BOS.Util; using System; using System.ComponentModel;using System.Text;namespace cux.button.test {[Description("测试循环遍…

【JavaScript 算法】回溯法:解决组合与排列问题

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、回溯法的基本概念回溯法的模板 二、经典问题及其 JavaScript 实现1. 组合问题2. 排列问题 三、回溯法的应用四、总结 回溯法是一种通过尝试所有可能的解来解决问题的算法策略。它在组合和排列问题中尤为有效&#xff0c…

在 PostgreSQL 里如何处理数据的归档和恢复的权限管理?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 在 PostgreSQL 里如何处理数据的归档和恢复的权限管理&#xff1f;一、数据归档的重要性及方法&#x…