扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
APP 启动页在国内是最常见也是必备的场景,其中启动页在 iOS 上算是强制性的要求,其实配置启动页挺简单,因为在 Flutter 里现在只需要:
为海门等地区用户提供了全套网页设计制作服务,及海门网站建设行业解决方案。主营业务为网站建设、成都网站设计、海门网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
一般只要配置无误并且图片尺寸匹配,基本上就不会有什么问题, 那既然这样,还有什么需要适配的呢?
事实上大部分时候 iOS 是不会有什么问题, 因为 LaunchScreen.storyboard 的流程本就是 iOS 官方用来做应用启动的过渡;而对于 Andorid 而言,直到 12 之前 windowBackground 这种其实只能算“民间”野路子 ,所以对于 Andorid 来说,这其中就涉及到一个点:
所以下面主要介绍 Flutter 在 Android 上为了这个启动图做了哪些骚操作~
在已经忘记版本的“远古时期” , FlutterActivity 还在 io.flutter.app.FlutterActivity 路径下的时候,那时启动页的逻辑相对简单,主要是通过 App 的 AndroidManifest 文件里是否配置了 SplashScreenUntilFirstFrame 来进行判断。
在 FlutterActivity 内部 FlutterView 被创建的时候,会通过读取 meta-data 来判断是否需要使用 createLaunchView 逻辑 :
是不是很简单,那就会有人疑问为什么要这样做?我直接配置 Activity 的 android:windowBackground 不就完成了吗?
这就是上面提到的时间差问题, 因为启动页到 Flutter 渲染完第一帧画面中间,会出现概率出现黑屏的情况,所以才需要这个行为来实现过渡 。
经历了“远古时代”之后, FlutterActivity 来到了 io.flutter.embedding.android.FlutterActivity , 在到 2.5 版本发布之前,Flutter 又针对这个启动过程做了不少调整和优化,其中主要就是 SplashScreen 。
自从开始进入 embedding 阶段后, FlutterActivity 主要用于实现了一个叫 Host 的 interface ,其中和我们有关系的就是 provideSplashScreen 。
默认情况下它会从 AndroidManifest 文件里是否配置了 SplashScreenDrawable 来进行判断 。
默认情况下当 AndroidManifest 文件里配置了 SplashScreenDrawable ,那么这个 Drawable 就会在 FlutterActivity 创建 FlutterView 时被构建成 DrawableSplashScreen 。
DrawableSplashScreen 其实就是一个实现了 io.flutter.embedding.android.SplashScreen 接口的类,它的作用就是:
之后 FlutterActivity 内会创建出 FlutterSplashView ,它是个 FrameLayout。
FlutterSplashView 将 FlutterView 和 ImageView 添加到一起, 然后通过 transitionToFlutter 的方法来执行动画,最后动画结束时通过 onTransitionComplete 移除 splashScreenView 。
所以整体逻辑就是:
当然这里也是分状态:
当然这个阶段的 FlutterActivity 也可以通过 override provideSplashScreen 方法来自定义 SplashScreen 。
看到没有,做了这么多其实也就是为了弥补启动页和 Flutter 渲染之间, 另外还有一个优化,叫 NormalTheme 。
通过该配置 NormalTheme ,在 Activity 启动时,就会首先执行 switchLaunchThemeForNormalTheme(); 方法将主题从 LaunchTheme 切换到 NormalTheme 。
大概配置完就是如下样子, 前面分析那么多其实就是为了告诉你,如果出现问题了,你可以从哪个地方去找到对应的点 。
讲了那么多, Flutter 2.5 之后 provideSplashScreen 和 io.flutter.embedding.android.SplashScreenDrawable 就被弃用了,惊不喜惊喜,意不意外,开不开心 ?
通过源码你会发现,当你设置了 splashScreen 的时候,会看到一个 log 警告:
为什么会弃用?
其实这个提议是在 这个 issue 上,然后通过 这个 pr 完成调整。
大概意思就是: 原本的设计搞复杂了,用 OnPreDrawListener 更精准,而且不需要为了后面 Andorid12 的启动支持做其他兼容,只需要给 FlutterActivity 等类增加接口开关即可 。
也就是2.5之后 Flutter 使用 ViewTreeObserver.OnPreDrawListener 来实现延迟直到加载出 Flutter 的第一帧。
为什么说默认情况? 因为这个行为在 FlutterActivity 里,是在 getRenderMode() == RenderMode.surface 才会被调用,而 RenderMode 又和 BackgroundMode 有关心 。
所以在 2.5 版本后, FlutterActivity 内部创建完 FlutterView 后就会执行一个 delayFirstAndroidViewDraw 的操作。
这里主要注意一个参数: isFlutterUiDisplayed 。
当 Flutter 被完成展示的时候, isFlutterUiDisplayed 就会被设置为 true。
所以当 Flutter 没有执行完成之前, FlutterView 的 onPreDraw 就会一直返回 false ,这也是 Flutter 2.5 开始之后适配启动页的新调整。
看了这么多,大概可以看到其实开源项目的推进并不是一帆风顺的,没有什么是一开始就是最优解,而是经过多方尝试和交流,才有了现在的版本,事实上开源项目里,类似这样的经历数不胜数:
CustomPaint class提供了让用户自定义widget的能力,它暴露了一个canvas,可以通过这个canvas来绘制widget,CustomPaint会先调用painter绘制背景,然后再绘制child,最后调用foregroundPainter来绘制前景,CustomPaint的定义如下
CustomPaint的绘制过程都将会交给CustomPainter来完成,CustomPainter是个抽象接口,在子类化CustomPainter的时候必须要重写它的 paint 跟 shouldRepaint 接口,可以根据自己的场景来选择性的重写 hitTest 跟 shouldRebuildSemantics 方法。
canvas--画布,真正的绘制是由canvas跟paint来完成的,画布提供了各种绘制的接口来绘制图形,除此以外画布还提供了平移、缩放、旋转等矩阵变换接口,画布都有固定大小跟形状,还可以使用画布提供的裁剪接口来裁剪画布的大小形状等等。
常用的绘制接口有 更多请查看官方文档
Paint---笔画,是用来设置在画布上面绘制图形时的一些笔画属性,如:颜色、线宽、绘制模式、抗锯齿等等。常用属性有 更多请查看官方文档
color : 设置画笔颜色
isAntiAlias : 设置画笔是否扛锯齿
shader : 着色器,填充形状或者画线时用到,如果没设置将会使用color
strokeWidth : 设置画笔画线宽度
style :绘制模式,画线或充满
下面这个例子来自于官方,通过 CustomPaint 画出了一个蓝天跟太阳出来
效果如下:
Flutter中自定义组件一般有两种方式:
CustomPaint继承自SingleChildRenderObjectWidget,即它可以在通过嵌套引入到widget树中,并且可以有一个child子widget。它的构造方法如下:
painter和foregroundPainter需要接收CustomPainter对象,是CustomPaint核心。CustomPainter是进行UI绘制的核心类,绘制时, CustomPaint 首先在画布上调用 painter绘制 , 然后再绘制它的 child Widget, child 绘制完成后再调用 foregroundPainter 进行绘制。
size属性标识绘制区域大小,但当CustomPaint有child,该属性将会忽略,而使用child的大小为绘制区域大小。
isComplex和willChange用于控制绘制层缓存处理的,这里暂不讨论。
可实现CustomPainter子类进行UI绘制
实现paint方法进行真正的绘制,canvas是画布对象,size是绘制区域,是从CustomPaint中size属性传递得到的。绘制过程与Android原生开发十分类似,连API都十分相像,这点对熟悉Android原生开发者真是太友好了。
Paint对象是画笔对象,就是绘图工具,我们可以设置画笔的颜色、粗细、是否抗锯齿、笔触形状以及作画风格等,通过这些属性我们可以很方便的来定制自己的UI效果,在绘制的过程中可以定义多个画笔,以便实现多种风格图形的集合。
根据需求选择合适的画笔属性,完成你的绘制。
Canvas是绘制的画布,它包含了很多绘制方法,可以绘制出各种形状的图形。需要注意的是,画布是应用所有控件都在使用的, 所以通过这个画布其实是可以绘制充满屏幕的内容的,每次绘制都应该限制在本控件的区域(Size)内, 以免绘制覆盖到其他组件。
下面介绍下Canvas的绘制方法:
PointMode是个枚举
p1、p2为线段两个端点
Rect定义矩形的大小位置,有多种构造方式:
RRect描述圆角矩形,他通过Rect和Radius来构造
画圆比较简单,c表示圆心位置,radius是半径。
椭圆使用外接矩形确定大小位置,rect就是外接矩形。
绘制弧形,先确定弧形对应的椭圆,同样地用外接矩形rect确定椭圆,然后根据起始点和结束点角度来确定那一段弧度,startAngle,sweepAngle分别代表起始和结束点角度,角度用弧度表示法。
useCenter表示是否连接闭合形状,userCenter = false表示不闭合,即画一段弧线,userCenter = true表示闭合,即绘制一个扇形。
绘制路径,关键在于构建路径Path,可以直接new Path对象,然后通过path方法可以连接出图形,path关键方法如下:
还有其他方法,有兴趣可以查看API。
在开发中,文本是我们最常接触的控件。这篇博客来分享一下 Flutter 中的 Text 文本组件,我们展示的文本都可以用这个组件来展示,希望看文章的小伙伴有所帮助。
这样我们就可以在界面当中显示一个文本,显示是 Hello Flutter 。下面我们来说说 Text 组件的属性:
设置文本颜色:
设置文本大小:
设置文本样式-加粗:
设置文本样式-斜体:
设置文本位置:
TextAlign可选属性: center 、 end 、 start 、 justify 、 left 、 right 。
设置文本高度:
设置文本最大行数:
设置文本有下划线:
设置文本有虚线类型下划线:
设置文字间隔:
文本超过最大行数设置 ... :
目前Flutter平台主流的两个播放器是video_player和fijkplayer
pub
github
1、Flutter平台官方插件,作者是国外的,有问题沟通比较困难,只能通过提交issue
2、硬解码
4、UI封装: better_player
基于video_player和Chewie的高级视频播放器。它解决了许多典型的用例,并且易于运行。
5、播放器宽高比例与视频内容宽高比例不一致时,会出现图像压缩变形的问题
6、调用原生内核播放器:iOS--AVPlayer, Android--ExoPlayer
7、对于分段源 m3u8 的播放不友好,如果一个切片播放超时,会导致整个播放都失败
8、better_player可以缓存视频,但不能自定义缓存的地址,只能指定key,和缓存的最大内存量(还未研究超出最大的话是不能缓存新的,还是删除最旧的)
9、better_player不能完全自定义UI,只能修改类中的一些开放属性,比如说icon图标,文字颜色啥的
10、无网络有缓存时,封面可以正常展示
11、better_player播放失败有手动retry的设计
pub
github
1、fijkplayer 是一个 Flutter 生态的媒体播放器,是对 ijkplayer 的 Flutter 封装,支持 Android 和 iOS。 fijkplayer 使用 ijkplayer 作为播放器内核,ijkplayer 使用 ffmpeg 进行音视频解封装和解码,同时添加了 Android 和 iOS 平台特有的硬件加速解码能力。
2 、国内有QQ群,但是活跃度也是不高。
3、可以缓存视频,可以自定义缓存的地址,方便后续的内存维护。
4、可以通过FijkPanelWidgetBuilder较大程度上自定义UI。
5、无网络有缓存视频时,无法展示封面,因为内部是通过imageProvider去加载网络图片的。
7、播放失败无手动retry的设计
1、两种播放器都是通过外接纹理方案 (Texture),将播放器视频画面渲染接入 flutter 中,性能上优于 PlatformView 的接入方法。
如何自己实现?
下面以video_palyer的iOS源码部分解释:
iOS用CVPixelBufferRef将渲染出来的数据存在内存中,Flutter engine会将Texture的数据在内存中直接进行映射无需通过Channel传输,然后Texture Widget就可以把你提供的这些数据显示出来。在我们传输数据的时候会需要将其与 TextureID 绑定,绑定的过程通过BasicMessageChannel实现数据流的传输,以做到实时展示的效果
1.要绘制贝塞尔线,我们需要四个点: 起点 , 终点 和 两个控制点 ,如下图所示。移动控制点会改变曲线的斜率。您可以在此 在线工具中 使用控制点。
我们可以使用类Path的cubicTo方法绘制贝塞尔曲线:
使用控制点(x1,y1)和(x2,y2)添加从当前点到给定点(x3,y3)的曲线的三次贝塞尔曲线段。
如您所见,该cubicTo方法接受三个参数。其中两个是控制点,最后一个参数是终点。起点是您的笔已经位于画布上的位置。
不要忘记在画布坐标中,左上角是(0,0)点,右下角是(size.width,size.height)。因此,请尝试相应地调整四点:
请记住,paint对象就像我们的笔,我们将其颜色设置为蓝色,宽度设置为3。
我们用path对象描述了bezier路径。该moveTo方法已用于将笔移动到路径的起点。然后我们调用cubicTo方法来定义控制点和终点。之后,我们使用该drawPath方法绘制了路径。
贝塞尔曲线参考:
///推荐一些曲线图/折线图/柱状图参考:
all first_rank_v2~rank_v25-2-95632571.nonecaseutm_term=flutter%20%E5%8A%A8%E6%80%81%E7%BB%98%E5%88%B6%E6%9B%B2%E7%BA%BF
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流