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

 更新时间:2023年01月29日 08:44:26   作者:流星雨在线  
这篇文章主要介绍了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滚动悬浮内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • android中WebView和javascript实现数据交互实例

    android中WebView和javascript实现数据交互实例

    这篇文章主要介绍了android中WebView和javascript实现数据交互实例,需要的朋友可以参考下
    2014-07-07
  • 如何通过Android Logcat插件分析firebase崩溃问题

    如何通过Android Logcat插件分析firebase崩溃问题

    android crash Crash(应用崩溃)是由于代码异常而导致App非正常退出,导致应用程序无法继续使用,所有工作都停止的现象,本文重点介绍如何通过Android Logcat插件分析firebase崩溃问题,感兴趣的朋友一起看看吧
    2024-01-01
  • Android Studio3.2中导出jar包的过程详解

    Android Studio3.2中导出jar包的过程详解

    这篇文章主要介绍了Android Studio3.2中导出jar包的过程,本文分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • Android设置图片圆角的方法

    Android设置图片圆角的方法

    这篇文章主要为大家详细介绍了Android设置图片圆角的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07
  • Android基于OpenCV实现Harris角点检测

    Android基于OpenCV实现Harris角点检测

    角点就是极值点,即在某方面属性特别突出的点。当然,你可以自己定义角点的属性(设置特定熵值进行角点检测)。角点可以是两条线的交叉处,也可以是位于相邻的两个主要方向不同的事物上的点。本文介绍如何基于OpenCV实现Harris角点检测
    2021-06-06
  • Android实现Service获取当前位置(GPS+基站)的方法

    Android实现Service获取当前位置(GPS+基站)的方法

    这篇文章主要介绍了Android实现Service获取当前位置(GPS+基站)的方法,较为详细的分析了Service基于GPS位置的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-09-09
  • Android实现新增及编辑联系人的方法

    Android实现新增及编辑联系人的方法

    这篇文章主要介绍了Android实现新增及编辑联系人的方法,是Android应用开发常见的功能,需要的朋友可以参考下
    2014-07-07
  • 如何造个android Flow流式响应的轮子

    如何造个android Flow流式响应的轮子

    这篇文章主要介绍了如何造个android Flow流式响应的轮子,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • Android activity堆栈及管理实例详解

    Android activity堆栈及管理实例详解

    这篇文章主要介绍了Android activity堆栈及管理实例详解的相关资料,非常不错,具有参考借鉴价值,对android activity堆栈相关知识感兴趣的朋友一起学习吧
    2016-09-09
  • Android 判断是否连接成功了指定wifi

    Android 判断是否连接成功了指定wifi

    本文主要介绍了Android 判断是否连接成功了指定wifi的相关知识。具有很好的参考价值。下面跟着小编一起来看下吧
    2017-04-04

最新评论