Flutter之根據執行的platform顯示對應風格的widget

入魔的冬瓜發表於2018-12-10

Flutter中的Cupertino和Material小部件

介紹

最近也在想,要如何實現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小部件作為其祖先之一,如果沒有的話,則執行的時候會報出下面的錯誤。

Flutter之根據執行的platform顯示對應風格的widget

實現思路:我們可以建立個抽象的元件,並讓它根據其執行的平臺,顯示不同風格的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;
                });
              }),


            ],
          )
      ),),
    );
  }
}

複製程式碼

demo原始碼

github.com/LXD31256949…

相關文章