Flutter 开发过程中视频列表应该是比较复杂的功能,因为这里涉及到同一个视频在不同页面之间无缝的切换,控制其他在播的视频停止播放并播放当前点击的视频,还有视频秒开,视频弹幕等问题。在这里仅仅实现列表播放和切换到全屏播放功能,其他功能后面有时间再去进行研究。Flutter 并没有提供自带的视频播放控件,所以必须依靠第三方插件来实现,比如 VideoPlayer,fijkplayer 等等,但是 VideoPlayer 是使用相对比较简单的,所以先介绍该视频播放器。

一、依赖

dependencies:video_player: ^2.2.3

二、介绍

class VideoPlayer extends StatefulWidget {/// Uses the given [controller] for all video rendered in this widget.VideoPlayer(this.controller);/// The [VideoPlayerController] responsible for the video being rendered in/// this widget.final VideoPlayerController controller;@override_VideoPlayerState createState() => _VideoPlayerState();
}

VideoPlayer 是播放视频的控件,VideoPlayerController 是视频播放的控制器。

初始化 VideoPlayerController 有多种方法,最常见的就是通过 asset 或者 network 静态方法来创建。通过 play() 和 pause() 方法来实现播放和停止播放,dispose() 方法在 Widget 执行 onDispose 之前执行调用可以释放资源。同时 VideoPlayerController 有value属性来获取总时长 duration,当前播放进度 position, 和播放状态 isPlaying 等其他属性内容,value的类型结构如下:

class VideoPlayerValue {/// Constructs a video with the given values. Only [duration] is required. The/// rest will initialize with default values when unset.VideoPlayerValue({required this.duration,this.size = Size.zero,this.position = Duration.zero,this.caption = Caption.none,this.buffered = const <DurationRange>[],this.isInitialized = false,this.isPlaying = false,this.isLooping = false,this.isBuffering = false,this.volume = 1.0,this.playbackSpeed = 1.0,this.errorDescription,});
....}

如上可以看出,可以获取当前播放控制器的属性有很多。如上基本上已经介绍了 VideoPlayer 常见的所有功能,下面会实现一个视频列表页面,该列表可以在点击播放其他视频的时候,停止播放当前视频,并且可以进入到全面播放状态。

三、代码

import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_ijkplayer/lifecycle/lifecycle_state.dart';
import 'package:flutter_ijkplayer/util/video_data.dart';
import 'package:flutter_ijkplayer/widget/video_item_new.dart';
import 'package:video_player/video_player.dart';class ListVideoPage extends StatefulWidget {const ListVideoPage({Key? key}) : super(key: key);@override_ListVideoPageState createState() => _ListVideoPageState();
}class _ListVideoPageState extends LifeCycleState<ListVideoPage> {List<String> _listVideo = ["assets/video/anranxiaohun.mp4","assets/video/xiangxinai.mp4","assets/video/ycxhs098.mp4",//"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",//"http://vjs.zencdn.net/v/oceans.mp4",//"https://media.w3.org/2010/05/sintel/trailer.mp4",//"http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4"];//创建一个多订阅流final StreamController<VideoPlayerController> _streamController = new StreamController.broadcast();//播放控制器late VideoPlayerController _videoPlayerController;//是否滚动bool isScroll = false;@overridevoid initState() {super.initState();_streamController.stream.listen((event) {if (!mounted) return;if (_videoPlayerController != event) {_videoPlayerController.pause(); // 原来的controller进行暂停}_videoPlayerController = event; //赋值新的videoController});_videoPlayerController = VideoPlayerController.asset(_listVideo[0])..initialize().then((_) {setState(() {});});}@overridevoid dispose() {_streamController.close();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('视频列表'),),body: buildListView(),);}buildListView() {return NotificationListener(onNotification: (ScrollNotification notification) {if (notification.runtimeType == ScrollStartNotification) {//开始滚动isScroll = true;//setState(() {});} else if (notification.runtimeType == ScrollEndNotification) {//结束滚动//setState(() {});isScroll = false;}return false;},child: ListView.separated( //创建视频列表separatorBuilder: (context, index) {return Divider();},//不缓存cacheExtent: 2,//加载20条数据itemCount: 20,itemBuilder: (BuildContext context, int index) {return buildListViewItem(index);},));}buildListViewItem(index) {return Container(height: 240,child: Column(children: [Container(padding: EdgeInsets.only(top: 10),child: Row(children: [Container(child: Icon(Icons.account_circle,size: 20,),),SizedBox(width: 10,),Text('Flutter开发-$index',style: TextStyle(fontSize: 16),)],),),Expanded(child: Container(child: VideoItemNew(url: _listVideo[index % _listVideo.length],streamController: _streamController,//isScroll: isScrollisScroll: false,type: VideoType.asset,)),)],),);}@overridevoid onCreate() {super.onCreate();log("视频列表 onCreate");}@overridevoid onResume() {super.onResume();log("视频列表 onResume");}@overridevoid onPause() {super.onPause();log("视频列表 onPause");}@overridevoid onForeground() {super.onForeground();log("视频列表 onForeground");}@overridevoid onBackground() {super.onBackground();log("视频列表 onBackground");}@overridevoid onDestroy() {super.onDestroy();log("视频列表 onDestroy");}
}

视频列表项代码

import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_ijkplayer/page/full_screen_advance_page.dart';
import 'package:flutter_ijkplayer/page/full_screen_page.dart';
import 'package:flutter_ijkplayer/util/date_time_util.dart';
import 'package:flutter_ijkplayer/util/video_data.dart';
import 'package:flutter_ijkplayer/widget/progress_colors.dart';
import 'package:video_player/video_player.dart';
import 'material_video_progressbar.dart';class VideoItemNew extends StatefulWidget {//视频类型VideoType type;//ListView是否在滚动bool isScroll;String url;//全局流控制器StreamController streamController;VideoItemNew({Key? key,required this.url,required this.streamController,required this.isScroll,this.type=VideoType.asset}) : super(key: key);@override_VideoItemState createState() => _VideoItemState();
}class _VideoItemState extends State<VideoItemNew> with TickerProviderStateMixin{late VideoPlayerController _controller;bool isPlaying = false; // true 播放 false 不播放@overridevoid initState() {super.initState();_controller = widget.type == VideoType.asset? VideoPlayerController.asset(widget.url): VideoPlayerController.network(widget.url)..initialize().then((_){setState(() {});});_controller.addListener(() {//监听播放器的状态//如果播放器不播放了,那么就需要更新isPlaying的状态if (!mounted) return;if (isPlaying && !_controller.value.isPlaying){isPlaying = false;setState(() {});}setState(() {_sliderValue = _controller.value.position.inMilliseconds/_controller.value.duration.inMilliseconds;});});}@overridevoid dispose() {if(!mounted) return;_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {//widget.isScroll 是否在滚动状态,如果是的,则显示加载中,否则就加载播放器视图return widget.isScroll? Center(child: Text('加载中'),): buildVideo();}buildVideo() {return Stack(children: [//占满全屏并设置播放器Positioned.fill(child: AspectRatio(aspectRatio: _controller.value.aspectRatio,child: VideoPlayer(_controller),)),//编辑透明文字buildCenterText(),//底部滑动条Positioned(bottom: 2,right: 10,left: 10,child: isPlaying? Container(): buildSlider())],);}buildCenterText() {return Positioned.fill( //占满全屏child: AnimatedOpacity(opacity: _controller.value.isPlaying? 0: 1,duration: Duration(milliseconds: 500),child: GestureDetector(onTap: buildOnTap,child: Container(color: Colors.grey.withOpacity(0.5),child: Center(child: Icon(_controller.value.isPlaying? Icons.pause: Icons.play_arrow,color: Colors.white,),),),),),);}//点击播放buildOnTap(){isPlaying = !isPlaying;if (isPlaying){widget.streamController.add(_controller);//视频播放总时长Duration duration = _controller.value.duration;//视频播放进度Duration currPosition = _controller.value.position;if (currPosition == duration){_controller.seekTo(Duration.zero);}isPlaying= true;_controller.play();} else {isPlaying= false;_controller.pause();}setState(() {});}//当前播放器播放进度double _sliderValue = 0.0;buildSlider() {return Container(height: 48,child: Row(children: [//进度Text(buildTextString(_controller.value.position),style: TextStyle(color: Colors.white, fontSize: 14),),//进度条//initSlider(),_buildProgressBar(),//总进度Text(buildTextString(_controller.value.duration),style: TextStyle(color: Colors.white, fontSize: 14),),initFullScreen()],),);}//这里弃用了,不使用Slider来作为视频播放器的进度条,我们通过自定义来实现initSlider() {return Expanded(child: Slider(activeColor: Colors.lightBlue,inactiveColor: Colors.grey,//默认为0 必须小于或者等于最大值//如果min 和 max 相同,则滑块禁用min: 0,//默认1, 必须大于或者等于minmax: 1,value: _sliderValue,onChanged: (double value){setState(() {_sliderValue = value;//通过进度条修改视频播放进度_controller.seekTo(_controller.value.duration*_sliderValue);});},//滑块开始滑动onChangeStart: (double value){//log("onChangeStart...");},//滑块结束onChangeEnd: (double value){//log("onChangeEnd....");},));}initFullScreen(){return GestureDetector(onTap:(){Navigator.of(context).push(MaterialPageRoute(builder: (context){//return VideoFullScreenPage(url: widget.url, videoType: widget.type);return VideoFullScreenAdvancePage(url: widget.url, videoType: widget.type, controller: _controller,);}));},child: Icon(Icons.fullscreen,color: Colors.white,),);}bool _dragging = false;//自定义MaterialVideoProgressBar来实现进度条Widget _buildProgressBar() {return Expanded(child: Padding(padding: EdgeInsets.only(right: 15,left: 15),child: MaterialVideoProgressBar(_controller,onDragStart: () {setState(() {_dragging = true;});},onDragEnd: () {setState(() {_dragging = false;});},colors: ProgressColors(playedColor: Theme.of(context).accentColor,handleColor: Theme.of(context).accentColor,bufferedColor: Theme.of(context).backgroundColor,backgroundColor: Theme.of(context).disabledColor),onDragUpdate: () {},),),);}}

自定义播放器进度

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_ijkplayer/widget/progress_colors.dart';
import 'package:video_player/video_player.dart';class MaterialVideoProgressBar extends StatefulWidget {final VideoPlayerController controller;final ProgressColors colors;final Function() onDragStart;final Function() onDragEnd;final Function() onDragUpdate;MaterialVideoProgressBar(this.controller, {required ProgressColors colors,required this.onDragEnd,required this.onDragStart,required this.onDragUpdate,Key? key,}): colors = colors, super(key: key);@override_VideoProgressBarState createState() {return _VideoProgressBarState();}
}class _VideoProgressBarState extends State<MaterialVideoProgressBar> {late VoidCallback listener;bool _controllerWasPlaying = false;VideoPlayerController get controller => widget.controller;_VideoProgressBarState() {listener = () {if (!mounted) return;setState(() {});};}@overridevoid initState() {super.initState();controller.addListener(listener);}@overridevoid deactivate() {controller.removeListener(listener);super.deactivate();}@overrideWidget build(BuildContext context) {void seekToRelativePosition(Offset globalPosition) {final box = context.findRenderObject() as RenderBox;final Offset tapPos = box.globalToLocal(globalPosition);final double relative = tapPos.dx / box.size.width;final Duration position = controller.value.duration * relative;controller.seekTo(position);}return GestureDetector(onHorizontalDragStart: (DragStartDetails details) {if (!controller.value.isInitialized) {return;}_controllerWasPlaying = controller.value.isPlaying;if (_controllerWasPlaying) {controller.pause();}if (widget.onDragStart != null) {widget.onDragStart();}},onHorizontalDragUpdate: (DragUpdateDetails details) {if (!controller.value.isInitialized) {return;}seekToRelativePosition(details.globalPosition);if (widget.onDragUpdate != null) {widget.onDragUpdate();}},onHorizontalDragEnd: (DragEndDetails details) {if (_controllerWasPlaying) {controller.play();}if (widget.onDragEnd != null) {widget.onDragEnd();}},onTapDown: (TapDownDetails details) {if (!controller.value.isInitialized) {return;}seekToRelativePosition(details.globalPosition);},child: Center(child: Container(height: MediaQuery.of(context).size.height / 2,width: MediaQuery.of(context).size.width,color: Colors.transparent,child: CustomPaint(painter: _ProgressBarPainter(controller.value,widget.colors,),),),),);}
}class _ProgressBarPainter extends CustomPainter {_ProgressBarPainter(this.value, this.colors);VideoPlayerValue value;ProgressColors colors;@overridebool shouldRepaint(CustomPainter painter) {return true;}@overridevoid paint(Canvas canvas, Size size) {const height = 2.0;canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromPoints(Offset(0.0, size.height / 2),Offset(size.width, size.height / 2 + height),),const Radius.circular(4.0),),colors.backgroundPaint,);if (!value.isInitialized) {return;}final double playedPartPercent =value.position.inMilliseconds / value.duration.inMilliseconds;final double playedPart =playedPartPercent > 1 ? size.width : playedPartPercent * size.width;for (final DurationRange range in value.buffered) {final double start = range.startFraction(value.duration) * size.width;final double end = range.endFraction(value.duration) * size.width;canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromPoints(Offset(start, size.height / 2),Offset(end, size.height / 2 + height),),const Radius.circular(4.0),),colors.bufferedPaint,);}canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromPoints(Offset(0.0, size.height / 2),Offset(playedPart, size.height / 2 + height),),const Radius.circular(4.0),),colors.playedPaint,);canvas.drawCircle(Offset(playedPart, size.height / 2 + height / 2),height * 3,colors.handlePaint,);}
}

全屏播放代码

import 'dart:developer';import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_ijkplayer/util/screen_util.dart';
import 'package:flutter_ijkplayer/util/video_data.dart';
import 'package:video_player/video_player.dart';//可以继续接着外部的进度来进行播放
class VideoFullScreenAdvancePage extends StatefulWidget {String url;VideoType videoType;VideoPlayerController controller;VideoFullScreenAdvancePage({Key? key,required this.url,required this.videoType,required this.controller}) : super(key: key);@override_VideoFullScreenPageState createState() => _VideoFullScreenPageState();}class _VideoFullScreenPageState extends State<VideoFullScreenAdvancePage> {late VideoPlayerController _controller;//是否是横屏 默认横屏bool isHorizontal = true;@overridevoid initState() {super.initState();//如果不进行初始化,而是从外部传过来的// _controller = widget.videoType == VideoType.asset// ? VideoPlayerController.asset(widget.url)// : VideoPlayerController.network(widget.url)// ..initialize().then((value){//      setState(() {});// });_controller = widget.controller;_controller.addListener(() {//当前进度 == 总进度if (_controller.value.position ==  _controller.value.duration){//播放结束之后,自动退出Future.delayed(Duration.zero, (){//Navigator.of(context).pop();});}});//将状态栏的颜色改变为透明//ScreenUtil.setStatusBarColor(Colors.black);//设置横屏ScreenUtil.setHorizontal();}@overridevoid dispose() {//设置竖屏ScreenUtil.setVertical();SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top, SystemUiOverlay.bottom]);super.dispose();}@overrideWidget build(BuildContext context) {//显示顶部栏并隐藏底部栏//SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top]);//显示底部栏并隐藏顶部栏//SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);//隐藏顶部栏和底部栏SystemChrome.setEnabledSystemUIOverlays([]);return Scaffold(resizeToAvoidBottomInset: false,body: Container(alignment: Alignment.center,color: Colors.black,child: Stack(children: [//初始化视频initVideo(),//初始化开始按钮initStartButton(),//返回按钮initPopButton()],),),);}initVideo(){return Center(child: _controller.value.isInitialized? AspectRatio(child: VideoPlayer(_controller),aspectRatio: _controller.value.aspectRatio): Container(),);}initStartButton(){return Positioned.fill(child: Stack(children: [//初始化渐变开始按钮initOpacityStartButton(),//竖屏按钮initPosition()],));}initOpacityStartButton() {return AnimatedOpacity(opacity: _controller.value.isPlaying? 0: 1,duration: Duration(seconds: 1),child: GestureDetector(onTap: (){_controller.value.isPlaying? _controller.pause(): _controller.play();setState(() {});},child: Container(color: Colors.grey.withOpacity(0.3),child: Center(child: Icon(_controller.value.isPlaying? Icons.pause: Icons.play_arrow,color: Colors.white,),),),),);}initPosition() {return Positioned(right: 40,bottom: 40,child: GestureDetector(onTap: (){isHorizontal = !isHorizontal;isHorizontal? ScreenUtil.setHorizontal(): ScreenUtil.setVertical();setState(() {});},child: Container(width: 100,height: 45,alignment: Alignment.center,decoration: BoxDecoration(color: Colors.grey.withOpacity(0.8),borderRadius: BorderRadius.circular(10)),child: Text(isHorizontal? "竖屏" : "横屏",style: TextStyle(color: Colors.white),),),));}initPopButton(){return Positioned(top: 20,left: 20,child: GestureDetector(onTap: (){//如果和列表项是共享的,那么就不能将controller给dispose//_controller.dispose();if (_controller.value.isPlaying){_controller.pause();}Navigator.of(context).pop();},child: Container(width: 100,height: 45,alignment: Alignment.center,decoration: BoxDecoration(color: Colors.grey.withOpacity(0.8),borderRadius: BorderRadius.circular(10)),child: Icon(Icons.keyboard_return,color: Colors.white,),),));}}

ScreenUtil 类负责设置屏幕横屏和竖屏,这里用到了 SystemChrome, 该类可以通过静态方法 setPreferredOrientations() 来进行横竖屏设置,静态方法 setSystemUIOverlayStyle 来设置状态栏的颜色。

import 'package:flutter/services.dart';class ScreenUtil{//横屏static void setHorizontal(){SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);}//竖屏static void setVertical(){SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);}//修改顶部状态栏颜色static void setStatusBarColor(Color color){SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: color);SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);}}

其他代码

//video_data.dart
enum VideoType{asset, //本地视频network //网络视频
}
//date_time_util.dart
String buildTextString(Duration duration){int inMinutes = duration.inMinutes;//int inSeconds = duration.inSeconds%60; //时间跨越整分钟数String inMinutesStr = inMinutes <10? "0$inMinutes": "$inMinutes";String inSecondsStr = inSeconds <10? "0$inSeconds": "$inSeconds";return "$inMinutesStr:$inSecondsStr";
}

四、效果

如下就是视频播放列表的三种样式:

五、总结

通过上面的代码,虽然可以实现简单的视频播放功能,但是仍然有很多缺陷,比如滑动时比较卡顿,每一个列表项都对应一个 VideoPlayer,是否合理,能否重复利用,减少对原生系统的资源的利用,这样就可以提供内存使用效率,降低内存消耗。同时也有体验上的优化,比如在视频全屏播放的时候,是否可以加上音量和亮度调节,倍速播放,连续播放,小窗播放等功能。

Flutter入门系列-VideoPlayer在列表使用相关推荐

  1. Flutter入门系列(二)---Flutter的原理及美团的实践

    转载自:美团技术团队 导读 Flutter是Google开发的一套全新的跨平台.开源UI框架,支持iOS.Android系统开发,并且是未来新操作系统Fuchsia的默认开发套件.自从2017年5月发 ...

  2. Flutter入门系列-Flutter读取assets文件并写入应用程序路径

    一.思考 在Android开发中经常会遇到需要将asset中的文件拷贝到本地目录中,所以Flutter 是否有能够读取 asset 中文件的API呢?rootBundle 就可以解决该问题. 二.代码 ...

  3. 【Python零基础快速入门系列 | 03】AI数据容器底层核心之Python列表

    • 这是机器未来的第7篇文章 原文首发地址:https://blog.csdn.net/RobotFutures/article/details/124957520 <Python零基础快速入门 ...

  4. Flutter 入门指北(Part 9)之弹窗和提示(SnackBar、BottomSheet、Dialog)

    该文已授权公众号 「码个蛋」,转载请指明出处 前面的小节把常用的一些部件都介绍了,这节介绍下 Flutter 中的一些操作提示.Flutter 中的操作提示主要有这么几种 SnackBar.Botto ...

  5. 【Flutter 问题系列第 22 篇】在 Flutter 中如何截取屏幕并显示到页面中,以及如何将截图保存到相册

    这是[Flutter 问题系列第 22 篇],如果觉得有用的话,欢迎关注专栏. 关于在 Flutter 中如何截取屏幕,以及如何将截图保存到相册的文章少之又少,即使有,也是错误一大片,有的甚至运行后都 ...

  6. flutter 入门示例_AnyChart入门— 10个实用示例

    flutter 入门示例 If your website is data-intensive, then you will need to make that data easy to visuali ...

  7. ASP.NET AJAX入门系列(1):概述

    经常关注我的Blog的朋友可能注意到了,在我Blog的左边系列文章中,已经移除了对Atlas学习手记系列文章的推荐,因为随着ASP.NET AJAX 1.0 Beta版的发布,它们已经不再适用,为了不 ...

  8. [Eclipse]GEF入门系列(六、添加菜单和工具条)

    我发现一旦稍稍体会到GEF的妙处,就会很自然的被它吸引住.不仅是因为用它做出的图形界面好看,更重要的是,UI中最复杂和细微的问题,在GEF的设计中无不被周到的考虑并以适当的模式解决,当你了解了这些,完 ...

  9. eslint vscode 自动格式化_Vue 入门系列第二期,开发环境与 ESLint 配置

    引言 开发 Vue 项目前,做好开发环境的相关配置非常重要,它可以提高我们的开发效率. 在「Vue 入门系列」第一期,新手快速入门指南,初识 Vue 一期中,我们利用 Vue CLI 搭建了项目骨架, ...

  10. python语言入门m-「数据挖掘入门系列」Python快速入门

    Python环境搭建 本次入门系列将使用Python作为开发语言.要使用Python语言,我们先来搭建Python开发平台.我们将基于Python 2.7版本.以及Python的开发发行版本Anaco ...

最新文章

  1. 康奈尔大学王飞博士:AI处理医疗数据面临的8大挑战
  2. 全球及中国潜水压力传感器行业运行态势及发展战略研究报告2022-2027年
  3. 通过 html5 FileReader 实现上传图片预览功能
  4. date得到当前日期
  5. 单机版简易考试系统开发过程讲解(C#注册机、用户注册、考试系统、×××全部源码)...
  6. 凸多边形面积_C++计算任意多边形的面积
  7. 协议簇:IPv4 解析
  8. 【CSS】当图片加载缓慢时,图片如何自适应高度
  9. EF6 Codefirst+MySql 数据库迁移
  10. python中如何导入数据包_如何在python中发送数据包?
  11. 可代替 ASM,使用 AnnotationProcessor 做代码插桩
  12. vmd安装包_VMD分子模拟软件下载
  13. 拟合程度的评估--判定系数
  14. 阿里云服务器安装jdk8版本
  15. Python + Django4 搭建个人博客(十):实现文章详情页面
  16. Oracle-存储过程(procedure、function、package、tigger)
  17. 钢铁侠马斯克的野望:实现载人航天,开源特斯拉自动驾驶!
  18. 献给那些浮躁的职场人(转)
  19. 如何设计一个秒杀系统
  20. Opencv学习笔记 图像分割三(ImageJ 分水岭)

热门文章

  1. 我们计划招收300名深度学习者,免费攻读傅里叶变换和MATLAB
  2. Python爬虫:数据提取
  3. android编写计算器代码,Android入门计算器编写代码
  4. 【NOIP2017】宝藏
  5. 批量替换字符串的python实现
  6. Modelsim 10.2c 百度网盘下载
  7. FA:萤火虫算法的改进及Python实现
  8. RouterOS利用(L2TP)实现异地组网
  9. 为Navicat ER图增添字段备注
  10. 通过海康sdk捕获码流数据实现抓图功能