發(fā)表日期:2019-01 文章編輯:小燈 瀏覽次數(shù):2056
最近Flutter一直比較火,我也它也是非常感興趣,看了下官網(wǎng)的基礎教程后我決定直接上手做一個App,一是這樣學的比較快印象更加深刻,二是可以記錄其中遇到的一些坑,幫助大家少走一些彎路.本篇文章我會盡可能詳細的講到每一個點上.
下載項目后報錯是因為沒有添加依賴,在pubspec.yaml文件中點擊Packages get下載依賴,有時候會在這里出現(xiàn)卡死的情況,可以配置一下環(huán)境變量,詳情請看修改Flutter環(huán)境變量.
先看看效果圖吧.
iOS效果圖
Android效果圖
怎么搭建Flutter環(huán)境我就不多說了,官網(wǎng)上講的很詳細,還沒有搭建開發(fā)環(huán)境的可以看看這個Flutter中文網(wǎng).
DefaultTabController
這個控件,使用DefaultTabController
包裹需要用到Tab的頁面即可,它的child為Scaffold,Scaffold有個appBar屬性,在AppBar中設置具體的樣式,大家看代碼會更加清楚.相關(guān)注釋也都寫上了. home: new DefaultTabController( length: titleList.length, child: new Scaffold( appBar: new AppBar( elevation: 0.0,//導航欄下面那根線 title: new TabBar( isScrollable: false,//是否可滑動 unselectedLabelColor: Colors.black26,//未選中按鈕顏色 labelColor: Colors.black,//選中按鈕顏色 labelStyle: TextStyle(fontSize: 18),//文字樣式 indicatorSize: TabBarIndicatorSize.label,//滑動的寬度是根據(jù)內(nèi)容來適應,還是與整塊那么大(label表示根據(jù)內(nèi)容來適應) indicatorWeight: 4.0,//滑塊高度 indicatorColor: Colors.yellow,//滑動顏色 indicatorPadding: EdgeInsets.only(bottom: 1),//與底部距離為1 tabs: titleList.map((String text) {//tabs表示具體的內(nèi)容,是一個數(shù)組 return new Tab( text: text, ); }).toList(), ), ), //body表示具體展示的內(nèi)容 body:TabBarView(children: [News(url: 'http://app3.qdaily.com/app3/homes/index_v2/'),News(url: 'http://app3.qdaily.com/app3/papers/index/')]) , ), ),
大家也可以看看官網(wǎng)的示例Flutter官網(wǎng)示例
樣式一
這種布局的大概結(jié)構(gòu)如下
注意這里圖片是緊貼著右邊屏幕的,所以這里需要用到
Expanded
控件,用于自動填充子控件.
樣式二
這個樣式的控件布局就很簡單了,結(jié)構(gòu)如下
樣式三
這個和樣式二差不多,只不過最上面多了一塊.
這里需要注意的是,那個你猜這個圖片是堆疊在整個大圖上面的,所以需要用到
Stack
這個控件,其中Stack中有個屬性const FractionalOffset(double dx, double dy)
用于表示子控件相對于父控件的位置
樣式四
這種樣式稍微復雜一點,結(jié)構(gòu)如下
用青花瓷抓取了好奇心數(shù)據(jù).青花瓷使用教程
首先在pubspec.yaml中導入
dependencies:
json_annotation: ^2.0.0
dev_dependencies:
build_runner: ^1.0.0
json_serializable: ^2.0.0
創(chuàng)建一個model.dart文件
引入文件
import 'package:json_annotation/json_annotation.dart';
part 'model.g.dart';
其中這個model.g.dart等會兒會自動生成.這里需要掌握兩個知識點
1.@JsonSerializable() 這是表示告訴編譯器這個類是需要生成Model類的
2,@JsonKey 由于服務器返回的部分數(shù)據(jù)名稱在Dart語言中是不被允許的,比如has_more,Dart中命名不能出現(xiàn)下劃線,所以就需要用到@JsonKey來告訴編譯器這個參數(shù)對于json中的哪個字段
@JsonSerializable() class Feed { String image; int type; @JsonKey(name: 'index_type') int indexType; Post post; @JsonKey(name: 'news_list') List<News> newsList; Feed(this.image,this.type,this.post,this.indexType,this.newsList); factory Feed.fromJson(Map<String,dynamic> json) => _$FeedFromJson(json); Map<String, dynamic> toJson() => _$FeedToJson(this); }
好了,寫完后會報錯,因為FeedFromJson
和FeedToJson
沒有找到,這個時候在控制到輸入flutter packages pub run build_runner build
指令后會自動生成一個moded.g.dart
文件,于是在網(wǎng)絡請求下來數(shù)據(jù)后就可以用Feed feed = Feed.fromJson(data)
這個方法來將Json中數(shù)據(jù)轉(zhuǎn)換保存在Feed這個實例中了.在model類中還有些復雜的Json嵌套,但是也都很簡單,大家看一眼應該就會了,哈哈.JSON和序列化具體教程
Flutter中的輪播圖我用到了Fluuter_Swiper這個組件,這里設置小圓點屬性的時候稍微麻煩了點,網(wǎng)上好像也沒有講到,我這里講一下.
首先要創(chuàng)建DotSwiperPaginationBuilder
DotSwiperPaginationBuilder builder = DotSwiperPaginationBuilder( color: Colors.white,//未選中圓點顏色 activeColor: Colors.yellow,//選中圓點顏色 size:7,//未選中大小 activeSize: 7,//選中圓點大小 space: 5//圓點間距 );
然后在Swiper中的pagination屬性中設置它
pagination: new SwiperPagination( builder: builder, ),
StatefulWidget
,因為需要動態(tài)更新數(shù)據(jù)和列表.initState
方法中請求數(shù)據(jù)表示剛加載頁面的時候進行網(wǎng)絡請求,請求數(shù)據(jù)方法如下void getData()async{ if (lastKey == '0'){ dataList = [];//下拉刷新的時候?qū)ataList制空 } Dio dio = new Dio(); Response response = await dio.get("$url$lastKey.json"); Reslut reslut = Reslut.fromJson(response.data); if(!reslut.response.hasMore){ return;//如果沒有數(shù)據(jù)就不繼續(xù)了 } if(reslut.response.columns != null) { columnList = reslut.response.columns; } lastKey = reslut.response.lastKey;//更新lastkey setState(() { if (reslut.response.banners != null){ banners = reslut.response.banners;//給輪播圖賦值 } dataList.addAll(reslut.response.feeds);//給數(shù)據(jù)源賦值 }); }
因為用到了setState()方法,所以在該方法中改變了的數(shù)據(jù)會對其相應的地方進行刷新,比如設置了ListView的itemCount個數(shù)為dataList.length,如果在SetState方法中dataList.length改變了,那么ListView的itemCount樹也會自動改變并刷新ListView.
Flutter中有RefreshIndicator
用于下拉刷新,它有個onRefresh
閉包方法,表示下拉的時候執(zhí)行的方法,一般用于網(wǎng)絡請求.onRefresh方法如下
Future<void> _handleRefresh() { final Completer<void> completer = Completer<void>(); Timer(const Duration(seconds: 1), () { completer.complete(); }); return completer.future.then<void>((_) { lastKey = '0'; getData(); }); }
下拉加載的話需要初始化一個ScrollController
,將它設為ListView的controller,并對其進行監(jiān)聽,當滑動到最底部的時候進行網(wǎng)絡請求.
@override void initState() { url = widget.url; getData(); _scrollController.addListener(() { ///判斷當前滑動位置是不是到達底部,觸發(fā)加載更多回調(diào) if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { getData(); } }); } final ScrollController _scrollController = new ScrollController();
上拉加載loading框用到了flutter_spinkit插件,提供了大量的加載樣式.
///上拉加載更多 Widget _buildProgressIndicator() { ///是否需要顯示上拉加載更多的loading Widget bottomWidget = new Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ ///loading框 new SpinKitThreeBounce(color: Color(0xFF24292E)), new Container( width: 5.0, ), ]); return new Padding( padding: const EdgeInsets.all(20.0), child: new Center( child: bottomWidget, ), ); }
由于最上面有一個輪播圖,最下面有加載框,所以ListView的itemCount個數(shù)為dataList.length+2,又因為每個item之間都有一個淺灰色的風格線,所以需要用到ListView.separated
,具體代碼如下:
Widget build(BuildContext context) { return RefreshIndicator( onRefresh:(()=> _handleRefresh()), color: Colors.yellow,//刷新控件的顏色 child: ListView.separated( physics: const AlwaysScrollableScrollPhysics(), itemCount: _getListCount(),//item個數(shù) controller: _scrollController,//用于監(jiān)聽是否滑到最底部 itemBuilder: (context,index){ if(index == 0){ return SwiperWidget(context, banners);//如果是第一個,則展示banner }else if(index < dataList.length + 1){ return WidgetUtils.GetListWidget(context, dataList[index - 1]);//展示數(shù)據(jù) }else { return _buildProgressIndicator();//展示加載loading框 } }, separatorBuilder: (context,idx){//分割線 return Container( height: 5, color: Color.fromARGB(50,183, 187, 197), ); }, ), ); }
這種的話也稍微復雜一點,有兩種樣式.并且到滑到最右邊的時候可以繼續(xù)請求并加載數(shù)據(jù).
首先來分析一下數(shù)據(jù)
那么思路就很清晰了,在請求獲得數(shù)據(jù)后遍歷colunmns,根據(jù)每個colunmn的location插入一個Map,如下
data.insert(colunm.location,{'id':colunm.id,'showType':colunm.showType});
,再創(chuàng)建一個ColumnsListWidget
類,繼承自StatefulWidget,是一個新item,在滑動到該列表的位置的時候,會將該Map數(shù)據(jù)傳給ColumnsListWidget
,這個時候ColumnsListWidget
就會加載數(shù)據(jù)并展示出來了,滑到最右邊的時候加載和滑到最底部加載的方法一樣,就不多說了.具體可以查看源碼,關(guān)鍵代碼如下:
static Widget GetListWidget(BuildContext context, dynamic data) { Widget widget; if(data.runtimeType == Feed) { if (data.indexType != null) { widget = NewsListWidget(context, data); } else if (data.type == 2) { widget = ListImageTop(context, data); } else if (data.type == 0) { widget = ActivityWidget(context, data); } else if (data.type == 1) { widget = ListImageRight(context, data); } }else{ widget = ColumnsListWidget(id: data['id'],showType: data['showType'],); }
1.橫向ListView外需要用
Flexible
包裹,Flexible組件可以使Row、Column、Flex等子組件在主軸方向有填充可用空間的能力(例如,Row在水平方向,Column在垂直方向),但是它與Expanded組件不同,它不強制子組件填充可用空間。
2.ListView初始位置用到padding: new EdgeInsets.symmetric(horizontal: 12.0)
,用padding: EdgeInsets.only(left: 12)
的話會讓ListView和最左邊一直有條線
FlutterWebviewPlugin.m
文件中的- (void)navigate:(FlutterMethodCall*)call
方法中的最后一排,將[self.webview loadRequest:request]
方法改為[self.webview loadHTMLString:url baseURL:nil]
WebViewManager.java
文件中webView.loadUrl(url)
方法改為webView.loadData(url, "text/html", "UTF-8")
,以及下面那排的void reloadUrl(String url) { webView.loadUrl(url); }
改為void reloadUrl(String url) { webView.loadData(url, "text/html", "UTF-8"); }
/assets/app3
開頭的,所以需要替換成絕對路徑,所以要用到這個方法htmlBody.replaceAll( '/assets/app3','http://app3.qdaily.com/assets/app3')
在點擊橫向滑動列表的總標題的時候,會進入到相關(guān)欄目的詳情頁,如圖
這個ListView包含上下兩部分.上面這部分為:
下面就是一個GridView,不過有時候下面會是ListView,根據(jù)shouwType字段來判斷,GridView的代碼如下:
Widget ColumnsDetailTypeTwo(BuildContext context,List<Feed> feesList){ return GridView.count( physics: NeverScrollableScrollPhysics(), crossAxisCount: 2, shrinkWrap: true, mainAxisSpacing: 10.0, crossAxisSpacing: 15.0, childAspectRatio: 0.612, padding: new EdgeInsets.symmetric(horizontal: 20.0), children: feesList.map((Feed feed) { returnColumnsTypeTwoTile(context, feed); }).toList()); }
其中 childAspectRatio
表示寬高比.
圓角頭像需要用到
CircleAvatar(backgroundImage:NetworkImage(url),),
這個控件
做了這個項目最大的感受就是界面布局是真的很方便很簡單,因為做了一遍對很多知識點也理解的更深了.如果覺得有幫助到你的話,希望可以給個 Star