本文适合已经会flutter,想在unity中尝试flutter的人群
虽然用UIWidget做的app UnityConnect就要停服了,UIWidget似乎也没什么人维护,Unityconnect github开源代码和flutter对比的话大部分基础函数都是一样的,对于在学习安卓flutter的同学可以尝试一下,很容易,而且我也不会unity中的控件布局,UiWidget的基础控件已经够用了。
个人觉得用这个库开发app还是不太现实,不如直接flutter。有3d交互但是对unity组件不熟练的可以试试。
UIWidget AnimatedBuilder动画的封装
如图是移动文字并淡出的动画
实现上述效果的代码示例
TestApp.cs
using System;
using System.Collections.Generic;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.scheduler;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Color = Unity.UIWidgets.ui.Color;
public class TestApp : UIWidgetsPanel
{
protected override Widget createWidget()
{
return new WidgetsApp(
home: new TestAppSt(),
pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
new PageRouteBuilder(
settings: settings,
pageBuilder: (BuildContext context, Animation<float> animation,
Animation<float> secondaryAnimation) => builder(context)
)
);
}
class TestAppSt : StatefulWidget
{
public TestAppSt(
Key key = null,
AnimationController controller = null
) : base(key: key)
{
this.controller = controller;
}
public readonly AnimationController controller;
public override State createState()
{
return new _CustomActivityIndicatorState();
}
}
class _CustomActivityIndicatorState : State<TestAppSt>, TickerProvider
{
AnimationController _controller;
public override void initState()
{
base.initState();
if (this.widget.controller == null)
{
this._controller = new AnimationController(
duration: new TimeSpan(0, 0, 3),
vsync: this
);
this._controller.forward();
}
else
{
this._controller = this.widget.controller;
}
this._controller.addStatusListener((status =>
{
Debug.Log($"{status}");
} ));
}
public Ticker createTicker(TickerCallback onTick)
{
return new Ticker(onTick: onTick, () => $"created by {this}");
}
public override void didUpdateWidget(StatefulWidget oldWidget)
{
base.didUpdateWidget(oldWidget: oldWidget);
if (oldWidget is TestAppSt customActivityIndicator)
{
}
}
public override Widget build(BuildContext context)
{
int sideLength;
return new AnimatedBuilder(
animation: this._controller,
builder: (cxt, widget) =>
{
var value = _controller.value;
//Debug.Log(_controller?.value);
return new Stack(children: new List<Widget>
{
new Positioned(left:Screen.width/2-15,top: (value<0.2f?0:value-0.2f) * 100, child:
new Container(child: new Text("第一关", style: new TextStyle(color: new Color(0xFF0E3311).withOpacity(1-_controller.value))),
decoration: new BoxDecoration(
)
)
),
});
}
);
}
public override void dispose()
{
if (this.widget.controller == null)
{
this._controller.dispose();
}
base.dispose();
}
}
}
话说这个csdn的代码块,dart不配拥有姓名…
这段代码在每次animatedbuilder的回调builder中都会大量创建ui控件,似乎会产生性能浪费,但是其实在体验上感受不出来。相反,builder执行完了反而出现了卡顿。
动画builder执行完了之后卡成了ppt,同样,在一般flutter写法的控件UIWidget中也会出现这样的3d世界卡顿问题,除非Destroy这个脚本。看来这个库有很大的问题。我在更换了unity的版本之后,性能有提升,但是依然卡顿。
为了尽可能达到像flutter那样易用的效果,需要进行一定的封装才能使用。基本上做UI是为了3D交互,消除卡顿必须要套一层AnimatedBuilder。(应该没人再用这个做纯app吧,unity Connect已经相当卡顿)。
3D世界解决办法
在github UIWidget官方issue中找到如下
UIWidgets这个组件会自动调节刷新率,数值最低为1,为最流畅。原本是为了安卓手机开发设计的,因为这样可以节约电量。
对应github issue链接
也就是在OnUpdate函数中添加如下代码
OnDemandRendering.renderFrameInterval = 1;
封装需求分析
flutter中的两种控件stateful,stateless 控件,基于上述的结论,对于stateless和stateless,我只关心child控件,由于animatedBuilder是时刻刷新的(这很unity,就像update函数),我们在修改变量的时候不需要再调用setState方法(控件时时刻刻都在销毁重建,也就不存在flutter中的跳转)。
对于有动画的布局,我只关心动画的效果,启动的相关问题。
基于上述需求创建两个基本类
MyAnimateWidgetParent.cs(不可直接使用,默认在控件的最外层套一个AnimatedBuilder)
using System;
using System.Collections.Generic;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.scheduler;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Color = Unity.UIWidgets.ui.Color;
public class MyAnimateWidgetParent : StatefulWidget
{
public delegate Widget WidgetBuilder(BuildContext context);
public MyAnimateWidgetParent(
Key key = null,
WidgetBuilder builder = null
) : base(key: key)
{
this.controller = controller;
this.builder = builder;
}
public WidgetBuilder builder;
public readonly AnimationController controller;
public override State createState()
{
return new _CustomActivityIndicatorState();
}
}
class _CustomActivityIndicatorState : State<MyAnimateWidgetParent>, TickerProvider
{
AnimationController _controller;
public override void initState()
{
base.initState();
if (this.widget.controller == null)
{
this._controller = new AnimationController(
duration: new TimeSpan(0, 0, 1),
vsync: this
);
this._controller.repeat();
}
else
{
this._controller = this.widget.controller;
}
//this._controller.addStatusListener((status => { Debug.Log($"{status}"); }));
}
public Ticker createTicker(TickerCallback onTick)
{
return new Ticker(onTick: onTick, () => $"created by {this}");
}
public override void didUpdateWidget(StatefulWidget oldWidget)
{
base.didUpdateWidget(oldWidget: oldWidget);
if (oldWidget is MyAnimateWidgetParent customActivityIndicator)
{
}
}
public override Widget build(BuildContext context)
{
int sideLength;
return new AnimatedBuilder(
animation: this._controller,
builder: (cxt, widget) => { return this.widget.builder(context); }
);
}
public override void dispose()
{
if (this.widget.controller == null)
{
this._controller.dispose();
}
base.dispose();
}
}
MyApp.cs(自己的ui继承此类)
using System;
using System.Collections.Generic;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.scheduler;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Color = Unity.UIWidgets.ui.Color;
public abstract class MyApp : UIWidgetsPanel
{
//public delegate Widget build(BuildContext context);
public abstract Widget onFlutterUpdate();
protected override Widget createWidget()
{
Debug.Log("createwidget");
return new WidgetsApp(
home: new MyAnimateWidgetParent(builder:(context => onFlutterUpdate())),
pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
new PageRouteBuilder(
settings: settings,
pageBuilder: (BuildContext context, Animation<float> animation,
Animation<float> secondaryAnimation) => builder(context)
)
);
}
class ExampleStfl : StatefulWidget
{
public ExampleStfl(
Key key = null,
AnimationController controller = null
) : base(key: key)
{
this.controller = controller;
}
public readonly AnimationController controller;
public override State createState()
{
return new _CustomActivityIndicatorState();
}
}
class _CustomActivityIndicatorState : State<ExampleStfl>, TickerProvider
{
AnimationController _controller;
public override void initState()
{
base.initState();
if (this.widget.controller == null)
{
this._controller = new AnimationController(
duration: new TimeSpan(0, 0, 1),
vsync: this
);
this._controller.repeat();
}
else
{
this._controller = this.widget.controller;
}
this._controller.addStatusListener((status =>
{
Debug.Log($"{status}");
} ));
}
public Ticker createTicker(TickerCallback onTick)
{
return new Ticker(onTick: onTick, () => $"created by {this}");
}
public override void didUpdateWidget(StatefulWidget oldWidget)
{
base.didUpdateWidget(oldWidget: oldWidget);
if (oldWidget is ExampleStfl customActivityIndicator)
{
}
}
public override Widget build(BuildContext context)
{
int sideLength;
return new AnimatedBuilder(
animation: this._controller,
builder: (cxt, widget) =>
{
var value = _controller.value;
//Debug.Log("t..."+Time.deltaTime);
Debug.Log(_controller?.value);
return new Stack(children: new List<Widget>
{
new Positioned(left:Screen.width/2-15,top: (value<0.2f?0:value-0.2f) * 100, child:
new Container(child: new Text("第一关", style: new TextStyle(color: new Color(0xFF0E3311).withOpacity(1-_controller.value))),
decoration: new BoxDecoration(
)
)
),
});
}
);
}
public override void dispose()
{
if (this.widget.controller == null)
{
this._controller.dispose();
}
base.dispose();
}
}
}
示例代码:FirstUi.cs
using System.Collections;
using System.Collections.Generic;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Stack = Unity.UIWidgets.widgets.Stack;
public class FirstUi : MyApp
{
private int counter = 0;
public override Widget onFlutterUpdate()
{
return new Stack(children: new List<Widget>
{
new Positioned(top: Time.time, child: new GestureDetector(child:new Text($"{counter} - " + Time.time),onTap:(() =>
{
counter++;
})))
});
}
}
在FirstUi.cs中可以看到,页面的布局相当简单,onFlutterUpdate()时刻都在调用,很符合unity的风格,至于页面跳转,setState可以通过保存Widget变量的方式。当然,如果喜欢flutter的那种页面切换方式也可以自己去实现。AnimatedController.value可以以unity的Time.deltaTime代替。