flutter 页面导航和返回

2023-11-14

记录一下自己的学习过程~

一般页面导航和返回

导航的使用在任何程序里都至关重要,这也是一个程序的灵魂。
ElevatedButton 按钮控件
它有两个最基本的属性:

child:可以放入容器,图标,文字。让你构建多彩的按钮。
onPressed:点击事件的相应,一般会调用Navigator控件。
我们在作页面导航时,大量的使用了ElevatedButton控件,这个控件的使用在实际工作中用的也比较多。

Navigator.push 和 Navigator.pop
Navigator.push:是跳转到下一个页面,它要接受两个参数一个是上下文context,另一个是要跳转的函数。

Navigator.pop:是返回到上一个页面,使用时传递一个context(上下文)参数,使用时要注意的是,你必须是有上级页面的,也就是说上级页面使用了Navigator.push。

写一个Demo
我们现在就来作一个简单的案例,我们打开一个页面,页面上只有一个简单的按钮,按钮写着“查看商品详情页面”,然后点击后进入下一个页面,页面有一个按钮,可以直接返回。

代码如下,一定要动手敲一敲哦。

import 'package:flutter/material.dart';

void main(){
  runApp(MaterialApp(
    title:'导航演示1',
    home:new FirstScreen()
  ));
}

class FirstScreen extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return new Scaffold(
      appBar: AppBar(title:Text('导航页面')),
      body:Center(
        child:ElevatedButton(
          child:Text('查看商品详情页面'),
          onPressed: (){
            Navigator.push(context,new  MaterialPageRoute(
              builder:(context) =>new SecondScreen())
            );
          },
        )
      )
    );
  }
}

class SecondScreen extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return Scaffold(
      appBar:AppBar(title:Text('YoYo商品详情页')),
      body:Center(child:ElevatedButton(
        onPressed: () {  },
        child:ElevatedButton(
          child:Text('返回'),
          onPressed: (){
            Navigator.pop(context);
          },
        )
      ))
    );

  }
}

填坑,如果是1.0以下版本,热更新的时候会有时不能实现导航,这个需要你重新启动一下虚拟机。

导航参数的传递和接收(1)

页面跳转的时候参数的传递和接受。

这个挑选商品的过程就是参数的传递和接受,用在程序上解释就是比如你进入一个商品选择列表,当你想选择一个商品的具体信息的时候,你就要传递商品编号,详细页面接受到编号后,显示出不同的内容。

Awesome Flutter snippets组件的使用

因为我们的代码有些多,这时候我们要加快敲代码的速度,可以使用VSCode 中的Awesome Flutter snippets插件。它可以帮忙我们快速生成常用的Flutter代码片段。

比如输入stlss就会给我们生成如下代码:

class name extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: child,
    );
  }
}

安装完成后,我们可以看这个插件的说明,可以快速生成很多代码片段,小伙伴们可以自己测试。熟练掌握后,能大大加快我们的代码编写速度。

声明数据结构类

Dart中可以使用类来抽象一个数据,比如我们模仿一个商品信息,有商品标题和商品描述。我们定义了一个Product类,里边有两个字符型变量,title和description。

title:是商品标题。
description: 商品详情描述
代码如下:

class Product{
  final String title;  //商品标题
  final String description;  //商品描述
  Product(this.title,this.description);
}

构建一个商品列表
作一个商品的列表,这里我们采用动态的构造方法,在主方法里传递一个商品列表(List)到自定义的Widget中。

先来看看主方法的编写代码:

void main(){
  runApp(MaterialApp(
    title:'数据传递案例',
    home:ProductList(
      products:List.generate(
        20, 
        (i)=>Product('商品 $i','这是一个商品详情,编号为:$i')
      ),
    )
  ));
}

上面的代码是主入口文件,主要是在home属性中,使用了ProductList,这个自定义控件,而且时候会报错,因为缺少这个组件。这个控件传递了一个products参数,也就是商品的列表数据,这个数据是用List.generate生成的。并且这个生成的List原型就是我们刚开始定义的Product这个类(抽象数据)。

ProductList自定义控件的代码:

class ProductList extends StatelessWidget{
  final List<Product> products;
  ProductList({Key? key,required this.products}):super(key:key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title:Text('商品列表')),
      body:ListView.builder(
        itemCount:products.length,
        itemBuilder: (context,index){
          return ListTile(
            title:Text(products[index].title),
            onTap:(){
            }
          );
        },
      )
    );
  }
}

先接受了主方法传递过来的参数,接受后用ListView.builder方法,作了一个根据传递参数数据形成的动态列表。

导航参数的传递和接收(2)

接着上面的代码讲解,已经有了商品列表,下面要做的就是把商品数据传递过去,然后显示商品详情页面。

导航参数的传递

我们还是使用Navigator组件,然后使用路由MaterialPageRoute传递参数,具体代码如下。

Navigator.push(
  context, 
  MaterialPageRoute(
    builder:(context)=> ProductDetail(product:products[index])
  )
);
这段代码要写在onTap相应事件当中。这时候ProductDetail会报错,因为我们还没有生命这个控件或者说是类。

子页面接受参数并显示
现在需要声明ProductDetail这个类(控件),先要作的就是接受参数,具体代码如下。

class ProductDetail extends StatelessWidget {
  final Product product;
  ProductDetail({Key? key ,required this.product}):super(key:key);


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: AppBar(
        title:Text('${product.title}'),
      ),
      body:Center(child: Text('${product.description}'),)
    );
  }
}

先接受了参数,并把数据显示在了页面中。

Demo全部代码如下,我把所有的传递参数和接受参数的代码附在了下面。

import 'package:flutter/material.dart';

//传递的数据结构,也可以理解为对商品数据的抽象
class Product{
  final String title;  //商品标题
  final String description;  //商品描述
  Product(this.title,this.description);
}

void main(){
  runApp(MaterialApp(
    title:'数据传递案例',
    home:ProductList(
      products:List.generate(
        20, 
        (i)=>Product('商品 $i','这是一个商品详情,编号为:$i')
      ),
    )
  ));
}

class ProductList extends StatelessWidget{
  final List<Product> products;
  ProductList({Key? key,required this.products}):super(key:key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title:Text('商品列表')),
      body:ListView.builder(
        itemCount:products.length,
        itemBuilder: (context,index){
          return ListTile(
            title:Text(products[index].title),
            onTap:(){
              Navigator.push(
                context, 
                MaterialPageRoute(
                  builder:(context)=>new ProductDetail(product:products[index])
                )
              );
            }
          );
        },
      )
    );
  }
}

class ProductDetail extends StatelessWidget {
  final Product product;
  ProductDetail({Key? key ,required this.product}):super(key:key);


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: AppBar(
        title:Text('${product.title}'),
      ),
      body:Center(child: Text('${product.description}'),)
    );
  }
}

页面跳转并返回数据

页面跳转后,当我们返回页面时返回结果到上一个页面(也就是父页面)。这样的场景经常用于,我们去子页面选择了一项选项,然后把选择的结果返回给父级页面。

异步请求和等待

Dart中的异步请求和等待和ES6中的方法很像,直接使用async…await就可以实现。比如下面做了一个找商品的方法,然后进行跳转,注意这时候是异步的。等待结果回来之后,我们再显示出来内容。具体代码如下:

  _navigateToXiaoXiao(BuildContext context) async{ //async是启用异步方法

    final result = await Navigator.push(//等待
      context, 
      MaterialPageRoute(builder: (context)=> XiaoXiao())
      );
		// 使用 showSnackBae 要加上下面这句忽略
      // ignore: deprecated_member_use
      Scaffold.of(context).showSnackBar(SnackBar(content:Text('$result')));
      // SnackBar 是用户操作后显示提示信息的一个控件,会自动隐藏
  }
SnackBar的使用

SnackBar是用户操作后,显示提示信息的一个控件,类似Tost,会自动隐藏。SnackBar是以Scaffold的showSnackBar方法来进行显示的。

Scaffold.of(context).showSnackBar(SnackBar(content:Text('$result')));
返回数据的方式

返回数据其实是特别容易的,只要在返回时带第二个参数就可以了。

 Navigator.pop(context,'xxxx');  //xxx就是返回的参数

Demo代码其实挺多的,所有代码都放到下面,可以照着练习。

import 'package:flutter/material.dart';

void main(){
  runApp(MaterialApp(
    title:'页面跳转返回数据',
    home:FirstPage()
  ));
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:AppBar(title:Text("杂货间")),
      body:Center(
        child: RouteButton(),
      )
    );
  }
}

//跳转的Button
class RouteButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed:(){
          _navigateToXiaoXiao(context);
      },
      child: Text('找商品'),
    );
  }

  _navigateToXiaoXiao(BuildContext context) async{ //async是启用异步方法

    final result = await Navigator.push(//等待
      context, 
      MaterialPageRoute(builder: (context)=> XiaoXiao())
      );

      // ignore: deprecated_member_use
      Scaffold.of(context).showSnackBar(SnackBar(content:Text('$result')));
      // SnackBar 是用户操作后显示提示信息的一个控件,会自动隐藏
  }
}

class XiaoXiao extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:AppBar(
        title:Text('我是商品小姐姐')
      ),
      body:Center(
        child:Column(
          children: <Widget>[
            ElevatedButton(
              child: Text('小仙女'),
              onPressed: (){
                Navigator.pop(context,'小仙女:15725860005');
              },
            ) ,
            ElevatedButton(
              child: Text('小酒窝'),
              onPressed: (){
                Navigator.pop(context,'小酒窝:15620063600');
              },
            ) ,
          ],
        ) 
      ) ,
    );
  }
}

静态资源和项目图片的处理

pubspec.yaml 文件

如果想配置项目资源文件,就需要使用pubspec.yaml文件,需要把资源文件在这里声明。

比如在项目根目录下新建了一个images文件夹,文件夹下面放了一个图片,图片的名称叫做blogtouxiang.jpg,那我们在pubspec.yaml文件里就要写如下代码进行声明。

assets:
    - images/blogtouxiang.jpg

使用项目图片资源
有了声明后,我们就可以直接在项目中引用这个文件了。这里使用最简单的代码结构,只用了一张图片。代码如下:

import 'package:flutter/material.dart';

void main()=>runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Image.asset('images/blogtouxiang.jpg'),
    );
  }
}

这时候已经在项目中引入成功了,可以预览看一下效果。当然样子很丑,这里主要学知识,就不做过多的修饰了。

Flutter客户端打包

到现在为止,我相信小伙伴都能做出一些漂亮的页面了,也有了拿到朋友面前显一显的冲动。想要安装到手机上,我们必须要进行打包,这节课我们就学学Android客户端如何打包apk。

其实这个打包的坑还是比较多的,官方文档显然是站在大神级程序员的角度写的,所以如果你看文档,特别是前端,那基本是打不成功的。

这里就详细的把打包的过程和坑演示一下。

配置APP的图标

想配置APP的图片,你需要找到下面的目录:

项目根目录/android/app/src/main/res/

进入之后你会看到很多mipmap-为前缀命名的文件夹,后边的是像素密度,可以看出图标的分辨率。

mdpi () ~160dpi
hdpi (高) ~240dip
xhdpi (超高) ~320dip
xxhdpi (超超高) ~480dip
xxxhdpi (超超超高) ~640dip

将对应像素密度的图片放入对应的文件夹中,图片记得用png格式,记得名字要统一,才能一次性进行配置。

AndroidManifest.xml 文件

这个文件主要用来配置APP的名称、图标和系统权限,所在的目录在:

项目根目录/android/app/src/main/AndroidManifest.xml

android:label="flutter_app"   //配置APP的名称,支持中文
android:icon="@mipmap/ic_launcher" //APP图标的文件名称

生成 keystore
这里的坑挺多的,小伙伴一定要注意。官方写的非常简单,只要在终端运行如下代码就可以成功,但事实是报错。

keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

根本找不到这个目录,真的很坑,其实我们只是没有配置环境变量。但是为了一个包配置环境变量是不知道的。

这时候可以用下面的命令找到keytool.exe的位置。

flutter doctor -v

这时候你直接拷贝命令并进行输入,但这里也有个坑,就是如果文件夹中间带有空空,你需要用带引号扩上。

D:\Program\Android\'Android Studio'\jre\bin\keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

这就可以了吗?那你就太天真了,还是会报错。

这个错误的主要问题是目录不存在和没有写权限,所以我们要更换一个有写权限的目录。我们把命令改成了下面的形式。

D:\Program\Android\'Android Studio'\jre\bin\keytool -genkey -v -keystore D:\key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

这时候就可以创建成功了。你的D盘下面就会有一个Jks的文件,记住这个文件不能共享给任何人。

有了这个key.jks文件后,可以到项目目录下的android文件夹下,创建一个名为key.properties的文件,并打开粘贴下面的代码。

storePassword=<password from previous step>    //输入上一步创建KEY时输入的 密钥库 密码
keyPassword=<password from previous step>    //输入上一步创建KEY时输入的 密钥 密码
keyAlias=key
storeFile=<E:/key.jks>    //key.jks的存放路径

我的文件最后是这样的:

storePassword=123123
keyPassword=123123
keyAlias=key
storeFile=D:/key.jks

这个工作中也不要分享出去哦,这个Key就算生成成功了。

配置key注册

key生成好后,需要在build.gradle文件中进行配置。这个过程其实很简单,就是粘贴复制一些东西,你是不需要知道这些文件的具体用处的。

第一项:

进入项目目录的/android/app/build.gradle文件,在android{这一行前面,加入如下代码:

def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

把如下代码进行替换

buildTypes {
    release {
        signingConfig signingConfigs.debug
    }
}

替换成的代码:

signingConfigs {
    release {
        keyAlias keystoreProperties['keyAlias']
        keyPassword keystoreProperties['keyPassword']
        storeFile file(keystoreProperties['storeFile'])
        storePassword keystoreProperties['storePassword']
    }
}
buildTypes {
    release {
        signingConfig signingConfigs.release
    }
}
生成apk

直接在终端中输入:

flutter build apk

这时候就打包成功了,剩下的安装过程我就省略,不作过多的介绍了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

flutter 页面导航和返回 的相关文章

  • 删除 Android 中切换按钮的填充

    我正在 android 中创建一个简单的切换按钮并将背景设置为可绘制对象
  • Android Wear:在手持设备上启动服务

    我正在构建一个 Wear 应用程序 它将与手持设备上的 WearableListenerService 进行通信 但是 我想确保当应用程序在手表上启动时服务已启动并运行 我最初的想法是发送意图或广播消息来启动服务 但是 我一直无法弄清楚如何
  • 将片段部分移出屏幕

    我已经被这个问题难住有一段时间了 我试图将包含在 FrameLayout 中的片段向右移动 以便只有片段的左侧 20 可见 我认为我遇到的问题是父级不会让片段移出其边界 或者我不知道如何移动它 我所尝试的一切只是将片段推到右墙上 然后缩放片
  • 在我授予许可后,表面视图不显示相机

    public View onCreateView LayoutInflater inflater Nullable ViewGroup container Bundle savedInstanceState View view inflat
  • InflateException 膨胀类 android.support.design.widget.CoordinatorLayout 时出错

    E AndroidRuntime 致命异常 main 进程 com atech a business PID 30662 java lang RuntimeException 无法启动活动 ComponentInfo com atech a
  • 如何使用 Retrofit 2 和 RxJava 处理分页

    我知道如何处理 Retrofit 响应 但在使用 rx java 处理来自 REST API 的分页时遇到问题 背景 我使用的其余 api 为我提供了以下响应 并在标题中提供了下一页的链接 HTTP 200 OK Allow GET HEA
  • Flutter 中声明式编程和命令式编程有什么区别?

    最近 我正在寻找一种方法来增强 Flutter 应用程序中屏幕之间的导航 我发现了新的编程概念 陈述性的 and 至关重要的编程 我需要了解更多关于陈述性的 and 至关重要的范例以及 Flutter 中的示例 声明式路由意味着您的应用程序
  • 无法从视图转换为按钮

    我在这里遇到非常令人沮丧的问题 我有这个代码 Button b findViewById android R id button1 我收到了这个错误 类型不匹配 无法将表单视图转换为按钮 但是按钮1is一个按钮 在我的 XML 布局文档中
  • 如何实现可运行队列

    我正在尝试实现一个可运行队列 在异步任务期间依次执行 意味着队列中的下一个将在另一个完成后执行 我编写了一个管理器来管理这些可运行对象和本身就是可运行对象的任务 然后 我获取异步任务中的第一个任务并运行它 希望它能够在队列中运行 但是它最终
  • 当我单击 GridView 项时返回 ImageView 实例

    当我点击GridView项时如何返回ImageView实例 我为 ItemClick 创建自定义绑定事件 public class ItemClickSquareBinding MvxBaseAndroidTargetBinding pri
  • XML 文档结构必须在同一实体内开始和结束

    我是 eclipse 的新手 我刚刚开始编写一些代码 实际上只是从网站复制并粘贴 谁能帮我解决这个问题 错误出现在最后一行
  • Android appwidget 远程视图未更新

    当我从某些活动更新小部件时 列表远程视图不会更新 我的意思是刷新自身 它会出现直到应用程序小部件的更新 日志显示 但不会进入列表视图的适配器以用新数据填充它 public void onUpdate Context context AppW
  • Android 中可以导入 java.rmi.* 吗?

    我的分布式系统课程中有一个项目 我们必须在我们的项目中使用 java rmi 而且我知道由于 dalvik VM 问题 android 不提供这个库 所以我只是想问是否可以在 Android 上使用这些库 Thanks Android 不支
  • 应用程序启动器图标显示在活动的操作栏上

    在我的操作栏上显示应用程序图标 我不希望它出现在操作栏上 我修改了 androidmanifest xml 并删除了android icon从活动元素中 即使图标正在显示
  • 将 TextField 下划线颜色更改为渐变

    我可以更改轮廓颜色TextField s使用以下代码将颜色更改为纯色 TextField decoration InputDecoration focusedBorder UnderlineInputBorder borderSide Bo
  • 不幸的是应用程序已在 Android 模拟器中停止

    我是 Android 新手 正在尝试一些小应用程序 例如 Compass 当我在模拟器中运行应用程序时 它会给出消息Unfortunately Compass has Stopped 我没有编译时错误 我该如何解决这个问题 是什么原因造成的
  • Android smoothScrollTo 不调用 onScrollStateChanged

    我在用smoothScrollBy 滚动到 a 中的特定位置ListView 我希望在以下情况时得到通知ListView完成滚动以将其与当前集成onScrollStateChanged 当用户用手指滚动时触发的事件 目前我正在使用Timer
  • 我收到“循环依赖”Android Dagger Hilt 错误

    我从头开始检查了所有内容 但找不到错误 我找不到错误 可能是什么 我收到以下编译错误 HomeViewModel java 6 error ComponentProcessor MiscError dagger internal codeg
  • Android - onLoadFinished 未调用

    我遇到了装载机的问题 我有一个活动 它显示从本地数据库检索的记录列表 当活动启动时 记录会通过 LoaderManager initLoader 方法自动加载 还可以通过 ActionBarSherlock 中的刷新按钮手动刷新列表 但是
  • Visual Studio代码无法检测到模拟器设备或连接的电话

    I was running my app with vscode using Android emulator or my phone however all of a sudden vscode could not identify an

随机推荐