介紹
最近也在想,要如何實現FLutter在Android平臺上顯示Material風格的UI,在ios平臺顯示Cupertino風格的UI,剛好看到一篇外國文章:Do Flutter apps dream of platform aware widgets?,講的就是這個問題,於是自己也跟著實現了一下。總的來說,不難,主要是抽象出一個BasePlatformWidget就可以了。
背景
Flutter是一個新的跨平臺應用程式開發框架。Flutter中有兩套UI元件,分別適用於ios和Android,分別叫做Cupertino和Material。
但是如果希望在各自的平臺上繼續保持原來的風格的話,比如在Android手機上保持Material風格,在ios上保持Cupertino風格的話,那應該如何實現呢?
如果要寫兩套程式碼的話,就太麻煩了。因為兩套程式碼其實有大部分東西是一樣的,只是因為風格不一樣,而用了不一樣的Widget而已。在FLutter中還要注意的一點問題就是,某一些小部件需要一個屬於同一“平臺特定”庫的祖先。舉個例子,RaisedButton和Switch屬於Material包庫,它需要一個Material小部件作為其祖先之一,如果沒有的話,則執行的時候會報出下面的錯誤。
實現思路:我們可以建立個抽象的元件,並讓它根據其執行的平臺,顯示不同風格的Widget。
程式碼實現
建立一個抽象類,子類只需要重寫對應平臺的抽象方法就可以了。
/**
* 會根據平臺,去適配所在平臺的小部件
* Flutter中包含了適用於IOS和Android的兩套原生小部件,名為Cupertino和Material
*/
abstract class BasePlatformWidget<A extends Widget, I extends Widget>
extends StatelessWidget {
A createAndroidWidget(BuildContext context);
I createIosWidget(BuildContext context);
@override
Widget build(BuildContext context) {
/**如果是IOS平臺,返回ios風格的控制元件
* Android和其他平臺都返回materil風格的控制元件
*/
if (Platform.isIOS) {
return createIosWidget(context);
}
return createAndroidWidget(context);
}
}
複製程式碼
下面根據這個基礎的平臺類,去建立一些基本的Widget。比如去建立一個在Android平臺顯示AppBar,在ios平臺顯示CupertinoNavigationBar的Widget。
/**
* 腳手架
*/
class PlatformScaffold
extends BasePlatformWidget<Scaffold, CupertinoPageScaffold> {
PlatformScaffold({this.appBar, this.body});
final PlatformAppBar appBar;
final Widget body;
@override
Scaffold createAndroidWidget(BuildContext context) {
return Scaffold(
appBar: appBar.createAndroidWidget(context),
body: body,
);
}
@override
CupertinoPageScaffold createIosWidget(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: appBar.createIosWidget(context),
child: body,
);
}
}
/**
* AppBar
*/
class PlatformAppBar
extends BasePlatformWidget<AppBar, CupertinoNavigationBar> {
final Widget title;
final Widget leading;
PlatformAppBar({this.title, this.leading});
@override
AppBar createAndroidWidget(BuildContext context) {
return new AppBar(leading: leading, title: title,);
}
@override
CupertinoNavigationBar createIosWidget(BuildContext context) {
return new CupertinoNavigationBar(leading: leading, middle: title,);
}
}
/**
* 對話方塊
*/
class PlatformAlertDialog
extends BasePlatformWidget<AlertDialog, CupertinoAlertDialog> {
final Widget title;
final Widget content;
final List<Widget> actions;
PlatformAlertDialog({this.title, this.content, this.actions});
@override
AlertDialog createAndroidWidget(BuildContext context) {
return new AlertDialog(title: title, content: content, actions: actions,);
}
@override
CupertinoAlertDialog createIosWidget(BuildContext context) {
return new CupertinoAlertDialog(
title: title, content: content, actions: actions,);
}
}
/**
* Switch
*/
class PlatformSwicth extends BasePlatformWidget<Switch, CupertinoSwitch> {
final bool value;
final ValueChanged<bool> onChanged;
PlatformSwicth({this.value, this.onChanged});
@override
Switch createAndroidWidget(BuildContext context) {
return new Switch(value: value, onChanged: onChanged);
}
@override
CupertinoSwitch createIosWidget(BuildContext context) {
return new CupertinoSwitch(value: value, onChanged: onChanged);
}
}
/**
* Button
*/
class PlatformButton extends BasePlatformWidget<FlatButton, CupertinoButton> {
final VoidCallback onPressed;
final Widget child;
PlatformButton({this.onPressed, this.child});
@override
FlatButton createAndroidWidget(BuildContext context) {
return new FlatButton(onPressed: onPressed, child: child);
}
@override
CupertinoButton createIosWidget(BuildContext context) {
return new CupertinoButton(child: child, onPressed: onPressed);
}
}
複製程式碼
執行例項
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
super.initState();
}
bool androidSelected = false;
bool iosSelected = false;
@override
Widget build(BuildContext context) {
return new PlatformScaffold(
appBar: new PlatformAppBar(
title: new Text(widget.title),
),
body: new SafeArea(child: new Container(
child:
new Column(
children: <Widget>[
new CupertinoButton(
child: new Text("顯示ios風格的對話方塊"), onPressed: () {
showDialog(context: context, builder: (context) {
return new CupertinoAlertDialog(
title: new Text("title"),
content: new Text("content"),
actions:
<Widget>[
new CupertinoButton(onPressed: () {},
child: new Text("Cancel")),
new CupertinoButton(onPressed: () {},
child: new Text("Confirm")),
]
,
);
});
}),
new FlatButton(child: new Text("顯示android風格的對話方塊"), onPressed: () {
showDialog(context: context, builder: (context) {
return new AlertDialog(
title: new Text("title"),
content: new Text("content"),
actions:
<Widget>[
new FlatButton(onPressed: () {},
child: new Text("Cancel")),
new FlatButton(onPressed: () {},
child: new Text("Confirm")),
]
,
);
});
}),
//下面這段程式碼在ios平臺執行會出錯,在android平臺可以執行,所以暫時註釋掉,原因就是Switch需要有一個Material的祖先
// new Switch(
// value: androidSelected, onChanged: (value) {
// setState(() {
// androidSelected = value;
// });
// },),
new CupertinoSwitch(value: iosSelected, onChanged: (value) {
setState(() {
iosSelected = value;
});
}),
],
)
),),
);
}
}
複製程式碼