Search code examples
dartkeyeventfluttertelevision

The method nextPage() and previousPage() in PageController doesn't work sometimes


Here is my code: I response key event in Android MainActivity, and use BasicMessageChannel to post key message:

public class MainActivity extends FlutterActivity {

private static final String CHANNEL = "scroll";
private static final String KEY_LEFT = "keyLeft";
private static final String KEY_RIGHT = "keyRight";
private BasicMessageChannel messageChannel;
private FlutterView flutterView;

private String[] getArgsFromIntent(Intent intent) {
    // Before adding more entries to this list, consider that arbitrary
    // Android applications can generate intents with extra data and that
    // there are many security-sensitive args in the binary.
    ArrayList<String> args = new ArrayList<String>();
    if (intent.getBooleanExtra("trace-startup", false)) {
        args.add("--trace-startup");
    }
    if (intent.getBooleanExtra("start-paused", false)) {
        args.add("--start-paused");
    }
    if (intent.getBooleanExtra("enable-dart-profiling", false)) {
        args.add("--enable-dart-profiling");
    }
    if (!args.isEmpty()) {
        String[] argsArray = new String[args.size()];
        return args.toArray(argsArray);
    }
    return null;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
    flutterView = new FlutterView(this);
    String[] args = getArgsFromIntent(getIntent());
    FlutterMain.ensureInitializationComplete(getApplicationContext(), args);
    flutterView.runFromBundle(FlutterMain.findAppBundlePath(getApplicationContext()), null);
    messageChannel = new BasicMessageChannel<>(flutterView, CHANNEL, StringCodec.INSTANCE);
}

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        Log.d(TAG, "dispatchKeyEvent: ACTION_DOWN keyCode = " + event.getKeyCode());
        switch (event.getKeyCode()) {
            case KeyEvent.KEYCODE_DPAD_LEFT:
                messageChannel.send(KEY_LEFT);
                return true;

            case KeyEvent.KEYCODE_DPAD_RIGHT:
                messageChannel.send(KEY_RIGHT);
                return true;

            default:
                break;
        }
    }
    return super.dispatchKeyEvent(event);
}

@Override
protected void onDestroy() {
    if (flutterView != null) {
        flutterView.destroy();
    }
    super.onDestroy();
}

@Override
protected void onPause() {
    super.onPause();
    flutterView.onPause();
}

@Override
protected void onPostResume() {
    super.onPostResume();
    flutterView.onPostResume();
}

}

When Flutter receives the message, I call nextPage() and previousPage() to scroll PageView, it doesn't work. But I find if I call nextPage() and previousPage() in onTap() method of the GestureDetector, it works:

class _Page {
  _Page({
    this.imagePath,
  });

  final String imagePath;
}

final List<_Page> _allPages = <_Page>[
  new _Page(imagePath: 'images/1.jpg'),
  new _Page(imagePath: 'images/2.jpg'),
  new _Page(imagePath: 'images/3.jpg'),
  new _Page(imagePath: 'images/4.jpg'),
  new _Page(imagePath: 'images/5.jpg'),
  new _Page(imagePath: 'images/6.jpg'),
];

class ScrollablePageDemo extends StatefulWidget {
  @override
  _ScrollablePageDemoState createState() => new _ScrollablePageDemoState();
}

class _ScrollablePageDemoState extends State<ScrollablePageDemo>
    with SingleTickerProviderStateMixin {
  PageController _controller;
  static const String _channel = 'scroll';
  static const String _emptyMessage = '';
  static const String KEY_LEFT = "keyLeft";
  static const String KEY_RIGHT = "keyRight";
  static const BasicMessageChannel<String> platform =
      const BasicMessageChannel<String>(_channel, const StringCodec());

  @override
  void initState() {
    super.initState();
    _controller = new PageController(
        initialPage: 0, keepPage: true, viewportFraction: 1.0);
    platform.setMessageHandler(changePage);
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }

  Future<String> changePage(String message) async {
    setState(() {
      print(message);
      if (message == KEY_RIGHT) {
        //here doesn't work
        _controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
        print("tab right: page = " + _controller.page.toString());
      } else if (message == KEY_LEFT) {
        //here doesn't work
        _controller.previousPage(duration: kTabScrollDuration, curve: Curves.ease);
        print("tab left: page = " + _controller.page.toString());
      }
    });
    return _emptyMessage;
  }

  PageView buildPageView() {
    return new PageView(
      controller: _controller,
      children: _allPages.map((_Page page) {
        return new Container(
            key: new ObjectKey(page.imagePath),
            padding: const EdgeInsets.all(12.0),
            child: new Card(
                child: new Image.asset(page.imagePath, fit: BoxFit.fill)));
      }).toList(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
        onTap: () {
          print('Listen PageView');
          //here works
          _controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
        },
        child: new Scaffold(
            appBar: new AppBar(
              title: new Text('PageView'),
            ),
            body: buildPageView()));
  }
}

void main() {
  runApp(new MaterialApp(
    title: 'Flutter Study',
    home: new ScrollablePageDemo(),
  ));
}

Solution

  • The problem is with setState method inside changePage. The setState invokes build method whenever the state set. Thus the page gets built again whenever setState called. You can just remove the setState from changePage method.

    example:

    Future<String> changePage(String message) async {
      print(message);
      if (message == KEY_RIGHT) {
        _controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
        print("tab right: page = " + _controller.page.toString());
      } else if (message == KEY_LEFT) {
        _controller.previousPage(duration: kTabScrollDuration, curve: Curves.ease);
        print("tab left: page = " + _controller.page.toString());
      }
      return _emptyMessage;
    }
    

    Hope that helped!