Flutter高仿驾考宝典之顺序练习——中间页面的滑动切换

1,007 阅读3分钟

首先效果上图:

再上代码

now,let‘s go。

分为三节:

1、底部下拉框,用于概括全局

2、中间页面的滑动切换

3、两个地方的配合,增加阴影,点击阴影下拉框回退等

本节为第二节底部中间页面的滑动切换

思想:放置三个页面到一个布局,使用Stack包住,一个在最左边,一个在最右边,最后一个在中间,他们三宽高是一致的,同样是通过Transform改变方位,你只需要在中间的页面上通过手势来控制左边的页面即可,通过设置阴影达到翻书式效果,当每次翻页完成时,再还原三个布局的位置并且重新设置三个页面的内容即可,毫无违和感。

老规矩:只上核心代码,其余的自己去看,你们别看那些看不清晰的,只需要知道核心原理即可,不要只见树木不见森林

top、center、bottom即左中右三个页面,此处使用别的布局包裹,是想着写出一个插件来着,但是后来发现这个专用性太强,我就懒得写了。

 Widget build(BuildContext context) {
    return SliverPageSelf(
        topChild: QuestionPage(QuestionLayer.top),
        centerChild: QuestionPage(QuestionLayer.center, selfControll: widget.controll),
        bottomChild: QuestionPage(QuestionLayer.bottom),
        controll: widget.controll,
    );

下面偏移和阴影,最底部的写再最开始让他被覆盖

 child: Stack(
        children: <Widget>[
          Container(
            width: double.infinity,
            height: double.infinity,
            child: widget.bottomChild,
          ),
          Transform.translate(
            offset: Offset(centerOffsetDistance, 0.0),
            child: _centerLayer(),
          ),
          Align(
            alignment: Alignment.centerLeft,
            child: Transform.translate(
              offset: Offset(leftOffsetDistance, 0.0),
              child: Container(
                width: double.infinity,
                height: double.infinity,
                decoration: BoxDecoration(
                  boxShadow: (leftOffsetDistance != -ScreenUtil.getInstance().setWidth(350) ) ? [
                    BoxShadow(
                        color: Colors.black54, blurRadius: 10.0, spreadRadius: 2.0)
                  ] : null,
                  color: Colors.white,
                ),
                child: widget.topChild,
              ),
            ),
          ),
        ],
      ),
    );

中间那个_centerLayer()加上滑动

  Widget _centerLayer() {
    return RawGestureDetector(
      gestures: {CenterPicDragGestureRecognizer: getRecognizer()},
      child: Container(
          width: double.infinity,
          height: double.infinity,
          child: widget.centerChild,
          decoration: BoxDecoration(
            boxShadow: (centerOffsetDistance != 0) ? [
              BoxShadow(
                  color: Colors.black54, blurRadius: 10.0, spreadRadius: 2.0)
            ] : null,
            color: Colors.white,
          )),
    );
  }

下面时滑动时的,和上节很像,要做其他限制。

  void _onUpdate(DragUpdateDetails details) {
    print('水平滑动{details.delta}');
    currentDrag = currentDrag + details.delta.dx;
    //初始化时,当下标为0的page在中间时,左侧不允许滑动
    if (currentDrag >= 0) {
      //向右滑动
      if (Provider.of<QuestionSourceChange>(context).current == 0) {
        //如果当前图为第一张则不让右滑
        currentDrag = -1;
        return;
      }
      leftOffsetDistance = leftOffsetDistance + details.delta.dx;
    } else {
      //向左滑动
      if (Provider.of<QuestionSourceChange>(context).current ==
          Provider.of<QuestionSourceChange>(context).questionMap.length - 1) {
        //如果当前图为第一张则不让右滑
        currentDrag = 1;
        return;
      }
      centerOffsetDistance = centerOffsetDistance + details.delta.dx;
    }
    setState(() {});
  }

一下是抬手的时候,要确定是向左还是向右,和上一节一样,只是多了一个整体方向问题。

 ///手指离开屏幕
  void _onEnd(DragEndDetails details) {
    print('离开屏幕');
    double halfOffset = MediaQuery.of(context).size.width / 2;
    double velocityX = details.velocity.pixelsPerSecond.dx;
    if (currentDrag > 0) {
      //向右滑动,动leftOffsetDistance
      if (velocityX.abs() > 400) {
        if (velocityX > 0) {
          endSliver(false);
        } else {
          endSliver(true);
        }
      } else {
        if (leftOffsetDistance >= -halfOffset) {
          endSliver(false);
        } else {
          endSliver(true);
        }
      }
    } else {
      //向左滑动,动centerOffsetDistance
      if (velocityX.abs() > 400) {
        if (velocityX > 0) {
          //还原,即中间的图回退到0,0的位置
          centerEndSliver(false);
        } else {
          centerEndSliver(true);
        }
      } else {
        if (centerOffsetDistance >= -halfOffset) {
          //还原,即中间的图回退到0,0的位置
          centerEndSliver(false);
        } else {
          centerEndSliver(true);
        }
      }
    }
  }

同样centerEndSliver封装起来依然很好用,此处我最开始用 with SingleTicker 想用一个动画通过重新设置start和end解决,但是一直有bug, 后来改了 TickerProviderStateMixin,并且设置了两个动画控制器

void centerEndSliver(bool isShow) {
    if (isShow) {
      print('左平移');
      start = centerOffsetDistance;
      end = -ScreenUtil.getInstance().setWidth(350);
      animalController2.value = 0.0;
      animation2 = Tween(begin: start, end: end).animate(curve2)
        ..addListener(() {
          centerOffsetDistance = animation2.value;
          setState(() {});
        });
      animalController2.forward().then((_) {
        Provider.of<QuestionSourceChange>(context).forward();
        centerOffsetDistance = 0;
      });
    } else {
      //还原0,0
      print('还原');
      start = centerOffsetDistance;
      end = 0;

      animalController2.value = 0.0;
      animation2 = Tween(begin: start, end: end).animate(curve2)
        ..addListener(() {
          centerOffsetDistance = animation2.value;
          setState(() {});
        });
      animalController2.forward();
    }
  }

最后千万不要忘记重置值,其中的forword().then((){})中的

  Provider.of<QuestionSourceChange>(context).forward();
    //即通知所有人要换了, 左page为 count-1, 右page为 count+1
    void forward(){
    current++;
    notifyListeners();
  }

并且在每次再次按下的时候重置滑动距离

  ///接受触摸事件
  void _onStart(DragStartDetails details) {
    print('触摸屏幕${details.globalPosition}');
    currentDrag = 0;
  }

就是这么简单。如果觉得能看,请看下节==》这俩节配合使用。