如何为 Tween<Offset> 转换 RenderBox 全局位置坐标?

How to convert RenderBox global position coordinates for Tween<Offset>?

我正在尝试使用 SlideTransition 创建自己的 Hero 样式转换,位置 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 并将其用作 initState:

Animation<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();
}

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

如果有人知道我在匹配这些值时必须执行的正确 method/exact 数学运算,我会永远爱你。

编辑:我正在尝试实现的自包含示例,您可以尝试一下。我目前正在使用 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;
  }
}

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

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

  var scrollOffset = controller.position.pixels;
  double position =  ((i * 175).toDouble() + 100 - scrollOffset) / 200;
  1. (i * 175) : 每张卡片从顶部开始的偏移量。
  2. 100 : 首屏卡片高度差 和第二个屏幕(第一个 300,第二个 200)。我们将这个添加到 补偿差异,因此卡的位置将是 一样。
  3. 200 : 卡片在副屏的高度。我们除以这个 因为

The translation is expressed as an Offset scaled to the child's size

最后你需要给你的 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();
  }
}