【问题标题】:Flutter Scroll view to focused widget on a columnFlutter 滚动视图到列上的焦点小部件
【发布时间】:2021-11-10 07:25:24
【问题描述】:

我正在为 Android TV 开发一个应用,并使用 DPAD 导航。 我在一列中有多个小部件。当我导航到视图之外的小部件时,小部件/视图不会移动以反映所选小部件。

// ignore_for_file: avoid_print

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: const MyStatelessWidget(),
      ),
    );
  }
}

class MyStatelessWidget extends StatelessWidget {
  const MyStatelessWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;
    return DefaultTextStyle(
      style: textTheme.headline4!,
      child: ChangeNotifierProvider<SampleNotifier>(
          create: (context) => SampleNotifier(), child: const CardHolder()),
    );
  }
}

class CardHolder extends StatefulWidget {
  const CardHolder({Key? key}) : super(key: key);

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

class _CardHolderState extends State<CardHolder> {
  late FocusNode _focusNode;
  late FocusAttachment _focusAttachment;

  @override
  void initState() {
    super.initState();
    _focusNode = FocusNode(debugLabel: "traversal_node");
    _focusAttachment = _focusNode.attach(context, onKey: _handleKeyPress);
    _focusNode.requestFocus();
  }

  @override
  Widget build(BuildContext context) {
    _focusAttachment.reparent();
    return Focus(
      focusNode: _focusNode,
      autofocus: true,
      onKey: _handleKeyPress,
      child: Consumer<SampleNotifier>(
        builder: (context, models, child) {
          int listSize = Provider.of<SampleNotifier>(context).listSize;
          return SingleChildScrollView(
            child: SampleRow(cat: "Test", models: models.modelList),
          );
        },
      ),
    );
  }

  KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
    if (event is RawKeyDownEvent) {
      print("t:FocusNode: ${node.debugLabel} event: ${event.logicalKey}");
      if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
        Provider.of<SampleNotifier>(context, listen: false).moveRight();
        return KeyEventResult.handled;
      } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
        Provider.of<SampleNotifier>(context, listen: false).moveLeft();
        return KeyEventResult.handled;
      }
    }
    // debugDumpFocusTree();
    return KeyEventResult.ignored;
  }
}

class SampleCard extends StatefulWidget {
  final int number;
  final SampleModel model;
  final bool focused;
  const SampleCard(
      {required this.number,
      required this.focused,
      required this.model,
      Key? key})
      : super(key: key);

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

class _SampleCardState extends State<SampleCard> {
  late Color _color;

  @override
  void initState() {
    super.initState();
    _color = Colors.red.shade900;
  }

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 10),
      child: widget.focused
          ? Container(
              width: 150,
              height: 300,
              color: Colors.white,
              child: Center(
                child: Text(
                  "${widget.model.text} ${widget.model.num}",
                  style: TextStyle(color: _color),
                ),
              ),
            )
          : Container(
              width: 150,
              height: 300,
              color: Colors.black,
              child: Center(
                child: Text(
                  "${widget.model.text} ${widget.model.num}",
                  style: TextStyle(color: _color),
                ),
              ),
            ),
    );
  }
}

class SampleRow extends StatelessWidget {
  final String cat;
  final List<SampleModel> models;

  SampleRow({Key? key, required this.cat, required this.models}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final int selectedIndex =
        Provider.of<SampleNotifier>(context).selectedIndex;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Padding(
          padding: EdgeInsets.only(left: 16, bottom: 8),
        ),
        models.isNotEmpty
            ? SizedBox(
                height: 200,
                child: ListView.custom(
                  padding: const EdgeInsets.all(8),
                  scrollDirection: Axis.horizontal,
                  childrenDelegate: SliverChildBuilderDelegate(
                    (context, index) => Padding(
                        padding: const EdgeInsets.symmetric(horizontal: 8),
                        child: SampleCard(
                          focused: index == selectedIndex,
                          model: models[index],
                          number: index,
                        ),
                      ),
                    childCount: models.length,
                    findChildIndexCallback: _findChildIndex,
                  ),
                ),
              )
            : SizedBox(
                height: 200,
                child: Container(
                  color: Colors.teal,
                ),
              )
      ],
    );
  }

  int _findChildIndex(Key key) => models.indexWhere((model) =>
      "$cat-${model.text}_${model.num}" == (key as ValueKey<String>).value);
}

class SampleNotifier extends ChangeNotifier {
  final List<SampleModel> _models = [
    SampleModel(0, "zero"),
    SampleModel(1, "one"),
    SampleModel(2, "two"),
    SampleModel(3, "three"),
    SampleModel(4, "four"),
    SampleModel(5, "five"),
    SampleModel(6, "six"),
    SampleModel(7, "seven"),
    SampleModel(8, "eight"),
    SampleModel(9, "nine"),
    SampleModel(10, "ten")
  ];

  int _selectedIndex = 0;

  List<SampleModel> get modelList => _models;

  int get selectedIndex => _selectedIndex;

  int get listSize => _models.length;

  void moveRight() {
    if (_selectedIndex < _models.length - 1) {
      _selectedIndex = _selectedIndex + 1;
    }
    notifyListeners();
  }

  void moveLeft() {
    if (_selectedIndex > 0) {
      _selectedIndex = _selectedIndex - 1;
    }
    notifyListeners();
  }
}

class SampleModel {
  int num;
  String text;

  SampleModel(this.num, this.text);
}

我需要一种将小部件移动/滚动到视图中的方法。有什么办法可以做到这一点,在 android tv 上使用 DPAD 导航

Here is the gist

【问题讨论】:

    标签: flutter dart android-tv


    【解决方案1】:

    您可以使用scrollable_positioned_list 包。

    这个小部件不是基于像素滚动的ListView.custom,而是基于索引:

    final ItemScrollController itemScrollController = ItemScrollController();
    
    ScrollablePositionedList.builder(
      itemCount: 500,
      itemBuilder: (context, index) => Text('Item $index'),
      itemScrollController: itemScrollController,
      itemPositionsListener: itemPositionsListener,
    );
    

    因此,您可以保持当前滚动位置的索引,并在 DPAD 上按下:

    itemScrollController.jumpTo(index: currentItem);
    setState((){currentItem++;})
    

    【讨论】:

      猜你喜欢
      • 2019-05-31
      • 1970-01-01
      • 2017-05-12
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 2018-08-15
      • 2019-04-26
      相关资源
      最近更新 更多