Writing in the front

Flutter is a cross-platform, high-fidelity, high-performance mobile application development framework launched by Google and open source. Dart allows developers to develop apps, a set of code that runs on both iOS and Android.

Website of Flutter: Flutter – IO

In our last post on gesture interaction, we learned about GestureDetector, Transform, and Hero animations and completed a couple of gesture interaction effects in TikTok. This post picks up where we left off. If you’re looking at the last post, check out the following link:

Gesture interaction with Flutter mimicking TikTok :dwz.cn/xoY8eDs0

Let’s take a look at the results:

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

Github address: github.com/ditclear/ti…

Download experience

Interactive decomposition

There are two main pull-down interactions: pull-down refresh and pull-down return.

  • The drop-down refresh

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

As the finger slides down, offsetY changes, there are two changes:

  1. transparency
  2. The shift in the y direction

Opacity here you can use the Opacity widget provided by Flutter, which is used exclusively for Opacity changes and is easy to use.

Opacity(
  opacity: currentOpacity,
  child:childWidget
)  
Copy the code

Opacity is simply passed in, and all we need to do is express the value of opacity as offsetY.

Note that there are two transparency changes: the top navigation bar and the drop-down refresh content text.

And the two will not appear at the same time, pull down, the top navigation bar gradually become transparent, to a certain distance (can be considered to be half of the maximum pull-down distance) hidden, pull down the refresh content of the text will appear, and its transparency gradually become opaque.

We set the maximum sliding distance of the pull-down to 40, so the critical point is 20.

From this, we can get the formula of the transparency change of the top navigation bar with the pull-down distance.

opacity = 1 – offsetY / 20

While opacity must be between 0 and 1, offsetY has a maximum of 40. So the resulting formula is:

opacity = max(0, 1 – offsetY / 20)

Concrete implementation:

When pulling down, record the total offset offsetY, control it to be greater than 0 and not exceed the maximum distance.

onVerticalDragUpdate: (details) {
        final tempY = offsetY + details.delta.dy / 2;
        if (currentIndex == 0) {
          // The maximum pull-down distance does not exceed 40
          if (tempY > 0) {
            if (tempY < 40) {
              setState(() {
                offsetY = tempY;
              });
            } else if(offsetY ! =40) {
              setState(() {
                setState(() {
                  offsetY = 40;
                });
              });
              // When the pull-down reaches the maximum distance, the vibration effect is triggeredvibrate(); }}}else {
          offsetY = 0; }}Copy the code

When pulled down to the maximum distance, use vibrate to produce the vibrate effect.

When the finger leaves the screen, do another animation, set OffsetY to 0, and notify the UI via setState to rerender.

// End of the dropdown
onVerticalDragEnd: (_) {
    if(offsetY ! =0) { animateToTop(); }}/// slide to the top
///
/ / / / offsetY to 0.0
void animateToTop() {
  animationControllerY =
      AnimationController(duration: Duration(milliseconds: offsetY.abs() * 1000~ /40), vsync: this);
  final curve = CurvedAnimation(parent: animationControllerY, curve: Curves.easeOutCubic);
  animationY = Tween(begin: offsetY, end: 0.0).animate(curve) .. addListener(() { setState(() { offsetY = animationY.value; }); }); animationControllerY.forward(); }Copy the code
  • The drop-down return

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

In simple terms, the whole page is offset by a Y-axis when the drop down. When it exceeds a specified distance, the page can be quit.

// Slide cutoff
onPanEnd: (_) {
  if (offsetY > 100) {
    // If the drop-down distance exceeds 100, exit the page
    Navigator.pop(context);
  } else if (offsetY > 0) {
    // If the drop-down distance is less than 100, restore the original state
    animateToBottom(screenHeight);
  } else if (offsetY < 0) {
    // The dropup determines whether to expand or shrink based on whether the comment box [isCommentShow] and offsetY are already displayed
    if(! isCommentShow && offsetY.abs() > screenHeight *0.2) {
      if (offsetY.abs() > screenHeight * 0.2) {
        animateToTop(screenHeight);
      } else{ animateToBottom(screenHeight); }}else {
      if (offsetY.abs() > screenHeight * 0.4) {
        animateToTop(screenHeight);
      } else{ animateToBottom(screenHeight); }}}},Copy the code

Apply a layer of transform. translate to offset the page. Note that the offset must be greater than 0.

Transform.translate(
  offset: Offset(0, max(0, offsetY), child: childWidget)Copy the code

Once you’ve done the basic logic of the appeal, it’s going to run a little differently than you expected.

The background is not transparent, at that time I thought there was a black background, and I needed to set the transparency of the background color, but I have tried all kinds of suspicious colors in ThemeData, and found that it does not work, and later I thought it should be PageRoute.

When we call Navigator for push operation, we need to pass in a PageRoute, such as the commonly used MaterialPageRoute or CupertinoPageRoute. In PageRoute, there is an opaque meaning. And always true.

@override
bool get opaque => true;
Copy the code

To solve the above problem, here is a copy of the source code of the MaterialPageRoute and change Opaque to false.

/// Copy a copy of the MaterialPageRoute and modify Opaque
class TransparentPage<T> extends PageRoute<T> {
  / /...
  /// false indicates background transparency
  @override
  bool get opaque => false;
   
  / /...
}
Copy the code

Finally, in the last article, some students asked how to animate the Hero in the list. We also simulate the Hero in the code to ensure that the Hero has the same tag. When the Flutter switches between old and new routes, the Hero parts with the same tag will be animated.

/// detail_page.dart
child: Hero(
  tag: "detail_$currentIndex",
  child: GestureDetector(
      child:PageView(
                onPageChanged: (index) {
                  setState(() {
                    currentIndex = index;
                  });
                },
          //..
         ),
      ),
    )
    
/// right_page.dart
child: Hero(
      tag: "detail_0",
      child: Image.asset(
             assets/detail.png",
             fit: BoxFit.fill,
       ),
   ),
Copy the code

Write in the last

With the experience of gesture interaction, it is not difficult to achieve the above effects.

The background color will take a little more time, but Flutter is open source, and the annotations and use cases are written in great detail. If the Flutter framework doesn’t meet your needs, you can always modify the underlying Flutter source code to achieve the desired effect.

With that, the content of this article is also over, the last thing left is the handling of gesture conflict, this content will be put in the next article, follow me for the latest article.

Github address: github.com/ditclear/ti…

If this article helped you, please give it a thumbs up.

==================== split line ======================

If you want to learn more about MVVM, Flutter, and responsive programming, please follow me.

You can find me at:

Jane: www.jianshu.com/u/117f1cf0c…

The Denver nuggets: juejin. Cn/user / 817692…

Github: github.com/ditclear