View篇
有幾種視圖框架
總體來說有兩種泣洞,Column和Row,前者表示豎直方向默色,后者表示水平方向球凰。
怎么實現(xiàn)類似wrap_content和match_parent的效果
Widget parent = Container(
width: 360,
height: 360,
color: Colors.lightGreen,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
width: 120,
height: 86,
color: Colors.brown,
),
Container(
width: 120,
height: 86,
color: Colors.red,
),
Container(
width: 120,
height: 86,
color: Colors.cyan,
),
],
),
);
可以看到Container里面包含的Column面積設(shè)置的多大就展示多大的區(qū)域,主要起作用的是mainAxisSize和mainAxisAlignment參數(shù)腿宰。
mainAxisSize
表示剩余空間的占用情況呕诉,是最大限度(max)還是最小限度(min)的占用剩余空間
mainAxisAlignment
表示Column或Row里面子布局相互之間的空間應(yīng)該怎么分布。
- MainAxisAlignment.start 表示所有子控件往首部的區(qū)域集中吃度,剩余空間集中在尾部
- MainAxisAlignment.spaceBetween 表示所有子控件之間平分剩余的空間甩挫,不包含首尾
- MainAxisAlignment.center 表示剩余的空間平均分配到子控件的首胃,而子控件之間沒有空間
- MainAxisAlignment.spaceEvenly 表示子控件之間椿每,包含首尾都要均分剩余空間
- MainAxisAlignment.end 表示所有子控件往尾部的區(qū)域集中伊者,剩余空間集中在首部
- MainAxisAlignment.spaceAround 剩余空間除以子控件數(shù)量 + 1,子控件之間的間距是等分的间护,多出來的空間除以2亦渗,分布于子控件的首尾。比如剩余空間是42兑牡,有2個子控件央碟,控件之間的間隔是42 / (2 + 1) = 14,控件之間的間距是14,而兩個控件前后的間隔是14 / 2 = 7.
crossAxisAlignment
這個參數(shù)決定了與排布方向垂直方向子控件的位置。與mainAxisAlignment是相對的亿虽。
還是利用上面的代碼做例子說明:
Widget parent = Container(
width: 360,
height: 360,
color: Colors.lightGreen,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
width: 120,
height: 86,
color: Colors.brown,
),
Container(
width: 120,
height: 86,
color: Colors.red,
),
Container(
width: 120,
height: 86,
color: Colors.cyan,
),
],
),
);
子控件之間和首尾都有了空間菱涤,另外子控件對齊最右邊。如下:
到這里洛勉,我們就知道粘秆,Column的Main方向是豎直的峦筒,Cross方向是水平的晓褪,而Row正好相反虚茶,Main方向是水平的耕挨,Cross方向是豎直的帆精。
綜述一下抑月,要實現(xiàn)wrap_content效果就設(shè)置mainAxisSize為min竹观,并且mainAxisAlignment設(shè)置為start;要實現(xiàn)match_parent就設(shè)置mainAxisSize為max土辩,并且mainAxisAlignment不要設(shè)置為start或end或center输拇。
怎么設(shè)置視圖的寬高
一般情況下使用Container里面的width和height設(shè)置寬高摘符。
當然了,如果不想設(shè)置具體的寬高策吠,只想設(shè)置一個大體的約束逛裤,可以設(shè)置Container里面的constraints屬性,這個屬性對應(yīng)的類是BoxConstraints類猴抹,這個類有四個參數(shù)可以設(shè)置带族,如下:
BoxConstraints(
minHeight: 360,
minWidth: 360,
maxWidth: 360,
maxHeight: 360);
怎么設(shè)置控件的背景顏色
Container里面有color參數(shù)可以設(shè)置背景顏色,前提是Container沒有顯式的設(shè)置decoration屬性蟀给。
decoration屬性用于為Container設(shè)置裝飾蝙砌,這個裝飾屬性對應(yīng)的是BoxDecoration類,如果設(shè)置了這個屬性就不能在Container中設(shè)置color的值了坤溃,只能通過decoration設(shè)置拍霜。
怎么設(shè)置點擊之后的波紋效果
InkWell可以滿足需求,父Widget用InkWell薪介,然后child參數(shù)設(shè)置為要點擊的子Widget祠饺。
怎么設(shè)置字體以及富文本
- 普通文本
Text(
"flutter教程",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: Colors.lightGreenAccent,
fontWeight: FontWeight.bold,
decoration: TextDecoration.lineThrough,
),
)
- 富文本
RichText(
text: TextSpan(
text: "first text",
style: TextStyle(color: Colors.white, fontSize: 18),
children: <TextSpan>[
TextSpan(
text: "second text",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
),
);
怎么設(shè)置控件圓角
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)))
);
怎么將控件背景設(shè)置為我想要的icon
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/your image source.png"),
fit: BoxFit.cover,
),
),
);
怎么設(shè)置控件背景漸變
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,//起始方向
end: Alignment.centerRight,//終止方向
colors: [
ColorUtil.hexToColor("#FF8602"),
ColorUtil.hexToColor("#FE3F01")
]),
),
);
怎么為控件設(shè)置點擊事件
Widget backWidget = GestureDetector(
child: Image.asset(
"images/your image source",
width: 24,
height: 24,
),
onTap: () => Navigator.of(context).pop(),//your own action
);
控件怎么居中
- 第一種方法
Align(
alignment: Alignment.center,
child: Text(
"flutter教程",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: Colors.lightGreenAccent,
fontWeight: FontWeight.bold,
decoration: TextDecoration.lineThrough,
),
),
);
- 第二種方法
Center(
child: Text(
"flutter教程",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: Colors.lightGreenAccent,
fontWeight: FontWeight.bold,
decoration: TextDecoration.lineThrough,
),
),
)
控件怎么設(shè)置上下左右的margin
Widget parent = Container(
margin: EdgeInsets.only(top: 40, bottom:40, left:40, right:40),
child: childWidget,
);
控件怎么設(shè)置padding
Padding(
child: Text("your own widget")
padding: EdgeInsets.only(top: 40, bottom:40, left:40, right:40),
),
圖片怎么設(shè)置圓角
ClipRRect(
borderRadius: new BorderRadius.circular(8.0),
child: Image.network(
goodsImage,
width: 80,
height: 80,
),
),
列表怎么用
- ListView.builder
ListView.builder(
itemBuilder: (context, index) {
return Text("your child list item widget");
},
itemCount: dataSet.length,
),
- ListView.separated可以設(shè)置子item之間的分割,separatorBuilder屬性返回間隔widget
Widget parent = ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Text("this is index $index");
},
separatorBuilder: (BuildContext context, int index) {
return Container(
width: 360,
height: 8,
color: Colors.green,
);
},
itemCount: 20);
- ListView.custom可以自定義子item的展現(xiàn)模式
Widget parent = ListView.custom(
childrenDelegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Text("this is index $index");
},
childCount: 20,
),
);
滾動視圖怎么用
- SingleChildScrollView 只能包含有一個child
Widget parent = SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
color: Colors.blue,
child: Text("this is 1"),
width: 360,
height: 240,
),
Container(
color: Colors.red,
child: Text("this is 2"),
width: 360,
height: 240,
),
Container(
color: Colors.green,
child: Text("this is 3"),
width: 360,
height: 240,
),
Container(
color: Colors.cyan,
child: Text("this is 4"),
width: 360,
height: 240,
),
Container(
color: Colors.yellow,
child: Text("this is 5"),
width: 360,
height: 240,
),
],
),
);
- CustomScrollView 這個控件比較強大汁政,里面可以有多個child widget,并且必須是sliver屬性的控件道偷。
不包含appBar
CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Text("child item");
},
childCount: dataSet.length,
)),
SliverToBoxAdapter(child: footerView),
],
);
其實有sliver屬性的控件還有SliverGrid,SliverAppBar等记劈。
- NestedScrollView 有收縮伸展appBar能力的滾動控件
Widget childContent = NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverOverlapAbsorber(
child: SliverSafeArea(
top: false,
sliver: SliverAppBar(
backgroundColor: Colors.white,
expandedHeight: expandedHeight,
flexibleSpace: featureParentWidget,
pinned: true,
floating: true,
elevation: 179,
bottom: PreferredSize(
child: sortItemWidget,
preferredSize: Size(double.infinity, 42),
),
),
),
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
],
body: body);
expandedHeight 指可以擴展的高度
flexibleSpace 在appbar里面還可以設(shè)置彈性空間的widget勺鸦,比如希望appbar伸展之后的背景是一張圖片,就可以在這里設(shè)置
pinned
appbar是否應(yīng)該保留在滾動視圖頂部
floating
用戶向appbar滾動時目木,是否應(yīng)立即顯示appbar换途。
其他參數(shù)參考:
https://api.flutter.dev/flutter/material/SliverAppBar-class.html
各個參數(shù)的示例圖片如下:
TabLayout + ViewPager怎么實現(xiàn)
使用TabBar和TabBarView實現(xiàn),具體實現(xiàn)方式如下:
- TabBar實現(xiàn)title
Widget titleBar = Container(
height: 48,
color: Colors.green,
margin: EdgeInsets.only(top: 24),
child: TabBar(
tabs: titleArray.map((f) {
return Text(
f,
style: TextStyle(color: Colors.red, fontSize: 14),
);
}).toList(),
onTap: _tabClicked,
controller: _controller,
indicatorSize: TabBarIndicatorSize.label,
isScrollable: false,
indicatorColor: Colors.pink,
),
);
- TabBarView實現(xiàn)
Widget contentView = Container(
color: Colors.amber,
child: TabBarView(
controller: _controller,
children: titleArray.map((f) {
return Center(
child: Text(
"this is $f",
style: TextStyle(color: Colors.red, fontSize: 24),
),
);
}).toList(),
));
一個實現(xiàn)了Tab標題,一個實現(xiàn)了Tab下的具體頁面內(nèi)容军拟,兩者產(chǎn)生關(guān)系的紐帶是_controller變量剃执,這個變量是TabController類型。
全部實現(xiàn)代碼如下:
class TabViewDemoWidget extends StatelessWidget {
TabController _controller;
var initSelectedIndex = 0;
final titleArray = ["A", "B", "C", "D", "E"];
_tabScrollListener() {
if (_controller.index != initSelectedIndex) {
//todo tab changed
}
}
@override
Widget build(BuildContext context) {
_controller = TabController(
initialIndex: initSelectedIndex, //初始選中頁
length: 5, //總頁數(shù)
vsync: ScrollableState(),
);
_controller.addListener(_tabScrollListener);
Widget titleBar = Container(
height: 48,
color: Colors.green,
margin: EdgeInsets.only(top: 24),
child: TabBar(
tabs: titleArray.map((f) {
return Text(
f,
style: TextStyle(color: Colors.red, fontSize: 14),
);
}).toList(),
onTap: _tabClicked,
controller: _controller,
indicatorSize: TabBarIndicatorSize.label,
isScrollable: false,
indicatorColor: Colors.pink,
),
);
Widget contentView = Container(
color: Colors.amber,
child: TabBarView(
controller: _controller,
children: titleArray.map((f) {
return Center(
child: Text(
"this is $f",
style: TextStyle(color: Colors.red, fontSize: 24),
),
);
}).toList(),
));
return Scaffold(
appBar: PreferredSize(
child: titleBar,
preferredSize: Size(double.infinity, 48),
),
body: contentView,
);
}
_tabClicked(int index) {}
}
底部導(dǎo)航欄怎么實現(xiàn)
Scaffold有一個bottomNavigationBar屬性懈息,設(shè)置這個屬性就可以實現(xiàn)肾档,具體實現(xiàn)如下:
Scaffold(
body: Container(),//define your own body content
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
type: BottomNavigationBarType.fixed,
selectedFontSize: 12,
unselectedFontSize: 12,
unselectedItemColor: Color.fromARGB(0XFF, 0X40, 0X40, 0X40),
selectedItemColor: Color.fromARGB(0XFF, 0XFE, 0X3F, 0X01),
items: [
BottomNavigationBarItem(
activeIcon: Container(
width: 24,
height: 24,
child: Image.asset("images/main_activity_main_pressed.png"),
),
icon: Container(
width: 24,
height: 24,
child: Image.asset("images/main_activity_main_normal.png"),
),
title: Text(
"主頁",
)),
BottomNavigationBarItem(
activeIcon: Container(
width: 24,
height: 24,
child: Image.asset(
"images/main_activity_classify_pressed.png"),
),
icon: Container(
width: 24,
height: 24,
child:
Image.asset("images/main_activity_classify_normal.png"),
),
title: Text(
"分類",
)),
],
onTap: onItemTaped),
);
onTap 屬性要給一個item點擊的響應(yīng)事件,攜帶index參數(shù)辫继。當這個方法觸發(fā)之后可以做狀態(tài)改變操作怒见。
AlertDialog怎么實現(xiàn)
flutter自身有AlertDialog Widget,但是可能不滿足我們的需求姑宽,可以自定義
Dialog Widget里面有一個child屬性遣耍,可以設(shè)置自己想要的對話框形式,通過showDialog方法將對話框顯示出來低千。
class DialogDemoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _willPopScope(context),
child: Scaffold(
body: Center(
child: Container(
child: Text("show dialog demo"),
)),
));
}
Future<bool> _willPopScope(BuildContext context) async {
return showDialog(
context: context,
builder: (context) {
//design your own dialog content
Widget dialogChild = Container(
width: 302,
height: 242,
child: Center(
child: Text("this is dialog"),
),
);
return Dialog(
backgroundColor: null,
elevation: 24,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(24))),
child: dialogChild,
);
}) ??
false;
}
}
ProgressDialog怎么實現(xiàn)
ProgressDialog可以通過PopupRoute實現(xiàn)配阵,他的原理是在已有的路由上面再繪制一層。
借鑒別人的實現(xiàn)示血,加自己的改造
///加載彈框
class ProgressDialog {
static bool _isShowing = false;
///展示
static void showProgress(BuildContext context, {Widget child}) {
if (child == null) {//define your own child
child = Theme(
data: Theme.of(context)
.copyWith(accentColor: ColorUtil.hexToColor("#FFFE7501")),
child: Stack(
children: <Widget>[
Center(
child: Container(
width: 46,
height: 46,
child: new CircularProgressIndicator(
strokeWidth: 3,
),
),
),
Center(
child: Text(
"加載中",
style: TextStyle(
fontSize: 12, color: ColorUtil.hexToColor("#FF202020")),
)),
],
));
}
if (!_isShowing) {
_isShowing = true;
Navigator.push(
context,
_PopRoute(
child: _Progress(
child: child,
),
),
);
}
}
///隱藏
static void dismiss(BuildContext context) {
if (context == null) {
return;
}
if (_isShowing) {
Navigator.of(context).pop();
_isShowing = false;
}
}
}
///Widget
class _Progress extends StatelessWidget {
final Widget child;
_Progress({
Key key,
@required this.child,
}) : assert(child != null),
super(key: key);
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Center(
child: child,
));
}
}
///Route
class _PopRoute extends PopupRoute {
final Duration _duration = Duration(milliseconds: 300);
Widget child;
_PopRoute({@required this.child});
@override
Color get barrierColor => null;
@override
bool get barrierDismissible => true;
@override
String get barrierLabel => null;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return child;
}
@override
Duration get transitionDuration => _duration;
}
點擊控件之后怎么響應(yīng)事件更新視圖
Widget從大體上可以分為StatelessWidget和StatefullWidget,其中StatefullWidget可以根據(jù)setState方法觸發(fā)對整個視圖的刷新。
粗略地救拉,StatefullWidget的生命周期可以分為initState,build,dispose难审。
在調(diào)用了setState方法之后,會觸發(fā)build方法使子widget重新構(gòu)建亿絮。
方法如下:
class SetStateDemoWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return SetStateDemoWidgetState();
}
}
class SetStateDemoWidgetState extends State<SetStateDemoWidget> {
var index = 0;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
Widget numberText = Text(
"$index",
style: TextStyle(fontSize: 18, color: Colors.cyan),
);
Widget addButton = Container(
margin: EdgeInsets.only(top: 16),
child: FlatButton(
onPressed: _buttonClicked,
child: Icon(
Icons.add,
size: 36,
),
),
);
return Scaffold(
body: Container(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
numberText,
addButton,
],
),
),
);
}
_buttonClicked() {
setState(() {
++index;
});
}
}
在_buttonClicked方法響應(yīng)了點擊事件之后告喊,再調(diào)用setState方法將index自增,此時會觸發(fā)build重新渲染view tree.
InheritedWidget怎么用
InheritedWidget用來解決父Widget的數(shù)據(jù)變化時通知子Widget派昧。
具體用法是黔姜,父Widget繼承自InheritedWidget,并定義一個static方法用來對外提供對象實例蒂萎,另外有一個child屬性用于設(shè)置子Widget秆吵,還要設(shè)置數(shù)據(jù),用于給這個子widget引用五慈。同時覆寫是否刷新數(shù)據(jù)的方法纳寂。當數(shù)據(jù)變化時,子widget就收到通知了泻拦。使用方法如下:
///先繼承InheritedWidget
class InheritedData extends InheritedWidget {
final String data;//用于給子Widget使用
InheritedData({
this.data,
Widget child,//子Widget
}) : super(child: child);
//提供static方法給外部使用
static InheritedData of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<InheritedData>();
}
//判斷是否通知子Widget
@override
bool updateShouldNotify(InheritedData old) => true;
}
///子Widget定義
class TestInheritedDataWidget extends StatefulWidget {
@override
_TestInheritedDataWidgetState createState() =>
_TestInheritedDataWidgetState();
}
class _TestInheritedDataWidgetState extends State<TestInheritedDataWidget> {
@override
Widget build(BuildContext context) {
return Center(
child: Text(
//引用父Widget的數(shù)據(jù)
"${InheritedData.of(context).data}",
style: TextStyle(fontSize: 18, color: Colors.pink),
),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
//收到通知
print("didChangeDependencies invoked");
}
}
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
var data = "you are beautiful";
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Container(
color: Colors.cyan,
width: double.infinity,
child: InheritedData(
data: data,
child: TestInheritedDataWidget(),
),
),
onTap: _buttonClicked,
),
);
}
_buttonClicked() {
setState(() {
//點擊按鈕之后數(shù)據(jù)發(fā)生變化
data = "in white";
});
}
}
LinearLayout的sumWeight以及單個child的weight怎么實現(xiàn)
用Expanded Widget可以實現(xiàn)這種效果毙芜。源碼里面針對Expanded的注釋是它作為Row,Column争拐,F(xiàn)lex的子Widget腋粥,里面可以含有多個子Widget,并且每個子widget通過flex來分配剩余的空間。
用下面這個例子說明:
class FlexWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Container(
color: Colors.pink,
),
flex: 1,
),
Expanded(
child: Container(
color: Colors.lightGreenAccent,
),
flex: 2,
),
],
),
),
);
}
}
運行下看效果可以知道隘冲,粉色占據(jù)了三分之一金赦,淺綠色占據(jù)了三分之二的空間。
視圖比較復(fù)雜对嚼,寫的代碼比較長夹抗,導(dǎo)致代碼結(jié)尾有海量的反括號,怎么優(yōu)化
還是以上面實現(xiàn)weight效果的代碼做示例纵竖,可以看到代碼后邊有9個反括號漠烧,看起來比較累,容易造成心理壓力靡砌。
class FlexWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
var child1 = Expanded(
child: Container(
color: Colors.pink,
),
flex: 1,
);
var child2 = Expanded(
child: Container(
color: Colors.lightGreenAccent,
),
flex: 2,
);
var child3 = Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
child1,
child2,
],
);
var child4 = Container(
width: double.infinity,
child: child3,
);
return Scaffold(
body: child4,
);
}
}
把每個child拆出來作為一個widget賦值給一個變量已脓,這樣就可以將一些小的邏輯分出來,避免每個子widget的業(yè)務(wù)邏輯堆積在一起通殃,使得邏輯看起來比較清晰度液。
FrameLayout怎么實現(xiàn)
Stack配合Positioned使用,可以實現(xiàn)FrameLayout的效果画舌。
class StackDemoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
var child = Stack(
children: <Widget>[
Container(
width: 480,
height: 480,
color: Colors.pink,
),
Positioned(
child: Container(
width: 360,
height: 360,
color: Colors.amber,
),
top: 84,
bottom: 84,
),
Positioned(
child: Container(
width: 240,
height: 240,
color: Colors.deepOrange,
),
right: 12,
left: 12,
)
],
);
var child1 = Center(
child: Container(
child: child,
width: 480,
height: 480,
),
);
return Scaffold(
body: child1,
);
}
}
Positioned的左右上下屬性是相對于Stack邊緣的偏移堕担,如果設(shè)置左右或上下會影響到Positioned的寬高。
數(shù)據(jù)處理篇
Android的SharePreference在這里怎么用
在工程下的pubspec.yaml文件中添加引用:
dependencies:
shared_preferences: ^0.5.6
然后在命令行執(zhí)行:
flutter pub get
然后在代碼中就可以使用SharePreference的功能了曲聂。flutter中的SharePreference跟Android里面的一樣霹购,可能也會阻塞UI線程,需要使用async修飾相關(guān)方法朋腋。使用舉例:
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferenceUtil {
static SharedPreferences _prefs;
SharedPreferenceUtil._privateConstructor();
static final SharedPreferenceUtil instance =
SharedPreferenceUtil._privateConstructor();
Future<SharedPreferences> get prefs async {
if (_prefs != null) return _prefs;
_prefs = await SharedPreferences.getInstance();
return prefs;
}
static const String KEY_SEARCH_HISTORY = "search_history";
static const String KEY_USER_TOKEN = "token";
void setValue(String key, String value) async {
SharedPreferences sharedPreferences = await instance.prefs;
sharedPreferences.setString(key, value);
}
Future<String> getValue(String key) async {
SharedPreferences sharedPreferences = await instance.prefs;
return sharedPreferences.getString(key);
}
}
在這里我們把SharedPreferences抽象出來齐疙,做一個單例對外提供。所有的set和get必須用async修飾旭咽,因為他是阻塞式的贞奋。調(diào)用的地方舉例:
SharedPreferenceUtil.instance
.getValue(SharedPreferenceUtil.KEY_USER_TOKEN)
.then((value) {
//do something
});
這里getValue之后,然后調(diào)用then方法穷绵,意思是執(zhí)行完了轿塔,在then方法里面獲取到返回的值,可以做接下來的操作了请垛。
Android的數(shù)據(jù)庫怎么用
在工程下的pubspec.yaml文件中添加引用:
dependencies:
sqflite: ^1.1.7+3
然后在命令行執(zhí)行:
flutter pub get
然后再代碼中就可以使用數(shù)據(jù)庫的相關(guān)功能了催训。
使用示例:
import 'package:flutter_hello/database/user_info_impl.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DataBaseHelper {
static Database _database;
static final _databaseName = "coupon.db";
static final _databaseVersion = 1;
static const String _CREATE_TABLE = "CREATE TABLE IF NOT EXISTS ";
DataBaseHelper._privateConstructor();
static final DataBaseHelper instance = DataBaseHelper._privateConstructor();
Future<Database> get database async {
if (_database != null) return _database;
_database = await _initDatabase();
return _database;
}
_initDatabase() async {
String path = join(await getDatabasesPath(), _databaseName);
return await openDatabase(path,
version: _databaseVersion, onCreate: _onCreate);
}
Future _onCreate(Database db, int version) async {
await db.execute(_CREATE_TABLE +
UserInfoImpl.TABLE_USER_INFO +
UserInfoImpl.generateCreateSql());
}
Future<int> insert(String dbName, Map<String, dynamic> data) async {
Database db = await instance.database;
return db.insert(
dbName,
data,
);
}
Future<List<Map<String, dynamic>>> query(String dbName) async {
Database db = await instance.database;
final List<Map<String, dynamic>> maps = await db.query(dbName);
return maps;
}
Future<void> update(String tableName, Map<String, dynamic> data, String keyId,
String equalValue) async {
Database db = await instance.database;
await db.update(
tableName,
data,
where: "$keyId = ?",
whereArgs: [equalValue],
);
}
Future<void> delete(String tableName, String keyId, String equalValue) async {
Database db = await instance.database;
await db.delete(
tableName,
where: "$keyId = ?",
whereArgs: [equalValue],
);
}
}
這是一個數(shù)據(jù)庫操作的類,將增刪改查抽象出來宗收,做具體的操作漫拭。在做操作之前獲取數(shù)據(jù)庫操作句柄database,其實他也是一個單例混稽,使用了單例模式采驻。
具體操作類實現(xiàn)之后审胚,如果我們要操作具體的某個表就比較方便了。比如針對user_info這個表的操作類UserInfoImpl礼旅,我們可以這樣操作:
insertUserInfo(UserInfoBean userInfoBean) {
DataBaseHelper.instance.insert(TABLE_USER_INFO, userInfoBean.toJson());
}
Future<UserInfoBean> getUserInfo() async {
List<Map<String, dynamic>> lists =
await DataBaseHelper.instance.query(TABLE_USER_INFO);
if (lists != null && lists.length > 0) {
return UserInfoBean.fromJson(lists[0]);
}
return null;
}
deleteUserInfo(String userId) {
DataBaseHelper.instance.delete(TABLE_USER_INFO, "id", userId);
}
updateUserInfo(UserInfoBean bean) {
DataBaseHelper.instance
.update(TABLE_USER_INFO, bean.toJson(), "id", bean.id.toString());
}
Json數(shù)據(jù)怎么處理
在工程下的pubspec.yaml文件中添加引用:
//庫版本可能已經(jīng)更新膳叨,可以在https://pub.dev/packages/網(wǎng)站搜索最新的版本
dev_dependencies:
build_runner: ^1.0.0
json_serializable: ^2.0.0
然后在命令行執(zhí)行:
flutter pub get
接下來就可以處理json數(shù)據(jù)了。處理json數(shù)據(jù)主要分三個步驟:
- 根據(jù)網(wǎng)絡(luò)請求的字段痘系,定義對應(yīng)的類及屬性字段
- 在類中添加序列化和反序列化接口
- 通過flutter指令生成對應(yīng)的賦值處理
用以下json數(shù)據(jù)舉例說明:
{
"total_count":12,
"total_page":1,
"page":1,
"data":[]
}
首先定義類及屬性字段菲嘴,文件名是withdraw_detail_bean.dart
import 'package:json_annotation/json_annotation.dart';
part 'withdraw_detail_bean.g.dart';
@JsonSerializable()
class WithdrawDetailBean {
WithdrawDetailBean(this.data, this.totalPage, this.totalCount, this.page);
@JsonKey(name: "total_count")
int totalCount;
@JsonKey(name: "total_page")
int totalPage;
int page;
List<WithDrawItemBean> data;
factory WithdrawDetailBean.fromJson(Map<String, dynamic> json) =>
_$WithdrawDetailBeanFromJson(json);
Map<String, dynamic> toJson() => _$WithdrawDetailBeanToJson(this);
}
首先要添加part
'withdraw_detail_bean.g.dart',因為part關(guān)鍵字可以幫助創(chuàng)建一個模塊化的汰翠,可共享的代碼庫龄坪。后面執(zhí)行指令的時候可以生成一個withdraw_detail_bean.g.dart文件,里面包含了當前數(shù)據(jù)序列化相關(guān)的處理代碼复唤。
其次在定義的類前面添加JsonSerializable注解健田。
定義構(gòu)造函數(shù),定義與返回數(shù)據(jù)對應(yīng)的key佛纫,通過JsonKey注解妓局,可以簡化key對應(yīng)的變量命名。
定義序列化和反序列化的接口呈宇,用于外部調(diào)用
最后一步就是執(zhí)行生成處理json的代碼好爬,用指令實現(xiàn),分兩種攒盈。
一種是一次生成抵拘,后面再有修改的話,再執(zhí)行型豁,再生成:
flutter packages pub run build_runner build
另外一種是持續(xù)生成,一旦修改了json定義類尚蝌,就會馬上自動生成對應(yīng)的處理類:
flutter packages pub run build_runner watch
最后可以看到data數(shù)組里面定義了一個WithDrawItemBean類迎变,同樣的WithDrawItemBean類,需要再像上面這樣定義一遍就可以了飘言。
網(wǎng)絡(luò)請求
網(wǎng)絡(luò)請求一般借助現(xiàn)有的庫實現(xiàn)衣形,http庫對于各種方式的網(wǎng)絡(luò)請求支持比較好,網(wǎng)址是:https://pub.dev/packages/http姿鸿。
get請求
這里以最復(fù)雜的情況做例子谆吴,query + header:
Future<NetResponseBean> _getResponse(
String baseUrl, String api, Map<String, String> queryMap) async {
var _client = http.Client();
if (!api.contains("?")) {
api = api + "?";
}
try {
var url = baseUrl + api + _getQuery(queryMap);
Map<String, String> headers = Map();
_getCommonHeaders(headers);
http.Response response = await _client.get(url, headers: headers);
if (response.statusCode == HttpStatus.ok) {
var body = response.body;
Map<String, dynamic> data = jsonDecode(body);
NetResponseBean responseData = NetResponseBean.fromJson(data);
if (responseData != null) {
return responseData;
}
}
} catch (exception, stackTrace) {
print("_getResponse stackTrace:$stackTrace");
} finally {
if (_client != null) {
_client.close();
}
}
return null;
}
第一步,獲取client對象苛预,這個對象在finally里面必須關(guān)掉句狼,跟Android的操作是一樣的。
第二步热某,拼接query到url中腻菇。
第三步胳螟,填充header到一個Map集合中。
這幾步走完了調(diào)用對應(yīng)的接口筹吐,填充對應(yīng)的參數(shù)就可以了糖耸。
檢查返回碼,ok的話丘薛,獲取body數(shù)據(jù)嘉竟,轉(zhuǎn)換成json格式,然后用對應(yīng)json類序列化就可以使用返回的數(shù)據(jù)了洋侨。
post請求
這里以最復(fù)雜的情況舉例舍扰,header + request body
Future<NetResponseBean> _getPostResponse(
String api, Map<String, String> parameters) async {
var _client = http.Client();
var url = _BAE_URL + api;
try {
//設(shè)置header
Map<String, String> headersMap = new Map();
_getCommonHeaders(headersMap);
http.Response response = await _client.post(url,
headers: headersMap, body: parameters, encoding: Utf8Codec());
if (response.statusCode == 200) {
String bodyData = response.body;
if (bodyData != null) {
Map<String, dynamic> data = jsonDecode(bodyData);
NetResponseBean response = NetResponseBean.fromJson(data);
return response;
}
} else {
print('_getPostResponse error');
}
} catch (exception) {
print("_getPostResponse fail:" + exception.toString());
} finally {
if (_client != null) {
_client.close();
}
}
return null;
}
跟上面get請求的流程差別不大,主要是接口調(diào)用不同凰兑,另外需要注意的是這里body是dynamic類型妥粟,傳的是Map集合。當然也可以傳json字符串吏够,一個常見的場景是bean類轉(zhuǎn)化為json勾给,然后傳遞,對應(yīng)的處理是:
var body = json.encode(jsonBean);
上傳
Future requestUploadFile(File file, Function callBack) async {
var url = _URL + "your sub url";
var client = http.MultipartRequest("post", Uri.parse(url));
http.MultipartFile.fromPath(
'file',
file.path,
).then((http.MultipartFile file) {
_getOSCommonHeaders(client.headers);
client.files.add(file);
client.fields["description"] = "descriptiondescription";
client.send().then((http.StreamedResponse response) {
if (response.statusCode == 200) {
response.stream.transform(utf8.decoder).join().then((String string) {
print("requestUploadFile:$string");
Map<String, dynamic> data = jsonDecode(string);
if (data != null && data.length > 0) {
NetResponseBean uploadResponse = NetResponseBean.fromJson(data);
if (uploadResponse != null) {
if (uploadResponse.code == 0) {
UploadImageResultBean uploadImageResultBean =
UploadImageResultBean.fromJson(uploadResponse.data);
callBack(uploadImageResultBean.fileUrl, 0);
} else {
callBack("", uploadResponse.code);
}
}
}
});
}
}).catchError((error) {
print("requestUploadFile error:$error");
});
});
}
代碼如上锅知,一般性的上傳操作播急。
下載
Future<File> _downloadFile(String url, String filename) async {
http.Client _client = new http.Client();
try {
var req = await _client.get(Uri.parse(url));
var bytes = req.bodyBytes;
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/$filename');
await file.writeAsBytes(bytes);
return file;
} finally {
_client.close();
}
}
上面是比較簡單的下載,下面從別處抄一個帶進度的:
https://gist.github.com/ajmaln/c591cfb71d66bb6e688fe7027cbbe606
import 'dart:typed_data';
import 'dart:io';
import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';
downloadFile(String url, {String filename}) async {
var httpClient = http.Client();
var request = new http.Request('GET', Uri.parse(url));
var response = httpClient.send(request);
String dir = (await getApplicationDocumentsDirectory()).path;
List<List<int>> chunks = new List();
int downloaded = 0;
response.asStream().listen((http.StreamedResponse r) {
r.stream.listen((List<int> chunk) {
// Display percentage of completion
debugPrint('downloadPercentage: ${downloaded / r.contentLength * 100}');
chunks.add(chunk);
downloaded += chunk.length;
}, onDone: () async {
// Display percentage of completion
debugPrint('downloadPercentage: ${downloaded / r.contentLength * 100}');
// Save the file
File file = new File('$dir/$filename');
final Uint8List bytes = Uint8List(r.ntentLength);
int offset = 0;
for (List<int> chunk in chunks) {
bytes.setRange(offset, offset + chunk.length, chunk);
offset += chunk.length;
}
await file.writeAsBytes(bytes);
return;
});
}
公眾號: