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 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 {
    int curIndex = 0;

    List _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 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" : "[email protected]"
        },
        {
            "userId" : "002",
            "userName" : "十年之后2",
            "email" : "[email protected]"
        },
        {
            "userId" : "003",
            "userName" : "十年之后3",
            "email" : "[email protected]"
        },
        {
            "userId" : "004",
            "userName" : "十年之后4",
            "email" : "[email protected]"
        }
        ***
    ]
}

构建数据模型

//这是每个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 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: [
//用法在这里
             Consumer(builder:(context,t,child){
                return Text(t.userInfo["userName"])
              })
//用法在这里
             RaisedButton(onPressed: () => Provider.of(context, listen:false).setUserName('十年之后V5');child: Text("改变用户名"),)
           ],
         ),
       ),
    );
  }
}

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

context.watch().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().setChildCategoty(this.cetegorryLeftData[index].bxMallSubDto);
//this.cetegorryLeftData[index].bxMallSubDto这个就是我们需要重新复制的数据
右侧获取改变的数据
我们可以在全局定义一个变量
然后在
@override build 进行赋值
listRightData = context.watch().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> 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> 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类型的数据
即:[{}]不能被缓存我们可以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 {
//重点
  Future _prefs = SharedPreferences.getInstance();
  List testList = [];
  @override
  Widget build(BuildContext context) {
    getList();//重点
    return Scaffold(
      appBar: AppBar(title: Text("购物车"),centerTitle: true,),
      body: Container(
        child: Column(
          children: [
            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: [
                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=[];
    });
  }
}

你可能感兴趣的:(Flutter实战(百姓生活+))