在碼農的世界裡,優美的應用體驗,來源於程式設計師對細節的處理以及自我要求的境界,年輕人也是忙忙碌碌的碼農中一員,每天、每週,都會留下一些腳印,就是這些創作的內容,有一種執著,就是不知為什麼,如果你迷茫,不妨來瞅瞅碼農的軌跡。
如果你有興趣 你可以關注一下公眾號 biglead 來獲取最新的學習資料。
先來看看本文章實現的效果
直接來看程式碼吧
首先是啟動函式
main() {
runApp(MaterialApp(
//不顯示 debug標籤
debugShowCheckedModeBanner: false,
//顯示的首頁面
home: DemoSelectImageWidgetPage(),
));
}
複製程式碼
然後是這個首頁面,核心程式碼就是 SelectPhotoWidget 這個元件
///程式碼清單
class DemoSelectImageWidgetPage extends StatefulWidget {
@override
_DemoSelectImageWidgetPageState createState() =>
_DemoSelectImageWidgetPageState();
}
class _DemoSelectImageWidgetPageState extends State<DemoSelectImageWidgetPage> {
@override
Widget build(BuildContext context) {
//
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(title: Text("圖片選擇元件")),
body: Center(
child: Container(
padding: EdgeInsets.all(12),
//圖片選擇元件
child: SelectPhotoWidget(
header: Text(
"請選擇照片",
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 18),
),
//標題下的紅色提醒文字
tips: "請注意 最多選擇5張圖片",
//圖片選擇回撥
imageSelectAction: (List<String> list) {
print("實時選擇回撥${list.toString()}");
},
//最大選擇圖片資料
maxSelect: 6,
//預設圖片
imageList: [],
),
),
),
);
}
}
複製程式碼
將核心功能封裝在了 SelectPhotoWidget 元件中,大家可以直接複製使用
///
class SelectPhotoWidget extends StatefulWidget {
///每次點選選擇圖片後的回撥
final Function(List<String>) imageSelectAction;
///自定義標題
final Widget header;
///標題下的小捍
final String tips;
///預顯示使用的圖片
final List<String> imageList;
///最多可選擇的圖片數量
final int maxSelect;
///為true 時顯示使用網路圖片
final ImageType imageType;
const SelectPhotoWidget(
{Key key,
this.header,
this.tips,
this.imageList,
this.imageType = ImageType.asset,
this.imageSelectAction,
this.maxSelect = 5})
: super(key: key);
@override
State<StatefulWidget> createState() {
return _SelectPhotoWidgetState();
}
}
複製程式碼
class _SelectPhotoWidgetState extends State<SelectPhotoWidget>
with WidgetsBindingObserver {
///當前是否正在選擇圖片
bool _isSelect = false;
@override
void initState() {
super.initState();
if (widget.imageList != null) {
//判斷一下最大選擇圖片資料
if (widget.imageList.length <= widget.maxSelect) {
_imageList = widget.imageList;
} else {
//擷取圖片
_imageList = widget.imageList.sublist(0, widget.maxSelect);
}
}
//繫結檢視監聽
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.inactive:
// 處於這種狀態的應用程式應該假設它們可能在任何時候暫停。
break;
case AppLifecycleState.resumed:
//從後臺切換前臺,介面可見
break;
case AppLifecycleState.paused:
// 介面不可見,後臺
break;
case AppLifecycleState.detached:
// APP結束時呼叫
break;
}
}
@override
void dispose() {
//解綁檢視監聽
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
//圓角矩形剪裁
return ClipRRect(
//圓角
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Container(
color: Color(0xffFFFFFF),
//寬度填充
width: double.infinity,
//統一內邊距
padding: EdgeInsets.all(10),
//垂直方向的線性排列
child: Column(
//水平方向
crossAxisAlignment: CrossAxisAlignment.start,
//包裹
mainAxisSize: MainAxisSize.min,
children: [
//標題
buildHeaderWidget(),
//第二行的小提示
buildTipsWidget(),
//顯示的圖片
buildGridView(),
SizedBox(
height: 10,
),
],
),
),
);
}
buildHeaderWidget() {
return widget.header != null ? widget.header : Container();
}
buildTipsWidget() {
if (widget.tips == null || widget.tips.length == 0) {
return Container();
}
return Container(
padding: EdgeInsets.only(top: 10, bottom: 16),
//圓角矩形裁剪
child: ClipRRect(
//圓角
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Container(
padding: EdgeInsets.only(left: 10, right: 10, top: 6, bottom: 6),
color: Color(0xffFFF1F1),
child: Text(
"${widget.tips}",
style: TextStyle(
color: Color(0xffBD2F2F),
fontSize: 14,
),
),
),
),
);
}
List<String> _imageList = [];
buildGridView() {
return Container(
child: GridView.builder(
padding: EdgeInsets.only(top: 8, bottom: 8),
//包裹
shrinkWrap: true,
//不可滑動
physics: NeverScrollableScrollPhysics(),
//圖片個數
itemCount: getSelectCount(),
//SliverGridDelegateWithFixedCrossAxisCount 構建一個橫軸固定數量Widget
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//橫軸元素個數
crossAxisCount: 4,
//主軸間距
mainAxisSpacing: 1.0,
//從軸間距
crossAxisSpacing: 1.0,
//子元件寬高長度比例
childAspectRatio: 1.0),
itemBuilder: (BuildContext context, int index) {
//Widget Function(BuildContext context, int index)
if (index == _imageList.length) {
if (_isSelect) {
return Center(child: Text("..."));
}
return Container(
margin: EdgeInsets.only(top: 10),
child: IconButton(
icon: Icon(Icons.add),
onPressed: () {
onSelectImageFunction();
},
),
color: Color(0xFFF1F1F2),
);
}
//顯示當前的圖片
String imageUrl = _imageList[index];
return Container(
//層疊佈局
child: Stack(
children: [
//向左下偏移一點
Positioned.fill(
top: 10,
right: 10,
child: GestureDetector(
onTap: () {
//檢視大圖
},
child: Container(
padding: EdgeInsets.all(1),
child: buildImageWidget(imageUrl),
color: Colors.grey[200],
),
),
),
Positioned(
top: 0,
right: 0,
child: GestureDetector(
onTap: () {
onDeleteImageFunction(index);
},
child: ClipOval(
child: Container(
padding: EdgeInsets.all(2),
color: Colors.red,
child: Icon(
Icons.close,
color: Colors.white,
size: 14,
),
),
),
),
),
],
),
);
},
),
);
}
Widget buildImageWidget(String image) {
if (widget.imageType == ImageType.net) {
return Image.network(
image,
fit: BoxFit.fitWidth,
);
} else if (widget.imageType == ImageType.asset) {
return Image.asset(
image,
fit: BoxFit.fitWidth,
);
}
return Image.file(
File(image),
fit: BoxFit.fitWidth,
);
}
///最大選擇圖片資料限制
getSelectCount() {
if (_imageList.length >= widget.maxSelect) {
return widget.maxSelect;
}
return _imageList.length + 1;
}
//刪除照片
void onDeleteImageFunction(int index) {
_imageList.removeAt(index);
setState(() {});
widget.imageSelectAction(_imageList);
}
void onSelectImageFunction() async {
_isSelect = true;
setState(() {});
String localImageUrl = "assets/images/sp03.png";
await Future.delayed(Duration(milliseconds: 1000));
_isSelect = false;
if (localImageUrl.length > 0) {
_imageList.add(localImageUrl);
setState(() {});
widget.imageSelectAction(_imageList);
}
}
}
複製程式碼