Unity的Flutter——UIWidgets简介及入门
介紹
UIWidgets(https://github.com/UnityTech/UIWidgets)是Unity編輯器的一個插件包,可幫助開發人員通過Unity引擎來創建、調試和部署高效的跨平臺應用。
UIWidgets主要來自Flutter。但UIWidgets通過使用強大的Unity引擎為開發人員提供了許多新功能,顯著地改進他們開發的應用性能和工作流程。
效率
通過使用最新的Unity渲染SDK,UIWidgets應用可以非常快速地運行并且大多數時間保持大于60fps的速度。
跨平臺
與任何其他Unity項目一樣,UIWidgets應用可以直接部署在各種平臺上,包括PC,移動設備和網頁等。
多媒體支持
除了基本的2D UI之外,開發人員還能夠將3D模型,音頻,粒子系統添加到UIWidgets應用中。
文檔
官方文檔只是簡單介紹,代碼中沒有任何中英文注釋,部分demo還未完成。
不過由于基于Flutter,大部分API與Flutter一致,所以可以參考Flutter文檔,或者先學習一下Flutter。
Unity Connect App
Unity Connect App是使用UIWidgets開發的一個移動App產品,可以在Android下載以及iOS App Store下載最新的版本.
github地址https://github.com/UnityTech/ConnectAppCN.
安裝
首先需要 Unity 2018.3 或更高版本。
新建一個unity項目或一個已有的項目。
訪問UIWidgets的github庫https://github.com/UnityTech/UIWidgets下載最新UIWidgets包,將其移至項目的Package文件夾中
或者可以在終端中通過git命令來完成這個操作:
cd <YourProjectPath>/Packagesgit clone https://github.com/UnityTech/UIWidgets.git com.unity.uiwidgets官方示例
在示例中,我們將創建一個非常簡單的UIWidgets應用。 該應用只包含文本標簽和按鈕。 文本標簽將計算按鈕上的點擊次數。
首先,使用Unity編輯器打開項目。
場景構建
選擇 File > New Scene來創建一個新場景。
選擇 GameObject > UI > Canvas
在場景中創建UI Canvas。
右鍵單擊Canvas并選擇UI > Panel,將面板添加到UI Canvas中。 然后刪除面板中的 Image 組件。
最后為場景命名并保存至Assets/Scenes目錄下
創建部件
創建一個新C#腳本,命名為“UIWidgetsExample.cs”
using System.Collections.Generic;using Unity.UIWidgets.animation;using Unity.UIWidgets.engine;using Unity.UIWidgets.foundation;using Unity.UIWidgets.material;using Unity.UIWidgets.painting;using Unity.UIWidgets.ui;using Unity.UIWidgets.widgets;using UnityEngine;using FontStyle = Unity.UIWidgets.ui.FontStyle;namespace UIWidgetsSample {public class UIWidgetsExample : UIWidgetsPanel {protected override void OnEnable() {// if you want to use your own font or font icons.// FontManager.instance.addFont(Resources.Load<Font>(path: "path to your font"), "font family name");// load custom font with weight & style. The font weight & style corresponds to fontWeight, fontStyle of// a TextStyle object// FontManager.instance.addFont(Resources.Load<Font>(path: "path to your font"), "Roboto", FontWeight.w500,// FontStyle.italic);// add material icons, familyName must be "Material Icons"// FontManager.instance.addFont(Resources.Load<Font>(path: "path to material icons"), "Material Icons");base.OnEnable();}protected override Widget createWidget() {return new WidgetsApp(home: new ExampleApp(),pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>new PageRouteBuilder(settings: settings,pageBuilder: (BuildContext context, Animation<float> animation,Animation<float> secondaryAnimation) => builder(context)));}class ExampleApp : StatefulWidget {public ExampleApp(Key key = null) : base(key) {}public override State createState() {return new ExampleState();}}class ExampleState : State<ExampleApp> {int counter = 0;public override Widget build(BuildContext context) {return new Column(children: new List<Widget> {new Text("Counter: " + this.counter),new GestureDetector(onTap: () => {//這里使用setState來改變counter的值則可以同步改變Text顯示,如果不用直接counter++則無法改變顯示 this.setState(() => {this.counter++;});},child: new Container(padding: EdgeInsets.symmetric(20, 20),color: Colors.blue,child: new Text("Click Me")))});}}}}保存腳本,并附加到panel中作為其組件。
(如果添加失敗,請檢查文件名與類名是否一致)
保存場景,運行就可以看到效果了。
Image組件
簡單介紹一下Image組件
加載資源文件
將資源圖片放入Assets/Resources目錄下
使用asset函數即可創建一個Image并加載相應資源
Unity.UIWidgets.widgets.Image.asset("test")注意不需要文件后綴。
加載網絡資源
Unity.UIWidgets.widgets.Image.network("https://www.baidu.com/img/xinshouyedong_4f93b2577f07c164ae8efa0412dd6808.gif")Image支持Gif!可以直接加載顯示Gif。
除了上面兩種方式,還可以通過文件和byte數組來加載資源,函數分別為file()和memory()。
改變大小等屬性
Unity.UIWidgets.widgets.Image.asset(name: "test",height: 100 )這里涉及到默認參數,先來看一個aseet函數源碼
public static Image asset(string name,Key key = null,AssetBundle bundle = null,float? scale = null,float? width = null,float? height = null,Color color = null,BlendMode colorBlendMode = BlendMode.srcIn,BoxFit? fit = null,Alignment alignment = null,ImageRepeat repeat = ImageRepeat.noRepeat,Rect centerSlice = null,bool gaplessPlayback = false,FilterMode filterMode = FilterMode.Bilinear) {var image = scale != null? (AssetBundleImageProvider) new ExactAssetImage(name, bundle: bundle, scale: scale.Value): new AssetImage(name, bundle: bundle);return new Image(key,image,width,height,color,colorBlendMode,fit,alignment,repeat,centerSlice,gaplessPlayback,filterMode);}除了name,其他參數都設置了默認參數,這樣在使用這個函數時,無需改變默認參數的參數不必傳入,這樣就需要傳參時帶上參數名。但是如果只傳一個name參數的時候,可以省略參數名。
所以想改變或設置Image或其他組件的屬性時,只需要添加對應參數即可。
如改變圖片的拉伸規則
Unity.UIWidgets.widgets.Image.asset(name: "test",height: 100,width: 100,fit: Unity.UIWidgets.painting.BoxFit.fill )Navigation頁面跳轉
參考官方示例中的demo,簡化代碼如下:
using System; using System.Collections.Generic; using Unity.UIWidgets.animation; using Unity.UIWidgets.engine; using Unity.UIWidgets.foundation; using Unity.UIWidgets.gestures; using Unity.UIWidgets.painting; using Unity.UIWidgets.rendering; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets; using TextStyle = Unity.UIWidgets.painting.TextStyle;namespace UIWidgetsSample {public class NavigationEx : UIWidgetsPanel{protected override Widget createWidget() {return new WidgetsApp(initialRoute: "/",textStyle: new TextStyle(fontSize: 24),pageRouteBuilder: this.pageRouteBuilder,//初始化所有route路由routes: new Dictionary<string, WidgetBuilder> {{"/", (context) => new HomeScreen()},{"/detail", (context) => new DetailScreen()}});}protected PageRouteFactory pageRouteBuilder {get {return (RouteSettings settings, WidgetBuilder builder) =>new PageRouteBuilder(settings: settings,pageBuilder: (BuildContext context, Animation<float> animation,Animation<float> secondaryAnimation) => builder(context),//設置轉場動畫,去掉則沒有轉場動畫transitionsBuilder: (BuildContext context, Animation<float>animation, Animation<float> secondaryAnimation, Widget child) =>new _FadeUpwardsPageTransition(routeAnimation: animation,child: child));}}}//首頁class HomeScreen : StatelessWidget {public override Widget build(BuildContext context) {//封裝了一個NavigationPage,詳細見后續代碼return new NavigationPage(body: new Container(//設置背景色color: new Color(0xFF888888),//Center組件可以實現相對parent居中child: new Center(//設置按鈕點擊后跳轉到“/detail”child: new GestureDetector(onTap: () => { Navigator.pushNamed(context, "/detail"); },child: new Text("Go to Detail")))),title: "Home");}}//詳情頁class DetailScreen : StatelessWidget {public override Widget build(BuildContext context) {return new NavigationPage(body: new Container(color: new Color(0xFF1389FD),child: new Center(child: new Column(children: new List<Widget>() {//設置按鈕點擊關閉頁面,回到上一頁new GestureDetector(onTap: () => { Navigator.pop(context); }, child: new Text("Back"))}))),title: "Detail");}}//轉場動畫class _FadeUpwardsPageTransition : StatelessWidget {internal _FadeUpwardsPageTransition(Key key = null,Animation<float> routeAnimation = null, // The route's linear 0.0 - 1.0 animation.Widget child = null) : base(key: key) {//設置滑動的偏移動畫,并且添加了動畫曲線fastOutSlowIn,具體見后面代碼this._positionAnimation = _bottomUpTween.chain(_fastOutSlowInTween).animate(routeAnimation);//設置顯隱動畫,并且添加了動畫曲線easeIn,具體見后面代碼this._opacityAnimation = _easeInTween.animate(routeAnimation);this.child = child;}//設置(滑動)動畫的x、y偏移,是百分比值。開始點是x無偏移,y偏移0.25,即從頁面高度的1/4開始。結束點是無偏移,即原位置。所以動畫是從頁面1/4處向上滑動到頂部static Tween<Offset> _bottomUpTween = new OffsetTween(begin: new Offset(0.0f, 0.25f),end: Offset.zero);//動畫曲線fastOutSlowIn,先加速再減速static Animatable<float> _fastOutSlowInTween = new CurveTween(curve: Curves.fastOutSlowIn);//動畫曲線easeIn,初始緩動static Animatable<float> _easeInTween = new CurveTween(curve: Curves.easeIn);readonly Animation<Offset> _positionAnimation;readonly Animation<float> _opacityAnimation;public readonly Widget child;public override Widget build(BuildContext context) {//轉場動畫是包含一個滑動動畫和一個顯隱動畫,是一個層次關系return new SlideTransition(position: this._positionAnimation,child: new FadeTransition(opacity: this._opacityAnimation,//需要實現動畫的組件child: this.child));}}//封裝了一個導航布局,頂部導航欄,底部頁面內容class NavigationPage : StatelessWidget {//頁面內容public readonly Widget body;//頁面標題,顯示在導航欄public readonly string title;public NavigationPage(Widget body = null, string title = null) {this.title = title;this.body = body;}public override Widget build(BuildContext context) {Widget back = null;//判斷是否可以回退頁面,即頁面列表的size大于1。如果可以則創建back按鈕,點擊關閉當前頁回退到上一頁if (Navigator.of(context).canPop()) {back = new GestureDetector(onTap: () => { Navigator.pop(context); },child: new Text("Go Back"));back = new Column(mainAxisAlignment: MainAxisAlignment.center, children: new List<Widget>() {back});}return new Container(child: new Column(children: new List<Widget>() {//頂部是一個導航欄,這里可以看到用了ConstrainedBox和DecoratedBoxnew ConstrainedBox(constraints: new BoxConstraints(maxHeight: 80),child: new DecoratedBox(decoration: new BoxDecoration(color: new Color(0XFFE1ECF4)),//NavigationToolbar導航工具欄,其中分為三個區域:左側leading、中間middle和右側trailing。這里左側放置了回退按鈕child: new NavigationToolbar(leading: back,middle: new Text(this.title, textAlign: TextAlign.center)))),//內容部分。這里使用了一個Flexible,作用是可以填滿剩余的空間,如果使用普通的container無法達到效果new Flexible(child: this.body)}));}} }可以看到在創建WidgetsApp時,設定所有路由的路徑和實現,然后通過Navigator類切換路徑來控制頁面跳轉。
Material
UIWidgets同樣提供了Material風格的組件,通過一個demo簡單認識一下
using System.Collections.Generic; using Unity.UIWidgets.animation; using Unity.UIWidgets.engine; using Unity.UIWidgets.material; using Unity.UIWidgets.painting; using Unity.UIWidgets.rendering; using Unity.UIWidgets.service; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets; using UnityEngine; using Image = Unity.UIWidgets.widgets.Image;namespace UIWidgetsEx {public class MaterialEx : UIWidgetsPanel{protected override Widget createWidget(){return new MaterialApp(home: new MaterialThemeSampleWidget(),darkTheme: new ThemeData(primaryColor: Colors.black26));}protected override void OnEnable(){//加載Material字體,用于iconFontManager.instance.addFont(Resources.Load<Font>(path: "MaterialIcons-Regular"), "Material Icons");base.OnEnable();}}public class MaterialThemeSampleWidget : StatefulWidget{public override State createState(){return new _MaterialThemeSampleWidgetState();}}class _MaterialThemeSampleWidgetState : State<MaterialThemeSampleWidget>{//GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>.key("lalala");public override Widget build(BuildContext context){return new Theme(//data設置樣式data: new ThemeData(appBarTheme: new AppBarTheme(color: Colors.purple),bottomAppBarTheme: new BottomAppBarTheme(color: Colors.blue),cardTheme: new CardTheme(color: Colors.red,//設置深度,即陰影大小elevation: 2.0f)),//頁面根布局是一個Scaffold,大致分為三個區域appBar、body和bottomNavigationBar。child: new Scaffold(//key: scaffoldKey,//導航欄AppBarappBar: new AppBar(//設置深度,即陰影大小//elevation: 25f,title: new Text("Test App Bar Theme")),body: new Center(//組件Card,自帶陰影效果child: new Card(//設置深度,即陰影大小//elevation: 25f,//設置圓角borderRadius,還可以設置邊框sideshape: new RoundedRectangleBorder(//side: new BorderSide(Colors.blue, 5.0f),borderRadius: BorderRadius.all(5.0f)),child: new Container(height: 250,child: new Column(children: new List<Widget> {Image.asset("products/backpack",fit: BoxFit.cover,width: 200,height: 200),new Text("Card Theme")})))),//底部是BottomAppBarbottomNavigationBar: new BottomAppBar(child: new Row(mainAxisSize: MainAxisSize.max,//設置對齊方式mainAxisAlignment: MainAxisAlignment.spaceBetween,children: new List<Widget> {//底部是兩個IconButton,使用字體中的iconnew IconButton(icon: new Icon(Unity.UIWidgets.material.Icons.menu), onPressed: () => { }),new IconButton(icon: new Icon(Unity.UIWidgets.material.Icons.account_balance), onPressed: () => { })}))//,//floatingActionButton: new FloatingActionButton(// child: new Text("go"),// onPressed: () => {// scaffoldKey.currentState.showSnackBar(// new SnackBar(// content: new Text("go")// )// );// }//),//floatingActionButtonLocation: FloatingActionButtonLocation.endDocked));}} }有關Material部分跟Android基本一致,比如組件都有深度屬性elevation,比如Card就是Material中的CardView。
IconButtom使用字體Font,實際上就是5.0之后引入的SVG,即Vector Image
我們簡單補充一下:
Scaffold
實際上對應著Android中的CoordinateLayout,分為三大區域appBar、body和bottomNavigationBar,當未設置時則隱藏該區域,body會補充填充。
除了三大區域還包括:懸浮按鈕floatingActionButton、bottomSheet、drawer、endDrawer等
其中floatingActionButton同樣保持著android中的behavior效果,下面會詳細介紹
AppBar
AppBar則包含:title、左側leading、右側擴展菜單actions
MainAxisAlignment
對齊方式,這個對齊方式相對于android更靈活一些,包括:
- start 左(上)側對齊
- end 右(下)側對齊
- center 居中對齊
- spaceBetween 每兩個item的間隔space相同,兩側的item與父布局沒有間隔
- spaceAround 每個item四周都有同樣的間隔,相當于margin,所以兩個item之間是兩倍間隔
- spaceEvenly item在父布局中均勻分布,即所有間隔相同
FloatingActionButton
在Scaffold中floatingActionButton和floatingActionButtonLocation要搭配使用,floatingActionButtonLocation決定著按鈕的位置,包含:
- endFloat: body的右下角
- endDocked: bottomNavigationBar右側,中心錨點在bottomNavigationBar的頂端
- centerDocked: bottomNavigationBar中間,中心錨點在bottomNavigationBar的頂端
- centerFloat: body的底部中間
- startTop:appBar左側,中心錨點appBar的底端
- miniStartTop:與startTop類似,與左側間隔更小一點
- endTop:appBar右側,中心錨點appBar的底端
除了上面這些我們還可以實現FloatingActionButtonLocation這個抽象類自定義位置
SnackBar
在Android中FloatingActionButton是存在一個默認behavior的,最明顯的就是與SnackBar互動。
在UIWidgets中也有SnackBar組件,使用起來比較簡單。
首先需要創建一個GlobalKey,并將它賦給Scaffold的key。這樣我們就可以使用
scaffoldKey.currentState.showSnackBar()這個函數來顯示一個SnackBar。
Animation
在前面的示例中我們使用了轉場動畫,但是并沒有體現動畫的完整應用,因為部分邏輯比如執行動畫等封裝在底層了。下面這個例子將完整的展現如何創建并執行一個動畫。
using System.Collections.Generic; using Unity.UIWidgets.animation; using Unity.UIWidgets.cupertino; using Unity.UIWidgets.engine; using Unity.UIWidgets.foundation; using Unity.UIWidgets.scheduler; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets;namespace UIWidgetsSample {public class AnimEx : UIWidgetsPanel{protected override void OnEnable(){base.OnEnable();}protected override Widget createWidget(){return new WidgetsApp(home: new ExampleApp(),pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>new PageRouteBuilder(settings: settings,pageBuilder: (BuildContext context, Animation<float> animation,Animation<float> secondaryAnimation) => builder(context)));}class ExampleApp : StatefulWidget{public ExampleApp(Key key = null) : base(key){}public override State createState(){return new ExampleState();}}class ExampleState : State<ExampleApp>{//IconData iconData = Unity.UIWidgets.material.Icons.menu;//Curve switchCurve = new Interval(0.4f, 1.0f, curve: Curves.fastOutSlowIn);//TextAnim類用一個動畫組件將Image組件封裝起來,見后面TextAnim textAnim = new TextAnim();public override Widget build(BuildContext context){return new Column(children: new List<Widget> {切換動畫//new AnimatedSwitcher(// duration: new System.TimeSpan(0, 0, 1),// switchInCurve: switchCurve,// switchOutCurve: switchCurve,// child: new IconButton(// //不同的key才會認為是不同的組件,否則不會執行動畫// key: new ValueKey<IconData>(iconData),// icon: new Icon(icon :iconData, color: Colors.white),// onPressed: () => {// this.setState(() => {// if (iconData.Equals(Unity.UIWidgets.material.Icons.menu))// {// iconData = Unity.UIWidgets.material.Icons.close;// }// else// {// iconData = Unity.UIWidgets.material.Icons.menu;// }// });// }// )//),//這里使用了Cupertino風格的buttonnew CupertinoButton(onPressed: () => {//點擊后通過controller執行動畫textAnim.controller.forward(); },child: new Text("Go"),color: CupertinoColors.activeBlue),textAnim.build(context)});}}//繼承SingleTickerProviderStateMixin,這是TickerProvider接口的實現類private class TextAnim : SingleTickerProviderStateMixin<ExampleApp>{public AnimationController controller = null;public override Widget build(BuildContext context){//定一個一個AnimationController。TimeSpan(0, 0, 2)代表0小時0分2秒,也就是動畫時長是兩秒controller = new AnimationController(duration: new System.TimeSpan(0, 0, 2), vsync: this);//設置動畫監聽,當結束時還原controller.addStatusListener((status) => {if(status == AnimationStatus.completed){controller.reset();}});//定義Animation,從原點下移自身兩個高度Animation<Offset> offset = controller.drive(new OffsetTween(begin: Offset.zero,end: new Offset(0.0f, 2f)));//創建一個滑動動畫組件return new SlideTransition(position: offset,child: Unity.UIWidgets.widgets.Image.asset(name: "test",height: 100));}}}}執行后點擊button,圖片會執行下移動畫,執行完回到原位置。
當然我們還可以設置動畫曲線、組合多個動畫等等。
而且我們可以看到使用了ios的Cupertino風格的button。與Material一樣,UIWidgets也有一套Cupertino風格的組件。
vsync
創建AnimationController時,需要設置一個vsync,是TickerProvider類型的,即計時器。那么這個東西到底有什么用?
vsync對象會綁定動畫的定時器到一個可視的widget,所以當widget不顯示時,動畫定時器將會暫停,當widget再次顯示時,動畫定時器重新恢復執行,這樣就可以避免動畫相關UI不在當前屏幕時消耗資源。
AnimatedSwitcher
切換動畫,可以同時對其新、舊子元素添加顯示、隱藏動畫。新舊子元素可以是是兩個組件,也可以利用key將一個組件同時用于新舊子元素。
Demo
github
總結
以上是生活随笔為你收集整理的Unity的Flutter——UIWidgets简介及入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 8.0 targetsd
- 下一篇: Android中的拍照camera和ca