Flutter主要是进行移动应用开发外,最近尝试了下Flutter开发TV应用。虽然写出来了,效果也还可以,体验流畅,自动适配。不过开发成本还是挺高的,按键监听、焦点处理和焦点框处理比较麻烦,由于Google官方并没有推出Flutter TV应用的SDK,所以暂时还是不要用Flutter编写TV应用了,使用原生leanback或者B/S结构开发吧,等官方推出后可以继续尝试对比使用。接下来,就分享下其中的技术点。本文将主要介绍:
- Flutter TV应用开发主要难点
- Flutter TV应用开发按键监听
- Flutter TV应用开发焦点处理
- Flutter TV应用开发焦点框效果处理
Flutter Dart QQ技术交流群:979966470
由于Google官方并没有退出TV版Flutter SDK,所以用Flutter尝试编写TV应用,主要是焦点框和焦点的处理,其他的和手机应用差别不大。按键监听、焦点框和焦点处理比较麻烦,所以Flutter的TV应用开发还不成熟,体验还不错,很流畅,开发成本比较高,还是用原生leanback开发可能要快一些。
Fast to Study Flutter And Dart. QQ群:979966470
下面为效果图:
下面为效果图:
手机上也可以自动适配:
运行视频:https://github.com/flutteranddart/flutterTV/blob/master/device.webm
APK下载体验地址:https://github.com/flutteranddart/flutterTV/blob/master/app-release.apk?raw=true
其实其他地方和Flutter开发移动应用基本没区别,主要就是按键监听、焦点框和焦点处理。原生Android的控件就默认有焦点的处理属性,直接配置使用即可。
//焦点处理
android:focusable="true"
//触摸模式下是否可以点击,可选可不选
android:focusableInTouchMode="true"
Flutter开发TV应用就要自己处理按键监听、焦点和焦点框了,比较的麻烦,处理好了这几个问题,开发起来也就没太大难度了。
Flutter Widget能够监听到我们的遥控器或者手机端的按键事件的前提是这个Widget已经获取了焦点才可以。获取焦点后面会讲到,这里暂时不提了。
按键监听需要使用RawKeyboardListener这个Widget,构造方法如下:
const RawKeyboardListener({
Key key,
@required this.focusNode,//焦点结点
@required this.onKey,//按键接收处理事件
@required this.child,//接收焦点的子控件
})
很简单给个例子:
FocusNode focusNode0 = FocusNode();
... ...
RawKeyboardListener(
focusNode: focusNode0,
child: Container(
decoration: getCircleDecoration(color0),
child: Padding(
child: Card(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
child: Text(''),
backgroundImage: AssetImage("assets/icon_tv.png"),
radius: radius,
),
),
padding: EdgeInsets.all(padding),
),
),
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent && event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid = rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(_focusNode);
break;
case 20: //KEY_DOWN
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNode4);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNode1);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
)
这样就实现了我们的Card Widget监听我们的按键事件,遥控器、手机的按键都能监听到。
Flutter TV的Widget获取焦点的处理通过FocusScope这个Widget处理。
主动获取焦点代码如下:
FocusNode focusNode0 = FocusNode();
... ...
//主动获取焦点
FocusScope.of(context).requestFocus(focusNode0);
//自动获取焦点
FocusScope.of(context).autofocus(focusNode0);
这样就可以了进行焦点获取处理了。FocusNode这个类也很重要,负责监听焦点的工作。
有了焦点、按键事件监听,剩下的就是选中的焦点框效果的实现了,主要原理这里使用的是用边框,然后动态设置边框颜色就实现了焦点框选中显示,移走不显示的效果。例如选中后焦点框颜色设置为黄色、未选中时就设置为透明色,通过setState({…})进行刷新页面。
FocusNode focusNode0 = FocusNode();
...
//改变颜色状态,刷新页面
_setDecorationBorder0() {
setState(() {
if (focusNode0.hasFocus) {
colorB0 = Colors.orange;
} else {
colorB0 = Colors.transparent;
}
});
}
...
//为FocusNode添加监听事件方法
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
focusNode0.addListener(_setDecorationBorder0);
}
...
//销毁时要注销监听
@override
void dispose() {
super.dispose();
focusNode0.removeListener(_setDecorationBorder0);
focusNode0.dispose();
}
RawKeyboardListener(
//焦点监听
focusNode: focusNode0,
child: Container(
//这里就是设置焦点框效果的地方
decoration: getCircleDecoration(color0),
child: Padding(
child: Card(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
child: Text(''),
backgroundImage: AssetImage("assets/icon_tv.png"),
radius: radius,
),
),
padding: EdgeInsets.all(padding),
),
),
onKey: (RawKeyEvent event) {
print('监听');
if (event is RawKeyDownEvent && event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid = rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(_focusNode);
break;
case 20: //KEY_DOWN
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNode4);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNode1);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
);
...
//焦点框颜色变换
Decoration getCircleDecoration(Color colors) {
return BoxDecoration(
border: Border.all(width: borderWidth, color: colors),
shape: BoxShape.circle);
}
扩充一点,如果你想获取某个Widget或者布局的宽高信息,可以通过如下代码获取:
//这里的context为你要获取宽高信息的Widget的context
RenderObject renderObject = context.findRenderObject();
...
//这个context可以通过设置key来进行获取
GlobalKey _bodyKey = new GlobalKey();
...
Widget body1() {
return Expanded(
key: _bodyKey,
child: Container(
...
我们需要在页面渲染完毕后获取Widget的宽高信息,所以还要监听页面渲染刷新完毕的事件,用with WidgetsBindingObserver来实现:
class _MyHomePageState extends State with WidgetsBindingObserver {
...
@override
void initState() {
//注册监听
WidgetsBinding.instance.addObserver(this);
super.initState();
}
@override
void dispose() {
super.dispose();
//取消监听
WidgetsBinding.instance.removeObserver(this);
}
@override
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
super.didChangeDependencies();
}
@override
void didUpdateWidget(Widget oldWidget) {
WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
super.didUpdateWidget(oldWidget);
}
void _onAfterRendering(Duration timeStamp) {
//这里编写获取元素大小和位置的方法
RenderObject renderObject = context.findRenderObject();
Size size = renderObject.paintBounds.size;
print('onAfterRendering:');
print(size.height);
//获取对应key的context所在的Widget的宽高信息
RenderObject _renderObject = _bodyKey.currentContext.findRenderObject();
Size _size = _renderObject.paintBounds.size;
print('_onAfterRendering:');
print(_size.height);
bodyHeight = _size.height;
setState(() {});
}
最后给一个完整的Flutter TV的应用开发示例代码:
/*
* @Author: Tan Dong
* @Date: 2019-03-16 12:39:05
* @Last Modified by: Tan Dong
* @Last Modified time: 2019-03-16 12:39:05
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TV Page',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blueGrey,
backgroundColor: Colors.teal,
),
routes: {
// '/home': (context) =>` VideoPlay(),
},
home: MyHomePage(title: 'TV Page'),
);
}
}
const double radius = 30;
const double padding = 2;
const double borderWidth = 2;
double itemWidth = 0;
FocusNode focusNode = null;
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State with WidgetsBindingObserver {
FocusNode focusNodeB0 = FocusNode();
FocusNode focusNodeB1 = FocusNode();
FocusNode focusNodeB2 = FocusNode();
FocusNode focusNodeB3 = FocusNode();
FocusNode focusNodeB4 = FocusNode();
Color colorB0 = Colors.transparent;
Color colorB1 = Colors.transparent;
Color colorB2 = Colors.transparent;
Color colorB3 = Colors.transparent;
Color colorB4 = Colors.transparent;
bool init = false;
_setDecorationBorderB0() {
setState(() {
if (focusNodeB0.hasFocus) {
colorB0 = Colors.orange;
} else {
colorB0 = Colors.transparent;
}
});
}
_setDecorationBorderB1() {
setState(() {
if (focusNodeB1.hasFocus) {
colorB1 = Colors.orange;
} else {
colorB1 = Colors.transparent;
}
});
}
_setDecorationBorderB2() {
setState(() {
if (focusNodeB2.hasFocus) {
colorB2 = Colors.orange;
} else {
colorB2 = Colors.transparent;
}
});
}
_setDecorationBorderB3() {
setState(() {
if (focusNodeB3.hasFocus) {
colorB3 = Colors.orange;
} else {
colorB3 = Colors.transparent;
}
});
}
_setDecorationBorderB4() {
setState(() {
if (focusNodeB4.hasFocus) {
colorB4 = Colors.orange;
} else {
colorB4 = Colors.transparent;
}
});
}
@override
void initState() {
WidgetsBinding.instance.addObserver(this);
// SystemChrome.setEnabledSystemUIOverlays([]);
// SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top, SystemUiOverlay.bottom]);
super.initState();
focusNodeB0.addListener(_setDecorationBorderB0);
focusNodeB1.addListener(_setDecorationBorderB1);
focusNodeB2.addListener(_setDecorationBorderB2);
focusNodeB3.addListener(_setDecorationBorderB3);
focusNodeB4.addListener(_setDecorationBorderB4);
}
@override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
focusNodeB0.removeListener(_setDecorationBorderB0);
focusNodeB0.dispose();
focusNodeB1.removeListener(_setDecorationBorderB1);
focusNodeB1.dispose();
focusNodeB2.removeListener(_setDecorationBorderB2);
focusNodeB2.dispose();
focusNodeB3.removeListener(_setDecorationBorderB3);
focusNodeB3.dispose();
focusNodeB4.removeListener(_setDecorationBorderB4);
focusNodeB4.dispose();
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
itemWidth = MediaQuery.of(context).size.width / 3;
return Scaffold(
backgroundColor: Color(0xff277188),
body:
view1(), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget view1() {
return Column(
children: [
SizedBox(
height: 20,
),
TopWidget(),
body1(),
SizedBox(
height: 20,
),
BottomWidget(focusNodeB0),
SizedBox(
height: 20,
),
],
);
}
@override
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
super.didChangeDependencies();
}
@override
void didUpdateWidget(Widget oldWidget) {
WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
super.didUpdateWidget(oldWidget);
}
void _onAfterRendering(Duration timeStamp) {
//这里编写获取元素大小和位置的方法
RenderObject renderObject = context.findRenderObject();
Size size = renderObject.paintBounds.size;
print('onAfterRendering:');
print(size.height);
RenderObject _renderObject = _bodyKey.currentContext.findRenderObject();
Size _size = _renderObject.paintBounds.size;
print('_onAfterRendering:');
print(_size.height);
bodyHeight = _size.height;
setState(() {});
// Navigator.of(context).push(MaterialPageRoute(builder: (_) {
// return PermissionSamples();
// }));
}
GlobalKey _bodyKey = new GlobalKey();
double bodyHeight = 600;
Widget image1() {
return RawKeyboardListener(
focusNode: focusNodeB0,
onKey: (event) {
if (event is RawKeyDownEvent && event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid = rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
break;
case 20: //KEY_DOWN
FocusScope.of(context).requestFocus(focusNode);
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNodeB4);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNodeB1);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
child: Expanded(
child: Container(
height: bodyHeight,
decoration: getRectangleDecoration(colorB0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
elevation: 10,
child: ClipRRect(
child: Image.asset(
'assets/hmjz.jpg',
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
),
)),
);
}
Widget body1() {
return Expanded(
key: _bodyKey,
child: Container(
margin: EdgeInsets.all(5),
child: Row(
children: [
image1(),
Expanded(
child: Column(
children: [
RawKeyboardListener(
focusNode: focusNodeB1,
onKey: (event) {
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
break;
case 20: //KEY_DOWN
FocusScope.of(context).requestFocus(focusNodeB2);
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNodeB0);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNodeB3);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
child: Expanded(
child: Container(
width: itemWidth,
decoration: getRectangleDecoration(colorB1),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
elevation: 10,
child: ClipRRect(
child: Image.asset(
'assets/zqc.jpg',
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
),
)),
),
RawKeyboardListener(
focusNode: focusNodeB2,
onKey: (event) {
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(focusNodeB1);
break;
case 20: //KEY_DOWN
FocusScope.of(context).requestFocus(focusNode);
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNodeB0);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNodeB4);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
child: Expanded(
child: Container(
decoration: getRectangleDecoration(colorB2),
width: itemWidth,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
elevation: 10,
child: ClipRRect(
child: Image.asset(
'assets/lifeandpi.jpg',
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
),
)),
),
],
),
),
Expanded(
child: Column(
children: [
RawKeyboardListener(
focusNode: focusNodeB3,
onKey: (event) {
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
break;
case 20: //KEY_DOWN
FocusScope.of(context).requestFocus(focusNodeB4);
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNodeB1);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNodeB0);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
child: Expanded(
child: Container(
width: itemWidth,
decoration: getRectangleDecoration(colorB3),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
elevation: 10,
child: ClipRRect(
child: Image.asset(
'assets/zzx.jpg',
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
),
)),
),
RawKeyboardListener(
focusNode: focusNodeB4,
onKey: (event) {
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(focusNodeB3);
break;
case 20: //KEY_DOWN
FocusScope.of(context).requestFocus(focusNode);
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNodeB2);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNodeB0);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
child: Expanded(
child: Container(
width: itemWidth,
decoration: getRectangleDecoration(colorB4),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
elevation: 10,
child: ClipRRect(
child: Image.asset(
'assets/mgdz.jpg',
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
),
)),
),
],
),
),
],
),
),
);
}
}
class TopWidget extends StatefulWidget {
@override
State createState() {
return TopWidgetState();
}
}
class TopWidgetState extends State {
String time = '';
Timer timer;
@override
void initState() {
super.initState();
var now = DateTime.now();
timer = Timer.periodic(Duration(seconds: 1), (Timer timer) {
now = DateTime.now();
setState(() {
time = now.year.toString() +
"-" +
now.month.toString() +
"-" +
now.day.toString() +
" " +
now.hour.toString() +
":" +
now.minute.toString() +
":" +
now.second.toString() +
" " +
weekFormat(now.weekday);
});
});
}
@override
Widget build(BuildContext context) {
return Padding(
child: Row(
children: [
Text(
'Page',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
Expanded(
child: Container(),
),
Text(
time,
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
padding: EdgeInsets.all(10),
);
}
}
class BottomWidget extends StatefulWidget {
FocusNode _focusNode;
BottomWidget(this._focusNode);
@override
State createState() {
return BottomWidgetState(_focusNode);
}
}
class BottomWidgetState extends State
with WidgetsBindingObserver {
FocusNode _focusNode;
BottomWidgetState(this._focusNode);
FocusNode focusNode0 = FocusNode();
FocusNode focusNode1 = FocusNode();
FocusNode focusNode2 = FocusNode();
FocusNode focusNode3 = FocusNode();
FocusNode focusNode4 = FocusNode();
Color color0 = Colors.transparent;
Color color1 = Colors.transparent;
Color color2 = Colors.transparent;
Color color3 = Colors.transparent;
Color color4 = Colors.transparent;
bool init = false;
_setDecorationBorder0() {
setState(() {
if (focusNode0.hasFocus) {
color0 = Colors.orange;
} else {
color0 = Colors.transparent;
}
});
}
_setDecorationBorder1() {
setState(() {
if (focusNode1.hasFocus) {
color1 = Colors.orange;
} else {
color1 = Colors.transparent;
}
});
}
_setDecorationBorder2() {
setState(() {
if (focusNode2.hasFocus) {
color2 = Colors.orange;
} else {
color2 = Colors.transparent;
}
});
}
_setDecorationBorder3() {
setState(() {
if (focusNode3.hasFocus) {
color3 = Colors.orange;
} else {
color3 = Colors.transparent;
}
});
}
_setDecorationBorder4() {
setState(() {
if (focusNode4.hasFocus) {
color4 = Colors.orange;
} else {
color4 = Colors.transparent;
}
});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
focusNode0.addListener(_setDecorationBorder0);
focusNode1.addListener(_setDecorationBorder1);
focusNode2.addListener(_setDecorationBorder2);
focusNode3.addListener(_setDecorationBorder3);
focusNode4.addListener(_setDecorationBorder4);
focusNode = focusNode0;
}
@override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
focusNode0.removeListener(_setDecorationBorder0);
focusNode0.dispose();
focusNode1.removeListener(_setDecorationBorder1);
focusNode1.dispose();
focusNode2.removeListener(_setDecorationBorder2);
focusNode2.dispose();
focusNode3.removeListener(_setDecorationBorder3);
focusNode3.dispose();
focusNode4.removeListener(_setDecorationBorder4);
focusNode4.dispose();
}
@override
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
super.didChangeDependencies();
}
@override
void didUpdateWidget(Widget oldWidget) {
WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
super.didUpdateWidget(oldWidget);
}
void _onAfterRendering(Duration timeStamp) {
//这里编写获取元素大小和位置的方法
RenderObject renderObject = context.findRenderObject();
Size size = renderObject.paintBounds.size;
print(size.height);
}
GlobalKey _myKey = new GlobalKey();
@override
Widget build(BuildContext context) {
if (!init) {
FocusScope.of(context).requestFocus(focusNode0);
init = true;
}
final sizeM = MediaQuery.of(context).size;
print(sizeM.width);
print(sizeM.height);
return Row(
key: _myKey,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
width: 10,
height: 2,
),
Column(
children: [
RawKeyboardListener(
focusNode: focusNode0,
child: Container(
decoration: getCircleDecoration(color0),
child: Padding(
child: Card(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
child: Text(''),
backgroundImage: AssetImage("assets/icon_tv.png"),
radius: radius,
),
),
padding: EdgeInsets.all(padding),
),
),
onKey: (RawKeyEvent event) {
print('监听');
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(_focusNode);
break;
case 20: //KEY_DOWN
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNode4);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNode1);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
),
Text(
'TV',
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
Column(
children: [
RawKeyboardListener(
focusNode: focusNode1,
child: Container(
decoration: getCircleDecoration(color1),
child: Padding(
child: Card(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
child: Text(''),
backgroundImage: AssetImage("assets/icon_voice.png"),
radius: radius,
),
),
padding: EdgeInsets.all(padding),
),
),
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(_focusNode);
break;
case 20: //KEY_DOWN
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNode0);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNode2);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
),
Text(
'Voice',
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
Column(
children: [
RawKeyboardListener(
focusNode: focusNode2,
child: Container(
decoration: getCircleDecoration(color2),
child: Padding(
child: Card(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
child: Text(''),
backgroundImage: AssetImage("assets/icon_video.png"),
radius: radius,
),
),
padding: EdgeInsets.all(padding),
),
),
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(_focusNode);
break;
case 20: //KEY_DOWN
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNode1);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNode3);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
),
Text(
'Video',
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
Column(
children: [
RawKeyboardListener(
focusNode: focusNode3,
child: Container(
decoration: getCircleDecoration(color3),
child: Padding(
child: Card(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
child: Text(''),
backgroundImage: AssetImage("assets/icon_phone.png"),
radius: radius,
),
),
padding: EdgeInsets.all(padding),
),
),
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(_focusNode);
break;
case 20: //
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNode2);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNode4);
break;
case 23: //KEY_CENTER
Navigator.of(context).pushNamed('/home');
break;
default:
break;
}
}
},
),
Text(
'Phone',
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
Column(
children: [
RawKeyboardListener(
focusNode: focusNode4,
child: Container(
decoration: getCircleDecoration(color4),
child: Padding(
child: Card(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
child: Text(''),
backgroundImage: AssetImage("assets/icon_pad.png"),
radius: radius,
),
),
padding: EdgeInsets.all(padding),
),
),
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent &&
event.data is RawKeyEventDataAndroid) {
RawKeyDownEvent rawKeyDownEvent = event;
RawKeyEventDataAndroid rawKeyEventDataAndroid =
rawKeyDownEvent.data;
print("keyCode: ${rawKeyEventDataAndroid.keyCode}");
switch (rawKeyEventDataAndroid.keyCode) {
case 19: //KEY_UP
FocusScope.of(context).requestFocus(_focusNode);
break;
case 20: //KEY_DOWN
break;
case 21: //KEY_LEFT
FocusScope.of(context).requestFocus(focusNode3);
break;
case 22: //KEY_RIGHT
FocusScope.of(context).requestFocus(focusNode0);
break;
case 23: //KEY_CENTER
break;
default:
break;
}
}
},
),
Text(
'Pad',
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
SizedBox(
width: 10,
height: 2,
),
],
);
}
}
Decoration getCircleDecoration(Color colors) {
return BoxDecoration(
border: Border.all(width: borderWidth, color: colors),
shape: BoxShape.circle);
}
Decoration getRectangleDecoration(Color colors) {
List boxShadows = List();
boxShadows.add(BoxShadow(color: Colors.orange[200]));
boxShadows.add(BoxShadow(color: Colors.yellow[100]));
boxShadows.add(BoxShadow(color: Colors.yellow[300]));
List colorsGradient = List();
colorsGradient.add(Colors.teal);
colorsGradient.add(Colors.teal[200]);
return BoxDecoration(
border: Border.all(width: borderWidth, color: colors),
borderRadius: BorderRadius.all(Radius.circular(10)),
shape: BoxShape.rectangle,
);
}
String weekFormat(int week) {
switch (week) {
case 1:
return '星期一';
break;
case 2:
return '星期二';
break;
case 3:
return '星期三';
break;
case 4:
return '星期四';
break;
case 5:
return '星期五';
break;
case 6:
return '星期六';
break;
case 7:
return '星期日';
break;
}
}
关于Flutter TV开发就讲解这么多。