Flutter 滚动视图到列上的焦点小部件
Posted
技术标签:
【中文标题】Flutter 滚动视图到列上的焦点小部件【英文标题】:Flutter Scroll view to focused widget on a column 【发布时间】: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
【问题讨论】:
【参考方案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++;)
【讨论】:
以上是关于Flutter 滚动视图到列上的焦点小部件的主要内容,如果未能解决你的问题,请参考以下文章
Flutter ListView.Builder() 在可滚动列中与其他小部件