Flutter:仿京東專案實戰(1)-首頁功能實現
在我個人認為學習一門新的語言(快速高效學習) 一定是通過實踐,最好的就是做專案,這裡我會簡單寫一個京東的Demo。
Scalfold Widget 是用來描述頁面的主結構,一個 MaterialApp
由多個 Scalfold
頁面組成,每個 Scalfold 的普遍結果如下:
- AppBar:頂部導航欄
- body:中間內容體
- BottomNavigationBar:底部導航欄
第一天搭建專案框架,實現首頁的功能。
第二天實現 分類和商品列表頁面: https://juejin.cn/post/7044716539550892068。
第三天實現 商品詳情頁功能實現:https://juejin.cn/post/7046186331940061191
Flutter-混合工程的持續整合實踐: https://juejin.cn/post/7042099001679675422
用到的知識點
BottomNavigationBar
底部導航欄基本屬性
-
命名路由:關於更多內容可以參考 Flutter-導航與路由堆疊詳解
-
螢幕適配:使用了
flutter_screenutil
外掛,具體有以下這些屬性
傳入設計稿的px尺寸 px px px !
ScreenUtil().setWidth(540) //根據螢幕寬度適配尺寸
ScreenUtil().setHeight(200) //根據螢幕高度適配尺寸
ScreenUtil().setSp(24) //適配字型
ScreenUtil().setSp(24, allowFontScalingSelf: true) //適配字型(根據系統的“字型大小”輔助選項來進行縮放)
ScreenUtil().setSp(24, allowFontScalingSelf: false) //適配字型(不會根據系統的“字型大小”輔助選項來進行縮放)
ScreenUtil().pixelRatio //裝置的畫素密度
ScreenUtil().screenWidth //裝置寬度
ScreenUtil().screenHeight //裝置高度
ScreenUtil().bottomBarHeight //底部安全區距離,適用於全面屏下面有按鍵的
ScreenUtil().statusBarHeight //狀態列高度 劉海屏會更高 單位dp
ScreenUtil().textScaleFactor //系統字型縮放比例
ScreenUtil().scaleWidth // 實際寬度的dp與設計稿px的比例
ScreenUtil().scaleHeight // 實際高度的dp與設計稿px的比例
4. 網路請求使用 dio
外掛實現,詳解可以看官網:https://github.com/flutterchina/dio
``` import 'package:dio/dio.dart';
void main() async { var dio = Dio(); final response = await dio.get('https://google.com'); print(response.data); } ```
配置抓包
-
引入這兩個
dio
的標頭檔案import 'package:dio/adapter.dart'; import 'package:dio/dio.dart';
-
配置抓包程式碼
//設定只在debug模式下抓包
final kReleaseMode = false;
final Dio dio = Dio();
if (!kReleaseMode){
//設定代理 抓包用
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) {
client.findProxy = (uri) {
return "PROXY localhost:8888";
};
};
}
- 輪播圖使用
swiper
外掛實現
SwiperPagination
屬性說明:
//如果要將分頁指示器放到其他位置,可以修改這個引數
alignment Alignment.bottomCenter
分頁指示器與容器邊框的距離
margin EdgeInsets.all(10.0)
分頁指示器的樣式,fraction自定義樣式
builder SwiperPagination.dots
- 頁面框架使用
ListView
實現列表
- 商品列表用到了
Wrap
,流式佈局、自動換行
屬性解析
direction:主軸(mainAxis)的方向,預設為水平。
alignment:主軸方向上的對齊方式,預設為start。
spacing:主軸方向上的間距。
runAlignment:run的對齊方式。run可以理解為新的行或者列,如果是水平方向佈局的話,run可以理解為新的一行。
runSpacing:run的間距。
crossAxisAlignment:交叉軸(crossAxis)方向上的對齊方式。
textDirection:文字方向。
verticalDirection:定義了children擺放順序,預設是down,見Flex相關屬性介紹。
專案的搭建
建立 tabs資料夾,裡面新增tabs.dart和4個底導首頁(home.dart、category.dart、cart.dart、user.dart)。
這裡只是貼出主要程式碼:
bottomNavigationBar: BottomNavigationBar(
currentIndex:_currentIndex ,
onTap: (index){
setState(() {
_currentIndex=index;
});
},
type:BottomNavigationBarType.fixed ,
fixedColor:Colors.red,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "首頁"
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
label: "分類"
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
label: "購物車"
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
label: "我的"
)
],
),
配置命名路由
這裡為了便於統一管理,建立一個routers資料夾,在裡面建立一個router.dart類來管理路由
```
//配置路由
final Map
//固定寫法 var onGenerateRoute = (RouteSettings settings) { final String? name = settings.name; final Function? pageContentBuilder = routes[name]; if (pageContentBuilder != null) { if (settings.arguments != null) { final Route route = MaterialPageRoute( builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); return route; } else { final Route route = MaterialPageRoute(builder: (context) => pageContentBuilder(context)); return route; } } }; ```
螢幕適配
這裡我選擇flutter_screenutil
實現螢幕適配,flutter_screenutil 預設以750*1334
設計稿為標準 在 pubspec.yaml
新增
flutter_screenutil: ^5.0.0+2
然後點選 Pub get 拉取。建立一個services資料夾,在裡面建立screen_adapter.dart類,在裡面實現常用的功能
``` import 'package:flutter_screenutil/flutter_screenutil.dart';
class ScreenAdapter { //寬、高、字號大小轉換 static height(num value) { return ScreenUtil().setHeight(value); }
static width(num value) { return ScreenUtil().setWidth(value); }
static size(num value) { return ScreenUtil().setSp(value); }
//獲取裝置的物理寬度 static getScreenWidth() { return ScreenUtil().screenWidth; }
//獲取裝置的物理高度 static getScreenHeight() { return ScreenUtil().screenHeight; }
//狀態列高度 static double statusBarHeight = ScreenUtil().statusBarHeight; //底部安全區域距離 static double bottomBarHeight = ScreenUtil().bottomBarHeight; } ```
具體實現
頁面整體框架使用 ListView 實現,而內容是分下面三大塊實現: - banner區域的輪播圖,通過 Swiper 外掛實現 - 猜你喜歡的橫向滾動列表,通過 ListView 實現 - 熱門推薦的垂直滾動列表,通過 Wrap 實現
實現banner區域的輪播圖
首先引入 flutter_swiper_null_safety: ^1.0.2
, 輪播圖是網路獲取的資料,還需要引入 dio: ^4.0.0
進行網路請求,然後執行 pub get 拉取。
建立輪播圖的模型 focus_model.dart
,裡面的程式碼實現為
```
class FocusModel {
List
json['result'].forEach((v) {
result.add(new FocusItemModel.fromJson(v));
});
}
}
Map
class FocusItemModel { String? sId; //可空型別 String? title; String? status; String? pic; String? url;
FocusItemModel({this.sId, this.title, this.status, this.pic, this.url});
FocusItemModel.fromJson(Map
Map
獲取banner資料
//獲取熱門推薦的資料
_getBestProductData() async {
var api = 'https://jdmall.itying.com/api/plist?is_best=1';
var result = await Dio().get(api);
var bestProductList = ProductModel.fromJson(result.data);
setState(() {
this._bestProductList = bestProductList.result;
});
}
建立輪播圖
Widget _swiperWidget() {
if (this._focusData.length > 0) {
return Container(
child: AspectRatio(
aspectRatio: 2 / 1,
child: Swiper(
itemBuilder: (BuildContext context, int index) {
String pic = this._focusData[index].pic;
pic = Config.domain + pic.replaceAll('\', '/');
return new Image.network(
"${pic}",
fit: BoxFit.fill,
);
},
itemCount: this._focusData.length,
pagination: new SwiperPagination(),
autoplay: true),
),
);
} else {
return Text('載入中...');
}
}
具體程式碼如下:
``` List _focusData = []; @override void initState() { super.initState(); _getFocusData(); }
@override
Widget build(BuildContext context) {
return ListView(
children:
然後執行專案就實現了這樣的效果,輪播圖的功能做好了
實現猜你喜歡的效果
封裝一個方法,返回一個Widget
Widget _titleWidget(value) {
return Container(
height: ScreenAdapter.height(32),
margin: EdgeInsets.only(left: ScreenAdapter.width(20)),
padding: EdgeInsets.only(left: ScreenAdapter.width(20)),
decoration: BoxDecoration(
border: Border(
left: BorderSide(
color: Colors.red,
width: ScreenAdapter.width(10),
))),
child: Text(
value,
style: TextStyle(color: Colors.black54),
),
);
}
獲取猜你喜歡網路資料
//獲取猜你喜歡的資料
_getHotProductData() async {
var api = '${Config.domain}api/plist?is_hot=1';
var result = await Dio().get(api);
var hotProductList = ProductModel.fromJson(result.data);
setState(() {
this._hotProductList = hotProductList.result;
});
}
建立猜你喜歡橫向列表
//猜你喜歡
Widget _hotProductListWidget() {
if (_hotProductList.length > 0) {
return Container(
height: ScreenAdapter.height(234),
padding: EdgeInsets.all(ScreenAdapter.width(20)),
child: ListView.builder(
//設定滾動方向
scrollDirection: Axis.horizontal,
itemBuilder: (contxt, index) {
//處理圖片
String sPic = _hotProductList[index].sPic;
//得到圖片URL
sPic = Config.domain + sPic.replaceAll('\', '/');
return Column(
children: <Widget>[
Container(
height: ScreenAdapter.height(140),
width: ScreenAdapter.width(140),
margin: EdgeInsets.only(right: ScreenAdapter.width(21)),
child: Image.network(sPic, fit: BoxFit.cover),
),
Container(
padding: EdgeInsets.only(top: ScreenAdapter.height(10)),
height: ScreenAdapter.height(44),
child: Text(
"¥${_hotProductList[index].price}",
style: TextStyle(color: Colors.red),
),
)
],
);
},
itemCount: _hotProductList.length,
),
);
} else {
return Text("");
}
}
實現效果
實現熱門推薦功能
獲取熱門推薦的資料
_getBestProductData() async {
var api = '${Config.domain}api/plist?is_best=1';
var result = await Dio().get(api);
var bestProductList = ProductModel.fromJson(result.data);
setState(() {
this._bestProductList = bestProductList.result;
});
}
建立商品列表
``` Widget _recProductListWidget() {
var itemWidth = (ScreenAdapter.getScreenWidth() - 30) / 2; return Container( padding: EdgeInsets.all(10), child: Wrap( runSpacing: 10, spacing: 10, children: this._bestProductList.map((value) {
//圖片
String sPic = value.sPic == null ? '' : value.sPic;
sPic = Config.domain+sPic.replaceAll('\', '/');
return Container(
padding: EdgeInsets.all(10),
width: itemWidth,
decoration: BoxDecoration(
border: Border.all(
color: Color.fromRGBO(233, 233, 233, 0.9), width: 1)),
child: Column(
children: <Widget>[
Container(
width: double.infinity,
child: AspectRatio(
//防止伺服器返回的圖片大小不一致導致高度不一致問題
aspectRatio: 1 / 1,
child: Image.network(
"${sPic}",
fit: BoxFit.cover,
),
),
),
Padding(
padding: EdgeInsets.only(top: ScreenAdapter.height(20)),
child: Text(
"${value.title}",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.black54),
),
),
Padding(
padding: EdgeInsets.only(top: ScreenAdapter.height(20)),
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Text(
"¥${value.price}",
style: TextStyle(color: Colors.red, fontSize: 16),
),
),
Align(
alignment: Alignment.centerRight,
child: Text( "¥${value.oldPrice}",
style: TextStyle(
color: Colors.black54,
fontSize: 14,
decoration: TextDecoration.lineThrough)),
)
],
),
)
],
),
);
}).toList(),
),
); } ```
實現效果
- Flutter-官方推薦的Flutter與原生互動外掛Pigeon
- Flutter-flutter_sound錄音與播放
- iOS-CocoaPods的原理及Podfile.lock問題
- iOS配置多環境的三種方案
- iOS-各種Crash防護
- iOS-Swift中常見的幾種閉包
- Flutter:仿京東專案實戰(2)-分類和商品列表頁面功能實現
- Flutter:仿京東專案實戰(1)-首頁功能實現
- Flutter-JSON轉Model的四種便捷方案
- Flutter-導航與路由堆疊詳解
- Flutter 與原生通訊的三種方式
- iOS-記憶體洩漏檢測
- Fastlane實現自動打包
- 懶人必備神器-Xcode程式碼塊
- Jenkins實現自動化打包
- Json自動解析生成Model工具
- iOS 指令碼打包上傳蒲公英通知釘釘