Flutter实战(百姓生活+)

1、风格

我们之前的风格都是material风格

import 'package:flutter/material.dart';

其实flutter还自带一种风格cupertino

import 'package:flutter/cupertino.dart';

Icon(CupertinoIcons.home)//cupertino风格的图标

2、函数返回值不确定

使用Future定义

Future _test(){
  return ***;
}

3、解决内容越界

使用SingleChildScrollView解决越界问题,该组件是越界滚动效果。

4、异步请求改变界面的组件

使用FutureBuilder组件

FutureBuilder(builder: (context,snapshot){
  if(snapshot.hasData){//判断异步返回有没有值

  }else{

  }
},future:getHomepageContent()),

使用FutureBuilder组件会有一个问题,当里面的子组件有多个的时候,往往我们更新的数据只需要刷新一个子组件就好了,但是使用FutureBuilder会导致所有的子组件都会刷新
解决办法Flutter开发性能提升之:如何避免Widget重复Build

5、屏幕适配

随着移动手机的屏幕尺寸越来越多,我们怎么能够适配
使用flutter_ScreenUtilflutter_ScreenUtil
注意如果添加跟适配ScreenUtil.init需要放在TabPage中

  @override
  Widget build(BuildContext context) {
    ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: false);
    return Container(
      width:ScreenUtil.setWidth(250),
      height:ScreenUtil.setHeight(250)
    );
  }

6、点击事件调用手机的拨打电话

使用url_launcher 调用拨打电话
他可以打开网页、手机拨号、邮箱、短信

dependencies:
  url_launcher: ^5.4.10

import 'package:url_launcher/url_launcher.dart';
_launchURL() async {//打开网页
  const url = 'https://flutter.dev';
  if (await canLaunch(url)) {
    await launch(url);
  } else {
    throw 'Could not launch $url';
  }
}

  void _makePhoneCall(String phone) async {
    注意拨打手机多了'tel'
    例如 拨打1888888888
    里面的值应该是tel:1888888888
    if (await canLaunch('tel:'+phone)) {
      await launch('tel:'+phone);
    } else {
      throw 'Could not launch $url';
    }
  }

7、切换导航或者页面状态保持

类似前端vuekeep-live效果 页面缓存
使用继承AutomaticKeepAliveClientMixin这个类
使用这个类有三点
1、必须是statefulwidget组件
在我们需要保持状态的页面中加入

class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin{}

2、必须重构wantKeepAlive方法
在我们需要保持状态的页面中加入

@override
bool get wantKeepAlive => true;

3、需要在tabpage页面中body中套上IndexedStack
这个页面是我们的导航切换页面

class TabsPage extends StatefulWidget {
  TabsPage({Key key}) : super(key: key);

  @override
  _TabsPageState createState() => _TabsPageState();
}

class _TabsPageState extends State<TabsPage> {
    int curIndex = 0;

    List<BottomNavigationBarItem> _list_bottom_item = [
        BottomNavigationBarItem(icon: Icon(CupertinoIcons.home), title: Text("首页")),
        BottomNavigationBarItem(icon: Icon(CupertinoIcons.search), title: Text("分类")),
        BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.shopping_cart), title: Text("购物车")),
        BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.profile_circled), title: Text("会员中心"))
    ];

    List<Widget> list_page = [HomePage(), ListPage(), ShopCardPage(), UserPage()];

    _TabsPageState();

    @override
    Widget build(BuildContext context) {
    ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: false);
        return Scaffold(
      backgroundColor: Color.fromRGBO(244, 245, 245, 1),
            bottomNavigationBar: BottomNavigationBar(
            items: this._list_bottom_item,
            onTap: (i) {
                setState(() {
                    this.curIndex = i;
                });
            },
            currentIndex: this.curIndex,
            type: BottomNavigationBarType.fixed,
            ),
            body: IndexedStack(重点
              index:this.curIndex,当前索引
              children:this.list_page
            ),
        );
    }
}

8、使用GridView/ListView.builder的避坑

问题1、在使用GridView/ListView.builder如果这两个组件是被放在Cloumn、Row中作为子组件那么将会报错

解决1、设定高度
当确定GridView/ListView.builder这个两个组件的高度时,我们可以在外面包裹一层Container并设置高度

Column(
  children:[
    Container(
      height:500.0,
      child:GridView.count()
    )
  ]
)

解决2、换一种布局实现
可以使用Wrap流布局实现

Column(
  children:[Wrap(
    children:[]
  )]
)

问题2、当listView.buider/GridView.count()循环的数据为空的时候 会报错

遇到的情况,例如要从后台获取list数据使用listView.buider/GridView.count()渲染,会报错一瞬间(取决于获取数据的时间)

解决方法
使用三木运算判断数据是否为空

List data;
 body: new Center(
  重点
    child: data == null ? Container() : new ListView.builder(
        itemCount: data.length,
        itemBuilder: (BuildContext context, int position) {
          return new ListTile(
            title: new Text('${data[position]['name']}'),
          );
        }),
  ),

9、上拉加载/下拉刷新

使用flutter_pulltorefresh

10、通过后台返回的数据我们可以建立模型

建立模型就类似于java中的数据模型
dynamic未知类型

案例:例如后台返回的数据格式

{
    "code" : 0,
    "message" : "",
    "result" : [
        {
            "userId" : "001",
            "userName" : "十年之后",
            "email" : "592885211@qq.com"
        },
        {
            "userId" : "002",
            "userName" : "十年之后2",
            "email" : "5928852112@qq.com"
        },
        {
            "userId" : "003",
            "userName" : "十年之后3",
            "email" : "5928852113@qq.com"
        },
        {
            "userId" : "004",
            "userName" : "十年之后4",
            "email" : "5928852114@qq.com"
        }
        ***
    ]
}

构建数据模型

//这是每个Map的模型
class UserModel{
  String userId;
  String userName;
  String email;
  UserModel({//构造函数
    this.userId,
    this.userName,
    this.email
  });
  //工厂模式
  factory UserModel.formJson(dynamic json){
    return UserModel(//其实这一步是调用构造函数
      userId:json['userId'],
      userName:json['userName'],
      email:json['email'],
    )
  }
}
//有了每个Map的模型,我就在建一个List Map模型

class UserModelList{
  List<UserModel> modelList; //每一个List的元素 都是UserModel类型
  UserModelList(this.modelList);//构造函数
  //工程模式
  factory UserModelList.formJson(List json){ //注意这里 是list 我们传进来的肯定是个数据
    return UserModelList(
      json.map((i)=>UserModel.formJson(i)).toList()//遍历每一项
    );
  }
}

使用数据模型

在需要模型的页面引入刚刚建立的模型
var data = await getDataApi()//假如这是借口返回的数据
UserModelList userList = UserModelList.formJson(data);
//需要保证data是个数组,因为我们在创建UserModelList 定义了传入List 
userList.modelList;//就是我们的数据了 这个modelList就是上面的变量

11、 状态管理

类似于Vuex用于共享数据的管理
使用provider
请注意谷歌官方的状态管理插件已被弃用

dependencies:
  provider: ^4.1.3

我们在lib目录下创建文件夹provide,继续在provide创建counter.dart文件
我们不按照文档的例子来 这次我们改变Map中的数据

//count.dart
import 'package:flutter/material.dart';
class Counter with ChangeNotifier{
  Map userInfo = {
    "userName" : "十年之后"
  };
  //创建改变数据的函数通过调用该函数我们做到改变数据
  void setUserName(String name){
    userInfo['userName'] = name;
    notifyListeners();//通知去监听视图改变
  }
}

然后我们在main.dart中改造

import 'package:provider/provider.dart';
import './provide/counter.dart';
void main() {
  runApp(
    MultiProvider(
      providers:[
        ChangeNotifierProvider(
          create:(_)=>Counter())
      ],
      child:MyApp()
    )
  );
}

最后一步 我们在需要用到数据的页面中使用

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../provide/count.dart';
class ListPage extends StatelessWidget {
  const ListPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
       child: Center(
         child: Column(
           children: <Widget>[
//用法在这里
             Consumer<Counter>(builder:(context,t,child){
                return Text(t.userInfo["userName"])
              })
//用法在这里
             RaisedButton(onPressed: () => Provider.of<Counter>(context, listen:false).setUserName('十年之后V5');child: Text("改变用户名"),)
           ],
         ),
       ),
    );
  }
}

如果我们要监听某个存储的值,也就是:改了某个数据,我们视图也立即改变用Provider.of<Counter>(context, listen:false).get_userInfo["userName"]该方法并不能做到监听数据的改变,我们应该使用

context.watch<Counter>().get_userInfo["userName"];

结束语:我们在创建个页面 使用该状态管理 会发现 视图也会一起改变

12、分类页面点击左侧右侧内容相应改变

因为我们设计的内容结构是

Row(
  chidren : [
    Left(),
    Right(),
  ]
)

很明显这两个组件没有通信,但是我们又有数据的交互,我们可以使用状态管理来实现


class ChildCategory with ChangeNotifier, DiagnosticableTreeMixin {
  List childCategoryList = [];

  List get get_childList =>childCategoryList;

  void setChildCategoty(List list){
    childCategoryList = list;
    notifyListeners();
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ObjectFlagProperty('get_childList', get_childList));
  }
}

实现思路左侧点击改变状态管理中的数据
context.read<ChildCategory>().setChildCategoty(this.cetegorryLeftData[index].bxMallSubDto);
//this.cetegorryLeftData[index].bxMallSubDto这个就是我们需要重新复制的数据
右侧获取改变的数据
我们可以在全局定义一个变量
然后在
@override build 进行赋值
listRightData = context.watch<ChildCategory>().get_childList;

13、企业级路由

使用fluro
插件

dependencies:
 fluro: "^1.6.3"
import 'package:fluro/fluro.dart';

然后我们在MyApp builder函数中

class MyApp extends StatelessWidget {
 @override
  Widget build(BuildContext context) {
  final router = Router();}
}

然后我们创建一个路由文件,所有的路由Handler配置都放在这里面

import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import '../pages/details_page.dart';//这是我们需要跳转的页面

Handler detailsHanderl =Handler(
  handlerFunc: (BuildContext context,Map<String,List<String>> params){
    String goodsId = params['id'].first;
    print('index>details goodsID is ${goodsId}');
    return DetailsPage(goodsId);

  }
);

然后我们在创建路由配置文件

import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import './route_handler.dart' //导入我们刚刚配置的Handler

class Routes{
  static String rootPath = "/";
  static String detailsPath = "/details";//商品详情的路由
  static void configureRoutes(Router router){
    //配置下404 空路由
    router.notFoundHandler= Handler(
      handlerFunc:(BuildContext context,Map<String,List<String>> params){
        print("Not Found");
        return TabsPage();//可以跳转到我们写的tabs页面中
      }
    )
    router.define(detailsPath,Handler : detailsHanderl)
  }
} 

为了方便我们可以静态化路由一下(实例化)
创建一个文件application.dart

import 'package:fluro/fluro.dart';
class Application{
  static Router router;
}

再然后我们在MyAppbuilder中

class MyApp extends StatelessWidget {
   @override
  Widget build(BuildContext context) {
    final router = Router();
    Routes.configureRoutes(router);//重点
    Application.router=router;//重点
    return MaterialApp(
      onGenerateRoute: Application.router.generator,//重点
    );
  }
}

路由跳转

Application.router.navigateTo(context, "/detail?id=456456&ab=a456");

如果我们跳转至导航页面即Tab页面不希望他有返回页面按钮

Application.router.navigateTo(context, "/tab?curIndex=2",clearStack: true);

14、html转换widget

使用插件flutter_html

dependencies:
  flutter_html: ^1.0.0
import 'package:flutter_html/flutter_html.dart';
Html( //Html组件
  data: dataDic['information'], //html数据
)

15、数据持久化

类似于前端的localstoare
使用谷歌自带shared_preferences
注意此shared_preferences不能缓存List<Map>类型的数据
即:[{}]不能被缓存我们可以toString

dependencies:
  shared_preferences: ^0.5.7+3
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:shared_preferences/shared_preferences.dart';

class ShopCardPage extends StatefulWidget {
  ShopCardPage({Key key}) : super(key: key);

  @override
  _ShopCardPageState createState() => _ShopCardPageState();
}

class _ShopCardPageState extends State<ShopCardPage> {
//重点
  Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  List<String> testList = [];
  @override
  Widget build(BuildContext context) {
    getList();//重点
    return Scaffold(
      appBar: AppBar(title: Text("购物车"),centerTitle: true,),
      body: Container(
        child: Column(
          children: <Widget>[
            Container(
              width: double.infinity,
              height: ScreenUtil().setHeight(500.0),
              child: ListView.builder(itemBuilder: (context,index){
                return Container(
                  margin: EdgeInsets.only(bottom: 10.0),
                  child: Text(this.testList[index]),
                );
              },itemCount: testList.length,),
            ),
            Row(
              children: <Widget>[
                RaisedButton(onPressed: ()=>this.setList(),child: Text("增加数据"),),
                RaisedButton(onPressed: ()=>this.remList(),child: Text("删除数据"),),
              ],
            )
          ],
        ),
      ),
    );
  }
  //获取数据 查
  void getList()async{
    final SharedPreferences prefs = await _prefs;//重点
    var infoData = prefs.getStringList("testInfo");
    setState(() {
      testList = infoData == null ? [] : infoData;
    });
  }
  //增
  void setList()async{
    final SharedPreferences prefs = await _prefs;//重点
    testList.add("十年之后_flutter");
    prefs.setStringList("testInfo", testList);
    this.getList();
  }
  //删
  void remList()async{
    final SharedPreferences prefs = await _prefs;//重点
    prefs.remove("testInfo");
    setState((){
      testList=[];
    });
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342