上一篇flutter tv开发之按键消息分发机制(上)说到,flutter基本控件自身是不支持按键操作的,我们需要找到一个支持按键交互的控件来装饰我们的基本组件,那就是RawKeyboardListener
绘制的控件只有被这个控件包裹,控件才支持按键事件。实例化该对象时,有3个参数是必须要传的:
focusNode
控制该控件是否有焦点,要使控件获取焦点,可以这样写:
FocusScope.of(context).requestFocus(focusNode);
onKey
控件按键事件回调,该回调接口带一个参数,类型为RawKeyEvent,RawKeyEvent是一个抽象类,有一个抽象工厂方法,该方法接收android、ios和fuchsia等系统平台发送的按键数据,根据按键类型是KeyDown还是KeyUp,返回对应的继承该类的RawKeyDownEvent和RawKeyUpEvent子类对象。
因为onKey方法返回的参数是泛型,需要强转,以Android平台为例,具体做法如下:
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent && event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid = rawKeyDownEvent.data;
switch (rawKeyEventDataAndroid.keyCode) {
……
}
}
},
child
目标控件,也即RawKeyBoardListener的孩子树。
对于TV来说,交互依赖于焦点显示,而焦点显示方式按照实现机制分为真焦点和假焦点。
所谓真焦点就是:焦点的变化体现在目标控件的状态变化上,比如控件背景色、大小随着焦点得失而动态改变。
对应的假焦点就是:焦点的变化不影响目标控件本身的属性,有一个专门负责显示焦点改变的控件,一般称为焦点框,随着焦点位置改变而执行平移动画。焦点框的偏移距离为获得焦点的控件坐标减去失去焦点的控件坐标,同时焦点框形状始终适应目标控件的大小,获得焦点的控件看起来像是被焦点框包裹着。
以tv launcher菜单UI为例,我们来具体分析如何使用flutter实现假焦点交互。
首先我们需要绘制一个发光的矩形框,发光这一点只需要找UI设计师提供一张特效图即可,矩形框要怎么绘制呢?我们先看看flutter源码里有没有这样的实现,仔细找一找,我们看到了RelativeRectTween
类
这个类有两个参数begin和end,分别对应动画开始和动画结束时框的状态,参数类型为RelativeRect,RelativeRect
这个类负责绘制一个矩形边框,构造函数如下:
left、top、right、bottom分别表示边框距离屏幕左、上、右、下四个边界的间距。
我们实例化一个RelativeRectTween对象后,要给它绑定一个AnimationController
,这个动画控制器用来控制动画的执行。要开始动画,调用AnimationController
对象的forward()
方法,同时要保证动画连续执行,需要这样写:
接下来是布局的处理,在Android中,我们知道,焦点框是绘制在焦点View顶层的,一般采用RelativeLayout或者FrameLayout布局。
在Flutter中,层叠布局实现的控件是Stack
,Stack
控件的每一个子控件都是定位或不定位,定位的子控件是被Positioned
控件包裹的。Stack
控件本身包含所有不定位的子控件,其根据alignment
定位(默认为左上角)。然后根据定位的子控件的top
、right
、bottom
和left
属性将它们放置在Stack控件上。
具体焦点框和视图布局排列如下:
以上是假焦点交互的实现流程,下面我们再来看看真焦点交互如何实现,以按钮颜色随焦点变化而变化的效果为例:
第一步,如上面分析,让按钮被RawKeyboardListener控件包裹,该控件负责处理按键事件和焦点控制,这里不赘述。
第二步,定义按钮的颜色,我们创建一个颜色对象,负责按钮颜色显示:
Color color0 = Colors.grey; //这里给一个灰色默认值
具体焦点处理就在changeColor0()
这个方法里,我们根据绑定focusNode0
对象的控件是否有焦点,来改变color0
对象的值:
_changeColor0() {
print("focusNode0.hasFocus = ${focusNode0.hasFocus}");
setState(() {
if (focusNode0.hasFocus) {
color0 = Colors.red;
} else {
color0 = Colors.grey;
}
});
}
可以看到这个方法里,对color0
的赋值操作放在setState()
里面,这个setState()
的作用是通知框架此对象的内部状态已更改,从而引起视图的重绘,如果只是仅仅改变控件的属性值,而不通知系统,是不会引起界面重绘的。
Flutter开发TV首先就要解决交互问题,相信通过博客的抛砖引玉,你一定能得到一些启发。
文中demo源码地址:https://github.com/coderJohnZhang/flutter_tv