我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

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

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

有以下几种效果

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

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

这篇文章主要介绍了Flutter实现Android滚动悬浮效果,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
2023-01-29

Android实现顶部悬浮效果

本文实例为大家分享了Android实现顶部悬浮效果的具体代码,供大家参考,具体内容如下效果图 布局
2023-05-30

Android悬浮窗效果怎么实现

要实现Android的悬浮窗效果,可以采用以下几种方法:使用系统提供的WindowManager类来创建一个悬浮窗口。可以通过以下步骤实现:在AndroidManifest.xml文件中添加SYSTEM_ALERT_WINDOW权限。创建
2023-10-22

Android view如何实现滑动悬浮固定效果

这篇文章主要介绍了Android view如何实现滑动悬浮固定效果,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1.背景在项目开发过程中,有时候会碰到这样的需求:在滑动的过程
2023-05-30

Android实现美团、大众点评的购买悬浮效果(ScrollView滚动监听)

随着移动互联网的快速发展,它已经和我们的生活息息相关了,在公交地铁里面都能看到很多人的人低头看着自己的手机屏幕,从此“低头族”一词就产生了,作为一名移动行业的开发人员,我自己也是一名“低头族”,上下班时间在公交地铁上看看新闻来打发下时间,有
2022-06-06

Android 实现控件悬浮效果实例代码

随着移动互联网的快速发展,它已经和我们的生活息息相关了,在公交地铁里面都能看到很多人的人低头看着自己的手机屏幕,从此“低头族”一词就产生了,作为一名移动行业的开发人员,我自己也是一名“低头族”,上下班时间在公交地铁上看看新闻来打发下时间,有
2022-06-06

Android Flutter如何实现有趣的页面滚动效果

本文小编为大家详细介绍“Android Flutter如何实现有趣的页面滚动效果”,内容详细,步骤清晰,细节处理妥当,希望这篇“Android Flutter如何实现有趣的页面滚动效果”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一
2023-07-02

Android实现带磁性的悬浮窗体效果

本文实例讲述了Android实现带磁性的悬浮窗体效果。分享给大家供大家参考,具体如下: 带磁性的悬浮窗体,类似于360绿色小人 主要实现的是: 1.悬浮所有窗体之上 2.有吸引力,吸附于屏幕边上 3.有点击效果 下面我就实现上面三点,简单封
2022-06-06

Android利用悬浮按钮实现翻页效果

今天给大家分享下自己用悬浮按钮点击实现翻页效果的例子。 首先,一个按钮要实现悬浮,就要用到系统顶级窗口相关的WindowManager,WindowManager.LayoutParams。那么在AndroidManifest.xml中添加
2022-06-06

Android RecyclerView打造悬浮效果的实现代码

本文介绍了Android RecyclerView悬浮效果,分享给大家,具体如下:先看个效果这是一个City列表,每个City都有所属的Province,需要在滑动的时候,将对应的Province悬浮在顶部。悬浮顶部的Province需要根
2023-05-30

Android实现3D滚动效果

先上效果图 下载链接http://down.51cto.com/data/1076318
2023-01-31

Android怎么实现桌面悬浮小火箭效果

这篇文章将为大家详细讲解有关Android怎么实现桌面悬浮小火箭效果,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。具体内容如下思路使用serivce在后台启动小火箭小火箭使用windowmanager实现
2023-05-30

Android实现文字滚动效果

Android 实现文字滚动效果,自己写了个timer小计时器,textview文字上下翻动效果:public class AutoTextView extends TextSwitcher implementsViewSwitcher.V
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录