问题背景
在 Flutter
中,我们经常使用 ScrollView
+ Slider
这样的场景。
但是在这样的场景下,存在用户体验并不好的问题:
列表滑动的过程中 Slider 不能响应
举例:
1\. 滑动未完成,Slider 不能响应
SingleChildScrollView
在我们手指抬起的过程中,还是会有一定的惯性, 列表不会立刻停止。它这样做是为了用户体验,让用户感觉到丝滑。
当遇到 Slider
会发生什么情况?
不能滑动 | 可以滑动 |
以下是未处理的效果: | 以下是优化后的效果: |
以上是开启了
Flutter
中Disable Slow Animations
功能展示的效果慢动画展示的效果
根据视频观察到:
当手指抬起时,列表会根据惯性滑动一段距离,如果此时点击列表,列表会停止滑动。
意味着,如果用户想在这个时候想滑动 Slider
的话,需要等 SingleChildScrollView
完全停止的情况下,才可以滑动 Slider
。
2\. 滑动溢出,Slider 不能响应
在 SingleChildScrollView
中设置了 physics
为 BouncingScrollPhysics()
。
这样设置后 ScrollView
就会有滑动溢出的效果,对于用户来说能获得较好的用户体验。
但是不巧的是 Slider
在这种情况下不能响应滑动事件。
不能滑动 | 可以滑动 |
以下是未处理的效果: | 以下是优化后的效果: |
3\. 解决方案
一句话总结: 在 Listener
的 onPointerMove
中计算 value
值。
具体方案:
- 通过
Listener
监听原始指针事件,拿到PointerEvent
对象。 - 再通过
GlobalKey
拿到Slider
在SingleChildScrollView
中的位置。 - 通过他们可以在
onPointerMove
中计算Slider
中value
的值应该滑动的位置。
class SliderPage extends StatefulWidget {
@override
_SliderPageState createState() => _SliderPageState();
}
Rect rect = Rect.zero;
class _SliderPageState extends State<SliderPage> {
String? text;
double value = 0;
GlobalKey key = GlobalKey();
ScrollController controller = ScrollController();
Widget _buildBody() {
return SizedBox(
height: MediaQuery.of(context).size.height,
child: Listener(
onPointerMove: (p) {
final box = key.currentContext!.findRenderObject()! as RenderBox;
final offset = box.localToGlobal(Offset.zero);
final size = box.size;
rect = Rect.fromPoints(offset, offset.translate(size.width, size.height));
setState(() {});
if (rect.contains(p.position)) {
value = ((p.position.dx) / (rect.width - 2 * 24 )).clamp(0, 1);
setState(() {});
}
},
child: Column(
children: [
SizedBox(
height: 555,
child: SingleChildScrollView(
controller: controller,
physics: BouncingScrollPhysics(),
dragStartBehavior: DragStartBehavior.down,
child: Column(
children: [
Container(
height: 444,
color: Colors.blue,
),
Listener(
onPointerDown: (p) {},
onPointerMove: (p) {
// print('Slider ${p}');
},
child: Slider(
key: key,
value: value,
autofocus: true,
onChanged: (v) {
value = v;
setState(() {});
},
),
),
Container(
height: 333,
color: Colors.blue,
),
],
),
),
),
Expanded(child: Container(color: Colors.blue))
],
),
),
);
}
4\. 总结:
Slider
只有当SingleChildScrollView
完全停止的情况下才可以其他事件。
原因: 当指针按下时,
Flutter
会对应用程序执行命中测试(Hit Test) ,以确定指针与屏幕接触的位置存在哪些组件(widget)
, 指针按下事件(以及该指针的后续事件)然后被分发到由命中测试发现的最内部的组件
- 在
Listener
中计算value
值,可能是不准确。因为拿到Sinder
的长度还需要拿到Sinder
的Padding
,才能保证计算的准确性。 - 这篇文章提供一个解决滑动冲突的思路,如果有其他思路欢迎沟通交流。
想要了解更多Anrloid相关知识可以点击下方课堂链接 https://edu.51cto.com/course/32703.html Android课