文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Flutter实现Android滚动悬浮效果过程

2023-01-29 09:00

关注

有以下几种效果

1、tabBar透明度随偏移0-1渐变过度

2、app上下滚动触发tabBar同步滚动

3、tabBar切换触发app上下同步滚动

1、计算每个区块的高度

用keyList保存声明的key,用heightList保存每个key对应的组件高度

// key列表
List<GlobalKey> keyList = [
  GlobalKey(),
  GlobalKey(),
  GlobalKey(),
  GlobalKey(),
  GlobalKey(),
  GlobalKey(),
  GlobalKey(),
];
// 计算每个key对应的高度
List<double> heightList;

把key放到需要计算的组件中(这里最后计算的发现就是500)

Container(
  key: keyList[index],
  height: 500,
  color: colorList[index],
)

监听滚动。

备注:controller可以监听CustomScrollView、SingleScrollView、SmartRefresher等,不一定要用CustomScrollView,另外如果是监听SmartRefresher可能会出现负数的情况需要处理成0下。

// 滚动控制器
ScrollController scrollController = new ScrollController();
@override
void initState() {
  scrollController.addListener(() => _onScrollChanged());
  super.initState();
}
@override
Widget build(BuildContext context) {
  return CustomScrollView(
    controller: scrollController,
    slivers: <Widget>[
      SliverList(
        delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
            return Container(
              key: keyList[index],
              height: 500,
              color: colorList[index],
            );
          },
          childCount: keyList.length,
        ),
      ),
    ],
  );
}

在滚动动态计算组件高度

备注:这里计算可以用防抖优化,另外这个是计算已绘制的组件高度,因此一定要在滚动的时候动态计算。

// 监听ScrollView滚动
void _onScrollChanged() {
  initHeightList();
}
// 初始化heightList
initHeightList() {
  for (int i = 0; i < keyList.length; i++) {
    if (keyList[i].currentContext != null) {
      try {
        heightList[i] = keyList[i].currentContext.size.height;
      } catch (e) {
        // 这里只是计算可视部分,因此需要持续计算
        print("can not get size, so do not modify heightList[i]");
      }
    }
  }
}

2、实现分析-tabBar透明度渐变

小于起始点透明度:0

起始点->终点透明度:0-1

大于终点透明度:1

// 监听ScrollView滚动
void _onScrollChanged() {
  initHeightList(){
  // 是否显示tabBar
  double showTabBarOffset;
  try {
    showTabBarOffset = keyList[0].currentContext.size.height - TAB_HEIGHT;
  } catch (e) {
    showTabBarOffset = heightList[0] - TAB_HEIGHT;
  }
  if (scrollController.offset >= showTabBarOffset) {
    setState(() {
      opacity = 1;
    });
  } else {
    setState(() {
      opacity = scrollController.offset / showTabBarOffset;
      if (opacity < 0) {
        opacity = 0;
      }
    });
  }
}

3、实现分析-app上下滚动触发tabBar

首先接入tabController控制器

// tabBar控制器
TabController tabController;
@override
void initState() {
  tabController = TabController(vsync: this, length: listTitle.length);
  super.initState();
}
@override
Widget build(BuildContext context) {
  return TabBar(
    controller: tabController,
    indicatorColor: Color(0xfffdd108),
    labelColor: Color(0xff343a40),
    unselectedLabelColor: Color(0xff8E9AA6),
    unselectedLabelStyle: TextStyle(
        fontSize: 14, fontWeight: FontWeight.normal),
    isScrollable: true,
    labelStyle:
    TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
    tabs: _buildTabsWidget(listTitle),
    onTap: _onTabChanged,
  );
}

然后在滚动中使用tabController.animateTo滚动到tabBar

// 监听ScrollView滚动
void _onScrollChanged() {
  initHeightList();
  // 滑动页面触发tabBar水平滚动
  if (scrollController.position.userScrollDirection ==
          ScrollDirection.reverse ||
      scrollController.position.userScrollDirection ==
          ScrollDirection.forward) {
    double totalOffset = -TAB_HEIGHT;
    for (int i = 0; i < keyList.length; i++) {
      if (scrollController.offset >= totalOffset &&
          scrollController.offset < totalOffset + heightList[i]) {
        tabController.animateTo(
          i,
          duration: Duration(milliseconds: 0),
        );
        return;
      }
      totalOffset += heightList[i];
    }
  }
}

4、实现分析-tabBar切换触发app滚动

首先获取tab的改变事件,在改变时获取当前的targetKey,用于记录需要滚动到什么高度

@override
Widget build(BuildContext context) {
  return TabBar(
    controller: tabController,
    indicatorColor: Color(0xfffdd108),
    labelColor: Color(0xff343a40),
    unselectedLabelColor: Color(0xff8E9AA6),
    unselectedLabelStyle: TextStyle(
        fontSize: 14, fontWeight: FontWeight.normal),
    isScrollable: true,
    labelStyle:
    TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
    tabs: _buildTabsWidget(listTitle),
    onTap: _onTabChanged,
  );
}
void _onTabChanged(int index) {
  targetKey = keyList[index];
  _gotoAnchorPoint();
}

然后使用 scrollController.position

.ensureVisible滚动到targetKey所在位置即可

// 点击tabBar去对应锚点
void _gotoAnchorPoint() async {
  scrollController.position
      .ensureVisible(
    targetKey.currentContext.findRenderObject(),
    alignment: 0.0,
  );
}

5、源码

tabbar_scroll_demo_page.dart

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class TabBarScrollDemoPage extends StatefulWidget {
  TabBarScrollDemoPage({
    Key key,
  }) : super(key: key);
  @override
  _TabBarScrollDemoPageState createState() => _TabBarScrollDemoPageState();
}
class _TabBarScrollDemoPageState extends State<TabBarScrollDemoPage>
    with SingleTickerProviderStateMixin, WidgetsBindingObserver {
  // 滚动控制器
  ScrollController scrollController = new ScrollController();
  // key列表
  List<GlobalKey> keyList = [
    GlobalKey(),
    GlobalKey(),
    GlobalKey(),
    GlobalKey(),
    GlobalKey(),
    GlobalKey(),
    GlobalKey(),
  ];
  // 当前锚点key
  GlobalKey targetKey;
  // 计算每个key对应的高度
  List<double> heightList;
  // tabBar控制器
  TabController tabController;
  // 是否显示tabBar
  bool showTabBar = true;
  // 状态栏高度
  static const double TAB_HEIGHT = 48;
  // 
  List<String> listTitle = [
    "Red",
    "Orange",
    "Yellow",
    "Green",
    "Indigo",
    "Blue",
    "Purple"
  ];
  // 颜色
  List<Color> colorList = [
    Color(0xffFF0000),
    Color(0xffFF7F00),
    Color(0xffFFFF00),
    Color(0xff00FF00),
    Color(0xff00FFFF),
    Color(0xff0000FF),
    Color(0xff8B00FF),
  ];
  // tabBar过度透明度
  double opacity = 0.0;
  @override
  void initState() {
    heightList = List.filled(keyList.length, 0);
    targetKey = keyList[0];
    tabController = TabController(vsync: this, length: listTitle.length);
    scrollController.addListener(() => _onScrollChanged());
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }
  void _onTabChanged(int index) {
    targetKey = keyList[index];
    _gotoAnchorPoint();
  }
  // 监听ScrollView滚动
  void _onScrollChanged() {
    initHeightList();
    // 是否显示tabBar
    double showTabBarOffset;
    try {
      showTabBarOffset = keyList[0].currentContext.size.height - TAB_HEIGHT;
    } catch (e) {
      showTabBarOffset = heightList[0] - TAB_HEIGHT;
    }
    if (scrollController.offset >= showTabBarOffset) {
      setState(() {
        opacity = 1;
      });
    } else {
      setState(() {
        opacity = scrollController.offset / showTabBarOffset;
        if (opacity < 0) {
          opacity = 0;
        }
      });
    }
    // 滑动页面触发tabBar水平滚动
    if (scrollController.position.userScrollDirection ==
            ScrollDirection.reverse ||
        scrollController.position.userScrollDirection ==
            ScrollDirection.forward) {
      double totalOffset = -TAB_HEIGHT;
      for (int i = 0; i < keyList.length; i++) {
        if (scrollController.offset >= totalOffset &&
            scrollController.offset < totalOffset + heightList[i]) {
          tabController.animateTo(
            i,
            duration: Duration(milliseconds: 0),
          );
          return;
        }
        totalOffset += heightList[i];
      }
    }
  }
  // 初始化heightList
  initHeightList() {
    for (int i = 0; i < keyList.length; i++) {
      if (keyList[i].currentContext != null) {
        try {
          heightList[i] = keyList[i].currentContext.size.height;
        } catch (e) {
          // 这里只是计算可视部分,因此需要持续计算
          print("can not get size, so do not modify heightList[i]");
        }
      }
    }
  }
  // 点击tabBar去对应锚点
  void _gotoAnchorPoint() async {
    GlobalKey key = targetKey;
    if (key.currentContext != null) {
      scrollController.position
          .ensureVisible(
        key.currentContext.findRenderObject(),
        alignment: 0.0,
      )
          .then((value) {
        // 在此基础上再偏移一个TAB_HEIGHT的高度
        if (scrollController.offset - TAB_HEIGHT > 0) {
          scrollController.jumpTo(scrollController.offset - TAB_HEIGHT);
        }
      });
      return;
    }
    // 以下代码处理获取不到key.currentContext情况,没问题也可以去掉
    int nearestRenderedIndex = 0;
    bool foundIndex = false;
    for (int i = keyList.indexOf(key) - 1; i >= 0; i -= 1) {
      // find first non-null-currentContext key above target key
      if (keyList[i].currentContext != null) {
        try {
          // Only when size is get without any exception,this key can be used in ensureVisible function
          Size size = keyList[i].currentContext.size;
          print("size: $size");
          foundIndex = true;
          nearestRenderedIndex = i;
        } catch (e) {
          print("size not availabel");
        }
        break;
      }
    }
    if (!foundIndex) {
      for (int i = keyList.indexOf(key) + 1; i < keyList.length; i += 1) {
        // find first non-null-currentContext key below target key
        if (keyList[i].currentContext != null) {
          try {
            // Only when size is get without any exception,this key can be used in ensureVisible function
            Size size = keyList[i].currentContext.size;
            print("size: $size");
            foundIndex = true;
            nearestRenderedIndex = i;
          } catch (e) {
            print("size not availabel");
          }
          break;
        }
      }
    }
    int increasedOffset = nearestRenderedIndex < keyList.indexOf(key) ? 1 : -1;
    for (int i = nearestRenderedIndex;
        i >= 0 && i < keyList.length;
        i += increasedOffset) {
      if (keyList[i].currentContext == null) {
        Future.delayed(new Duration(microseconds: 10), () {
          _gotoAnchorPoint();
        });
        return;
      }
      if (keyList[i] != targetKey) {
        await scrollController.position.ensureVisible(
          keyList[i].currentContext.findRenderObject(),
          alignment: 0.0,
          curve: Curves.linear,
          alignmentPolicy: increasedOffset == 1
              ? ScrollPositionAlignmentPolicy.keepVisibleAtEnd
              : ScrollPositionAlignmentPolicy.keepVisibleAtStart,
        );
      } else {
        await scrollController.position
            .ensureVisible(
          keyList[i].currentContext.findRenderObject(),
          alignment: 0.0,
        )
            .then((value) {
          Future.delayed(new Duration(microseconds: 1000), () {
            if (scrollController.offset - TAB_HEIGHT > 0) {
              scrollController.jumpTo(scrollController.offset - TAB_HEIGHT);
            } else {}
          });
        });

        break;
      }
    }
  }
  // 悬浮tab的item
  List<Widget> _buildTabsWidget(List<String> tabList) {
    var list = List<Widget>();
    String keyValue = DateTime.now().millisecondsSinceEpoch.toString();
    for (var i = 0; i < tabList.length; i++) {
      var widget = Tab(
        text: tabList[i],
        key: Key("i$keyValue"),
      );
      list.add(widget);
    }
    return list;
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('滚动悬浮示例Demo'),
      ),
      body: Center(
        child: Stack(
          alignment: Alignment.topLeft,
          overflow: Overflow.clip,
          children: <Widget>[
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Expanded(
                  child: CustomScrollView(
                    controller: scrollController,
                    slivers: <Widget>[
                      SliverList(
                        delegate: SliverChildBuilderDelegate(
                          (BuildContext context, int index) {
                            return Container(
                              key: keyList[index],
                              height: 500,
                              color: colorList[index],
                            );
                          },
                          childCount: keyList.length,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            if (showTabBar)
              Positioned(
                top: 0,
                width: MediaQuery.of(context).size.width,
                child: Opacity(
                  opacity: opacity,
                  child: Container(
                    color: Colors.white,
                    child: TabBar(
                      controller: tabController,
                      indicatorColor: Color(0xfffdd108),
                      labelColor: Color(0xff343a40),
                      unselectedLabelColor: Color(0xff8E9AA6),
                      unselectedLabelStyle: TextStyle(
                          fontSize: 14, fontWeight: FontWeight.normal),
                      isScrollable: true,
                      labelStyle:
                          TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
                      tabs: _buildTabsWidget(listTitle),
                      onTap: _onTabChanged,
                    ),
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

直接运行上述文件即可

示例: main.dart

import 'package:flutter/material.dart';
import 'package:scroll_tabbar_sample/tabbar_scroll_demo_page.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: TabBarScrollDemoPage(),
    );
  }
}

到此这篇关于Flutter实现Android滚动悬浮效果过程的文章就介绍到这了,更多相关Flutter滚动悬浮内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯