如何转换Tween <offset>的RenderBox全局位置坐标?

时间:2019-01-21 10:13:32

标签: animation flutter offset tween

我正在尝试使用Hero位置为SlideTransition的{​​{1}}创建自己的Offset样式过渡,从用户在上一个屏幕上点击项目的位置开始。

这是我当前用来接收用户点击屏幕的坐标值的方式(我只需要dy值):

GestureDetector(
    child: //stuff
    onTapDown: (TapDownDetails details) async {
        RenderBox box = context.findRenderObject();
        double position = box.localToGlobal(details.globalPosition).dy;
            await Navigator.push(context, MaterialPageRoute(builder: (context) {
        return SecondPage(startPosition: position);
        }));
    },
)

然后我将其传递到SecondPage上,并将其用作Animation<Offset>在我的initState中的起始位置:

@override
void initState() {
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    curve = CurvedAnimation(parent: controller, curve: Curves.easeInOut);
    offset = new Tween<Offset>(begin: Offset(0.0, widget.startPosition), end: Offset.zero).animate(curve);
    controller.forward();
    super.initState();
}

我遇到的问题是找到一种将dy值正确转换为与Tween<Offset>所使用的值相匹配的方法,因为dy的值带有一个say 250-300在屏幕的一半下方,但Offset(0.0, widget.startPosition)的相同值将在2.0左右,以匹配相同的位置。我已经尝试过各种数学运算来匹配它们(例如,将dy除以屏幕高度),但是没有找到与其完全匹配的任何东西。

如果有人知道正确的方法/精确的数学运算法则,那么我将永远爱你。

编辑:我正在尝试实现的自我包含的示例,您可以尝试使用。目前,我正在使用double position = (box.globalToLocal(details.globalPosition).dy) / box.size.height * 3;,因为这是我找到的最接近的匹配项。

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  @override
  State createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.indigo.shade200,
        floatingActionButton: Padding(
            padding: EdgeInsets.only(
                bottom: MediaQuery.of(context).size.height / 35),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              backgroundColor: Colors.grey.shade200,
              foregroundColor: Colors.black,
              onPressed: () {},
            )),
        body: Stack(children: <Widget>[
          SingleChildScrollView(
              child: Padding(
                  padding: EdgeInsets.only(
                      bottom: MediaQuery.of(context).size.height / 11),
                  child:
                      Column(children: <Widget>[Stack(children: getCards())]))),
          Container(
              height: MediaQuery.of(context).size.height / 5,
              width: MediaQuery.of(context).size.width,
              decoration: BoxDecoration(
                  color: Colors.grey.shade200,
                  borderRadius:
                      BorderRadius.only(bottomLeft: Radius.circular(100.0))),
              child: Center(
                  child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: <Widget>[
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        InkWell(
                            child: Padding(
                          padding: EdgeInsets.all(6.0),
                          child: Container(
                            height: 40.0,
                            width: 40.0,
                            decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                border: Border.all(
                                    width: 2.0, color: Colors.pink.shade300)),
                            child: Icon(Icons.sentiment_satisfied),
                          ),
                        )),
                        Text('Tab1',
                            style: TextStyle(
                                fontSize: 10.0,
                                fontWeight: FontWeight.bold,
                                color: Colors.black38))
                      ],
                    ),
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        InkWell(
                            child: Padding(
                          padding: EdgeInsets.all(6.0),
                          child: Container(
                            height: 40.0,
                            width: 40.0,
                            decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                border: Border.all(
                                    width: 2.0, color: Colors.pink.shade300)),
                            child: Icon(Icons.trending_up),
                          ),
                        )),
                        Text('Tab2',
                            style: TextStyle(
                                fontSize: 10.0,
                                fontWeight: FontWeight.bold,
                                color: Colors.black38))
                      ],
                    ),
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        InkWell(
                            child: Padding(
                          padding: EdgeInsets.all(6.0),
                          child: Container(
                            height: 40.0,
                            width: 40.0,
                            decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                border: Border.all(
                                    width: 2.0, color: Colors.pink.shade300)),
                            child: Icon(Icons.favorite_border),
                          ),
                        )),
                        Text('Tab3',
                            style: TextStyle(
                                fontSize: 10.0,
                                fontWeight: FontWeight.bold,
                                color: Colors.black38))
                      ],
                    )
                  ])))
        ]));
  }

  List<Widget> getCards() {
    List<Widget> widgets = new List<Widget>();
    for (int i = 0; i < 5; i++) {
      widgets.add(GestureDetector(
        child: Container(
          margin: EdgeInsets.only(top: (i * 175).toDouble()),
          padding: EdgeInsets.all(60.0),
          height: 300.0,
          decoration: BoxDecoration(
            color: getColor(i),
            borderRadius: BorderRadius.only(bottomLeft: Radius.circular(100.0)),
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              Center(
                      child: Text('Text ' + (i + 1).toString(),
                          maxLines: 2,
                          overflow: TextOverflow.ellipsis,
                          style: TextStyle(
                              color: Colors.white,
                              fontSize: 26.0,
                              fontWeight: FontWeight.bold)))
            ],
          ),
        ),
        onTapDown: (TapDownDetails details) async {
          RenderBox box = context.findRenderObject();
          double position = (box.globalToLocal(details.globalPosition).dy) / box.size.height * 3;
          await Navigator.push(context, CustomPageRoute(builder: (context) {
            return SecondPage(index: i, startPosition: position);
          }));
        },
      ));
    }
    return widgets.reversed.toList();
  }
}

class SecondPage extends StatefulWidget {
  final int index;
  final double startPosition;

  SecondPage({this.index, this.startPosition});

  @override
  State createState() => SecondPageState();
}

class SecondPageState extends State<SecondPage> with TickerProviderStateMixin {
  AnimationController controller;
  Animation curve;
  Animation<Offset> offset;

  @override
  void initState() {
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    curve = CurvedAnimation(parent: controller, curve: Curves.easeInOut);
    offset = new Tween<Offset>(
            begin: Offset(0.0, widget.startPosition), end: Offset.zero)
        .animate(curve);
    controller.forward();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.indigo,
        body: Material(
            color: Colors.transparent,
            child: Stack(children: <Widget>[
              SlideTransition(
                  position: offset,
                  child: Container(
                      height: 200.0,
                      decoration: BoxDecoration(
                          color: getColor(widget.index),
                          borderRadius: BorderRadius.only(
                              bottomLeft: Radius.circular(100.0))),
                      child: Padding(
                          padding: EdgeInsets.only(top: 28.0),
                          child: Stack(
                            children: <Widget>[
                              Align(
                                  alignment: Alignment.topLeft,
                                  child: IconButton(
                                    icon: Icon(Icons.arrow_back,
                                        color: Colors.white),
                                    onPressed: () {
                                      Navigator.of(context).pop(true);
                                    },
                                  )),
                              Align(
                                  alignment: Alignment.topRight,
                                  child: IconButton(
                                    icon:
                                        Icon(Icons.launch, color: Colors.white),
                                    onPressed: () {
                                      print("launch website");
                                    },
                                  )),
                                  Align(
                                  alignment: Alignment.center,
                                  child: Padding(
                                      padding: EdgeInsets.symmetric(
                                          horizontal: MediaQuery.of(context)
                                                  .size
                                                  .width /
                                              5),
                                      child: Material(
                                            color: Colors.transparent,
                                            child: Text('Text ' + (widget.index + 1).toString(),
                                              maxLines: 2,
                                              overflow: TextOverflow.ellipsis,
                                              style: TextStyle(
                                                  color: Colors.white,
                                                  fontSize: 26.0,
                                                  fontWeight:
                                                      FontWeight.bold)))))
                            ],
                          ))))
            ])));
  }
}

class CustomPageRoute<T> extends MaterialPageRoute<T> {
  CustomPageRoute({WidgetBuilder builder, RouteSettings settings})
      : super(builder: builder, settings: settings);

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation, Widget child) {
    return child;
  }
}

Color getColor(int index) {
  switch (index) {
    case 0:
      return Colors.pink.shade300;
    case 1:
      return Colors.purple.shade300;
    case 2:
      return Colors.deepPurple.shade400;
    case 3:
      return Colors.deepPurple.shade900;
    case 4:
      return Colors.indigo.shade900;
    default:
      return Colors.red;
  }
}

1 个答案:

答案 0 :(得分:3)

在这种情况下,找不到renderBox是有用的,因为所有卡都位于上下文中的堆栈中。findRenderObject查找最顶层的堆栈。该堆栈覆盖了整个屏幕,因此当您从全局位置转换为本地位置时,您将获得相同的位置。

您可以像这样为您的第二个屏幕计算正确的偏移量:

  var scrollOffset = controller.position.pixels;
  double position =  ((i * 175).toDouble() + 100 - scrollOffset) / 200;
  1. (i * 175):每张卡从顶部的偏移量。
  2. 100:第一个屏幕中卡片高度之间的高度差 第二屏幕(第一屏幕为300,第二屏幕为200)。我们添加到 弥补差异,使卡的位置成为 一样。
  3. 200:第二个屏幕中卡的高度。用这个除 因为
  

翻译表示为按孩子的身材缩放的偏移量

最后,您需要为您的SingleChildScrollView提供一个scrollController以获得滚动偏移量。如果没有滚动偏移,则滚动卡片(即卡片4或卡片5)时,您将无法计算正确的位置

这是您的HomePageState的外观。

class HomePageState extends State<HomePage> {

  ScrollController controller = new ScrollController();


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.indigo.shade200,
        floatingActionButton: Padding(
            padding: EdgeInsets.only(
                bottom: MediaQuery.of(context).size.height / 35),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              backgroundColor: Colors.grey.shade200,
              foregroundColor: Colors.black,
              onPressed: () {},
            )),
        body: Stack(children: <Widget>[
          SingleChildScrollView(
            controller: controller,
              child: Padding(
                  padding: EdgeInsets.only(
                      bottom: MediaQuery.of(context).size.height / 11),
                  child:
                      Column(children: <Widget>[Stack(children: getCards())]))),
          Container(
              height: MediaQuery.of(context).size.height / 5,
              width: MediaQuery.of(context).size.width,
              decoration: BoxDecoration(
                  color: Colors.grey.shade200,
                  borderRadius:
                      BorderRadius.only(bottomLeft: Radius.circular(100.0))),
              child: Center(
                  child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: <Widget>[
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        InkWell(
                            child: Padding(
                          padding: EdgeInsets.all(6.0),
                          child: Container(
                            height: 40.0,
                            width: 40.0,
                            decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                border: Border.all(
                                    width: 2.0, color: Colors.pink.shade300)),
                            child: Icon(Icons.sentiment_satisfied),
                          ),
                        )),
                        Text('Tab1',
                            style: TextStyle(
                                fontSize: 10.0,
                                fontWeight: FontWeight.bold,
                                color: Colors.black38))
                      ],
                    ),
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        InkWell(
                            child: Padding(
                          padding: EdgeInsets.all(6.0),
                          child: Container(
                            height: 40.0,
                            width: 40.0,
                            decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                border: Border.all(
                                    width: 2.0, color: Colors.pink.shade300)),
                            child: Icon(Icons.trending_up),
                          ),
                        )),
                        Text('Tab2',
                            style: TextStyle(
                                fontSize: 10.0,
                                fontWeight: FontWeight.bold,
                                color: Colors.black38))
                      ],
                    ),
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        InkWell(
                            child: Padding(
                          padding: EdgeInsets.all(6.0),
                          child: Container(
                            height: 40.0,
                            width: 40.0,
                            decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                border: Border.all(
                                    width: 2.0, color: Colors.pink.shade300)),
                            child: Icon(Icons.favorite_border),
                          ),
                        )),
                        Text('Tab3',
                            style: TextStyle(
                                fontSize: 10.0,
                                fontWeight: FontWeight.bold,
                                color: Colors.black38))
                      ],
                    )
                  ])))
        ]));
  }

  List<Widget> getCards() {
    List<Widget> widgets = new List<Widget>();
    for (int i = 0; i < 5; i++) {
      widgets.add(GestureDetector(
        child: Container(
          margin: EdgeInsets.only(top: (i * 175).toDouble()),
          padding: EdgeInsets.all(60.0),
          height: 300.0,
          decoration: BoxDecoration(
            color: getColor(i),
            borderRadius: BorderRadius.only(bottomLeft: Radius.circular(100.0)),
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              Center(
                      child: Text('Text ' + (i + 1).toString(),
                          maxLines: 2,
                          overflow: TextOverflow.ellipsis,
                          style: TextStyle(
                              color: Colors.white,
                              fontSize: 26.0,
                              fontWeight: FontWeight.bold)))
            ],
          ),
        ),
        onTapDown: (TapDownDetails details) async {
          var scrollOffset = controller.position.pixels;
          double position =  ((i * 175).toDouble() + 100 - scrollOffset) / 200;

          await Navigator.push(context, CustomPageRoute(builder: (context) {
            return SecondPage(index: i, startPosition: position);
          }));
        },
      ));
    }
    return widgets.reversed.toList();
  }
}