实现切屏横竖屏切换
参考
参考

class MediaPage extends StatefulWidget {@overrideState<StatefulWidget> createState() {return MediaPageState();}
}class MediaPageState extends State<MediaPage> {// 记录当前设备是否横屏,后续用到bool get _isFullScreen => MediaQuery.of(context).orientation == Orientation.landscape;@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Media'),),body: Container(child: MyVideo( // 这个是等会儿要编写的组件url: 'http://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_20mb.mp4',title: '示例视频',width: vw, // 这个vw是MediaQueryData.fromWindow(window).size.width屏幕宽度height: vw/16*9, // 设置容器为16:9),),)}
}

OK,空页面准备好了。

打造播放器
我这里选用的是官方的video_play,因为其他插件都增加了东西缺让我不满意,所以我只能选择最原始的插件,自己增加满意的东西。

video_player: ^0.10.2+5

好,开始编写我们想要的ui~~

// MyVideo.dart
import 'package:video_player/video_player.dart'; // 引入官方插件class MyVideo extends StatefulWidget {MyVideo({@required this.url, // 当前需要播放的地址@required this.width, // 播放器尺寸(大于等于视频播放区域)@required this.height,this.title = '', // 视频需要显示的标题});// 视频地址final String url;// 视频尺寸比例final double width;final double height;// 视频标题final String title;@overrideState<MyVideo> createState() {return _MyVideoState();}
}class _MyVideoState extends State<MyVideo> {// 指示video资源是否加载完成,加载完成后会获得总时长和视频长宽比等信息bool _videoInit = false;// video控件管理器VideoPlayerController _controller;// 记录video播放进度Duration _position = Duration(seconds: 0);// 记录播放控件ui是否显示(进度条,播放按钮,全屏按钮等等)Timer _timer; // 计时器,用于延迟隐藏控件uibool _hidePlayControl = true; // 控制是否隐藏控件uidouble _playControlOpacity = 0; // 通过透明度动画显示/隐藏控件ui// 记录是否全屏bool get _isFullScreen => MediaQuery.of(context).orientation==Orientation.landscape;@overrideWidget build(BuildContext context) {// 继续往下看}
}

现在已经完成了视频播放器组件的大框架了,开始编写渲染播放器和控件ui。

想法:控件的ui我希望分为上半部分和下半部分,上半部显示标题,下半部显示播放按钮,全屏按钮,进度条,点击视频区域控制控件ui显示/隐藏。ok。开干

先写视频播放区

class _MyVideoState extends State<MyVideo> {// ......@overrideWidget build(BuildContext context) {return Container(width: widget.width,height: widget.height,color: Colors.black,child: widget.url!=null?Stack( // 因为控件ui和视频是重叠的,所以要用定位了children: <Widget>[GestureDetector( // 手势组件onTap: () { // 点击显示/隐藏控件ui_togglePlayControl();},child: _videoInit?Center(child: AspectRatio( // 加载url成功时,根据视频比例渲染播放器aspectRatio: _controller.value.aspectRatio,child: VideoPlayer(_controller),),):Center( // 没加载完成时显示转圈圈loadingchild: SizedBox(width: 20,height: 20,child: CircularProgressIndicator(),),),),],):Center( // 判断是否传入了url,没有的话显示"暂无视频信息"child: Text('暂无视频信息',style: TextStyle(color: Colors.white),),),)}@overridevoid initState() {_urlChange(); // 初始进行一次url加载super.initState();}@overridevoid didUpdateWidget(MyVideo oldWidget) {if (oldWidget.url != widget.url) {_urlChange(); // url变化时重新执行一次url加载}super.didUpdateWidget(oldWidget);}@overridevoid dispose() {if (_controller!=null) { // 惯例。组件销毁时清理下_controller.removeListener(_videoListener);_controller.dispose();}super.dispose();}void _urlChange() {if (widget.url==null || widget.url=='') return;if (_controller!=null) { // 如果控制器存在,清理掉重新创建_controller.removeListener(_videoListener);_controller.dispose();}setState(() { // 重置组件参数_hidePlayControl = true;_videoInit = false;_position = Duration(seconds: 0);});// 加载network的url,也支持本地文件,自行阅览官方api_controller = VideoPlayerController.network(widget.url)..initialize().then((_) {// 加载资源完成时,监听播放进度,并且标记_videoInit=true加载完成_controller.addListener(_videoListener);setState(() {_videoInit = true;});});}
}

然后编写控件ui

// 控件ui下半部
Widget _bottomControl = Positioned( // 需要定位left: 0,bottom: 0,child: Offstage( // 控制是否隐藏offstage: _hidePlayControl,child: AnimatedOpacity( // 加入透明度动画opacity: _playControlOpacity,duration: Duration(milliseconds: 300),child: Container( // 底部控件的容器width: widget.width,height: 40,decoration: BoxDecoration(gradient: LinearGradient( // 来点黑色到透明的渐变优雅一下begin: Alignment.bottomCenter,end: Alignment.topCenter,colors: [Color.fromRGBO(0, 0, 0, .7), Color.fromRGBO(0, 0, 0, .1)],),),child: _videoInit?Row( // 加载完成时才渲染,flex布局children: <Widget>[IconButton( // 播放按钮padding: EdgeInsets.zero,iconSize: 26,icon: Icon( // 根据控制器动态变化播放图标还是暂停_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,color: Colors.white,),onPressed: (){setState(() { // 同样的,点击动态播放或者暂停_controller.value.isPlaying? _controller.pause(): _controller.play();_startPlayControlTimer(); // 操作控件后,重置延迟隐藏控件的timer});},),Flexible( // 相当于前端的flex: 1child: VideoProgressIndicator( // 嘻嘻,这是video_player编写好的进度条,直接用就是了~~_controller,allowScrubbing: true, // 允许手势操作进度条padding: EdgeInsets.all(0),colors: VideoProgressColors( // 配置进度条颜色,也是video_player现成的,直接用playedColor: Theme.of(context).primaryColor, // 已播放的颜色bufferedColor: Color.fromRGBO(255, 255, 255, .5), // 缓存中的颜色backgroundColor: Color.fromRGBO(255, 255, 255, .2), // 为缓存的颜色),),),Container( // 播放时间margin: EdgeInsets.only(left: 10),child: Text( // durationToTime是通过Duration转成hh:mm:ss的格式,自己实现。durationToTime(_position)+'/'+durationToTime(_controller.value.duration),style: TextStyle(color: Colors.white),),),IconButton( // 全屏/横屏按钮padding: EdgeInsets.zero,iconSize: 26,icon: Icon( // 根据当前屏幕方向切换图标_isFullScreen?Icons.fullscreen_exit:Icons.fullscreen,color: Colors.white,),onPressed: (){ // 点击切换是否全屏_toggleFullScreen();},),],):Container(),),),),
);

先看下显示/隐藏控件ui的方法

void _togglePlayControl() {setState(() {if (_hidePlayControl) { // 如果隐藏则显示_hidePlayControl = false;_playControlOpacity = 1;_startPlayControlTimer(); // 开始计时器,计时后隐藏} else { // 如果显示就隐藏if (_timer!=null) _timer.cancel(); // 有计时器先移除计时器_playControlOpacity = 0;Future.delayed(Duration(milliseconds: 300)).whenComplete(() {_hidePlayControl = true; // 延迟300ms(透明度动画结束)后,隐藏});}});
}void _startPlayControlTimer() { // 计时器,用法和前端js的大同小异if (_timer!=null) _timer.cancel();_timer = Timer(Duration(seconds: 3), () { // 延迟3s后隐藏setState(() {_playControlOpacity = 0;Future.delayed(Duration(milliseconds: 300)).whenComplete(() {_hidePlayControl = true;});});});
}

再来看下全屏的方法,此处采用了切换横屏竖屏的插件orientation

void _toggleFullScreen() {setState(() {if (_isFullScreen) { // 如果是全屏就切换竖屏OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp);} else {OrientationPlugin.forceOrientation(DeviceOrientation.landscapeRight);}_startPlayControlTimer(); // 操作完控件开始计时隐藏});
}

切换成横屏后,需要用_isFullScreen和Offstage把你不想显示的组件隐藏掉,例如appBar等等。

其中实现横竖屏翻转是用orientation插件

orientation: 1.3.0
  // 记录是否全屏bool get _isFullScreen =>MediaQuery.of(context).orientation == Orientation.landscape;void _toggleFullScreen() {setState(() {if (_isFullScreen) {// 如果是全屏就切换竖屏OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp);} else {OrientationPlugin.forceOrientation(DeviceOrientation.landscapeRight);}});}

可以用AspectRatio设置视频的长宽比例

      Container(margin: _isFullScreen? EdgeInsets.only(): EdgeInsets.only(),width: _isFullScreen ? height : null,child: AspectRatio(//设置长宽比例// aspectRatio: _videoPlayerController//     .value.aspectRatio,aspectRatio: _isFullScreen?12.0/5.0:_videoPlayerController.value.aspectRatio,child:VideoPlayer(_videoPlayerController),)),

flutter 实现切屏横竖屏切换相关推荐

  1. ZFPlayer 全屏、横竖屏使用小记

    关于这个库大家都不陌生,下面小结下自己使用过程中的经验,主要是关于全屏横竖屏的几个小点. 使用cell上直接播放的创建方式(先小屏播放,然后点击全屏按钮),全屏后完全取决于外部设置的全屏模式(强制改变 ...

  2. android 图片横竖判断_Android横竖屏切换及其对应布局加载问题详解

    本文为大家分享了Android横竖屏切换及其对应布局加载问题,供大家参考,具体内容如下 第一,横竖屏切换连带横竖屏布局问题: 如果要让软件在横竖屏之间切换,由于横竖屏的高宽会发生转换,有可能会要求不同 ...

  3. 你还在问android横竖屏切换的生命周期?

    本文原创,转载请注明来自xiaoQLu http://www.cnblogs.com/xiaoQLu/p/3324503.html 开源帮助android获得了飞速的发展,开源也导致了数不清的碎片问题 ...

  4. Android横竖屏切换的生命周期

    关于Android手机横竖屏切换时Activity的生命周期问题,网上有很多相似的文章,大多数都是说明在竖屏切换横屏时Activity会重启一次,而在横屏切换竖屏时Activity会重启两次. 我本身 ...

  5. Activity 横竖屏切换

    前言 在开发中常要处理横竖屏切换,怎么处理先看生命周期 申明 Activity 横竖屏切换时需要回调两个函数 ,所以在此将这个两个函数暂时看成是Activity 横竖屏切换的生命周期的一部分,这两个函 ...

  6. WebView网页视频统一全屏播放及横竖屏切换

    WebView 支持 Html5 video 进行全屏播放及横竖屏自动切换 1.检查AndroidManifest.xml清单文件,WebView控件所在的Activity配置信息;检查Activit ...

  7. Android 横竖屏切换

    2019独角兽企业重金招聘Python工程师标准>>> Android开发中,大多APP可能根据实际情况直接将APP的界面方向设死了,或竖屏或横屏.但是,我们还是会遇到横竖屏切换的功 ...

  8. android电视横竖屏切换在哪里,Android横竖屏切换总结

    之前在网上看到博客说Activity横竖屏切换的规律如下: (1)不设置android:configChanges,竖屏切换到横屏调用一次生命周期,横屏切竖屏调用两次生命周期. (2)设置 andro ...

  9. android横竖屏切换方法,Android横竖屏切换的生命周期

    关于Android手机横竖屏切换时Activity的生命周期问题,网上有很多相似的文章,大多数都是说明在竖屏切换横屏时Activity会重启一次,而在横屏切换竖屏时Activity会重启两次. 我本身 ...

最新文章

  1. CodeGen结构循环回路
  2. 【临实战】使用 Python 处理 Nginx 日志
  3. 浅谈压缩感知(三十一):压缩感知重构算法之定点连续法FPC
  4. .SQL Server中 image类型数据的比较
  5. (四)协同过滤算法之基于用户的推荐算法python实现
  6. 个人或者企业怎么进行app开发?开发一款APP应用大概须要多少钱?
  7. Spring Boot 配置随机数那些小技巧
  8. [Silverlight]奇技银巧系列-2
  9. Asp.Net中备份还原SqlServer数据库
  10. Java的历史及发展
  11. python数值类型和序列类型_数值类型和序列类型(python)
  12. 《Java基础入门》笔记——01 Java初步
  13. 开源视频处理工具Shotcut的用法: 剪切、合并、增加背景音乐、添加字幕、 插入视频、图片转视频并加背景音乐、制作电子相册
  14. 8 随机积分与随机微分方程
  15. linux centos7 apache+mariadb+php 虚拟机vmware workstation lamp环境搭建
  16. doom3的UI系统
  17. 微信小程序——“茶点错过你“奶茶店
  18. 华为Android彩蛋,华为手机DIY拨号及彩蛋功能介绍
  19. 查询快递物流提前签收的单号,快速分析筛选的方法
  20. 计算机的收获初一作文,写收获的初一作文五篇

热门文章

  1. STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信)
  2. Windows Vista Ultimate 简体中文旗舰版 32位
  3. 2021-2027全球与中国同种异体人类软骨细胞市场现状及未来发展趋势
  4. 清华计算机金融学,清华大学王牌专业排名 经济与金融专业上榜(10个)
  5. 南开大学计算机科学与技术研究生院,2021年南开大学计算机科学与技术(081200)硕士研究生招生信息_考研招生计划和招生人数 - 学途吧...
  6. netty整合shiro,报There is no session with id [xxxxxx]问题定位及解决
  7. npm run serve起项目报错node-sass not find
  8. 关于文件“Makefile”的修改时间在将来 XXX S后的问题
  9. 上班第一天总结(以装环境为主)
  10. 服装品牌正向多元化全方面的发展