导语
接到了一个仿电影院的需要,上周简直是找遍了百度,谷歌,stackoverflow。均没有找到用flutter实现的成果,那只能本人写一个了。本文只讲思路,具体实现还需各位看官本人入手。只有看懂了上面的思路,实现起来非常简单。
间接上效果图
竖屏:
横屏:
初始化自适应屏幕的放大放大成果:
布局剖析
两头的座位=>矩阵
,通过Column
嵌套Row
实现,不能通过GridView实现(滑动抵触,下文会阐明)
左侧导航条
=>一个简略的Column(不能用ListView,同样会造成滑动抵触)
交互剖析&实现
放大放大拖动成果:
对于放大放大拖动的成果,Flutter当初有自带的组件InteractiveViewer
通过这个组件可完满实现放大放大成果。组件属性这边不开展解释,比较简单,可点击下面链接自行理解。
这里讲下两个重点属性:
1、回调事件
- 交互开始
onInteractionStart
- 交互更新
onInteractionUpdate
- 交互完结
onInteractionEnd
2、变换控制器transformationController
能够通过这个类来通过代码管制放大放大成果
导航条追随座位表放大放大拖动:
右边导航条追随两头座位的放大放大,以及行数定位不偏离:
下面讲的那些货色个别大家都能想到,也很好实现。这个交互成果的真正难点是这个追随滑动成果
。
因为右边的导航条是固定在最左侧的,而座位表能够全屏拖动,所以这座位表和导航条不能放在一个缩放组件里, 不然座位表放大的时候,间接将导航条放大出屏幕了。所以咱们的思路就是将导航条和座位表作为Stack的子组件,而后座位表实现放大放大成果,并且让导航条能追随座位表进行放大放大。笔者在这试了很多办法:
办法一:
左侧导航栏和两头座位表均应用InteractiveViewer
而后通过InteractiveViewe
r的回调事件和变换控器来实现成果同步
后果:
失败,transformationController
的原理是Matrix4泛型的ValueNotifier
(四维矩阵),简略的挪动放大还能实现,齐全克隆一个放大放大拖动成果,笔者做不到。。各位如果线性代数十分牛逼的能够试试。
办法二:
flutter有一个同步滚动组件叫linked_scroll_controller
他能将两个scrollController
绑定在一起,实现同步滚动。
所以让左侧导航栏应用ListView,两头座位表应用InteractiveViewer
嵌套GridView, 而后将ListView
和GridView
的ScrollController
绑定在一起实现同步滚动。
后果:
失败,InteractiveViewer
的滑动是通过Matrix4实现的,和ListView的滑动抵触。
同步滚动实现了,然而放大放大的拖动无奈执行。
办法三:
应用InteractiveViewer
是逃不过的,不然本人实现放大放大成果太头疼, 如果能像下面的linked_scroll_controller
一样,将InteractiveViewer
的缩放成果复制到另外一个InteractiveViewer
中去,那就完满了。
就是办法一的思路,然而用InteractiveViewer
凋谢的接口和控制器,无奈实现,这个时候就须要去浏览了解InteractiveViewer
的源码,看看有没有什么启发。
@override Widget build(BuildContext context) { Widget child = Transform( transform: _transformationController.value, child: KeyedSubtree( key: _childKey, child: widget.child, ), ); if (!widget.constrained) { child = OverflowBox( alignment: Alignment.topLeft, minWidth: 0.0, minHeight: 0.0, maxWidth: double.infinity, maxHeight: double.infinity, // maxHeight: 220.w, child: child, ); } if (widget.clipBehavior != Clip.none) { child = ClipRRect( clipBehavior: widget.clipBehavior, child: child, ); } // A GestureDetector allows the detection of panning and zooming gestures on // the child. return Listener( key: _parentKey, onPointerSignal: _receivedPointerSignal, child: GestureDetector( behavior: HitTestBehavior.opaque, // Necessary when panning off screen. dragStartBehavior: DragStartBehavior.start, onScaleEnd: onScaleEnd, onScaleStart: onScaleStart, onScaleUpdate: onScaleUpdate, child: child, ), ); }
不看不晓得,一看吓一跳,其实InteractiveViewer
曾经将所有的办法都替咱们封装好了。
留神下面的GestureDetector
,整个InteractiveViewer
的手势交互办法,其实就是onScaleEnd
,onScaleStart
,onScaleUpdate
这三个办法。
那咱们只须要将座位表组件回调的的这三个办法中的参数,传入到导航条组件中去就行,而后删掉导航条组件的GestureDetecto
r,让导航条组件只承受来自座位表组件的手势交互参数。
咱们只需重写两个InteractiveViewer
,一个为主组件(座位表),一个为从组件(导航条),并凋谢InteractiveViewerState,当座位表组件回调手势的三个办法时,通过key将三个办法的参数传入导航条组件就OK。
_onInteractionUpdate(ScaleUpdateDetails details) { if (controller.fromInteractiveViewKey.currentState != null) { controller.fromInteractiveViewKey.currentState.onScaleUpdate(details); } } _onInteractionStart(ScaleStartDetails details) { if (controller.fromInteractiveViewKey.currentState != null) { controller.fromInteractiveViewKey.currentState.onScaleStart(details); } } _onInteractionEnd(ScaleEndDetails details) { if (controller.fromInteractiveViewKey.currentState != null) { controller.fromInteractiveViewKey.currentState.onScaleEnd(details); } }
齐全无需任何加工,将参数照搬照抄的传入导航条组件。咱们就能实现同步缩放拖动的成果!
这里必须特地留神:座位表和导航条组件的单个item的高度必须完全相同,包含margin,padding,不然还是会呈现错位景象
至此,最大的难点同步缩放和滑动就解决了。
底部弹框悬浮在座位表上方:
点击座位后弹出底部弹框,遮蔽局部座位表,然而座位表能继续向上拖动显示完最初一行的数据
这个乍一看没啥难的,但细细一想也有点简单。首先, 明确座位表的显示区域是蕴含底部弹框的,因为底部弹框是悬浮在座位表下面的,那么咱们就只能应用margin而不是padding
,所以依据设计图底部弹框的height
,咱们将marginBottom
设成这个height
就行,然而会有个问题:
当整个座位表放大margin局部也会同步放大,这样就会导致放的越大,座位表间隔上面空出的间距就越大。
解决思路:
咱们须要拿到以后放大的倍数,动静调整margin, 以后放大X倍,原始margin为Y,则以后放大后的margin=Y/X
,Y已知,咱们只须要晓得X就行。然而在_onInteractionUpdate
接口中,X并非以后放大几倍,而是较上次缩放后的缩放倍数。即:
- 初始1.0倍。
- 第一次放大至2倍,接口回调的放大倍数为2
- 第二次放大至3倍,接口回调的放大倍数为1.5(较第一次又放大了1.5倍)。
并且更重大的是当放大到maxScale后,接口仍会继续回调放大倍数。这就很困扰咱们,起初浏览源码后发现,咱们所要的较原始放大倍数的以后放大倍数参数在InteractiveViewer
类中的。
// Return a new matrix representing the given matrix after applying the given // scale. Matrix4 _matrixScale(Matrix4 matrix, double scale) { if (scale == 1.0) { return matrix.clone(); } assert(scale != 0.0); // Don't allow a scale that results in an overall scale beyond min/max // scale. final double currentScale = _transformationController.value.getMaxScaleOnAxis(); final double totalScale =currentScale * scale; //改了算法 // final double totalScale = math.max( // currentScale * scale, // // Ensure that the scale cannot make the child so big that it can't fit // // inside the boundaries (in either direction). // math.max( // _viewport.width / _boundaryRect.width, // _viewport.height / _boundaryRect.height, // ), // ); final double clampedTotalScale = totalScale.clamp( widget.minScale, widget.maxScale, ); widget.scaleCallback?.call(clampedTotalScale); final double clampedScale = clampedTotalScale / currentScale; return matrix.clone()..scale(clampedScale); }
留神下面的scaleCallback,这是笔者本人实现的回调办法,其中的clampedTotalScale就是咱们想要的较初始缩放倍数的以后放大倍数, 即:初始1.0倍,第一次放大至2倍,接口回调的放大倍数为2,第二次放大至3倍,接口回调的放大倍数为3(较初始放大了3倍)。
且clampedTotalScale永远在minScale和maxScale的区间内。拿来即用十分不便。
下面代码中有一段算法被我正文掉了,这段代码的成果是:
当InteractiveViewer
中的child曾经齐全显示的时候,则无奈再放大,即minScale
不仅仅取决于咱们设置的值,还取决于InteractiveViewer的child显示成果,这里我不须要这个限度,则将他正文掉了。
其实如果要完满实现UI给出的成果,有很多中央要用到margin,比方座位表的上下左右margin,只有拿到了下面的clampedTotalScale
,均能够动静计算,很不便。
横竖屏适配成果
下面的gif图有横屏成果,横竖屏切换用的也是官网API,OrientationBuilder
,这个用起来也很简略。这里讲一个UI适配的注意事项:
因为笔者我的项目用了ScreenUtil
(UI自适应),所以在竖屏的时候,传入竖屏的UI尺寸图,且尺寸结尾应用.w进行适配,当横屏时,传入横屏的UI尺寸图(其实就是将竖屏的width和height倒置),而后尺寸结尾应用.h
进行适配。这样就根本能完满适配横竖屏,残余的细节就能够微调。
初始放大倍数
如下面的效果图, 在第一次进入或横竖屏切换时,当座位表布局过多(默认显示不下时),尽可能放大以显示更多的内容(上限放大至minScale
),当座位表布局过少(默认显示时屏幕很空),尽可能放大直至显示满屏幕(下限放大至maxScale
)。
下面成果可总结为:在尽可能显示齐全的前提下尽可能大。
InteractiveViewer
并没有初始放大倍数参数,默认进入都是放大1.0倍。这里就须要咱们本人来算出这个初始放大倍数。
计算
如果有用screenUtil
,以下计算留神辨别横竖屏,横屏时适配结尾用.w
,竖屏用.h
,其中异形屏的padding
不必辨别横竖屏,零碎会主动更改
- 1、整个座位表的显示区域:
屏幕高-异形屏高低padding-竖屏时底部悬浮框的height(横屏悬浮框如果不在底部,则为0)-标题栏高度以及本人加的一些其余布局的高度。屏幕宽-异形屏左右padding-横屏时右侧悬浮框width(竖屏时悬浮框如不在右侧,则为0)- 导航条宽度(这个导航栏宽度也须要依据放大放大倍数动静计算)-其余本人加的布局宽。
- 2、计算初始放大倍数(1.0)下的座位表item的width和height以及padding,这个间接按设计图的就行。
- 3、取得以后座位表的x轴和y轴。即每行几个座位,一共有几行座位。
- 4、计算假如要将所有座位表显示下,每个item的width和height。
即用下面1.所得的座位表显示区域的宽高别离除以座位表的x和y,
- 5、将2.的width除以4.width,即如X轴齐全显示下须要缩放的值SX,
将2.的height除以4.height,即如Y轴齐全显示下须要缩放的值SY,
- 6、比拟SX和SY两值,取小值defaultS(在尽可能显示齐全的前提下尽可能大)
- 7、如果defaultS在minScale和maxScale区间内,则取defaultS,反之取区间边界值。
缩放
用transformationController
将InteractiveViewer
缩放到defaultS
// 座位表mainTransformationController.value = Matrix4.identity()..scale(defaultS);// 导航条fromTransformationController.value = Matrix4.identity()..scale(defaultS);
这里留神座位表和导航条都要进行缩放。
缩放动静margin
最初别忘记将各种须要动静计算的margin也缩放到defaultS值。
如果有横竖屏切换成果的,在每次横竖屏切换的时候都动静计算初始放大值,须要留神,每次计算的时候都要将动静计算的margin置为初始值(即当缩放大小为1.0时的margin值)。
有时候想不进去就看源码,立马就会醍醐灌顶。
Android高级开发零碎进阶笔记、最新面试温习笔记PDF,我的GitHub
文末
您的点赞珍藏就是对我最大的激励!
欢送关注我,分享Android干货,交换Android技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!