flutter 如何判断在哪个页面_Agora 教程:构建你的第一个 Flutter 视频通话应用

作者:张乾泽,声网Agora 工程师

我们基于 Agora SDK 封装了 Agora Flutter SDK ,开发者可以在 Flutter 应用中快速实现稳定、可靠的实时音视频通话、互动直播应用。本文将介绍如何使用 Agora Flutter SDK 快速构建一个简单的移动跨平台视频通话应用。

环境准备

在 Flutter 中文网(flutterchina.club)上,关于搭建开放环境的教程已经相对比较完善了,有关 IDE 与环境配置的过程本文不再赘述,若 Flutter 安装有问题,可以执行 flutter doctor 做配置检查。

本文使用 MacOS 下的 VS Code 作为主开发环境。

目标

我们希望可以使用 Flutter+Agora Flutter SDK 实现一个简单的视频通话应用,这个视频通话应用需要包含以下功能,

  • 加入通话房间

  • 视频通话

  • 前后摄像头切换

  • 本地静音/取消静音

声网的视频通话是按通话房间区分的,同一个通话房间内的用户都可以互通。为了方便区分,这个演示会需要一个简单的表单页面让用户提交选择加入哪一个房间。同时一个房间内可以容纳最多 4 个用户,当用户数不同时我们需要展示不同的布局。

想清楚了?动手撸代码了。

2e03f4ba444123436568ee6ff40b2e19.png

项目创建

首先在 VS Code 选择 查看->命令面板(或直接使用 cmd + shift + P)调出命令面板,输入 flutter 后选择 Flutter: New Project 创建一个新的 Flutter 项目,项目的名字为 agora_flutter_quickstart,随后等待项目创建完成即可。

现在执行 启动->启动调试(或 F5)即可看到一个最简单的计数 App。

29750d448007fc35c6ff97ffd6521740.png

看起来我们有了一个很好的开始a72bd24b94eca6fcf1f5bbe41be7e0c9.png

接下去我们需要对我们新建的项目做一下简单的配置以使其可以引用和使用 Agora Flutter SDK。

打开项目根目录下的 pubspec.yaml 文件,在 dependencies下添加 agora_rtc_engine:^0.9.0

dependencies:

 flutter:

   sdk: flutter

 # The following adds the Cupertino Icons font to your application.

 # Use with the CupertinoIcons class for iOS style icons.

 cupertino_icons: ^0.1.2

 # add agora rtc sdk

 agora_rtc_engine: ^0.9.0

dev_dependencies:

 flutter_test:

   sdk: flutter

保存后 VS Code 会自动执行 flutter packages get 更新依赖。

应用首页

在项目配置完成后,我们就可以开始开发了。首先我们需要创建一个页面文件替换掉默认示例代码中的 MyHomePage 类。我们可以在 lib/src 下创建一个 pages目录,并创建一个  index.dart 文件。

如果你已经完成了官方教程 Write your first Flutter app,那么以下代码对你来说就应该不难理解。

class IndexPage extends StatefulWidget {

 @override

 State<StatefulWidget> createState() {

   return new IndexState();

 }

}

class IndexState extends State<IndexPage> {

 @override

 Widget build(BuildContext context) {

     // UI

 }

 onJoin() {

     //TODO

 }

}

现在我们需要开始在 build 方法中构造首页的 UI。

989f0387ad43ea3fbc652609f17853e7.png

按上图分解 UI 后,我们可以将我们的首页代码修改如下

@override

Widget build(BuildContext context) {

return Scaffold(

   appBar: AppBar(

     title: Text('Agora Flutter QuickStart'),

   ),

   body: Center(

     child: Container(

         padding: EdgeInsets.symmetric(horizontal: 20),

         height: 400,

         child: Column(

           children: <Widget>[

             Row(children: <Widget>[]),

             Row(children: <Widget>[

               Expanded(

                   child: TextField(

                 decoration: InputDecoration(

                     border: UnderlineInputBorder(

                         borderSide: BorderSide(width: 1)),

                     hintText: 'Channel name'),

               ))

             ]),

             Padding(

                 padding: EdgeInsets.symmetric(vertical: 20),

                 child: Row(

                   children: <Widget>[

                     Expanded(

                       child: RaisedButton(

                         onPressed: () => onJoin(),

                         child: Text("Join"),

                         color: Colors.blueAccent,

                         textColor: Colors.white,

                       ),

                     )

                   ],

                 ))

           ],

         )),

   ));

}

执行 F5 启动查看,应该可以看到下图

ff56b670d7938c4b0c54ded879dc1939.png

看起来不错!但也只是看起来不错。我们的UI现在只能看,还不能交互。我们希望可以基于现在的 UI 实现以下功能,

1. 为 Join 按钮添加回调导航到通话页面

2. 对频道名做检查,若尝试加入频道时频道名为空,则在 TextField 上提示错误

TextField 输入校验

TextField 自身提供了一个 decoration属性,我们可以提供一个 InputDecoration的对象来标识 TextField 的装饰样式。 InputDecoration里的 errorText属性非常适合在我们这里被拿来使用, 同时我们利用 TextEditingController对象来记录 TextField 的值,以判断当前是否应该显示错误。因此经过简单的修改后,我们的 TextField 代码就变成了这样:

   final _channelController = TextEditingController();

   /// if channel textfield is validated to have error

   bool _validateError = false;

   @override

   void dispose() {

       // dispose input controller

       _channelController.dispose();

       super.dispose();

   }

   @override

    Widget build(BuildContext context) {

       ...

       TextField(

         controller: _channelController,

         decoration: InputDecoration(

             errorText: _validateError

                 ? "Channel name is mandatory"

                 : null,

             border: UnderlineInputBorder(

                 borderSide: BorderSide(width: 1)),

             hintText: 'Channel name'),

       ))

       ...

   }

   onJoin() {

       // update input validation

       setState(() {

         _channelController.text.isEmpty

             ? _validateError = true

             : _validateError = false;

       });

   }

在点击加入频道按钮的时候回触发 onJoin回调,回调中会先通过 setState更新 TextField 的状态以做组件重绘。

552f6c55012fd7a25d01b7836fa1ca6d.png

注意: 不要忘了 override dispose方法在这个组件的生命周期结束时释放 _controller

前往通话页面

到这里我们的首页基本就算完成了,最后我们在 onJoin中创建 MaterialPageRoute将用户导航到通话页面,在这里我们将获取的频道名作为通话页面构造函数的参数传递到下一个页面 CallPage

import './call.dart';

class IndexState extends State<IndexPage> {

   ...

   onJoin() {

       // update input validation

       setState(() {

         _channelController.text.isEmpty

             ? _validateError = true

             : _validateError = false;

       });

       if (_channelController.text.isNotEmpty) {

         // push video page with given channel name

         Navigator.push(

             context,

             MaterialPageRoute(

                 builder: (context) => new CallPage(

                       channelName: _channelController.text,

                     )));

   }

}

通话页面

同样在 /lib/src/pages目录下,我们需要新建一个 call.dart文件,在这个文件里我们会实现我们最重要的实时视频通话逻辑。首先还是需要创建我们的 CallPage类。如果你还记得我们在 IndexPage的实现, CallPage会需要在构造函数中带入一个参数作为频道名。

class CallPage extends StatefulWidget {

   /// non-modifiable channel name of the page

   final String channelName;

   /// Creates a call page with given channel name.

   const CallPage({Key key, this.channelName}) : super(key: key);

   @override

   _CallPageState createState() {

       return new _CallPageState();

   }

}

class _CallPageState extends State<CallPage> {

   @override

   Widget build(BuildContext context) {

       return Scaffold(

           appBar: AppBar(

             title: Text(widget.channelName),

           ),

           backgroundColor: Colors.black,

           body: Center(

               child: Stack(

             children: <Widget>[],

           )));

   }

}

这里需要注意的是,我们并不需要把参数在创建 state实例的时候传入, state可以直接访问 widget.channelName获取到组件的属性。

引入声网SDK

因为我们在最开始已经在 pubspec.yaml中添加了 agora_rtc_engine的依赖,因此我们现在可以直接通过以下方式引入声网 SDK。

import 'package:agora_rtc_engine/agora_rtc_engine.dart';

引入后即可以使用创建声网媒体引擎实例。在使用声网 SDK 进行视频通话之前,我们需要进行以下初始化工作。初始化工作应该在整个页面生命周期中只做一次,因此这里我们需要 override initState方法,在这个方法里做好初始化。

class _CallPageState extends State<CallPage> {

   @override

   void initState() {

       super.initState();

       initialize();

   }

   void initialize() {

       _initAgoraRtcEngine();

       _addAgoraEventHandlers();

   }

   /// Create agora sdk instance and initialze

   void _initAgoraRtcEngine() {

       AgoraRtcEngine.create(APP_ID);

       AgoraRtcEngine.enableVideo();

   }

   /// Add agora event handlers

  void _addAgoraEventHandlers() {

   AgoraRtcEngine.onError = (int code) {

     // sdk error

   };

   AgoraRtcEngine.onJoinChannelSuccess =

       (String channel, int uid, int elapsed) {

     // join channel success

   };

   AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {

     // there's a new user joining this channel

   };

   AgoraRtcEngine.onUserOffline = (int uid, int reason) {

     // there's an existing user leaving this channel

   };

 }

}

注意: 有关如何获取声网 APP_ID,请访问 docs.gora.io 参阅官方文档。

在以上的代码中我们主要创建了声网的媒体 SDK 实例并监听了关键事件,接下去我们会开始做视频流的处理。

在一般的视频通话中,对于本地设备来说一共会有两种视频流,本地流与远端流 - 前者需要通过本地摄像头采集渲染并发送出去,后者需要接收远端流的数据后渲染。现在我们需要动态地将最多4人的视频流渲染到通话页面。

我们会以大致这样的结构渲染通话页面。

3abe84c7f27252fae91ad5290b17f5bb.png

这里和首页不同的是,放置通话操作按钮的工具栏是覆盖在视频上的,因此这里我们会使用 Stack组件来放置层叠组件。

为了更好地区分 UI 构建,我们将视频构建与工具栏构建分为两个方法。

本地流创建与渲染

要渲染本地流,需要在初始化 SDK 完成后创建一个供视频流渲染的容器,然后通过 SDK 将本地流渲染到对应的容器上。声网 SDK 提供了 createNativeView的方法以创建容器,在获取到容器并且成功渲染到容器视图上后,我们就可以利用SDK加入频道与其他客户端互通了。

   void initialize() {

       _initAgoraRtcEngine();

       _addAgoraEventHandlers();

       // use _addRenderView everytime a native video view is needed

       _addRenderView(0, (viewId) {

           // local view setup & preview

           AgoraRtcEngine.setupLocalVideo(viewId, 1);

           AgoraRtcEngine.startPreview();

           // state can access widget directly

           AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0);

       });

   }

   /// Create a native view and add a new video session object

   /// The native viewId can be used to set up local/remote view

   void _addRenderView(int uid, Function(int viewId) finished) {

       Widget view = AgoraRtcEngine.createNativeView(uid, (viewId) {

         setState(() {

           _getVideoSession(uid).viewId = viewId;

           if (finished != null) {

             finished(viewId);

           }

         });

       });

       VideoSession session = VideoSession(uid, view);

       _sessions.add(session);

   }

注意: 代码最后利用 uid 与容器信息创建了一个 VideoSession对象并添加到 _sessions中,这主要是为了视频布局需要,这块稍后会详细触及。

远端流监听与渲染

远端流的监听其实我们已经在前面的初始化代码中提及了,我们可以监听 SDK 提供的 onUserJoined与 onUserOffline回调来判断是否有其他用户进出当前频道,若有新用户加入频道,就为他创建一个渲染容器并做对应的渲染;若有用户离开频道,则去掉他的渲染容器。

   AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {

     setState(() {

       _addRenderView(uid, (viewId) {

         AgoraRtcEngine.setupRemoteVideo(viewId, 1, uid);

       });

     });

   };

   AgoraRtcEngine.onUserOffline = (int uid, int reason) {

     setState(() {

       _removeRenderView(uid);

     });

   };

   /// Remove a native view and remove an existing video session object

   void _removeRenderView(int uid) {

       VideoSession session = _getVideoSession(uid);

       if (session != null) {

         _sessions.remove(session);

       }

       AgoraRtcEngine.removeNativeView(session.viewId);

   }

注意: _sessions的作用是在本地保存一份当前频道内的视频流列表信息。因此在用户加入的时候,需要创建对应的 VideoSession对象并添加到 sessions,在用户离开的时候,则需要删除对应的 VideoSession实例。

视频流布局

在有了 _sessions数组,且每一个本地/远端流都有了一个对应的原生渲染容器后,我们就可以开始对视频流进行布局了。

   /// Helper function to get list of native views

   List<Widget> _getRenderViews() {

       return _sessions.map((session) => session.view).toList();

   }

   /// Video view wrapper

   Widget _videoView(view) {

       return Expanded(child: Container(child: view));

   }

   /// Video view row wrapper

   Widget _expandedVideoRow(List<Widget> views) {

       List<Widget> wrappedViews =

           views.map((Widget view) => _videoView(view)).toList();

       return Expanded(

           child: Row(

         children: wrappedViews,

   ));

   }

   /// Video layout wrapper

   Widget _viewRows() {

       List<Widget> views = _getRenderViews();

       switch (views.length) {

         case 1:

           return Container(

               child: Column(

             children: <Widget>[_videoView(views[0])],

           ));

         case 2:

           return Container(

               child: Column(

             children: <Widget>[

               _expandedVideoRow([views[0]]),

               _expandedVideoRow([views[1]])

             ],

           ));

         case 3:

           return Container(

               child: Column(

             children: <Widget>[

               _expandedVideoRow(views.sublist(0, 2)),

               _expandedVideoRow(views.sublist(2, 3))

             ],

           ));

         case 4:

           return Container(

               child: Column(

             children: <Widget>[

               _expandedVideoRow(views.sublist(0, 2)),

               _expandedVideoRow(views.sublist(2, 4))

             ],

           ));

         default:

       }

       return Container();

   }

工具栏(挂断、静音、切换摄像头)

在实现完视频流布局后,我们接下来实现视频通话的操作工具栏。工具栏里有三个按钮,分别对应静音、挂断、切换摄像头的顺序。用简单的 flexRow布局即可。

   /// Toolbar layout

   Widget _toolbar() {

       return Container(

         alignment: Alignment.bottomCenter,

         padding: EdgeInsets.symmetric(vertical: 48),

         child: Row(

           mainAxisAlignment: MainAxisAlignment.center,

           children: <Widget>[

             RawMaterialButton(

               onPressed: () => _onToggleMute(),

               child: new Icon(

                 muted ? Icons.mic : Icons.mic_off,

                 color: muted ? Colors.white : Colors.blueAccent,

                 size: 20.0,

               ),

               shape: new CircleBorder(),

               elevation: 2.0,

               fillColor: muted?Colors.blueAccent : Colors.white,

               padding: const EdgeInsets.all(12.0),

             ),

             RawMaterialButton(

               onPressed: () => _onCallEnd(context),

               child: new Icon(

                 Icons.call_end,

                 color: Colors.white,

                 size: 35.0,

               ),

               shape: new CircleBorder(),

               elevation: 2.0,

               fillColor: Colors.redAccent,

               padding: const EdgeInsets.all(15.0),

             ),

             RawMaterialButton(

               onPressed: () => _onSwitchCamera(),

               child: new Icon(

                 Icons.switch_camera,

                 color: Colors.blueAccent,

                 size: 20.0,

               ),

               shape: new CircleBorder(),

               elevation: 2.0,

               fillColor: Colors.white,

               padding: const EdgeInsets.all(12.0),

             )

           ],

         ),

       );

   }

   void _onCallEnd(BuildContext context) {

       Navigator.pop(context);

   }

   void _onToggleMute() {

       setState(() {

         muted = !muted;

       });

       AgoraRtcEngine.muteLocalAudioStream(muted);

   }

   void _onSwitchCamera() {

       AgoraRtcEngine.switchCamera();

   }

最终整合

现在两个部分的 UI 都完成了,我们接下去要将这两个组件通过 Stack组装起来。

   @override

   Widget build(BuildContext context) {

       return Scaffold(

           appBar: AppBar(

             title: Text(widget.channelName),

           ),

           backgroundColor: Colors.black,

           body: Center(

               child: Stack(

             children: <Widget>[_viewRows(), _toolbar()],

           )));

清理

若只在当前页面使用声网 SDK,则需要在离开前调用 destroy接口将 SDK 实例销毁。若需要跨页面使用,则推荐将 SDK 实例做成单例以供不同页面访问。同时也要注意对原生渲染容器的释放,可以至直接使用 removeNativeView方法释放对应的原生容器。

   @override

   void dispose() {

       // clean up native views & destroy sdk

       _sessions.forEach((session) {

         AgoraRtcEngine.removeNativeView(session.viewId);

       });

       _sessions.clear();

       AgoraRtcEngine.destroy();

       super.dispose();

   }

最终效果:

b1863407394e991c0482df8e7408eca5.png

总结

Flutter 作为新生事物,难免还是有他不成熟的地方,但我们已经从他现在的进步上看到了巨大的潜力。从目前的体验来看,只要有充足的社区资源,在 Flutter 上开发跨平台应用还是比较舒服的。声网提供的 Flutter SDK 基本已经覆盖了原生 SDK 提供的大部分方法,开发体验基本可以和原生 SDK 开发保持一致。这次也是基于学习的态度写下了这篇文章,希望对于想要使用 Flutter 开发 RTC 应用的同学有所帮助。

文中讲解的完整代码及 Agora Flutter SDK 可在 Github 获取。

Agora Flutter SDK:

https://github.com/AgoraIO/Flutter-SDK

Quickstart Demo:

https://github.com/AgoraIO-Community/Agora-Flutter-Quickstart

313beb9d9d16fcb5caf4613b0b200fd2.gif

相关阅读:

[1] Agora Flutter SDK:一套代码,实现双端通话

[2] Flutter 的渲染逻辑,以及与 Native 通信

[3] Flutter + WebRTC:Agora Flutter SDK 设计实践

如开发中遇到问题,可访问 RTC 开发者社区发帖提问

66c6904a7bf7ef4d9bbb7a2cb9e5bd52.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/528639.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net core高并发_高并发下的Node.js与负载均衡

阅读本文约需要6分钟大家好&#xff0c;我是你们的导师&#xff0c;我每天都会在这里给大家分享一些干货内容(当然了&#xff0c;周末也要允许老师休息一下哈)。上次老师跟大家分享了下浅谈前端自动化构建的相关知识&#xff0c;今天跟大家分享浅谈前端自动化构建的相关知识参考…

android arm 寄存器,ARM汇编

8种机械键盘轴体对比本人程序员&#xff0c;要买一个写代码的键盘&#xff0c;请问红轴和茶轴怎么选&#xff1f;Android Native 进程启动流程ARM传参&#xff0c;R0-R3传递前四个参数1. Thumb 指令集特点Thumb 指令集指令长度&#xff1a;16或32&#xff0c;16为为主Thumb-16 …

如何制作印章_用Word也能做出逼真的电子印章!简单3步!1分钟搞定

我们在办公的时候经常需要用到印章&#xff0c;以前可能是在纸上盖章&#xff0c;现在电子文档比较多&#xff0c;电子印章也开始流行使用&#xff0c;那么我们如何制作一个逼真的印章呢&#xff1f;很简单&#xff0c;用Word就可以搞定啦&#xff01;一、印章制作1、圆圈制作首…

鸿蒙唯独没有手机,想用鸿蒙OS,却没有华为手机?华为高层:还有1亿台设备可体验...

不再是PTT系统&#xff01;鸿蒙OS真的要来了&#xff0c;年内初步实现全场景交互从2019年开始&#xff0c;有关 鸿蒙OS 的消息越来越多&#xff0c;不仅公布了发展规划&#xff0c;也确定将适配多款机型&#xff0c;期待度直接拉满。可惜以目前的情况来看&#xff0c;鸿蒙OS 适…

@kafkalistener中id的作用_SSM框架(十一):Spring框架中的IoC(1)

控制反转 IOC&#xff1a;控制反转&#xff08;Inversion of Control&#xff0c;缩写为IoC&#xff09;&#xff0c;是面向对象编程中的一种设计原则&#xff0c;可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入&#xff08;Dependency Injection&#xff…

keras优化算法_目标检测算法 - CenterNet - 代码分析

代码出处吃水不忘打井人&#xff0c;分析github上的基于keras的实现&#xff1a;xuannianz/keras-CenterNet​github.com代码主体结构模型训练的主函数流程如下所示&#xff0c;该流程也是使用keras的较为标准的流程。其中代码篇幅较大的是数据准备的部分&#xff0c;通常的代码…

c语言中创建一个整数数组_VBA中动态数组的创建及利用方法

大家好&#xff0c;后疫情时代一定会到来&#xff0c;各行各业&#xff0c;都将是一场战胜萧条的无声的战役。无论怎样&#xff0c;我们一定要坚信&#xff0c;疫情终将会过去&#xff0c;曙光一定会到来。后疫情时代将会是一个全新的世界&#xff0c;很多理念都将被打破&#…

用计算机求函数公式,计算机常用的函数公式有哪些?

01计算机常用的函数公式包括RANK函数、COUNTIF函数、IF函数、ABS函数、AND函数、AVERAGE函数、COLUMN 函数等。RANK函数是Excel计算序数的主要工具&#xff0c;它的语法为&#xff1a;RANK(number&#xff0c;ref&#xff0c;order)&#xff0c;其中number为参与计算的数字或含…

golang 读取文件最后一行_python3从零学习-5.4.3、文件输入流fileinput

源代码: Lib/fileinput.py此模块实现了一个辅助类和一些函数用来快速编写访问标准输入或文件列表的循环。 如果你只想要读写一个文件请参阅 open().典型用法为:import fileinputfor line in fileinput.input(): process(line)这将遍历sys中列出的所有文件的行。argv[1:]如果…

云计算机具体应用场景,云计算的定义、类型及应用场景

云计算是20年来IT行业出现的最激动人心且最具颠覆性的技术&#xff0c;甚至比大型主机向客户端/服务器架构的迁移还更具颠覆性。无论是IT服务的交付方式&#xff0c;还是企业消费这些IT服务的方式&#xff0c;都因云计算而改变。用户也正在快速应对新架构带来的变革&#xff0c…

dataframe for循环 筛选_Python循环12种超强写法,又快又省内存

0 前言说到处理循环&#xff0c;我们习惯使用for, while等&#xff0c;比如依次打印每个列表中的字符&#xff1a;在打印内容字节数较小时&#xff0c;全部载入内存后&#xff0c;再打印&#xff0c;没有问题。可是&#xff0c;如果现在有成千上百万条车辆行驶轨迹&#xff0c;…

html5文字飞入插件,jquery使用CSS3实现文字动画效果插件Textillate.js

jquery使用CSS3实现文字动画效果插件Textillate.jsTextillate是一款基于CSS3动画效果的 JavaScript 库&#xff0c;您可非常轻轻松地把这些动画效果应该于网页中的任何文字。使用方法引入核心文件构建html标签My Title写入JS&#xff0c;初始化$(function () {$(.tlt).textilla…

工业机器人导轨 百度文库_工业机器人或许开创一个全新的PLC时代

自机器人诞生之日起人们就不断地尝试着说明到底什么是机器人。但随着机器人技术的飞速发展和信息时代的到来&#xff0c;机器人所涵盖的内容越来越丰富&#xff0c;机器人的定义也不断充实和创新。机器人技术作为20世纪人类最伟大的发明之一&#xff0c;自20世纪60年代初问世以…

银联分账与银联代付_第三方分账系统到底有哪些作用?

随着监管越来越严&#xff0c;业务越来越复杂&#xff0c;所有平台电商企业都需要通过第三方分账系统解决支付清算及二清等问题。作为第三方分账系统行业从业者&#xff0c;整理了部分关于系统的相关问题及解答&#xff0c;希望对大家有所帮助。问题一&#xff1a;第三方分账系…

计算机更改桌面,2010年职称计算机考试:更改桌面背景和颜色

Windows XP提供了各种桌面的颜色和背景方案,用户可以根据自己的爱好进行选择。颜 色充当桌面的最底层,背景覆盖于颜色之上。(l)桌面背景的更改在"显示属性"对话框中,选择"桌面"选项卡。在"桌面"选项卡上有一个"背景"列表框,选择列表框…

dell t40 固态系统盘_笔记本怎么安装固态硬盘 笔记本安装固态硬盘教程【详解】...

首先我不得不介绍一下我这古董级别的笔记本 硬件升级&#xff0c;然后给大家介绍一下笔记本安装固态硬盘的教程。dell戴尔1320&#xff0c;cpu t6500 主频2.1ghz 。内存运用了淘汰的ddr2 分别是两条1g&#xff0c;共2g 800频率。这样的古董用的着用固态么?SATA2.0接口完全成为…

如何进入zabbix的wab界面_如何不用光盘重装系统呢?

如果身边没有系统光盘如何重装系统呢&#xff1f;小鱼系统可以帮助你一键重装系统和U盘重装系统的方法&#xff0c;下面一起看下小鱼系统教你的如何不用光盘重装系统吧。一键重装系统方法1、首先请备份好电脑C盘重要资料&#xff0c;然后打开【小鱼系统】&#xff0c;进入电脑环…

全国高中计算机大赛,2019年含金量最大的中小学全国性竞赛活动——五大学科竞赛...

五大学科竞赛历史是我国含金量最高的中小学全国性竞赛活动&#xff0c;在各高校自主招生过程中&#xff0c;具有较高的参考价值。对相关方面有一定兴趣的同学&#xff0c;也可以积极参与。五大赛面向的对象都是高中学生。一、全国中学生数学奥林匹克竞赛此项竞赛的主办单位是中…

单片机红绿灯电路灯有几种_新农村建设的太阳能路灯如何选择?

随着我国城乡一体化发展进程&#xff0c;城乡道路照明已成为新农村建设必不可少的一部分。目前市面通用的道路照明有两种&#xff1a;即市电路灯照明&#xff08;市电220V&#xff09;和太阳能路灯照明。安装简便且不需要布线的太阳能路灯在新农村建设中应用广泛&#xff0c;下…

lr不能直接转ps编辑_摄影后期调色软件,Lr软件包

摄影后期调色除了用到PS软件还有一款与之媲美的就是LR软件了&#xff0c;全名是Adobe Lightroom&#xff0c;喜欢拍照的人&#xff1b;总会希望自己拍下来的照片更好看&#xff0c;不管是多好的器材直出的 jpg 本身总会多多少少有一些限制&#xff0c;而利用后期软件可以很好的…