flutter 首页制作和SharedPreference

1、首页怎么做?

这回新建了一个项目,完全的从头开始。

import 'package:day08/sp_demo.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';


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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  int _currentIndex = 0;

  final List<Widget> _pages = [
    const Center(child: Text('首页')),
    const Center(child: Text('发现')),
    const Center(child: Text('我的')),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(widget.title),
        ),
        body: _pages[_currentIndex],
        floatingActionButton: FloatingActionButton(
          onPressed: _incrementCounter,
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: _currentIndex,
          onTap: (index) {
            setState(() {
              _currentIndex = index;
            });
          },
          items: const [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              label: '首页',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.search),
              label: '发现',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.person),
              label: '我的',
            ),
          ],
        ),
      );
  }
}

一般页面结构就是AppBar-Body-BottomNavigationBar。本来还想着在body下方写一个小地方放BottomNavigationBar来着,没想到有槽位,那就老实点放槽位吧。

BottomNavigationBar也就是肉眼可见的仨地方:
currentIndex:位置下标;
onTap:(index){}:点击事件,setState刷新页面干呗;
items:数组放底部导航栏按钮,就直接写BottomNavigationBarItem就行,抄!
不过Material3主题放Material2样式的BottomNavigationBar——虽然确实不合适,但符合我国业务开发的国情。起码中庸是正确的——毕竟现在苹果的“超级毛玻璃”就暂时来讲国内大部分情况是不会跟。

body里面直接就是页面的列表数组,根据下标显示。

2、首页的“再按一次退出”问题

ios上可能不会存在,因为ios压根不存在全局返回按钮,自然也就没这一说——可我是个Android佬欸!
搞一下吧!

DateTime? _lastPressedAt; // 记录上次点击时间

Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        if (_lastPressedAt == null ||
            DateTime.now().difference(_lastPressedAt!) > const Duration(seconds: 2)) {
          // 两次点击间隔超过2秒,显示提示
          _lastPressedAt = DateTime.now();
          showToast(
             context: context,
             '再按一次退出应用',
             duration: const Duration(seconds: 2),
           );
          return false;
        }
        // 两次点击间隔小于2秒,退出应用
        return true;
      },
      child: Scaffold(
        //Scaffold内容省略
      ),
    );
  }

大致道理我懂!也还是Android老套路:
第一次 或 间隔超过设定时间后(这里是2秒):记下当前时间,然后给一个提示
在第一次操作间隔时间内第二次操作:执行退出操作。

但是! WillPopScope在flutter 3.12+被弃用……改用PopScope啦!

  DateTime? _lastPressedAt; // 记录上次点击时间

  bool _canPop = false; // 控制是否允许返回
  
  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: _canPop,
      onPopInvokedWithResult: (didPop, result) async { 
        if (didPop) return; // 已处理弹出则直接返回
        if(_currentIndex != 0){
          setState(() {
            _currentIndex = 0;
          });
          return;
        }else{
          final now = DateTime.now();
          if (_lastPressedAt == null || now.difference(_lastPressedAt!) > const Duration(seconds: 2)) {
            _lastPressedAt = now;
            showToast(
              context: context,
              '再按一次退出应用',
              duration: const Duration(seconds: 2),
            );
            return;
          }
          _canPop=true;
          if (mounted) {
            SystemNavigator.pop();
          }
        }

      },
      child: Scaffold(
        //Scaffold内容省略
      ),
    );
  }

至少这个写法IDE不会报错,也不会执行失灵。
新增的canPop参数就是控制让不让弹出的,但不过true还是false都是要经过onPopInvokedWithResult回调(前端称之为钩子)。
if (didPop) return;处理过就直接不处理了。没处理才有接下来的事情:首先如果不是首页的话先返回首页,然后就是老套路记下当前时间,第二次按下返回键时执行退出操作。
先把canPop改为true让页面允许退出,然后SystemNavigator.pop()退出。网上说的是Android原生部分还要处理东西了就拿这个退出就行。如果没有就直接exit(0)也并非不可。个人认为在安卓上俩都能随便用,反正纯flutter项目效果一样。

ios?不管!反正他们没有全局返回键,管好自己返回栈不用了直接上划小横条划回桌面或者后台划掉就没啥事了,操那么多心干嘛。

3、试一下SharedPreference

首先给个地方进入新的SharedPreference测试页面

import 'package:day08/sp_demo.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';


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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _currentIndex = 0;
  DateTime? _lastPressedAt; // 记录上次点击时间

  bool _canPop = false; // 控制是否允许返回

  final List<Widget> _pages = [
    const _HomePage(),
    const Center(child: Text('发现')),
    const Center(child: Text('我的')),
  ];

  void _incrementCounter() {
    setState(() {
      // 原有逻辑
    });
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: _canPop,
      onPopInvokedWithResult: (didPop, result) async { // 修改为 onPopInvokedWithResult [[1]][[2]]
        if (didPop) return; // 已处理弹出则直接返回
        if(_currentIndex != 0){
          setState(() {
            _currentIndex = 0;
          });
          return;
        }else{
          final now = DateTime.now();
          if (_lastPressedAt == null || now.difference(_lastPressedAt!) > const Duration(seconds: 2)) {
            _lastPressedAt = now;
            showToast(
              context: context,
              '再按一次退出应用',
              duration: const Duration(seconds: 2),
            );
            return;
          }
          _canPop=true;
          if (mounted) {
            SystemNavigator.pop();
          }
        }

      },
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(widget.title),
        ),
        body: _pages[_currentIndex],
        floatingActionButton: FloatingActionButton(
          onPressed: _incrementCounter,
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: _currentIndex,
          onTap: (index) {
            setState(() {
              _currentIndex = index;
            });
          },
          items: const [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              label: '首页',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.search),
              label: '发现',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.person),
              label: '我的',
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('首页'),
          ElevatedButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => const SharedPreferencesDemoPage()),
              );
            },
            child: const Text('跳转到 SharedPreferences 示例页面'),
          ),
        ],
      ),
    );
  }
}

然后进入新的SharedPreferencesDemoPage:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

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

  @override
  State<SharedPreferencesDemoPage> createState() => _SharedPreferencesDemoPageState();
}

class _SharedPreferencesDemoPageState extends State<SharedPreferencesDemoPage> {
  String _storedValue = '';
  final TextEditingController _textController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _loadValue();
  }

  Future<void> _saveValue() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('demo_key', _textController.text);
    _loadValue();
  }

  Future<void> _loadValue() async {
    final prefs = await SharedPreferences.getInstance();
    setState(() {
      _storedValue = prefs.getString('demo_key') ?? '';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SharedPreferences 示例'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _textController,
              decoration: const InputDecoration(
                labelText: '输入要保存的值',
              ),
            ),
            ElevatedButton(
              onPressed: _saveValue,
              child: const Text('保存'),
            ),
            Text('存储的值: $_storedValue'),
          ],
        ),
      ),
    );
  }
}

大概就这个样子。也就是final prefs = await SharedPreferences.getInstance()prefs.getString('demo_key') ?? ''await prefs.setString('demo_key', _textController.text)
和Android里的思路是差不多的,这个基本没啥大的变动,拿实例-写-读。

上一篇