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

Flutter如何用Change Notifier Provider实现点击收藏图标添加影片到收藏页

ChangeNotifierProvider 修复方案

1. 实现MovieProvider状态类

新增movie_provider.dart文件,代码如下:

import 'package:flutter/foundation.dart';
import 'movie.dart'; // 导入你定义的Movie类的文件

class MovieProvider extends ChangeNotifier {
  // 存储收藏的电影列表
  final List<Movie> _favorites = [];

  // 对外暴露只读的收藏列表
  List<Movie> get favorites => List.unmodifiable(_favorites);

  // 切换电影收藏状态
  void toggleFavorite(Movie movie) {
    if (_favorites.any((element) => element.title == movie.title)) {
      // 已收藏则移除
      _favorites.removeWhere((element) => element.title == movie.title);
    } else {
      // 未收藏则添加
      _favorites.add(movie);
    }
    // 通知所有监听的页面刷新
    notifyListeners();
  }

  // 判断某部电影是否已收藏
  bool isFavorite(Movie movie) {
    return _favorites.any((element) => element.title == movie.title);
  }
}

注:这里用title作为唯一标识判断电影是否重复,你也可以给Movie类新增id字段用于判断,避免重名电影误判

2. 全局注册Provider

修改main.dart入口代码,将Provider注册到应用顶层,确保所有页面都能访问到状态:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'movie_provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => MovieProvider(),
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 你的原有应用配置
      title: '电影应用',
      home: const YourHomePage(), // 替换为你的首页
    );
  }
}

3. 修改详情页逻辑

修改MovieScreen中收藏图标的相关代码,绑定点击事件和状态:

// 先构造当前页面对应的Movie对象
Movie currentMovie = Movie(
  imgUrl: widget.photo,
  title: widget.title,
  categories: widget.categories,
  year: widget.year,
  country: widget.country,
  length: widget.length,
  description: widget.description,
  screenshots: widget.screenshots,
);

// 修改收藏IconButton代码
IconButton(
  padding: EdgeInsets.only(right: 20),
  onPressed: () {
    // 调用provider方法切换收藏状态,listen设为false避免不必要的重绘
    Provider.of<MovieProvider>(context, listen: false).toggleFavorite(currentMovie);
  },
  // 根据收藏状态切换图标
  icon: Icon(
    Provider.of<MovieProvider>(context).isFavorite(currentMovie) 
        ? Icons.favorite 
        : Icons.favorite_outline
  ),
  iconSize: 30,
  color: Colors.red,
),

4. 修改收藏页逻辑

将收藏页写死的卡片替换为遍历收藏列表动态生成:

class _MyListState extends State<MyList> {
  @override
  Widget build(BuildContext context) {  
    // 获取收藏列表
    final favoriteMovies = Provider.of<MovieProvider>(context).favorites;
    return Scaffold(
      body: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Navbar1(),
          Container(
            width: MediaQuery.of(context).size.width - 60,
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 10),
              child: ListView(
                children: <Widget>[
                  const SizedBox(height: 50),
                  HeadMenuMylist(),
                  const SizedBox(height: 20),
                  GridView.count(
                    shrinkWrap: true,
                    physics: const NeverScrollableScrollPhysics(),
                    mainAxisSpacing: 10,
                    crossAxisSpacing: 10,
                    crossAxisCount: 2,
                    childAspectRatio: 1 / 2,
                    // 遍历收藏列表生成卡片
                    children: favoriteMovies.map((movie) {
                      return Stack(
                        children: [
                          Positioned.fill(
                            child: Container(
                              height: 200,
                              foregroundDecoration: BoxDecoration(
                                borderRadius: BorderRadius.circular(10),
                                gradient: const LinearGradient(
                                  begin: Alignment.topCenter,
                                  end: Alignment.bottomCenter,
                                  colors: [Colors.transparent, Colors.black],
                                ),
                              ),
                              child: ClipRRect(
                                borderRadius: BorderRadius.circular(10),
                                child: Image.network(
                                  movie.imgUrl,
                                  loadingBuilder: (BuildContext context,
                                      Widget child,
                                      ImageChunkEvent? loadingProgress) {
                                    if (loadingProgress == null) return child;
                                    return Center(
                                      child: SizedBox(
                                        height: 50,
                                        width: 50,
                                        child: CircularProgressIndicator(
                                          strokeWidth: 4,
                                          color: Colors.red,
                                        ),
                                      ),
                                    );
                                  },
                                  fit: BoxFit.cover,
                                ),
                              ),
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.only(bottom: 15),
                            child: Align(
                              alignment: Alignment.bottomCenter,
                              child: Text(
                                movie.title,
                                style: GoogleFonts.openSans(
                                    color: Colors.white,
                                    fontSize: 18,
                                    fontWeight: FontWeight.w700),
                              ),
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.only(top: 10, right: 10),
                            child: Align(
                                alignment: Alignment.topRight,
                                child: GestureDetector(
                                  onTap: () {
                                    // 点击删除按钮移除收藏
                                    Provider.of<MovieProvider>(context, listen: false).toggleFavorite(movie);
                                  },
                                  child: const Icon(
                                    Icons.delete,
                                    color: Colors.white,
                                  ),
                                )),
                          ),
                        ],
                      );
                    }).toList(),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}
替代方案
  • GetX状态管理:代码更简洁,无需顶层注册,定义继承自GetxController的类存储收藏列表,通过Get.put()实例化,Get.find()全局调用即可,适合小型项目快速开发。
  • 本地持久化扩展:如果需要退出应用后收藏数据不丢失,可以搭配Hive或shared_preferences存储序列化后的Movie数据,应用启动时读取到Provider中即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 07:06:04