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

有以下几种效果 1、tabBar透明度随偏移0-1渐变过度 2、app上下滚动触发tabBar同步滚动 3、tabBar切换触发app上下同步滚动1、计算每个区

有以下几种效果

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滚动悬浮内容请搜索好代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持好代码网!

标签: Flutter 滚动 悬浮