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或许是个不错的解决方案,但是我还是累了,不想陪微软折腾了。过两年再看看好了。

相关推荐
在路上`1 天前
前端学习之后端java小白(四)之数据库设计
sql·学习
咔咔学姐kk1 天前
大模型微调技术宝典:Transformer架构,从小白到专家
人工智能·深度学习·学习·算法·transformer
Jayyih1 天前
嵌入式系统学习Day35(sqlite3数据库)
数据库·学习·sqlite
SoaringHeart1 天前
Flutter组件封装:页面点击事件拦截
前端·flutter
lingggggaaaa1 天前
小迪安全v2023学习笔记(八十一讲)—— 框架安全&ThinkPHP&Laravel&Struts2&SpringBoot&CVE复现
笔记·学习·struts·安全·网络安全·laravel
CC数分1 天前
零基础3个月上岸[特殊字符]自学数据分析路线
学习·数据挖掘·数据分析·大学生·考证
HAH-HAH1 天前
【蓝桥杯 2024 国 Java A】粉刷匠小蓝
c++·学习·数学·算法·职场和发展·蓝桥杯·组合数学
酷讯网络_2408701601 天前
多语言共享贩卖机投资理财共享售卖机投资理财系统
学习·开源
番薯大佬1 天前
Python学习-day8 元组tuple
java·python·学习
wanzhong23331 天前
ArcGIS学习-17 实战-密度分析
学习·arcgis