使用重复动画时退出时会出现抖动?

时间:2018-06-02 15:54:55

标签: android flutter

我有一个动画小部件。如果我为它制作动画片,那么就转发它。然后退出,应用程序不会崩溃。但是,如果我尝试重复动画,应用程序会在退出时崩溃(Android上的后退按钮)。

问题:如何停止动画并正确清理动画,以便退出时不会显示应用程序墓碑?

我使用以下内容重复动画:

animation.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    controller.reverse();
  } else if (status == AnimationStatus.dismissed) {
    controller.forward();
  }
});
controller.forward();

(从Flutter docs复制/粘贴)...我也尝试过使用controller.repeat(),但同样的事情发生了。

应用程序在退出时崩溃并显示以下内容:

  

中止消息:'以std :: __ ndk1 :: system_error类型的未捕获异常终止:互斥锁失败:设备或资源忙'

追溯是:

 #00 pc 0000000000021abc  /system/lib64/libc.so (abort+124)
    #01 pc 00000000009c7070  /data/app/crashtest.com.crashtest-oEV70lFQG32MrmIJyVK_Mw==/lib/arm64/libflutter.so
    #02 pc 00000000009c7180  /data/app/crashtest.com.crashtest-oEV70lFQG32MrmIJyVK_Mw==/lib/arm64/libflutter.so
    #03 pc 00000000009c4bec  /data/app/crashtest.com.crashtest-oEV70lFQG32MrmIJyVK_Mw==/lib/arm64/libflutter.so
    #04 pc 00000000009c45a4  /data/app/crashtest.com.crashtest-oEV70lFQG32MrmIJyVK_Mw==/lib/arm64/libflutter.so
    #05 pc 000000000099e18c  /data/app/crashtest.com.crashtest-oEV70lFQG32MrmIJyVK_Mw==/lib/arm64/libflutter.so
    #06 pc 00000000001a2b60  /data/app/crashtest.com.crashtest-oEV70lFQG32MrmIJyVK_Mw==/lib/arm64/libflutter.so
    #07 pc 000000000015df78  /data/app/crashtest.com.crashtest-oEV70lFQG32MrmIJyVK_Mw==/lib/arm64/libflutter.so
    #08 pc 0000000000003338  /data/app/crashtest.com.crashtest-oEV70lFQG32MrmIJyVK_Mw==/oat/arm64/base.odex (offset 0x3000) (io.flutter.view.VsyncWaiter.nativeOnVsync+168)
    #09 pc 0000000000004990  /dev/ashmem/dalvik-jit-code-cache (deleted) (io.flutter.view.VsyncWaiter.access$000+48)
    #10 pc 0000000000005364  /dev/ashmem/dalvik-jit-code-cache (deleted) (io.flutter.view.VsyncWaiter$1.doFrame+84)
    #11 pc 0000000000004a88  /dev/ashmem/dalvik-jit-code-cache (deleted) (android.view.Choreographer$CallbackRecord.run+152)

这是有问题的代码(完整的演示应用,所以它相当长,但你应该只能复制/粘贴到一个空的Flutter应用程序,它会工作!):<​​/ p>

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Crash on exit Demo'),
    );
  }
}

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

  final String title;

  @override
  Widget build(BuildContext context) {
    return new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          new Container(
            height: 100.0,
            width: 100.0,
            child: new _AnimatedBlox(),
          ),
        ]
    );
  }
}

class _AnimatedBlox extends StatefulWidget {
  _AnimatedBloxState createState() => new _AnimatedBloxState();

}

class _AnimatedBloxState extends State<_AnimatedBlox> with TickerProviderStateMixin {
  Animation<int> animation;
  AnimationController controller;
  List<int> path = new List<int>();
  initState() {
    super.initState();
    controller = new AnimationController(duration: const Duration(seconds: 4), vsync: this);

    animation = new _PathWalker(path).animate(controller);

    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    controller.forward();
  }

  @override
  void dispose() {

    controller.stop();
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new LayoutBuilder(
        builder: (context, constraints) {
          return new _BlockGroup(path, constraints.maxHeight, animation: animation);
        });
  }
}


class _BlockGroup extends AnimatedWidget {

  List<int> _path;
  double _blockSize;

  _BlockGroup(this._path, blockSize, {Key key, Animation<int> animation})
      : super(key: key, listenable: animation) {

    // 4 blocks, 3 gaps
    double gap = 2.0;
    _blockSize = (blockSize - 3 * gap) / 4.0;
  }


  Widget _getBox(int index) {
    bool isInPath = _path.contains(index);

    return new Container(
        width: _blockSize,
        height: _blockSize,
        color: isInPath ? Colors.white : Colors.blueAccent);
  }

  @override
  Widget build(BuildContext context) {
    final Animation<int> animation = listenable;
    var ensure_animation_runs_by_getting_the_value = animation.value;

    return new Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        mainAxisSize: MainAxisSize.max,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          new Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              mainAxisSize: MainAxisSize.max,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                _getBox(0), _getBox(1), _getBox(2),
              ]
          ),
          new Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              mainAxisSize: MainAxisSize.max,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                _getBox(3), _getBox(4), _getBox(5),
              ]
          ),
          new Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              mainAxisSize: MainAxisSize.max,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                _getBox(6), _getBox(7), _getBox(8),
              ]
          ),
        ]
    );
  }
}


class _PathWalker extends Animatable<int> {
  int lastUpdateTracker = 0;      // tracks the index of the last call to animation from 0->15
  List<int> _path;

  _PathWalker(this._path);

  // We get handed a double that scaled from 0.0 -> 1.0
  // map this to 0 -> 8, and only change the current cell, when it changes
  @override
  int evaluate(Animation<double> animation) {
    int value = (9 * animation.value).round();

    if (lastUpdateTracker != value) {
      lastUpdateTracker = value;

      int newFirstElementIndex = 0;
      if (_path.length > 0) {
        newFirstElementIndex = _path.first;
      }
      switch (newFirstElementIndex) {
        case 0:
          newFirstElementIndex = 1;
          break;
        case 1:
          newFirstElementIndex = 2;
          break;
        case 2:
          newFirstElementIndex = 5;
          break;
        case 3:
          newFirstElementIndex = 6;
          break;
        case 4:
          newFirstElementIndex = 3;
          break;
        case 5:
          newFirstElementIndex = 4;
          break;
        case 6:
          newFirstElementIndex = 7;
          break;
        case 7:
          newFirstElementIndex = 8;
          break;
        case 8:
          newFirstElementIndex = 0;
          break;
      }

      _path.insert(0, newFirstElementIndex);
      // trim snake
      while (_path.length > 3) {
        _path.removeLast();
      }
    }
    return value;       // need to return something, but updates are done via the path
  }
}

2 个答案:

答案 0 :(得分:1)

每当您制作动画时,都应该在小部件的dispose()方法中清理它。

要清理动画,我们应该处理负责执行AnimationController animation

@override
  void dispose() {
    animationController?.dispose();
    super.dispose();
  }

答案 1 :(得分:1)

似乎是一个Android P错误。在模拟器中的Android O上不会出现。