【问题标题】:Flutter Listview swipe to change TabBar indexFlutter Listview 滑动更改 TabBar 索引
【发布时间】:2021-01-26 11:42:30
【问题描述】:

我在没有TabBarView 的情况下实现了TabBar。我使用单个ListView 作为body,因为选择选项卡后的布局对于所有选项卡都是相同的。

我想要实现的是,在列表视图中向左/向右滑动时更改选项卡。我该怎么做?

标签栏

TabBar(
        indicatorWeight: 3,
        indicatorSize: TabBarIndicatorSize.label,
        onTap: (index) {
          categoryId = newsProvider.categories[index].id;
          page = 1;
          fetchPosts(newsProvider);
        },
        isScrollable: true,
        tabs: [
          for (Category category in newsProvider.categories)
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
              child: Text(
                category.name,
                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
              ),
            ),
        ],
      ),

正文

ListView.builder(
                    padding: EdgeInsets.only(bottom: 60),
                    physics: BouncingScrollPhysics(),
                    controller: _scrollController,
                    itemCount: newsProvider.posts.length,
                    itemBuilder: (context, index) {
                      return GestureDetector(
                        onTap: () {
                          Navigator.of(context).push(MaterialPageRoute(
                            builder: (BuildContext context) =>
                                HabaruDetails(newsProvider.posts[index]),
                          ));
                        },
                        child: Container(
                          height: 200,
                          margin: EdgeInsets.only(left: 10, right: 10, top: 10),
                          child: ClipRRect(
                            borderRadius: BorderRadius.circular(7),
                            child: Stack(
                              children: [
                                Positioned.fill(
                                  child: Hero(
                                    tag: newsProvider.posts[index].id,
                                    child: FadeInImage.memoryNetwork(
                                        placeholder: kTransparentImage,
                                        fit: BoxFit.cover,
                                        image: newsProvider
                                            .posts[index].betterFeaturedImage.mediumLarge),
                                  ),
                                ),
                                Container(
                                  height: 200,
                                  decoration: BoxDecoration(
                                      color: Colors.white,
                                      gradient: LinearGradient(
                                          begin: FractionalOffset.topCenter,
                                          end: FractionalOffset.bottomCenter,
                                          colors: [
                                            Colors.black.withOpacity(0.0),
                                            Colors.black.withOpacity(0.95),
                                          ],
                                          stops: [
                                            0.0,
                                            1.0
                                          ])),
                                ),
                                Positioned.fill(
                                  child: Align(
                                    alignment: Alignment.bottomCenter,
                                    child: Padding(
                                      padding: const EdgeInsets.symmetric(
                                          horizontal: 16, vertical: 30),
                                      child: Text(
                                        newsProvider.posts[index].title.rendered,
                                        textAlign: TextAlign.center,
                                        textDirection: TextDirection.rtl,
                                        style: TextStyle(
                                          fontSize: 24,
                                          fontWeight: FontWeight.bold,
                                          color: Colors.white,
                                          height: 1.7,
                                        ),
                                      ),
                                    ),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ),
                      );
                    },
                  )

【问题讨论】:

  • 你的意思是水平滑动还是垂直滑动?
  • 我要水平滑动
  • 我的意思是你只有1个垂直列表但你想水平交换它,所以预期的行为是通过滑动来改变标签栏索引和列表内容或者只是改变标签索引,对不起对于长时间的提问,但如果我更了解您,我可能会有所帮助。
  • 你做对了。我想通过向左或向右滑动列表来更改选项卡索引。当标签索引更改时,我将调用 API 来获取新数据并加载到列表中。我不希望每个选项卡都有单独的列表视图。
  • 我不知道我是否应该为此写一个完整的答案,但解决方案很简单,您可以使用 GestureDetector 扭曲列表并检测滑动或使用 swipedetector 包链接:pub.dev/packages/swipedetector ,基本上它会为你提供 onSwipeLeft 和 onSwipeRight 函数,所以只需一些逻辑来传递标签索引就会得到你想要的。如果您需要进一步解释,我可以为您编写代码。

标签: flutter dart


【解决方案1】:

我认为您仍然必须使用 TabBarView,但您可以根据下面的类别列表动态生成其子项。

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: [...],
          ),
        ),
        body: TabBarView(
          children: newsProvider.categories.map(
                (e) => ListView.builder(...).toList(),
          ),
        ),
      ),
    ),
  );
}

【讨论】:

  • 但是这个方法会在TabBarView中创建多个页面。我需要单页并且滑动应该只影响 TabBar。现在,这也是滑动页面。
  • TabBar 只能与TabBarVeiw 配合使用,并且TabBarViews 必须与TabBar 中的选项卡顺序相对应,否则会抛出异常。你能解释一下你想要达到的目标吗?如果您滑动页面,页面的内容是否不应该相应更新,或者预期的行为是什么?
  • 我真正想要的是在 body 小部件上向左或向右滑动时更改标签栏。 body 是容器还是 ListView。我在想也许我可以使用 GestureDetector 并更改标签?
  • TabBarView 方法也可以,但我的问题是,当我像您的答案一样生成 TaBarView 子项时,它会为所有选项卡生成相同的页面。选择选项卡后,我只需要加载选项卡的内容。不是在那之前。
【解决方案2】:

据我所知,我认为您正在尝试创建一个标签栏,当用户在列表视图上向下滚动时它会移动?如果是这样,我创建了一个类的示例,该类使用一系列选项卡,您可以将其实现为 AppBar 中标题的 TabBar,其控制器设置为我在构建方法上方创建的 TabBarController。 listview 然后被设置为监听 tabbarcontroller 状态的 scrollcontroller 的控制器。

import 'package:flutter/material.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: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {

  TabController _tabController;
  ScrollController _scrollController;


  void _scrollListener() {
    var index = (_scrollController.offset / 70).round();
    if(index >= choices.length){
      index = choices.lastIndexOf(choices.last);
    }
    if(index <= 0){
      index = 0;
    }
    if (index == choices.length)
      index = choices.length;
    if(mounted){
      setState(() {
        _tabController.animateTo(index, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
      });
    }

  }


  @override
  void initState() {
    super.initState();

    _tabController = TabController(vsync: this, length: choices.length);
    _scrollController = ScrollController();
    _scrollController.addListener(_scrollListener);

  }

  @override
  void dispose() {
    super.dispose();
    _tabController.dispose();
    _scrollController.dispose();
  }



  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(

        title: TabBar(
          labelColor: Colors.black,
          unselectedLabelColor: Colors.black45,
          labelStyle: TextStyle(fontWeight: FontWeight.w700, fontFamily: 'Valera'),
          controller: _tabController,
          isScrollable: true,
          indicator: BoxDecoration(
              borderRadius: BorderRadius.circular(50),
              color: Colors.transparent),
          onTap: (int index){
            setState(() {
              _scrollController.animateTo(index*80.0, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
            });
          },

          tabs: choices.map((Choice choice) {
            return Padding(
              padding: const EdgeInsets.only(top: 20.0),
              child: Tab(
                text: choice.title,
              ),
            );
          }).toList(),
        ),
      ),
      body: Container(

        child: ListView.builder(
          controller: _scrollController,
            itemBuilder: (context, index){


            return ListTile(

            );

            }),

      ),

    );
  }
}



class Choice {
  const Choice({this.title});

  final String title;
}

const List<Choice> choices = const <Choice>[
  const Choice(title: 'First Tab'),
  const Choice(title: 'Second Tab'),
  const Choice(title: 'Third Tab'),
  const Choice(title: 'Fourth Tab'),
  const Choice(title: 'Fifth Tab'),
  const Choice(title: 'Sixth Tab'),
];

class ChoiceCard extends StatelessWidget {
  const ChoiceCard({Key key, this.choice}) : super(key: key);

  final Choice choice;

  @override
  Widget build(BuildContext context) {
    return Text(choice.title, style: TextStyle(
      color: Colors.black,
    ),
    );
  }
}

【讨论】:

    【解决方案3】:

    您可以通过TabControllerListener 完成此操作

    首先你可以定义一个TabController。它需要在 StatefulWidget 中。

    TabController _tabController;
    
    @override
    void initState() {
      super.initState();
      _tabController = TabController(length: tabLenght, vsync: this);
    }
    
    TabBar(
      ...
      controller: _tabController,
      ...
    )
    

    然后您可以将Listener 类添加到ListView。将点击位置和滑动距离保存在onPointerDown,onPointerMove,onPointerUponPointerCancel(与onPointerUp相同)。

    通过控制offset 和索引来更改选项卡。注意offset 在范围 (-1.0,1.0) 内。如果到达下一个索引,您应该自己制作动画。

    double startX;
    double sensitivity = 0.01;
    
        ...
        child: Listener(
          onPointerDown: (event) {
            startX = event.position.dx;
          },
    
          onPointerMove: (event) {
            double newX = event.position.dx;
            double offset = ((startX - newX) * sensitivity).clamp(-1.0, 1.0);
            if (!_tabController.indexIsChanging)
              _tabController.offset = offset;
            if (offset == 1.0 &&
                _tabController.index < _tabController.length - 1) {
              _tabController.animateTo(_tabController.index + 1);
              startX = newX;
            }
            if (offset == -1.0 && _tabController.index > 0) {
              _tabController.animateTo(_tabController.index - 1);
              startX = newX;
            }
          },
    
          onPointerUp: (_) {
            if (_tabController.offset > 0.5 &&
                _tabController.index < _tabController.length - 1)
              _tabController.animateTo(_tabController.index + 1);
            else if (_tabController.offset < -0.5 && _tabController.index > 0)
              _tabController.animateTo(_tabController.index - 1);
            else {
              if (!_tabController.indexIsChanging)
                _tabController.offset = 0;
            }
          },
    
          onPointerCancel: (_) {
            if (_tabController.offset > 0.5 &&
                _tabController.index < _tabController.length - 1)
              _tabController.animateTo(_tabController.index + 1);
            else if (_tabController.offset < -0.5 && _tabController.index > 0)
              _tabController.animateTo(_tabController.index - 1);
            else {
              if (!_tabController.indexIsChanging)
                _tabController.offset = 0;
            }
          },
          child: ListView.builder(
          ...
    

    我想你想要像TabBarView 这样的拖拽效果?也可以查看TabBarView里面的源码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-11
      • 2022-11-15
      • 2020-08-18
      • 1970-01-01
      • 2012-05-21
      • 1970-01-01
      • 2019-09-05
      • 2020-11-04
      相关资源
      最近更新 更多