导语
接到了一个仿电影院的需要,上周简直是找遍了百度,谷歌,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 技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!