You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter开发Android TV应用时如何实现ListView条目聚焦效果

解决方案

你之前的方案出问题大概率是手动管理FocusNode的状态同步、滚动偏移计算没处理好,推荐用以下可落地的实现方案:


1. 封装通用可聚焦列表项组件

直接用Focus组件自带的状态回调,不需要额外维护全局FocusNode数组,代码示例:

import 'package:flutter/services.dart';

class FocusableListItem extends StatefulWidget {
  final Widget child;
  final VoidCallback onPressed;
  final double scaleOnFocus; // 聚焦时的缩放比例,默认1.1
  final Color borderColor; // 聚焦时的边框颜色

  const FocusableListItem({
    super.key,
    required this.child,
    required this.onPressed,
    this.scaleOnFocus = 1.1,
    this.borderColor = Colors.blueAccent,
  });

  @override
  State<FocusableListItem> createState() => _FocusableListItemState();
}

class _FocusableListItemState extends State<FocusableListItem> with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _scaleAnimation;
  bool _isFocused = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 200),
    );
    _scaleAnimation = Tween<double>(begin: 1.0, end: widget.scaleOnFocus).animate(
      CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Focus(
      onFocusChange: (focused) {
        setState(() {
          _isFocused = focused;
        });
        if (focused) {
          _animationController.forward();
          // 自动滚动到聚焦项,解决首尾项显示不全的问题
          Scrollable.ensureVisible(
            context,
            alignment: 0.5, // 居中显示
            duration: const Duration(milliseconds: 200),
          );
        } else {
          _animationController.reverse();
        }
      },
      onKeyEvent: (node, event) {
        // 处理TV遥控器确认键点击
        if (event.logicalKey == LogicalKeyboardKey.select || event.logicalKey == LogicalKeyboardKey.enter) {
          if (event is KeyDownEvent) {
            widget.onPressed();
          }
          return KeyEventResult.handled;
        }
        return KeyEventResult.ignored;
      },
      child: AnimatedBuilder(
        animation: _scaleAnimation,
        builder: (context, child) {
          return Transform.scale(
            scale: _scaleAnimation.value,
            child: Container(
              decoration: BoxDecoration(
                border: _isFocused ? Border.all(color: widget.borderColor, width: 2) : null,
                borderRadius: BorderRadius.circular(8),
                boxShadow: _isFocused
                    ? [
                        BoxShadow(
                          color: widget.borderColor.withOpacity(0.5),
                          blurRadius: 12,
                          spreadRadius: 2,
                        )
                      ]
                    : null,
              ),
              child: child,
            ),
          );
        },
        child: widget.child,
      ),
    );
  }
}

2. ListView使用示例

不需要额外配置FocusScope,直接把列表项包裹到上面的组件里即可,多ListView场景下也能正常切换焦点:

ListView.builder(
  scrollDirection: Axis.horizontal,
  itemCount: 20,
  itemBuilder: (context, index) {
    return Padding(
      padding: const EdgeInsets.all(12.0),
      child: FocusableListItem(
        onPressed: () {
          // 处理条目点击逻辑
          debugPrint("点击了第$index项");
        },
        child: Container(
          width: 180,
          height: 120,
          color: Colors.grey[800],
          child: Center(child: Text("条目$index", style: const TextStyle(color: Colors.white))),
        ),
      ),
    );
  },
)

3. 额外适配要点

  • 如果有多列网格类布局,可直接把上述组件套到GridView的子项中使用
  • 要禁止页面其他非可交互元素获取焦点,可在这些元素上包裹ExcludeFocus组件
  • 如果需要控制焦点默认落在哪一个条目上,可给对应Focus组件设置autofocus: true即可

内容的提问来源于stack exchange,提问作者Aviraj Patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 21:18:00