草庐IT

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

coder 2023-07-23 原文

我正在尝试创建自己的 Hero使用 SlideTransition 的样式转换位置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();
}

我遇到的问题是找到一种方法来正确转换 dyTween<Offset> 匹配的值用作 dy value 带有 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;
  }
}

最佳答案

在这种情况下查找 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();
  }
}

关于animation - 如何为 Tween<Offset> 转换 RenderBox 全局位置坐标?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54287694/

有关animation - 如何为 Tween<Offset> 转换 RenderBox 全局位置坐标?的更多相关文章

  1. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  2. ruby - 如何为 emacs 安装 ruby​​-mode - 2

    我刚刚为fedora安装了emacs。我想用emacs编写ruby。为ruby​​提供代码提示、代码完成类型功能所需的工具、扩展是什么? 最佳答案 ruby-mode已经包含在Emacs23之后的版本中。不过,它也可以通过ELPA获得。您可能感兴趣的其他一些事情是集成RVM、feature-mode(Cucumber)、rspec-mode、ruby-electric、inf-ruby、rinari(用于Rails)等。这是我当前用于Ruby开发的Emacs配置:https://github.com/citizen428/emacs

  3. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  4. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

  5. ruby - 正则表达式在哪个位置失败? - 2

    我需要一个非常简单的字符串验证器来显示第一个符号与所需格式不对应的位置。我想使用正则表达式,但在这种情况下,我必须找到与表达式相对应的字符串停止的位置,但我找不到可以做到这一点的方法。(这一定是一种相当简单的方法……也许没有?)例如,如果我有正则表达式:/^Q+E+R+$/带字符串:"QQQQEEE2ER"期望的结果应该是7 最佳答案 一个想法:你可以做的是标记你的模式并用可选的嵌套捕获组编写它:^(Q+(E+(R+($)?)?)?)?然后你只需要计算你获得的捕获组的数量就可以知道正则表达式引擎在模式中停止的位置,你可以确定匹配结束

  6. ruby-on-rails - 没有参数的 `<<`(小于两倍)是什么意思? - 2

    我在一个我想在formtasticGem中覆盖的方法中找到了这个。该方法如下所示:defto_htmlinput_wrappingdohidden_field_html是什么意思?在第三行做什么?我知道它对数组有什么作用,但在这里我不知道。 最佳答案 你可以这样读:hidden_field_htmllabel_with_nested_checkbox是连接到hidden_​​field_html末尾的参数-为了“清晰”,他们将其分成两行 关于ruby-on-rails-没有参数的`

  7. ruby-on-rails - 找不到 gem railties (>= 0.a) (Gem::GemNotFoundException) - 2

    我已经看到了一些其他的问题,尝试了他们的建议,但没有一个对我有用。我已经使用Rails大约一年了,刚刚开始一个新的Rails项目,突然遇到了问题。我卸载并尝试重新安装所有Ruby和Rails。Ruby很好,但Rails不行。当我输入railss时,我得到了can'tfindgemrailties。我当前的Ruby版本是ruby2.2.2p95(2015-04-13修订版50295)[x86_64-darwin15],尽管我一直在尝试通过rbenv设置ruby​​2.3.0。如果我尝试rails-v查看我正在运行的版本,我会得到同样的错误。我使用的是MacOSXElCapitan版本10

  8. ruby-on-rails - 如何为空白字段编写 rspec? [Rails3.1] - 2

    我使用rails3.1+rspec和factorygirl。我对必填字段(validates_presence_of)的验证工作正常。我如何让测试将该事实用作“成功”而不是“失败”规范是:describe"Addanindustrywithnoname"docontext"Unabletocreatearecordwhenthenameisblank"dosubjectdoind=Factory.create(:industry_name_blank)endit{shouldbe_invalid}endend但是我失败了:Failures:1)Addanindustrywithnona

  9. ruby - 在 RSpec 中 stub /模拟全局常量 - 2

    我有一个gem,它有一个根据Rails.env的不同行为的方法:defself.envifdefined?(Rails)Rails.envelsif...现在我想编写一个规范来测试这个代码路径。目前我是这样做的:Kernel.const_set(:Rails,nil)Rails.should_receive(:env).and_return('production')...没关系,只是感觉很丑。另一种方法是在spec_helper中声明:moduleRails;end而且效果也很好。但也许有更好的方法?理想情况下,这应该有效:rails=double('Rails')rails.sho

  10. ruby - 将全局 $stdout 重新分配给控制台 - ruby - 2

    我正在尝试将$stdout设置为临时写入一个文件,然后返回到一个文件。test.rb:old_stdout=$stdout$stdout.reopen("mytestfile.out",'w+')puts"thisgoesinmytestfile"$stdout=old_stdoutputs"thisshouldbeontheconsole"$stdout.reopen("mytestfile1.out",'w+')puts"thisgoesinmytestfile1:"$stdout=old_stdoutputs"thisshouldbebackontheconsole"这是输出。r

随机推荐