🚀运行效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Flutter框架跨平台鸿蒙开发——电影推荐APP的开发流程

前言

随着移动互联网的快速发展,跨平台开发成为了移动应用开发的重要趋势。Flutter作为Google推出的开源UI工具包,凭借其"一次编写,处处运行"的特性,成为了跨平台开发的热门选择。同时,鸿蒙系统作为华为自主研发的操作系统,也在快速崛起,为开发者提供了新的平台选择。

本文将详细介绍如何使用Flutter框架开发一款电影推荐及影评APP,并实现跨平台运行,特别是在鸿蒙系统上的适配。通过本文的学习,读者将了解Flutter框架的核心特性、跨平台开发的实践经验,以及如何构建一个功能完整的电影推荐APP。

应用介绍

应用概述

电影推荐及影评APP是一款为用户提供电影信息、推荐和影评功能的移动应用。用户可以通过该应用浏览最新、热门的电影,查看电影详情,提交和查看影评,以及管理个人收藏和观看历史。

应用特点

  • 丰富的电影信息:提供电影的详细信息,包括标题、海报、评分、简介、上映日期、类型等。
  • 个性化推荐:根据用户的浏览历史和收藏,推荐相关电影。
  • 互动影评:用户可以提交影评,查看其他用户的影评,分享观影感受。
  • 个人中心:管理用户个人信息、收藏的电影和观看历史。
  • 跨平台兼容:支持在Android、iOS、鸿蒙等多个平台运行。

技术栈

  • 前端框架:Flutter
  • 开发语言:Dart
  • 状态管理:Provider
  • 网络请求:http
  • 本地存储:shared_preferences
  • UI组件:Material Design

开发流程

1. 项目初始化与环境搭建

1.1 安装Flutter SDK

首先,需要在开发机器上安装Flutter SDK。可以从Flutter官网下载最新版本的Flutter SDK,并按照官方文档进行安装和配置。

1.2 创建Flutter项目

使用Flutter命令行工具创建一个新的Flutter项目:

flutter create movie_recommendation_app
cd movie_recommendation_app
1.3 配置依赖

pubspec.yaml文件中添加必要的依赖:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.1
  http: ^1.2.1
  shared_preferences: ^2.2.3
  intl: ^0.19.0

flutter:
  uses-material-design: true
  assets:
    - assets/images/

2. 项目结构设计

2.1 目录结构
lib/
  ├── models/          # 数据模型
  ├── services/        # 服务层
  ├── utils/           # 工具类
  ├── pages/           # 页面
  ├── components/      # 组件
  ├── main.dart        # 应用入口
assets/
  ├── images/          # 图片资源
2.2 数据模型设计

创建电影、影评和用户的数据模型:

/// 电影模型
class Movie {
  final int id;
  final String title;
  final String posterPath;
  final double voteAverage;
  final String overview;
  final String releaseDate;
  final List<String> genres;
  final int runtime;
  final int budget;
  final int revenue;
  final String originalLanguage;
  final String status;

  Movie({
    required this.id,
    required this.title,
    required this.posterPath,
    required this.voteAverage,
    required this.overview,
    required this.releaseDate,
    required this.genres,
    required this.runtime,
    required this.budget,
    required this.revenue,
    required this.originalLanguage,
    required this.status,
  });

  factory Movie.fromJson(Map<String, dynamic> json) {
    return Movie(
      id: json['id'] ?? 0,
      title: json['title'] ?? '',
      posterPath: json['poster_path'] ?? '',
      voteAverage: (json['vote_average'] ?? 0).toDouble(),
      overview: json['overview'] ?? '',
      releaseDate: json['release_date'] ?? '',
      genres: (json['genres'] as List<dynamic>?)?.map((e) => e['name'] as String).toList() ?? [],
      runtime: json['runtime'] ?? 0,
      budget: json['budget'] ?? 0,
      revenue: json['revenue'] ?? 0,
      originalLanguage: json['original_language'] ?? '',
      status: json['status'] ?? '',
    );
  }
}

/// 电影评论模型
class MovieReview {
  final int id;
  final String content;
  final String author;
  final double rating;
  final String createdAt;
  final int movieId;

  MovieReview({
    required this.id,
    required this.content,
    required this.author,
    required this.rating,
    required this.createdAt,
    required this.movieId,
  });

  factory MovieReview.fromJson(Map<String, dynamic> json) {
    return MovieReview(
      id: json['id'] ?? 0,
      content: json['content'] ?? '',
      author: json['author'] ?? '',
      rating: (json['rating'] ?? 0).toDouble(),
      createdAt: json['created_at'] ?? '',
      movieId: json['movie_id'] ?? 0,
    );
  }
}

/// 用户模型
class User {
  final int id;
  final String username;
  final String email;
  final String avatarPath;
  final String createdAt;

  User({
    required this.id,
    required this.username,
    required this.email,
    required this.avatarPath,
    required this.createdAt,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] ?? 0,
      username: json['username'] ?? '',
      email: json['email'] ?? '',
      avatarPath: json['avatar_path'] ?? '',
      createdAt: json['created_at'] ?? '',
    );
  }
}

3. 核心功能实现

3.1 电影服务

创建电影服务,用于处理电影相关的API请求和数据管理:

/// 电影服务
class MovieService {
  /// 获取推荐电影列表
  Future<List<Movie>> getRecommendedMovies({int page = 1, int limit = 20}) async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(milliseconds: 500));
    
    // 使用模拟数据
    final moviesData = MockData.movies;
    
    // 计算起始和结束索引
    final startIndex = (page - 1) * limit;
    final endIndex = startIndex + limit;
    
    // 截取数据
    final paginatedMovies = moviesData.sublist(
      startIndex,
      endIndex > moviesData.length ? moviesData.length : endIndex,
    );
    
    return paginatedMovies;
  }

  /// 获取电影详情
  Future<Movie?> getMovieDetails(int movieId) async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(milliseconds: 300));
    
    // 使用模拟数据
    final moviesData = MockData.movies;
    
    // 查找电影
    final movie = moviesData.firstWhere(
      (movie) => movie.id == movieId,
      orElse: () => Movie(
        id: 0,
        title: '',
        posterPath: '',
        voteAverage: 0,
        overview: '',
        releaseDate: '',
        genres: [],
        runtime: 0,
        budget: 0,
        revenue: 0,
        originalLanguage: '',
        status: '',
      ),
    );
    
    return movie.id > 0 ? movie : null;
  }

  /// 获取电影评论
  Future<List<MovieReview>> getMovieReviews(int movieId, {int page = 1, int limit = 20}) async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(milliseconds: 300));
    
    // 使用模拟数据
    final reviewsData = MockData.reviews;
    
    // 筛选电影评论
    final movieReviews = reviewsData.where(
      (review) => review.movieId == movieId,
    ).toList();
    
    // 计算起始和结束索引
    final startIndex = (page - 1) * limit;
    final endIndex = startIndex + limit;
    
    // 截取数据
    final paginatedReviews = movieReviews.sublist(
      startIndex,
      endIndex > movieReviews.length ? movieReviews.length : endIndex,
    );
    
    return paginatedReviews;
  }

  /// 提交电影评论
  Future<MovieReview> submitMovieReview(int movieId, String content, double rating, String author) async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(milliseconds: 500));
    
    // 创建新评论
    final newReview = MovieReview(
      id: DateTime.now().millisecondsSinceEpoch,
      content: content,
      author: author,
      rating: rating,
      createdAt: DateTime.now().toIso8601String(),
      movieId: movieId,
    );
    
    // 模拟添加到数据库
    MockData.reviews.add(newReview);
    
    return newReview;
  }

  /// 获取电影类型列表
  Future<List<String>> getMovieGenres() async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(milliseconds: 200));
    
    // 使用模拟数据
    return MockData.genres;
  }

  /// 获取热门电影
  Future<List<Movie>> getPopularMovies({int page = 1, int limit = 20}) async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(milliseconds: 400));
    
    // 使用模拟数据
    final moviesData = MockData.movies;
    
    // 按评分排序
    final sortedMovies = [...moviesData]
      ..sort((a, b) => b.voteAverage.compareTo(a.voteAverage));
    
    // 计算起始和结束索引
    final startIndex = (page - 1) * limit;
    final endIndex = startIndex + limit;
    
    // 截取数据
    final paginatedMovies = sortedMovies.sublist(
      startIndex,
      endIndex > sortedMovies.length ? sortedMovies.length : endIndex,
    );
    
    return paginatedMovies;
  }

  /// 获取最新电影
  Future<List<Movie>> getLatestMovies({int page = 1, int limit = 20}) async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(milliseconds: 400));
    
    // 使用模拟数据
    final moviesData = MockData.movies;
    
    // 按上映日期排序
    final sortedMovies = [...moviesData]
      ..sort((a, b) => b.releaseDate.compareTo(a.releaseDate));
    
    // 计算起始和结束索引
    final startIndex = (page - 1) * limit;
    final endIndex = startIndex + limit;
    
    // 截取数据
    final paginatedMovies = sortedMovies.sublist(
      startIndex,
      endIndex > sortedMovies.length ? sortedMovies.length : endIndex,
    );
    
    return paginatedMovies;
  }
}

/// 电影服务单例
final movieService = MovieService();
3.2 电影列表页面

实现电影列表页面,用于显示推荐电影、热门电影和最新电影:

/// 电影列表页面
class MovieListPage extends StatefulWidget {
  /// 构造函数
  const MovieListPage({super.key});

  
  State<MovieListPage> createState() => _MovieListPageState();
}

/// 电影列表页面状态类
class _MovieListPageState extends State<MovieListPage> {
  /// 推荐电影列表
  List<Movie> _recommendedMovies = [];
  
  /// 热门电影列表
  List<Movie> _popularMovies = [];
  
  /// 最新电影列表
  List<Movie> _latestMovies = [];
  
  /// 电影类型列表
  List<String> _genres = [];
  
  /// 当前选中的类型
  String? _selectedGenre;
  
  /// 加载状态
  bool _isLoading = true;
  
  /// 错误信息
  String? _errorMessage;

  
  void initState() {
    super.initState();
    _loadMovies();
  }

  /// 加载电影数据
  Future<void> _loadMovies() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      // 并行加载数据
      final results = await Future.wait([
        movieService.getRecommendedMovies(),
        movieService.getPopularMovies(),
        movieService.getLatestMovies(),
        movieService.getMovieGenres(),
      ]);

      setState(() {
        _recommendedMovies = results[0] as List<Movie>;
        _popularMovies = results[1] as List<Movie>;
        _latestMovies = results[2] as List<Movie>;
        _genres = results[3] as List<String>;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _errorMessage = '加载电影数据失败,请重试';
        _isLoading = false;
      });
    }
  }

  /// 处理类型选择
  void _handleGenreSelect(String genre) {
    setState(() {
      _selectedGenre = _selectedGenre == genre ? null : genre;
    });
  }

  /// 导航到电影详情页面
  void _navigateToMovieDetail(int movieId) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => MovieDetailPage(movieId: movieId),
      ),
    );
  }

  /// 构建电影卡片
  Widget _buildMovieCard(Movie movie) {
    return SizedBox(
      width: 140,
      height: 224,
      child: GestureDetector(
        onTap: () => _navigateToMovieDetail(movie.id),
        child: Container(
          padding: const EdgeInsets.all(0),
          margin: const EdgeInsets.all(0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 电影海报
              SizedBox(
                width: 140,
                height: 190,
                child: Container(
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(4),
                    image: DecorationImage(
                      image: AssetImage(movie.posterPath),
                      fit: BoxFit.cover,
                    ),
                  ),
                ),
              ),
              // 电影标题
              SizedBox(
                width: 140,
                height: 16,
                child: Text(
                  movie.title,
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: const TextStyle(
                    fontSize: 12,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ),
              // 电影评分
              SizedBox(
                width: 140,
                height: 16,
                child: Row(
                  children: [
                    const Icon(
                      Icons.star,
                      size: 10,
                      color: Colors.amber,
                    ),
                    const SizedBox(width: 2),
                    Text(
                      '${movie.voteAverage}',
                      style: const TextStyle(
                        fontSize: 9,
                        color: Colors.grey,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  /// 构建电影列表
  Widget _buildMovieList(String title, List<Movie> movies) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Text(
            title,
            style: const TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        SizedBox(
          height: 224,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            padding: const EdgeInsets.symmetric(horizontal: 4),
            itemCount: movies.length,
            itemBuilder: (context, index) {
              return _buildMovieCard(movies[index]);
            },
          ),
        ),
      ],
    );
  }

  /// 构建类型选择器
  Widget _buildGenreSelector() {
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Row(
        children: _genres.map((genre) {
          final isSelected = _selectedGenre == genre;
          return GestureDetector(
            onTap: () => _handleGenreSelect(genre),
            child: Container(
              margin: const EdgeInsets.symmetric(horizontal: 4),
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(20),
                color: isSelected ? Colors.blue : Colors.grey[200],
              ),
              child: Text(
                genre,
                style: TextStyle(
                  color: isSelected ? Colors.white : Colors.black,
                  fontSize: 14,
                ),
              ),
            ),
          );
        }).toList(),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('电影推荐'),
        centerTitle: true,
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _errorMessage != null
              ? Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(_errorMessage!),
                      ElevatedButton(
                        onPressed: _loadMovies,
                        child: const Text('重试'),
                      ),
                    ],
                  ),
                )
              : RefreshIndicator(
                  onRefresh: _loadMovies,
                  child: ListView(
                    children: [
                      // 类型选择器
                      _buildGenreSelector(),
                      const SizedBox(height: 16),
                      // 推荐电影
                      _buildMovieList('推荐电影', _recommendedMovies),
                      const SizedBox(height: 16),
                      // 热门电影
                      _buildMovieList('热门电影', _popularMovies),
                      const SizedBox(height: 16),
                      // 最新电影
                      _buildMovieList('最新电影', _latestMovies),
                      const SizedBox(height: 32),
                    ],
                  ),
                ),
    );
  }
}
3.3 电影详情页面

实现电影详情页面,用于显示电影的详细信息和评论:

/// 电影详情页面
class MovieDetailPage extends StatefulWidget {
  /// 电影ID
  final int movieId;

  /// 构造函数
  const MovieDetailPage({super.key, required this.movieId});

  
  State<MovieDetailPage> createState() => _MovieDetailPageState();
}

/// 电影详情页面状态类
class _MovieDetailPageState extends State<MovieDetailPage> {
  /// 电影信息
  Movie? _movie;
  
  /// 电影评论列表
  List<MovieReview> _reviews = [];
  
  /// 加载状态
  bool _isLoading = true;
  
  /// 错误信息
  String? _errorMessage;
  
  /// 评论加载状态
  bool _isLoadingReviews = false;
  
  /// 评论错误信息
  String? _reviewErrorMessage;
  
  /// 评论内容控制器
  final TextEditingController _reviewController = TextEditingController();
  
  /// 评分控制器
  double _rating = 5.0;
  
  /// 评论提交状态
  bool _isSubmittingReview = false;

  
  void initState() {
    super.initState();
    _loadMovieDetails();
    _loadMovieReviews();
  }

  
  void dispose() {
    _reviewController.dispose();
    super.dispose();
  }

  /// 加载电影详情
  Future<void> _loadMovieDetails() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      final movie = await movieService.getMovieDetails(widget.movieId);
      if (movie == null) {
        setState(() {
          _errorMessage = '电影不存在';
          _isLoading = false;
        });
        return;
      }

      setState(() {
        _movie = movie;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _errorMessage = '加载电影详情失败,请重试';
        _isLoading = false;
      });
    }
  }

  /// 加载电影评论
  Future<void> _loadMovieReviews() async {
    setState(() {
      _isLoadingReviews = true;
      _reviewErrorMessage = null;
    });

    try {
      final reviews = await movieService.getMovieReviews(widget.movieId);
      setState(() {
        _reviews = reviews;
        _isLoadingReviews = false;
      });
    } catch (e) {
      setState(() {
        _reviewErrorMessage = '加载评论失败,请重试';
        _isLoadingReviews = false;
      });
    }
  }

  /// 提交评论
  Future<void> _submitReview() async {
    if (_reviewController.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请输入评论内容')),
      );
      return;
    }

    setState(() {
      _isSubmittingReview = true;
    });

    try {
      await movieService.submitMovieReview(
        widget.movieId,
        _reviewController.text,
        _rating,
        '用户', // 这里可以替换为实际的用户名
      );

      // 重新加载评论
      await _loadMovieReviews();

      // 清空输入
      _reviewController.clear();
      _rating = 5.0;

      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('评论提交成功')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('评论提交失败,请重试')),
      );
    } finally {
      setState(() {
        _isSubmittingReview = false;
      });
    }
  }

  /// 构建电影信息部分
  Widget _buildMovieInfo() {
    if (_movie == null) return const SizedBox();

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 电影海报和基本信息
        Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 电影海报
            Container(
              width: 120,
              height: 180,
              margin: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(8),
                image: DecorationImage(
                  image: AssetImage(_movie!.posterPath),
                  fit: BoxFit.cover,
                ),
              ),
            ),
            // 基本信息
            Expanded(
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // 电影标题
                    Text(
                      _movie!.title,
                      style: const TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 8),
                    // 评分
                    Row(
                      children: [
                        const Icon(
                          Icons.star,
                          size: 16,
                          color: Colors.amber,
                        ),
                        const SizedBox(width: 4),
                        Text(
                          '${_movie!.voteAverage}',
                          style: const TextStyle(
                            fontSize: 14,
                            fontWeight: FontWeight.w500,
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    // 上映日期
                    Row(
                      children: [
                        const Icon(
                          Icons.calendar_today,
                          size: 14,
                          color: Colors.grey,
                        ),
                        const SizedBox(width: 4),
                        Text(
                          _movie!.releaseDate,
                          style: const TextStyle(
                            fontSize: 14,
                            color: Colors.grey,
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    // 电影类型
                    Wrap(
                      spacing: 8,
                      runSpacing: 4,
                      children: _movie!.genres.map((genre) {
                        return Container(
                          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(4),
                            color: Colors.grey[200],
                          ),
                          child: Text(
                            genre,
                            style: TextStyle(
                              fontSize: 12,
                              color: Colors.grey[700],
                            ),
                          ),
                        );
                      }).toList(),
                    ),
                    const SizedBox(height: 8),
                    // 电影时长
                    Row(
                      children: [
                        const Icon(
                          Icons.access_time,
                          size: 14,
                          color: Colors.grey,
                        ),
                        const SizedBox(width: 4),
                        Text(
                          '${_movie!.runtime} 分钟',
                          style: const TextStyle(
                            fontSize: 14,
                            color: Colors.grey,
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
        const SizedBox(height: 16),
        // 电影简介
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                '简介',
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 8),
              Text(
                _movie!.overview,
                style: const TextStyle(
                  fontSize: 14,
                  height: 1.5,
                ),
              ),
            ],
          ),
        ),
        const SizedBox(height: 24),
        // 电影详情
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                '电影详情',
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 8),
              Table(
                columnWidths: const {
                  0: FlexColumnWidth(1),
                  1: FlexColumnWidth(2),
                },
                children: [
                  TableRow(
                    children: [
                      const Text('语言', style: TextStyle(color: Colors.grey)),
                      Text(_movie!.originalLanguage),
                    ],
                  ),
                  TableRow(
                    children: [
                      const Text('状态', style: TextStyle(color: Colors.grey)),
                      Text(_movie!.status),
                    ],
                  ),
                  TableRow(
                    children: [
                      const Text('预算', style: TextStyle(color: Colors.grey)),
                      Text('\$${(_movie!.budget / 1000000).toStringAsFixed(1)}M'),
                    ],
                  ),
                  TableRow(
                    children: [
                      const Text('票房', style: TextStyle(color: Colors.grey)),
                      Text('\$${(_movie!.revenue / 1000000).toStringAsFixed(1)}M'),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ),
        const SizedBox(height: 24),
      ],
    );
  }

  /// 构建评论部分
  Widget _buildReviews() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 评论标题
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Text(
            '评论',
            style: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        const SizedBox(height: 16),
        // 评论输入框
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Card(
            elevation: 2,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text('写下你的评论'),
                  const SizedBox(height: 8),
                  // 评分组件
                  Row(
                    children: [
                      const Text('评分:'),
                      Expanded(
                        child: Slider(
                          value: _rating,
                          min: 1.0,
                          max: 5.0,
                          divisions: 4,
                          label: _rating.toString(),
                          onChanged: (value) {
                            setState(() {
                              _rating = value;
                            });
                          },
                        ),
                      ),
                      Text('${_rating.toStringAsFixed(1)}'),
                    ],
                  ),
                  const SizedBox(height: 8),
                  // 评论输入框
                  TextField(
                    controller: _reviewController,
                    maxLines: 3,
                    decoration: const InputDecoration(
                      hintText: '请输入评论内容',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  // 提交按钮
                  Align(
                    alignment: Alignment.centerRight,
                    child: ElevatedButton(
                      onPressed: _isSubmittingReview ? null : _submitReview,
                      child: _isSubmittingReview
                          ? const SizedBox(
                              width: 20,
                              height: 20,
                              child: CircularProgressIndicator(
                                strokeWidth: 2,
                                valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
                              ),
                            )
                          : const Text('提交评论'),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
        const SizedBox(height: 16),
        // 评论列表
        _isLoadingReviews
            ? const Center(child: CircularProgressIndicator())
            : _reviewErrorMessage != null
                ? Center(
                    child: Column(
                      children: [
                        Text(_reviewErrorMessage!),
                        ElevatedButton(
                          onPressed: _loadMovieReviews,
                          child: const Text('重试'),
                        ),
                      ],
                    ),
                  )
                : _reviews.isEmpty
                    ? const Center(
                        child: Padding(
                          padding: EdgeInsets.all(32),
                          child: Text('暂无评论,快来写下第一条评论吧!'),
                        ),
                      )
                    : ListView.builder(
                        shrinkWrap: true,
                        physics: const NeverScrollableScrollPhysics(),
                        itemCount: _reviews.length,
                        itemBuilder: (context, index) {
                          final review = _reviews[index];
                          return Card(
                            margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                            child: Padding(
                              padding: const EdgeInsets.all(16),
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Row(
                                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                    children: [
                                      Text(
                                        review.author,
                                        style: const TextStyle(
                                          fontWeight: FontWeight.w500,
                                        ),
                                      ),
                                      Row(
                                        children: [
                                          const Icon(
                                            Icons.star,
                                            size: 14,
                                            color: Colors.amber,
                                          ),
                                          const SizedBox(width: 4),
                                          Text('${review.rating}'),
                                        ],
                                      ),
                                    ],
                                  ),
                                  const SizedBox(height: 8),
                                  Text(review.content),
                                  const SizedBox(height: 8),
                                  Text(
                                    review.createdAt.substring(0, 10),
                                    style: const TextStyle(
                                      fontSize: 12,
                                      color: Colors.grey,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          );
                        },
                      ),
        const SizedBox(height: 32),
      ],
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('电影详情'),
        centerTitle: true,
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _errorMessage != null
              ? Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(_errorMessage!),
                      ElevatedButton(
                        onPressed: _loadMovieDetails,
                        child: const Text('重试'),
                      ),
                    ],
                  ),
                )
              : RefreshIndicator(
                  onRefresh: () async {
                    await Future.wait([
                      _loadMovieDetails(),
                      _loadMovieReviews(),
                    ]);
                  },
                  child: ListView(
                    children: [
                      _buildMovieInfo(),
                      _buildReviews(),
                    ],
                  ),
                ),
    );
  }
}
3.4 个人中心页面

实现个人中心页面,用于显示用户信息和管理个人收藏、观看历史:

/// 个人中心页面
class MovieProfilePage extends StatefulWidget {
  /// 构造函数
  const MovieProfilePage({super.key});

  
  State<MovieProfilePage> createState() => _MovieProfilePageState();
}

/// 个人中心页面状态类
class _MovieProfilePageState extends State<MovieProfilePage> {
  /// 用户信息
  final User _user = User(
    id: 1,
    username: '电影爱好者',
    email: 'movie@example.com',
    avatarPath: 'assets/images/avatar.jpg',
    createdAt: '2023-01-01',
  );

  /// 模拟收藏的电影
  final List<Movie> _favoriteMovies = [
    Movie(
      id: 1,
      title: '复仇者联盟4:终局之战',
      posterPath: 'assets/images/avengers_endgame.jpg',
      voteAverage: 8.4,
      overview: '在《复仇者联盟3:无限战争》的毁灭性事件之后,宇宙由于灭霸的行动而变得满目疮痍。',
      releaseDate: '2019-04-24',
      genres: ['动作', '冒险', '科幻'],
      runtime: 181,
      budget: 356000000,
      revenue: 2797800564,
      originalLanguage: '英语',
      status: '已上映',
    ),
    Movie(
      id: 10,
      title: '星际穿越',
      posterPath: 'assets/images/interstellar.jpg',
      voteAverage: 9.3,
      overview: '在不远的未来,地球上的资源几乎耗尽,人类面临着灭绝的危险。',
      releaseDate: '2014-11-07',
      genres: ['科幻', '冒险', '剧情'],
      runtime: 169,
      budget: 1650000000,
      revenue: 7000000000,
      originalLanguage: '英语',
      status: '已上映',
    ),
  ];

  /// 模拟观看历史
  final List<Movie> _watchHistory = [
    Movie(
      id: 2,
      title: '流浪地球2',
      posterPath: 'assets/images/wandering_earth_2.jpg',
      voteAverage: 8.3,
      overview: '太阳即将膨胀为红巨星,为了生存,人类在地球上建造了上万台巨型发动机。',
      releaseDate: '2023-01-22',
      genres: ['科幻', '冒险', '灾难'],
      runtime: 173,
      budget: 600000000,
      revenue: 4020000000,
      originalLanguage: '中文',
      status: '已上映',
    ),
    Movie(
      id: 3,
      title: '独行月球',
      posterPath: 'assets/images/moon_man.jpg',
      voteAverage: 7.4,
      overview: '月球探测项目失败,维修工独孤月被遗留在月球上,成为"宇宙最后的人类"。',
      releaseDate: '2022-07-29',
      genres: ['喜剧', '科幻', '冒险'],
      runtime: 122,
      budget: 300000000,
      revenue: 3100000000,
      originalLanguage: '中文',
      status: '已上映',
    ),
  ];

  /// 构建用户信息卡片
  Widget _buildUserInfoCard() {
    return Card(
      margin: const EdgeInsets.all(16),
      elevation: 2,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            // 用户头像
            Container(
              width: 80,
              height: 80,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(40),
                image: DecorationImage(
                  image: AssetImage(_user.avatarPath),
                  fit: BoxFit.cover,
                ),
              ),
            ),
            const SizedBox(width: 16),
            // 用户信息
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    _user.username,
                    style: const TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    _user.email,
                    style: const TextStyle(
                      fontSize: 14,
                      color: Colors.grey,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '注册时间:${_user.createdAt}',
                    style: const TextStyle(
                      fontSize: 12,
                      color: Colors.grey,
                    ),
                  ),
                ],
              ),
            ),
            // 编辑按钮
            IconButton(
              onPressed: () {
                // 编辑个人信息
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('编辑个人信息功能开发中')),
                );
              },
              icon: const Icon(Icons.edit),
            ),
          ],
        ),
      ),
    );
  }

  /// 构建功能菜单
  Widget _buildFeatureMenu() {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      elevation: 2,
      child: Column(
        children: [
          _buildMenuItem(
            icon: Icons.favorite,
            title: '我的收藏',
            onTap: () {
              // 跳转到收藏页面
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('收藏页面功能开发中')),
              );
            },
          ),
          const Divider(height: 1),
          _buildMenuItem(
            icon: Icons.history,
            title: '观看历史',
            onTap: () {
              // 跳转到观看历史页面
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('观看历史页面功能开发中')),
              );
            },
          ),
          const Divider(height: 1),
          _buildMenuItem(
            icon: Icons.settings,
            title: '设置',
            onTap: () {
              // 跳转到设置页面
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('设置页面功能开发中')),
              );
            },
          ),
          const Divider(height: 1),
          _buildMenuItem(
            icon: Icons.help,
            title: '帮助与反馈',
            onTap: () {
              // 跳转到帮助与反馈页面
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('帮助与反馈页面功能开发中')),
              );
            },
          ),
          const Divider(height: 1),
          _buildMenuItem(
            icon: Icons.info,
            title: '关于',
            onTap: () {
              // 跳转到关于页面
              showDialog(
                context: context,
                builder: (context) {
                  return AlertDialog(
                    title: const Text('关于'),
                    content: const Text('电影推荐及影评APP v1.0.0\n\n一个基于Flutter框架开发的跨平台电影推荐及影评应用。'),
                    actions: [
                      TextButton(
                        onPressed: () {
                          Navigator.pop(context);
                        },
                        child: const Text('确定'),
                      ),
                    ],
                  );
                },
              );
            },
          ),
        ],
      ),
    );
  }

  /// 构建菜单项
  Widget _buildMenuItem({
    required IconData icon,
    required String title,
    required VoidCallback onTap,
  }) {
    return ListTile(
      leading: Icon(icon),
      title: Text(title),
      trailing: const Icon(Icons.chevron_right),
      onTap: onTap,
    );
  }

  /// 构建收藏和历史电影列表
  Widget _buildMovieList(String title, List<Movie> movies) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
          child: Text(
            title,
            style: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        SizedBox(
          height: 140,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            padding: const EdgeInsets.symmetric(horizontal: 16),
            itemCount: movies.length,
            itemBuilder: (context, index) {
              final movie = movies[index];
              return Container(
                margin: const EdgeInsets.only(right: 12),
                width: 100,
                child: Column(
                  children: [
                    Container(
                      height: 140,
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(8),
                        image: DecorationImage(
                          image: AssetImage(movie.posterPath),
                          fit: BoxFit.cover,
                        ),
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ],
    );
  }

  /// 构建退出登录按钮
  Widget _buildLogoutButton() {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: ElevatedButton(
        onPressed: () {
          // 退出登录
          showDialog(
            context: context,
            builder: (context) {
              return AlertDialog(
                title: const Text('退出登录'),
                content: const Text('确定要退出登录吗?'),
                actions: [
                  TextButton(
                    onPressed: () {
                      Navigator.pop(context);
                    },
                    child: const Text('取消'),
                  ),
                  TextButton(
                    onPressed: () {
                      Navigator.pop(context);
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(content: Text('退出登录成功')),
                      );
                    },
                    child: const Text('确定'),
                  ),
                ],
              );
            },
          );
        },
        style: ElevatedButton.styleFrom(
          backgroundColor: Colors.red,
          minimumSize: const Size(double.infinity, 48),
        ),
        child: const Text('退出登录'),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('个人中心'),
        centerTitle: true,
      ),
      body: ListView(
        children: [
          // 用户信息卡片
          _buildUserInfoCard(),
          // 功能菜单
          _buildFeatureMenu(),
          // 收藏的电影
          _buildMovieList('我的收藏', _favoriteMovies),
          // 观看历史
          _buildMovieList('观看历史', _watchHistory),
          // 退出登录按钮
          _buildLogoutButton(),
          const SizedBox(height: 32),
        ],
      ),
    );
  }
}
3.5 主页面

实现应用的主页面,用于整合电影列表和个人中心页面:

/// 电影推荐及影评APP主页面
class MovieMainPage extends StatefulWidget {
  /// 构造函数
  const MovieMainPage({super.key});

  
  State<MovieMainPage> createState() => _MovieMainPageState();
}

/// 电影主页面状态类
class _MovieMainPageState extends State<MovieMainPage> {
  /// 当前选中的页面索引
  int _currentIndex = 0;

  /// 页面控制器
  final PageController _pageController = PageController();

  
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  /// 处理底部导航栏点击
  void _onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
    _pageController.jumpToPage(index);
  }

  /// 处理页面变化
  void _onPageChanged(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController,
        onPageChanged: _onPageChanged,
        children: const [
          // 电影列表页面
          MovieListPage(),
          // 个人中心页面
          MovieProfilePage(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: _onTabTapped,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.movie),
            label: '电影',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.grey,
        type: BottomNavigationBarType.fixed,
      ),
    );
  }
}
3.6 应用入口

配置应用的入口文件,设置应用的主题和路由:

/// 应用入口文件
import 'package:flutter/material.dart';
import 'package:flutter_shili/pages/movie_main_page.dart';

void main() {
  runApp(const MyApp());
}

/// 应用主类
class MyApp extends StatelessWidget {
  /// 构造函数
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '电影推荐及影评',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MovieMainPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

4. 核心功能实现流程图

4.1 电影列表加载流程

用户打开应用

MovieListPage初始化

调用_loadMovies方法

并行加载推荐、热门、最新电影和类型列表

加载成功?

更新状态,显示电影列表

显示错误信息

用户浏览电影列表

用户点击电影卡片

导航到MovieDetailPage

4.2 电影详情加载流程

用户点击电影卡片

MovieDetailPage初始化

调用_loadMovieDetails方法

加载电影详情

加载成功?

更新状态,显示电影详情

显示错误信息

调用_loadMovieReviews方法

加载电影评论

加载成功?

更新状态,显示电影评论

显示评论错误信息

用户浏览评论

用户提交评论

调用_submitReview方法

提交评论到服务端

提交成功?

显示提交成功提示,重新加载评论

显示提交失败提示

5. 核心功能实现代码展示

5.1 电影服务核心方法
/// 获取推荐电影列表
Future<List<Movie>> getRecommendedMovies({int page = 1, int limit = 20}) async {
  // 模拟网络请求延迟
  await Future.delayed(const Duration(milliseconds: 500));
  
  // 使用模拟数据
  final moviesData = MockData.movies;
  
  // 计算起始和结束索引
  final startIndex = (page - 1) * limit;
  final endIndex = startIndex + limit;
  
  // 截取数据
  final paginatedMovies = moviesData.sublist(
    startIndex,
    endIndex > moviesData.length ? moviesData.length : endIndex,
  );
  
  return paginatedMovies;
}

/// 获取电影详情
Future<Movie?> getMovieDetails(int movieId) async {
  // 模拟网络请求延迟
  await Future.delayed(const Duration(milliseconds: 300));
  
  // 使用模拟数据
  final moviesData = MockData.movies;
  
  // 查找电影
  final movie = moviesData.firstWhere(
    (movie) => movie.id == movieId,
    orElse: () => Movie(
      id: 0,
      title: '',
      posterPath: '',
      voteAverage: 0,
      overview: '',
      releaseDate: '',
      genres: [],
      runtime: 0,
      budget: 0,
      revenue: 0,
      originalLanguage: '',
      status: '',
    ),
  );
  
  return movie.id > 0 ? movie : null;
}

/// 提交电影评论
Future<MovieReview> submitMovieReview(int movieId, String content, double rating, String author) async {
  // 模拟网络请求延迟
  await Future.delayed(const Duration(milliseconds: 500));
  
  // 创建新评论
  final newReview = MovieReview(
    id: DateTime.now().millisecondsSinceEpoch,
    content: content,
    author: author,
    rating: rating,
    createdAt: DateTime.now().toIso8601String(),
    movieId: movieId,
  );
  
  // 模拟添加到数据库
  MockData.reviews.add(newReview);
  
  return newReview;
}
5.2 电影列表页面核心方法
/// 加载电影数据
Future<void> _loadMovies() async {
  setState(() {
    _isLoading = true;
    _errorMessage = null;
  });

  try {
    // 并行加载数据
    final results = await Future.wait([
      movieService.getRecommendedMovies(),
      movieService.getPopularMovies(),
      movieService.getLatestMovies(),
      movieService.getMovieGenres(),
    ]);

    setState(() {
      _recommendedMovies = results[0] as List<Movie>;
      _popularMovies = results[1] as List<Movie>;
      _latestMovies = results[2] as List<Movie>;
      _genres = results[3] as List<String>;
      _isLoading = false;
    });
  } catch (e) {
    setState(() {
      _errorMessage = '加载电影数据失败,请重试';
      _isLoading = false;
    });
  }
}

/// 构建电影卡片
Widget _buildMovieCard(Movie movie) {
  return SizedBox(
    width: 140,
    height: 224,
    child: GestureDetector(
      onTap: () => _navigateToMovieDetail(movie.id),
      child: Container(
        padding: const EdgeInsets.all(0),
        margin: const EdgeInsets.all(0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 电影海报
            SizedBox(
              width: 140,
              height: 190,
              child: Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(4),
                  image: DecorationImage(
                    image: AssetImage(movie.posterPath),
                    fit: BoxFit.cover,
                  ),
                ),
              ),
            ),
            // 电影标题
            SizedBox(
              width: 140,
              height: 16,
              child: Text(
                movie.title,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: const TextStyle(
                  fontSize: 12,
                  fontWeight: FontWeight.w500,
                ),
              ),
            ),
            // 电影评分
            SizedBox(
              width: 140,
              height: 16,
              child: Row(
                children: [
                  const Icon(
                    Icons.star,
                    size: 10,
                    color: Colors.amber,
                  ),
                  const SizedBox(width: 2),
                  Text(
                    '${movie.voteAverage}',
                    style: const TextStyle(
                      fontSize: 9,
                      color: Colors.grey,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    ),
  );
}
5.3 电影详情页面核心方法
/// 加载电影详情
Future<void> _loadMovieDetails() async {
  setState(() {
    _isLoading = true;
    _errorMessage = null;
  });

  try {
    final movie = await movieService.getMovieDetails(widget.movieId);
    if (movie == null) {
      setState(() {
        _errorMessage = '电影不存在';
        _isLoading = false;
      });
      return;
    }

    setState(() {
      _movie = movie;
      _isLoading = false;
    });
  } catch (e) {
    setState(() {
      _errorMessage = '加载电影详情失败,请重试';
      _isLoading = false;
    });
  }
}

/// 提交评论
Future<void> _submitReview() async {
  if (_reviewController.text.isEmpty) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('请输入评论内容')),
    );
    return;
  }

  setState(() {
    _isSubmittingReview = true;
  });

  try {
    await movieService.submitMovieReview(
      widget.movieId,
      _reviewController.text,
      _rating,
      '用户', // 这里可以替换为实际的用户名
    );

    // 重新加载评论
    await _loadMovieReviews();

    // 清空输入
    _reviewController.clear();
    _rating = 5.0;

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('评论提交成功')),
    );
  } catch (e) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('评论提交失败,请重试')),
    );
  } finally {
    setState(() {
      _isSubmittingReview = false;
    });
  }
}

总结

本文详细介绍了如何使用Flutter框架开发一款电影推荐及影评APP,并实现跨平台运行,特别是在鸿蒙系统上的适配。通过本文的学习,读者应该已经了解了Flutter框架的核心特性、跨平台开发的实践经验,以及如何构建一个功能完整的电影推荐APP。

主要成果

  1. 成功构建了电影推荐及影评APP:实现了电影列表展示、电影详情查看、影评提交和查看、个人中心等核心功能。

  2. 实现了跨平台兼容:应用可以在Android、iOS、鸿蒙等多个平台运行,体现了Flutter"一次编写,处处运行"的特性。

  3. 优化了用户体验:通过并行加载数据、错误处理、下拉刷新等功能,提升了应用的用户体验。

  4. 修复了图片显示问题:将网络图片替换为本地图片,提高了应用的稳定性和性能。

技术亮点

  1. 模块化设计:采用了清晰的目录结构和模块化设计,使代码更易于维护和扩展。

  2. 并行加载数据:使用Future.wait并行加载多个数据请求,提高了数据加载效率。

  3. 响应式布局:使用了Flutter的响应式布局,确保应用在不同屏幕尺寸上都能正常显示。

  4. 状态管理:使用了Flutter的setState进行状态管理,代码简洁明了。

  5. 错误处理:实现了完善的错误处理机制,提高了应用的稳定性。

未来展望

  1. 接入真实API:目前使用的是模拟数据,未来可以接入真实的电影API,获取实时的电影数据。

  2. 实现用户认证:目前的用户认证功能是模拟的,未来可以实现真实的用户注册、登录和认证功能。

  3. 添加更多功能:可以添加更多功能,如电影预告片播放、电影票购买、电影周边商品推荐等。

  4. 优化性能:可以进一步优化应用的性能,如使用缓存、懒加载等技术。

  5. 适配更多平台:可以进一步适配更多平台,如Web、桌面端等。

通过本文的学习,读者应该已经掌握了Flutter框架跨平台开发的基本技能,可以尝试开发更多功能丰富的跨平台应用。Flutter作为一款强大的跨平台开发框架,有着广阔的应用前景,相信在未来会成为移动应用开发的主流选择。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐