1. Pre-knowledge

Test the ListView component, which is a list of color blocks, where each Item is a custom StatefulWidget named ColorBox, where the amount of state is the Checkbox selection, which can be switched when clicked.

The patch list The list of color blocks is selectable

Print information in the _ColorBoxState#initState and _ColorBoxState#dispose callback methods, respectively.

class ColorBox extends StatefulWidget {
  final Color color;
  final int index;

  ColorBox({Key key, this.color, this.index}) : super(key: key);

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

class _ColorBoxState extends State<ColorBox> {
  bool _checked = false;

  @override
  void initState() {
    super.initState();
    _checked = false;
    print('-----_ColorBoxState#initState---${widget.index}-- -- -- -- -- -- -- ');
  }

  @override
  void dispose() {
    print('-----_ColorBoxState#dispose---${widget.index}-- -- -- -- -- -- -- ');
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

    return Container(
      alignment: Alignment.center,
      height: 50,
      color: widget.color,
      child: Row(
        children: [
          SizedBox(width: 60),
          buildCheckbox(),
          buildInfo(),
        ],
      ),
    );
  }

  Text buildInfo() => Text(
          "index ${widget.index}: ${colorString(widget.color)}",
          style: TextStyle(color: Colors.white, shadows: [
            Shadow(color: Colors.black, offset: Offset(. 5.. 5), blurRadius: 2))); Widget buildCheckbox() => Checkbox( value: _checked, onChanged: (v) { setState(() { _checked = v; }); });String colorString(Color color) =>
      "#${color.value.toRadixString(16).padLeft(8.'0').toUpperCase()}";
}
Copy the code

Use ListView.builder to build the list of color blocks.

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: HomePage()); }}class HomePage extends StatelessWidget {
  final List<Color> data = [
    Colors.purple[50], Colors.purple[100],  Colors.purple[200],
    Colors.purple[300],   Colors.purple[400],  Colors.purple[500],
    Colors.purple[600],  Colors.purple[700], Colors.purple[800],
    Colors.purple[900],  Colors.red[50],  Colors.red[100],
    Colors.red[200], Colors.red[300],  Colors.red[400],
    Colors.red[500], Colors.red[600],  Colors.red[700],
    Colors.red[800],  Colors.red[900]];@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        height: 300, child: ListView.builder( itemCount: data.length, itemBuilder: (_, index) => ColorBox( color: data[index], index: index, ), ), ), ); }}Copy the code

After running the ListView, you can find that only 5 items are displayed on the screen, but 10 items are initialized, indicating that the ListView is a state class that will initialize a certain number of items in advance. You can control how many items are preloaded with cacheExtent. For example, if an item is 50, three items will be preloaded with cacheExtent = 50 *3.


Then scroll through the list to see what the State method callback looks like. When sliding down to the bottom, it can be seen that 0 was disposed after 13, and then the previous items were gradually disposed along with sliding. As the back slides up to the top, the front State is gradually initialized again.

In the end Slide to

So one phenomenon looms large: state loss.

In the end Slide to

2. Keep the State State

You may notice that the addAutomaticKeepAlives attribute exists in the ListView, but it doesn’t seem to work. Most people don’t know what it really does. Let’s look at how to keep State.

class _ColorBoxState extends State<ColorBox>
    with AutomaticKeepAliveClientMixin { // [1]. with AutomaticKeepAliveClientMixin
  bool _checked = false;

  @override
  bool get wantKeepAlive => true; // [2] Whether to keep the status
  
    @override
  Widget build(BuildContext context) {
    super.build(context); // [3] Call super.build in _ColorBoxState#build
Copy the code

Usage is very simple, will _ColorBoxState with AutomaticKeepAliveClientMixin, implement wantKeepAlive abstraction method, return true said can keep fit, you have anyway. The effect is as follows:

wantKeepAlive:true wantKeepAlive:false

Isn’t it amazing that the general introduction article may end here, after all, the problem has been solved. Unfortunately, this is in my BGM. I gently set addAutomaticKeepAlives to false (the default is true). Then, even if _ColorBoxState’s wantKeepAlive is true, the state will not be maintained, which indicates that addAutomaticKeepAlives are useful.

child: ListView.builder(
    addAutomaticKeepAlives: false.Copy the code

3. List#addAutomaticKeepAlives What did

Let’s catch up on what the addAutomaticKeepAlives do. Can see ListView. The builder into addAutomaticKeepAlives is to SliverChildBuilderDelegate refs.

---->[ListView#builder]----
 ListView.builder({
    / / a little...
    bool addAutomaticKeepAlives = true./ / a little...
  }) : assert(itemCount == null || itemCount >= 0),
       assert(semanticChildCount == null || semanticChildCount <= itemCount),
       childrenDelegate = SliverChildBuilderDelegate(
         itemBuilder,
         childCount: itemCount,
         addAutomaticKeepAlives: addAutomaticKeepAlives, / / < -- -- -- into the refs
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
Copy the code

AddAutomaticKeepAlives in SliverChildBuilderDelegate class attribute, you can see that the role of this property is: whether AutomaticKeepAlive components for each child.

---->[SliverChildBuilderDelegate]----
/// Whether to wrap each child in an [AutomaticKeepAlive].
/// Whether to wrap the AutomaticKeepAlive component for each child
  
/// Typically, children in lazy list are wrapped in [AutomaticKeepAlive]
/// widgets so that children can use [KeepAliveNotification]s to preserve
/// their state when they would otherwise be garbage collected off-screen.
  
/// Typically, children in lazy loading lists are wrapped in AutomaticKeepAlive components,
/// So that children can use KeepAliveNotification to save their status,
/// Otherwise they will be garbage collected off-screen.
  
/// 
/// This feature (and [addRepaintBoundaries]) must be disabled if the children
/// are going to manually maintain their [KeepAlive] state. It may also be
/// more efficient to disable this feature if it is known ahead of time that
/// none of the children will ever try to keep themselves alive.
  
/// You must disable this feature (and [addRepaintBoundaries]) if child nodes want to manually maintain their [KeepAlive] state.
/// It may be more effective to disable this feature if you know in advance that none of the child nodes will attempt to keep themselves alive.
  
/// Defaults to true.
final bool addAutomaticKeepAlives;
Copy the code

It can be seen that SliverChildBuilderDelegate# build, when addAutomaticKeepAlives = true, the child put on a layer of AutomaticKeepAlive components.

---->[SliverChildBuilderDelegate#build]----
 @override
 Widget build(BuildContext context, int index) {
 	/ / a little...
   if (addAutomaticKeepAlives)
     child = AutomaticKeepAlive(child: child);
   return KeyedSubtree(child: child, key: key);
 }
Copy the code

This shows that the AutomaticKeepAlive component is one of the keys to maintaining State. So keep fit is not just AutomaticKeepAliveClientMixin credit. Can draw AutomaticKeepAliveClientMixin and AutomaticKeepAlive must be a reason (jian) things (the qing).


4.AutomaticKeepAliveClientMixin What did

Yes it can only be used in subclasses of State. You can see from initState that if wantKeepAlive is true, _ensureKeepAlive is executed, which is why the wantKeepAlive abstract method is so valuable.

mixin AutomaticKeepAliveClientMixin<T extends StatefulWidget> on State<T> {
  // Listener
  KeepAliveHandle _keepAliveHandle;

  @override
  void initState() {
    super.initState();
    if (wantKeepAlive)
      _ensureKeepAlive();
  }
 / / Zan slightly...
}
Copy the code

There is a member variable of type KeepAliveHandle. KeepAliveHandle inherits from ChangeNotifier, which is a Listenable listener. Events are emitted through the release method. The comment states that when this method is fired, it means that the component no longer needs to hold state.

class KeepAliveHandle extends ChangeNotifier {
  /// Trigger the listeners to indicate that the widget
  /// no longer needs to be kept alive.
  voidrelease() { notifyListeners(); }}Copy the code

Now look at _ensureKeepAlive, instantiate KeepAliveHandle, create KeepAliveNotification object, and call dispatch.

void _ensureKeepAlive() {
  assert(_keepAliveHandle == null);
  _keepAliveHandle = KeepAliveHandle();
  KeepAliveNotification(_keepAliveHandle).dispatch(context);
}
Copy the code

In deactivate _releaseKeepAlive. The _keepAliveHandle release is used to notify the listener that the state is no longer needed. Build also ensures that _ensureKeepAlive is executed when _keepAliveHandle is null, which is why super.build is called.

@override
void deactivate() {
  if(_keepAliveHandle ! =null)
    _releaseKeepAlive();
  super.deactivate();
}

void _releaseKeepAlive() {
  _keepAliveHandle.release();
  _keepAliveHandle = null;
}

@mustCallSuper
@override
Widget build(BuildContext context) {
  if (wantKeepAlive && _keepAliveHandle == null)
    _ensureKeepAlive();
  return null;
}
Copy the code

So the whole logic is not very complicated. The most important thing is to create KeepAliveNotification to execute the Dispatch method. Take a look at the source code for these important classes:

  • AutomaticKeepAliveListening to themixinSent message
  • KeepAliveNotificationmixinNotification sent
  • AutomaticKeepAliveClientMixinApparently, it’s used to send keepalive messagesThe client (Clinet)

To deepen our understanding, we can write the core logic ourselves. As follows, this operation, if not mixed with AutomaticKeepAliveClientMixin, can also keep the implementation status.

class _ColorBoxState extends State<ColorBox> {
  bool _checked = false;

  KeepAliveHandle _keepAliveHandle;

  void _ensureKeepAlive() {
    _keepAliveHandle = KeepAliveHandle();
    KeepAliveNotification(_keepAliveHandle).dispatch(context);
  }

  void _releaseKeepAlive() {
    if (_keepAliveHandle == null) return;
    _keepAliveHandle.release();
    _keepAliveHandle = null;
  }

  @override
  void initState() {

    super.initState();
    _checked = false;
    _ensureKeepAlive();
    print('-----_ColorBoxState#initState---${widget.index}-- -- -- -- -- -- -- ');
  }


  @override
  void deactivate() {
    _releaseKeepAlive();
    super.deactivate();
  }

  @override
  void dispose() {
    print('-----_ColorBoxState#dispose---${widget.index}-- -- -- -- -- -- -- ');
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_keepAliveHandle == null)
      _ensureKeepAlive();
   / / a little...
Copy the code

What is the meaning of the AutomaticKeepAliveClientMixin existence, of course, is easy to use. Conversely, if a scenario has fixed logic around the life cycle of the State, we can use a mixin to add some functionality to the State in the same way. Most of the time, we get the purpose we want, we will not further explore, so that we only stay in the meeting. When faced with a problem, I just want to ask for a solution. Sometimes take another step forward and you will see a completely different style.


5. AutomaticKeepAliveClientMixinWhere else can I use it besides ListView?

The GridView, like ListView, internal use SliverChildBuilderDelegate

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(),
    body: Container(
      height: 300,
      child: GridView.builder(
        gridDelegate:SliverGridDelegateWithFixedCrossAxisCount(
          childAspectRatio: 1,
          crossAxisCount: 2,
        ),
        itemCount: data.length,
        itemBuilder: (_, index) => ColorBox(
            color: data[index],
            index: index,
          ),
        ),
    ),
  );
}
Copy the code

Since the GridView component is implemented based on the SliverGrid component, SliverGrid also works. Similarly, the ListView component is implemented based on the SliverFixedExtentList or SliverList component, which also work.


PageView USES SliverChildBuilderDelegate, so also has related features. But there is no way to expose the outside world with addAutomaticKeepAlives set to always true.

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(),
    body: Container(
      height: 300,
      child: PageView.builder(
        itemCount: data.length,
        itemBuilder: (_, index) => ColorBox(
            color: data[index],
            index: index,
          ),
        ),
    ),
  );
}
Copy the code

The TabBarView component is implemented internally based on PageView, so it also works.

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(),
    body: DefaultTabController(
      length: data.length,
      child: Column(
        children: <Widget>[
          _buildTabBar(),
          Container(
              color: Colors.purple,
              width: MediaQuery.of(context).size.width,
              height: 200,
              child: _buildTableBarView())
        ],
      ),
    ),
  );
}

Widget _buildTabBar() => TabBar(
      onTap: (tab) => print(tab),
      labelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
      unselectedLabelStyle: TextStyle(fontSize: 16),
      isScrollable: true,
      labelColor: Colors.blue,
      indicatorWeight: 3,
      indicatorPadding: EdgeInsets.symmetric(horizontal: 10),
      unselectedLabelColor: Colors.grey,
      indicatorColor: Colors.orangeAccent,
      tabs: data.map((e) => Tab(text: colorString(e))).toList(),
    );
Widget _buildTableBarView() => TabBarView(
    children: data
        .map((e) => Center(
                child: ColorBox(
              color: e,
              index: data.indexOf(e),
            )))
        .toList());

String colorString(Color color) =>
    "#${color.value.toRadixString(16).padLeft(8.'0').toUpperCase()}";
Copy the code

These are some of the most common components that need to be kept in state. As for when you need to keep in state, I can only say that when you’re hungry, you’ll know when you want to eat.

@Zhang Fengjietele 2020.12.18 not allowed to transfer my public number: the king of programming contact me – email :[email protected] – wechat :zdl1994328 ~ END ~