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

Flutter网格视图带动画产品快速预览功能开发需求

解决方案:GridView中产品预览面板的跨行列适配实现

问题根源

你之前的实现中,预览面板(AnimatedContainer)放在GridView外部,导致跨行列时,GridView的滚动复用和布局约束无法正确关联点击的产品项,从而出现失效问题。核心解决思路是将预览面板与每个GridView产品项绑定,并通过全局状态管理控制唯一展开的面板。

完整实现代码

import 'package:flutter/material.dart';

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

class ProductPreviewApp extends StatelessWidget {
  const ProductPreviewApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Product Quick Preview',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const ProductGridScreen(),
    );
  }
}

class ProductGridScreen extends StatefulWidget {
  const ProductGridScreen({super.key});

  @override
  State<ProductGridScreen> createState() => _ProductGridScreenState();
}

class _ProductGridScreenState extends State<ProductGridScreen> {
  String? _selectedProductId;
  final List<Product> _products = List.generate(
    20,
    (index) => Product(
      id: 'product_$index',
      name: 'Product $index',
      imageUrl: 'https://picsum.photos/200/200?random=$index',
      description: 'This is the detailed preview for Product $index. '
          'It includes key features, pricing, and more information.',
    ),
  );

  void _toggleProductPreview(String productId) {
    setState(() {
      _selectedProductId = _selectedProductId == productId ? null : productId;
    });
  }

  @override
  Widget build(BuildContext context) {
    final crossAxisCount = MediaQuery.of(context).size.width > 800 ? 4 : 2;
    return Scaffold(
      appBar: AppBar(title: const Text('Product Grid')),
      body: GridView.builder(
        padding: const EdgeInsets.all(16),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: crossAxisCount,
          crossAxisSpacing: 16,
          mainAxisSpacing: 16,
          childAspectRatio: 0.8,
        ),
        itemCount: _products.length,
        itemBuilder: (context, index) {
          final product = _products[index];
          return ProductItem(
            product: product,
            isSelected: _selectedProductId == product.id,
            onTap: () => _toggleProductPreview(product.id),
          );
        },
      ),
    );
  }
}

class Product {
  final String id;
  final String name;
  final String imageUrl;
  final String description;

  Product({
    required this.id,
    required this.name,
    required this.imageUrl,
    required this.description,
  });
}

class ProductItem extends StatefulWidget {
  final Product product;
  final bool isSelected;
  final VoidCallback onTap;

  const ProductItem({
    super.key,
    required this.product,
    required this.isSelected,
    required this.onTap,
  });

  @override
  State<ProductItem> createState() => _ProductItemState();
}

class _ProductItemState extends State<ProductItem> with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _expandAnimation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _expandAnimation = CurvedAnimation(parent: _animationController, curve: Curves.easeInOut);
    if (widget.isSelected) _animationController.forward();
  }

  @override
  void didUpdateWidget(covariant ProductItem oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isSelected != oldWidget.isSelected) {
      widget.isSelected ? _animationController.forward() : _animationController.reverse();
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: widget.onTap,
      child: Column(
        children: [
          Expanded(
            child: Card(
              elevation: 4,
              child: Column(
                children: [
                  Expanded(
                    child: Image.network(
                      widget.product.imageUrl,
                      fit: BoxFit.cover,
                      width: double.infinity,
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text(
                      widget.product.name,
                      style: const TextStyle(fontWeight: FontWeight.bold),
                      textAlign: TextAlign.center,
                    ),
                  ),
                ],
              ),
            ),
          ),
          SizeTransition(
            sizeFactor: _expandAnimation,
            axisAlignment: -1.0,
            child: Container(
              width: double.infinity,
              padding: const EdgeInsets.all(12),
              color: Colors.grey[100],
              child: Text(widget.product.description),
            ),
          ),
        ],
      ),
    );
  }
}

关键修复说明

  1. 状态管理优化:用_selectedProductId全局跟踪当前选中的产品,确保点击任何产品时,仅展开对应面板,其他自动关闭。
  2. 面板与项绑定:将预览面板嵌入每个GridView产品项内部,避免外部布局约束导致的跨行列失效问题。
  3. 动画适配:使用SizeTransition实现平滑的展开/收起动画,比AnimatedContainer更适配动态高度变化场景,同时通过AnimationController和didUpdateWidget处理item复用后的状态同步。
  4. 网页适配:通过MediaQuery根据屏幕宽度动态调整GridView列数,保证桌面端和移动端的显示效果。

内容的提问来源于stack exchange,提问作者Honey Bansal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:28:14