使用Flutter仿写TikTok的手势交互

写在前面

Flutter 是 Google推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者能够经过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平台。git

Flutter官网:flutter-io.cngithub

抖音,英文名TikTok,一款火遍全球的短视频App。在玩抖音的日子里,最令我感到舒服的就是抖音的手势交互,加上近期都在进行Flutter方面的学习,所以就产生了使用Flutter来仿写TikTok手势交互的想法。编程

来看看实现的效果:markdown

Gif:giphy.com/gifs/Y0nMQw…框架

Github地址:github.com/ditclear/ti…函数

demo下载: oop

GestureDetector以及Transform

既然是手势交互,那么就必然要检测手势,在Flutter中提供了GestureDetector来帮助开发者,并提供了多个回调来处理手势。布局

Property/Callback Description
onTapDown 用户每次和屏幕交互时都会被调用
onTapUp 用户中止触摸屏幕时触发
onTap 短暂触摸屏幕时触发
onTapCancel 用户触摸了屏幕,可是没有完成Tap的动做时触发
onDoubleTap 用户在短期内触摸了屏幕两次
onLongPress 用户触摸屏幕时间超过500ms时触发
onVerticalDragDown 当一个触摸点开始跟屏幕交互,同时在垂直方向上移动时触发
onVerticalDragStart 当触摸点开始在垂直方向上移动时触发
onVerticalDragUpdate 屏幕上的触摸点位置每次改变时,都会触发这个回调
onVerticalDragEnd 当用户中止移动,这个拖拽操做就被认为是完成了,就会触发这个回调
onVerticalDragCancel 用户忽然中止拖拽时触发
onHorizontalDragDown 当一个触摸点开始跟屏幕交互,同时在水平方向上移动时触发
onHorizontalDragStart 当触摸点开始在水平方向上移动时触发
onHorizontalDragUpdate 屏幕上的触摸点位置每次改变时,都会触发这个回调
onHorizontalDragEnd 水平拖拽结束时触发
onHorizontalDragCancel onHorizontalDragDown没有成功完成时触发
onPanDown 当触摸点开始跟屏幕交互时触发
onPanStart 当触摸点开始移动时触发
onPanUpdate 屏幕上的触摸点位置每次改变时,都会触发这个回调
onPanEnd pan操做完成时触发
onScaleStart 触摸点开始跟屏幕交互时触发,同时会创建一个焦点为1.0
onScaleUpdate 跟屏幕交互时触发,同时会标示一个新的焦点
onScaleEnd 触摸点再也不跟屏幕有任何交互,同时也表示这个scale手势完成

GestureDetector并不会监听上面全部的手势,只有传入的callbacks非空时,才会监听。因此,若是你想要禁用某个手势时,能够给对应的callback传null。性能

本文主要关注的的是拖动相关的,好比onPanXXonHorizontalDragXXonVerticalDragXX等等回调事件。学习

Transform能够在其子Widget绘制时对其应用一个矩阵变换(transformation),Matrix4是一个4D矩阵,经过它咱们能够实现各类矩阵操做。

Container(
  color: Colors.black,
  child: new Transform(
    alignment: Alignment.topRight, //相对于坐标系原点的对齐方式
    transform: new Matrix4.skewY(0.3), //沿Y轴倾斜0.3弧度
    child: new Container(
      padding: const EdgeInsets.all(8.0),
      color: Colors.deepOrange,
      child: const Text('Apartment for rent!'),
    ),
  ),
);
复制代码

效果以下:

在Flutter中提供了一些封装好的transform效果供开发者选择,好比:平移(translate)、旋转(rotate)、缩放(scale)。

在了解了这两点以后,咱们来逐步分解前文的效果。

交互分解

首先,须要明确的是这些交互效果其实都是经过检测手指的滑动,获得一个x坐标或者y坐标的偏移量,而后配合Transform进行各类不一样的变换,明白了这一点,想作到这样的效果并不难。

  • 首页的交互

Gif :p1-jj.byteimg.com/tos-cn-i-t2…

这里的交互都是横向的滑动,所以这里主要处理onHorizontalDragXX相关的事件。

而后来看看首页的布局:

Left:拍摄页 Middle:主页 Right:用户页

外层是一个GestureDetector用于处理整个页面的手势,里面用的是一个Stack,相似于Android中的FrameLayout,它包含3个Transform的子Widget。

这里选取拍摄页(left)来具体谈谈.

经过观察能够发现,随着偏移量的改变,这里其实包含两个变化:1.缩放 2. 前景色透明度

缩放能够直接采用前文提到的Transform.scale,前景色能够用foregroundDecoration经过改变Color的透明度来达到效果,看看实现:

/// 左侧Widget
///
/// 经过 [Transform.scale] 进行根据 [offsetX] 缩放
/// 最小 0.88 最大为 1
Transform buildLeftPage(double screenWidth) {
  return Transform.scale(
    scale: 0.88 + 0.12 * offsetX / screenWidth < 0.88 ? 0.88 : 0.88 + 0.12 * offsetX / screenWidth,
    child: Container(
      child: Image.asset(
        "assets/left.png",
        fit: BoxFit.fill,
      ),
      foregroundDecoration: BoxDecoration(
          color: Color.fromRGBO(0, 0, 0, 1 - (offsetX / screenWidth)),
         ),
    ),
  );
}
复制代码

当咱们的手指在横向移动的时候,记录下偏移总量offsetX,而后经过setState进行更新。

onHorizontalDragUpdate: (details) {
  // 控制 offsetX 的值在 -screenWidth 到 screenWidth 之间
  if (offsetX + details.delta.dx >= screenWidth) {
    setState(() {
      offsetX = screenWidth;
    });
  } else if (offsetX + details.delta.dx <= -screenWidth) {
    setState(() {
      offsetX = -screenWidth;
    });
  } else {
    setState(() {
      offsetX += details.delta.dx;
    });
  }
}
复制代码

经过setState更新偏移量offsetX以后,Flutter便会从新渲染视图,从而达到上图的效果。

  • Hero动画

Gif:p1-jj.byteimg.com/tos-cn-i-t2…

Flutter提供了Hero动画来实现这样的过渡效果。Hero指的是能够在路由(页面)之间“飞行”的widget,简单来讲Hero动画就是在路由切换时,有一个共享的Widget能够在新旧路由间切换,因为共享的Widget在新旧路由页面上的位置、外观可能有所差别,因此在路由切换时会逐渐过渡,这样就会产生一个Hero动画。

/// tiktok_page.dart
Widget build(BuildContext context) {
		return	Hero(
              tag: "detail",
              //child
            )
 )               
 
 /// detail_page.dart
 Widget build(BuildContext context) {
	return Hero(
      tag: "detail",
      // child
        )
  }
复制代码

保证tag一致就能够了。

  • 详情页的交互

Gif :p1-jj.byteimg.com/tos-cn-i-t2…

跟首页同样的思路,只是这里的手势是垂直方向。

布局一样是GestureDetector加上Stack再配合Transform.translate

Hero(
      tag: "detail",
      child: GestureDetector(
        onVerticalDragUpdate: (details){
          // dy 不超过 -screenHeight * 0.6
          dy += details.delta.dy;
          if ((dy < 0 && dy.abs() > screenHeight * 0.6)) {
            dy = -screenHeight * 0.6;
          } else {
            setState(() {});
          }
        },
        child: Stack(
          children: <Widget>[
            Image.asset(
              "assets/detail.png",
              fit: BoxFit.fitWidth,
              width: screenWidth,
              height: screenHeight,
            ),
            Transform.translate(
              offset: Offset(0, dy + screenHeight),
              child: Container(
                  height: screenHeight * 0.6,
                  child: GestureDetector(
                    onTap: () {},
                    child: Image.asset(
                        "assets/comment.png",),
                  )
              ),
            ),
          ],
        ),
      ),
    );
复制代码

在手指离开屏幕时,根据偏移利用动画进行调整。

onVerticalDragEnd: (_){
          // 滑动截止时,根据 dy 判断是展开仍是回缩
          if (dy < 0) {
            if (!isCommentShow && dy.abs() > screenHeight * 0.2) {
              if (dy.abs() > screenHeight * 0.2) {
                animateToTop(screenHeight);
              } else {
                animateToBottom(screenHeight);
              }
            } else {
              if (dy.abs() > screenHeight * 0.4) {
                animateToTop(screenHeight);
              } else {
                animateToBottom(screenHeight);
              }
            }
          }
        },
复制代码

写在最后

总的来讲,这些交互都是依靠着对手势的检测作到的,相比于Android,Flutter有着一切都是Widget的概念,

GestureDetector以及Hero都是Widget并且提供了不少回调函数,再配合数据驱动UI和Flutter优秀的渲染机制,减轻了开发者进行手势交互的难度。

Github地址:github.com/ditclear/ti…

若是本文对你有帮助,请点赞支持。

参考资料:

==================== 分割线 ======================

若是你想了解更多关于MVVM、Flutter、响应式编程方面的知识,欢迎关注我。

你能够在如下地方找到我:

简书:www.jianshu.com/u/117f1cf0c…

掘金:juejin.cn/user/817692…

Github: github.com/ditclear

相关文章
相关标签/搜索