Flutter 开发学习笔记(2):第一个简单的Flutter项目(下)

文章目录

前言

接着继续上一章的内容

官方Flutter案例

编写第一个 Flutter 应用

侧边栏添加

代码初始化

为了保证main.dart代码能正常跑通,我将完整的代码复制粘贴到这里

csharp 复制代码
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),

      ),
    );
  }
}

//可通知视图修改的类
class MyAppState extends ChangeNotifier {
  //这个就相当于成员变量
  var current = WordPair.random();

  //手动声明回调函数,理论上来说,最好通过函数显性修改父节点属性
  void getNext(){

    current = WordPair.random();
    // 手动通知刷新视图元素
    notifyListeners();
  }
  //手动通知
  void notify(){
    notifyListeners();

  }

  var favorites = <WordPair>[];

  void toggleFavorite() {
    if (favorites.contains(current)) {
      favorites.remove(current);
    } else {
      favorites.add(current);
    }
    notifyListeners();
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: 0,
              onDestinationSelected: (value) {
                print('selected: $value');
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}

class GeneratorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          BigCard(pair: pair),
          SizedBox(height: 10),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  appState.toggleFavorite();
                },
                icon: Icon(icon),
                label: Text('Like'),
              ),
              SizedBox(width: 10),
              ElevatedButton(
                onPressed: () {
                  appState.getNext();
                },
                child: Text('Next'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}


class BigCard extends StatelessWidget {
  const BigCard({
    super.key,
    required this.pair,
  });

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    //获取父节点的主题
    final theme = Theme.of(context);
    //手动设置style,这个就是类似于css的样式传递
    final style = theme.textTheme.bodyMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );
    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text(pair.asLowerCase,style: style),
      ),
    );
  }
}

展示效果

由于官方的案例是按照横向布局来设置的,所以我们安卓实时效果不太一样。

子组件私有数据空间

我们目前的数据都是用MyApp的数据,但是我们不可能将所有的数据都放在父节点,子节点也应该有自己的私有数据空间。

在Flutter中,这称之为无状态Widget(StatelessWidget)和有状态widget(StatefulWidget)

导航栏转为有状态Widget


setState

在 Flutter 中使用 setState 时的 6 个简单技巧

手动转换页面

在return之前添加如下代码

java 复制代码
Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

实现效果

响应式动态切换宽度

将【Scaffold】替换为【Builder】,【Builder】替换为【LayoutBuilder】

添加收藏夹,跨Widget传数据

添加一个新的Widget

java 复制代码
class FavoritesPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    if (appState.favorites.isEmpty) {
      return Center(
        child: Text('No favorites yet.'),
      );
    }

    return ListView(
      children: [
        Padding(
          padding: const EdgeInsets.all(20),
          child: Text('You have '
              '${appState.favorites.length} favorites:'),
        ),
        for (var pair in appState.favorites)
          ListTile(
            leading: Icon(Icons.favorite),
            title: Text(pair.asLowerCase),
          ),
      ],
    );
  }
}

实现效果

完整代码

java 复制代码
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),

      ),
    );
  }
}

//可通知视图修改的类
class MyAppState extends ChangeNotifier {
  //这个就相当于成员变量
  var current = WordPair.random();

  //手动声明回调函数,理论上来说,最好通过函数显性修改父节点属性
  void getNext(){

    current = WordPair.random();
    // 手动通知刷新视图元素
    notifyListeners();
  }
  //手动通知
  void notify(){
    notifyListeners();

  }

  var favorites = <WordPair>[];

  void toggleFavorite() {
    if (favorites.contains(current)) {
      favorites.remove(current);
    } else {
      favorites.add(current);
    }
    notifyListeners();
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {

    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = FavoritesPage();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return LayoutBuilder(
      builder: (context,constraints) {
        return Scaffold(
          body: Row(
            children: [
              SafeArea(
                child: NavigationRail(
                  extended: false,
                  destinations: [
                    NavigationRailDestination(
                      icon: Icon(Icons.home),
                      label: Text('Home'),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.favorite),
                      label: Text('Favorites'),
                    ),
                  ],
                  selectedIndex: selectedIndex,
                  onDestinationSelected: (value) {
                    setState(() {
                        selectedIndex = value;
                        print('selected: $selectedIndex');

                    });
                  },
                ),
              ),
              Expanded(
                child: Container(
                  color: Theme.of(context).colorScheme.primaryContainer,
                  child: page,
                ),
              ),
            ],
          ),
        );
      }
    );
  }
}

class FavoritesPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    if (appState.favorites.isEmpty) {
      return Center(
        child: Text('No favorites yet.'),
      );
    }

    return ListView(
      children: [
        Padding(
          padding: const EdgeInsets.all(20),
          child: Text('You have '
              '${appState.favorites.length} favorites:'),
        ),
        for (var pair in appState.favorites)
          ListTile(
            leading: Icon(Icons.favorite),
            title: Text(pair.asLowerCase),
          ),
      ],
    );
  }
}

class GeneratorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          BigCard(pair: pair),
          SizedBox(height: 10),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  appState.toggleFavorite();
                },
                icon: Icon(icon),
                label: Text('Like'),
              ),
              SizedBox(width: 10),
              ElevatedButton(
                onPressed: () {
                  appState.getNext();
                },
                child: Text('Next'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}


class BigCard extends StatelessWidget {
  const BigCard({
    super.key,
    required this.pair,
  });

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    //获取父节点的主题
    final theme = Theme.of(context);
    //手动设置style,这个就是类似于css的样式传递
    final style = theme.textTheme.bodyMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );
    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text(pair.asLowerCase,style: style),
      ),
    );
  }
}

后续进阶效果

进阶代码链接

总结

唉,该怎么说呢,但凡Avalonia或者MAUI对移动端的支持好一点,我也不至于学一个Flutter。但是没办法。我记得有个数据,有将近50%的移动端开发用的就是Flutter。Flutter和Avalonia用的都是自绘的方式,而MAUI用的却是原生映射的方式。所以会出现很多很多的Bug。MAUI+Blazor或许是个不错的解决方案,但是我还是累了,不想陪微软折腾了。过两年再看看好了。

相关推荐
linwq82 分钟前
OkHttp使用和源码分析学习(二)
学习·okhttp
肥肠可耐的西西公主6 分钟前
前端(AJAX)学习笔记(CLASS 2):图书管理案例以及图片上传
前端·笔记·学习
Long_poem30 分钟前
【自学笔记】Spring Boot框架技术基础知识点总览-持续更新
spring boot·笔记·后端
贩卖纯净水.2 小时前
REACT学习DAY02(恨连接不上服务器)
服务器·学习·react.js
南风过闲庭2 小时前
操作系统研究
大数据·人工智能·科技·学习·ai·系统架构
Niuguangshuo3 小时前
Python 单例模式笔记
笔记·python·单例模式
心无旁骛~5 小时前
Git笔记汇总,持续更新~
笔记·git
陈无左耳、6 小时前
HarmonyOS学习第2天: 解锁语言与框架的无限可能
学习·华为·harmonyos
朝九晚五ฺ6 小时前
【Linux探索学习】第三十弹——线程互斥与同步(上):深入理解线程保证安全的机制
linux·运维·学习
东方芷兰6 小时前
伯克利 CS61A 课堂笔记 12 —— Syntax
笔记·python