Flutter中的IndexedStack

通过Stack组件我们可以将一些widget叠放在其他widget之上,从而可以实现图像的组合功能,也是日常中最常用的一种组件了。今天我们要介绍的组件是Stack的近亲,叫做IndexedStack,它有什么功能呢?一起来看看吧。

IndexedStack简介

从名字可以看出,IndexedStack是给Stack添加了一个index的功能,事实是否如此呢?我们先来看一下IndexedStack的定义:

class IndexedStack extends Stack 
复制代码

可以看到IndexedStack继承自Stack,它实际上是Stack的子类,所以之前介绍的Stack有的功能IndexedStack全都有,并且IndexedStack是对Stack的功能进行了增强。

我们来看下它的构造函数:

  IndexedStack({
    Key? key,
    AlignmentGeometry alignment = AlignmentDirectional.topStart,
    TextDirection? textDirection,
    StackFit sizing = StackFit.loose,
    this.index = 0,
    List<Widget> children = const <Widget>[],
  }) : super(key: key, alignment: alignment, textDirection: textDirection, fit: sizing, children: children);

可以看到和Stack相比,IndexedStack多了一个index参数,但是这个参数并没有传入到super的构造函数中去,那么index到底是在哪里使用的呢?

别急,IndexedStack还重写了下面的两个方法,分别是createRenderObject和updateRenderObject:

  @override
  RenderIndexedStack createRenderObject(BuildContext context) {
    assert(_debugCheckHasDirectionality(context));
    return RenderIndexedStack(
      index: index,
      alignment: alignment,
      textDirection: textDirection ?? Directionality.maybeOf(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderIndexedStack renderObject) {
    assert(_debugCheckHasDirectionality(context));
    renderObject
      ..index = index
      ..alignment = alignment
      ..textDirection = textDirection ?? Directionality.maybeOf(context);
  }

和Stack相比,IndexedStack在这两个方法中使用的是RenderIndexedStack,而Stack使用的是RenderStack。

所以虽然IndexedStack继承自Stack,但是两者在表现上是有本质区别的。

对于Stack来说,一个widget被放在另外一个widget之上,但是多个widget可以同时展示出来。而对于IndexedStack来说,它只会展示对应index的widget。

RenderIndexedStack也是继承自RenderStack:

class RenderIndexedStack extends RenderStack 

我们看下它的paintStack方法:

扫描二维码关注公众号,回复: 14376072 查看本文章
  @override
  void paintStack(PaintingContext context, Offset offset) {
    if (firstChild == null || index == null)
      return;
    final RenderBox child = _childAtIndex();
    final StackParentData childParentData = child.parentData! as StackParentData;
    context.paintChild(child, childParentData.offset + offset);
  }

可以看到在paintStack方法中,只绘制了和index对应的_childAtIndex这个组件,所以如果index不匹配的话,并不会展示出来。

IndexedStack的表现有点像我们常见的tab。

IndexedStack的使用

从上面IndexedStack的构造函数中,我们知道IndexedStack需要传入一个index属性和对应的children。

在本例中,我们给IndexedStack传入一个可变的index属性,和4个child:

IndexedStack(
          index: _counter,
          children: [
            widgetOne(),
            widgetTwo(),
            widgetThree(),
            widgetFour(),
          ],
        )

_counter是定义在StatefulWidget中的变量。可以通过调用setState方法对index进行修改,从而实现动态切换child的目的。

这里的child widget很简单,我们使用了不同大小的SizedBox,SizedBox中设置不同的color来方便观察切换的效果:

  Widget widgetOne() {
    return SizedBox(
      width: 100,
      height: 100,
      child: Container(
        color: Colors.yellow,
      ),
    );
  }

最后,在Scaffold的floatingActionButton中调用_changeIndex方法实现index的改变,最终的代码如下:

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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _changeIndex() {
    setState(() {
      _counter = (_counter+1) % 4;
      print(_counter);
    });
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: IndexedStack(
          index: _counter,
          children: [
            widgetOne(),
            widgetTwo(),
            widgetThree(),
            widgetFour(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _changeIndex,
        tooltip: 'change index',
        child: const Icon(Icons.arrow_back),
      ), 
    );
  }

程序运行之后的效果如下:

通过点击右下方的按钮,我们得到了不同的widget。

总结

IndexWidget和tab有点类似,大家可以在需要的时候使用。


Flutter使用PageView和IndexedStack构造底部切换页面的区别

最开始的时候我是使用IndexedStack进行构造,但是发现一个问题,如果进去app的时候,首页模块,交易模块,个人中心模块会触发接口请求,如果用户token失效,我会切换到登录页面,但是如果使用IndexedStack,会触发3次请求切换3次登录页面,我想找到IndexedStack中页面只有在点击的时候才会触发 initState方法,但是没有找到。 暂时没有找到类似iOS中生命周期中的方法。所以改用pageView进行切换

 所以使用PageView,然后在页面中使用 AutomaticKeepAliveClientMixin进行混入,再重写 bool get wantKeepAlive => true; 就可以实现

 然后

 实现重写方法

 


底部 Tab 切换保持页面状态的几种方法

一、IndexedStack 保持页面状态
IndexedStack 和 Stack 一样,都是层布局控件, 可以在一个控件上面放置另一
个控件,但唯一不同的是 IndexedStack 在同一时刻只能显示子控件中的一个控
件,通过 Index 属性来设置显示的控件。

IndexedStack 来保持页面状态的优点就是配置简单。IndexedStack 保持页面状
态的缺点就是不方便单独控制每个页面的状态。

IndexedStack 用法:

Container(
width: double.infinity,
height: double.infinity,
child: new IndexedStack(
index: 0,
alignment: Alignment.center,
children: <Widget>[
Image.network("https://www.itying.com/images/flutter/list1.jpg",fit:
BoxFit.cover,),
Image.network("https://www.itying.com/images/flutter/list2.jpg",fit:
BoxFit.cover)
],
),
)

结合底部 tab 切换

body:IndexedStack(
children: this._pageList,
index: _currentIndex,
),



二、 AutomaticKeepAliveClientMixin 保持页面状态
AutomaticKeepAliveClientMixin 结合 tab 切换保持页面状态相比 IndexedStack 而言配置起来稍
微有些复杂。它结合底部 BottomNavigationBar 保持页面状态的时候需要进行如下配置。

import 'package:flutter/material.dart';
import 'Home.dart';
import 'Category.dart';
import 'Cart.dart';
import 'User.dart';
class Tabs extends StatefulWidget {
  Tabs({Key key}) : super(key: key);
  _TabsState createState() => _TabsState();
}
class _TabsState extends State<Tabs> {
  int _currentIndex=1;
//创建页面控制器
  var _pageController;
  @override
  void initState(){
//页面控制器初始化
    _pageController = new PageController(initialPage : _currentIndex);
    super.initState();
  }

  List<Widget> _pageList=[
    HomePage(),
    CategoryPage(),
    CartPage(),
    UserPage()
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("jdshop"),
        ),
//必须用 PageView 加载不同的页面
        body: PageView(
          controller: _pageController,
          children: this._pageList,
          onPageChanged: (index){
            _currentIndex = index;
          },
        ),
        bottomNavigationBar: BottomNavigationBar(
            currentIndex:this._currentIndex ,
            onTap: (index){
              setState(() {
//this._currentIndex=index;
//页面控制器进行跳转
                _pageController.jumpToPage(this._currentIndex);
              });
            },
            type:BottomNavigationBarType.fixed ,
            fixedColor:Colors.red,
            items: [
        BottomNavigationBarItem(
        icon: Icon(Icons.home),
        title: Text("首页")
    ),
    BottomNavigationBarItem(
    icon: Icon(Icons.category),
    title: Text("分类")
    ),
    BottomNavigationBarItem(
        icon: Icon(Icons.shopping_cart),
        title: Text("购物车")
    ),
              BottomNavigationBarItem(
                  icon: Icon(Icons.people),
                  title: Text("我的")
              )
            ],
        ),
    );
  }
}


需要持久化的页面加入如下代码:

class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);
  _HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin{
  @override
  bool get wantKeepAlive => true;
}

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/125786042