温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Flutter仿网易怎么实现广告卡片3D翻转效果

发布时间:2022-04-27 10:30:08 来源:亿速云 阅读:152 作者:zzz 栏目:开发技术

这篇文章主要介绍“Flutter仿网易怎么实现广告卡片3D翻转效果”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Flutter仿网易怎么实现广告卡片3D翻转效果”文章能帮助大家解决问题。

    先看下网易新闻的效果:

    Flutter仿网易怎么实现广告卡片3D翻转效果

    实现思路

    1、获取各种距离

    看图:

    Flutter仿网易怎么实现广告卡片3D翻转效果

    思路: 如上图,状态栏高度和AppBar的高度我们都可以得到,屏幕的高度我们也可以得到,那么自然我们就可以计算出内容区域的高度,拿到内容区域高度我们先放到一边,接下来我们需要获取广告区域距离AppBar的距离,这是一个进行翻转核心数据,这里我们可以通过GlobalKey获取这个组件的渲染对象RenderObject并转化为RenderBox,通过RenderBox我们可以获取到这个组件在屏幕上的坐标,这样我们拿到这个坐标Y轴的值就是当前组件距离顶部的距离

    核心代码:

    // 这里我们获取相对于屏幕左上角组件的坐标y轴
    
    GlobalKey _globalKey = GlobalKey();
    
    RenderBox? renderBox =
        _globalKey.currentContext?.findRenderObject() as RenderBox?;
    double? dy = renderBox?.localToGlobal(Offset.zero).dy;

    接下来我们就可以计算出几个关键数据:

    状态栏高度:stateHeight = MediaQuery.of(context).padding.top;已知。

    AppBar高度:appBarHeight = 56; 默认高度 已知。

    内容区域高度:contentHeight = MediaQuery.of(context).size.height - stateHeight -appBarHeight;

    假设我们广告区域的高度是200,广告组件的高度一般都是固定的。

    得出:广告上方距离顶部的最大距离:maxHeight= contentheight - 200;

    还记得我们上面获取的dy值吗,这个值是当前广告上面距离屏幕顶部的距离,那么我们就可以得出当前广告距离AppBar底部的距离: bannerY = dy - appBarHeight - stateHeight;

    同理可以得出当前广告的滑动距离:scrollY = contentheight - 200 - bannerY;

    滑动的最大距离就是:maxSrollY = contentHeight - bannerHeight;

    2、翻转

    搞定了这些数据,接下来的工作就比较简单了,我们使用Transform组件来进行180度的翻转就可以了,
    获取当前滑动的比例,那就是当前滑动距离/最大滑动距离,也就是 scrollY/maxHeight; 接下来我们看下Transform这个类,

    代码:

    Container(
        padding: EdgeInsetsDirectional.only(
            start: 20, end: 20, top: 30, bottom: 30),
        height: bannerHeight,
        key: _globalKey,
        child: Transform(
          alignment: Alignment.center, //相对于坐标系原点的对齐方式 从中间翻转
          transform: Matrix4.identity()//这是一个矩阵变换类,可以对组件的坐标进行翻转,有兴趣可以了解下
            ..rotateX(0)// 翻转X轴
            ..rotateY(angle),// 翻转Y轴 这里需要传入角度
          child: Image.asset(
            "images/img.png",
            fit: BoxFit.fill,
          ),
        ));

    通过rotateY就可以将组件绕着Y轴进行翻转,也就达到了我们想要的3D效果,上面我们得到了滑动比例,那么我们就可以用这个比例乘以PI值,刷新页面就可以了呗,接下来我们通过滑动监听将这个数字进行更新看下效果:

    核心代码:

    double h = MediaQuery.of(context).size.height; //屏幕高度
    RenderBox? renderBox =
        _globalKey.currentContext?.findRenderObject() as RenderBox?;
    double? dy = renderBox?.localToGlobal(Offset.zero).dy;
    // 56 AppBar 高度
    if (dy != null) {
      // 广告距离AppBar Y轴距离
      var bannerY = dy - appBarHeight - stateHeight;
      // 主内容区域高度
      var contentHeight = h - appBarHeight - stateHeight;
      if (bannerY + bannerHeight < contentHeight && bannerY > 0) {
        setState(() {
          //滑动的距离
          angle = pi * ((contentHeight - bannerHeight - bannerY) /
                  (contentHeight - bannerHeight));
         
        });
      }
    }

    效果:

    Flutter仿网易怎么实现广告卡片3D翻转效果

    翻转效果确实实现了,不过怎么看着有点不对劲呢,这里有两个问题:

    1、划上去翻过来的图片直接镜像了。

    Flutter仿网易怎么实现广告卡片3D翻转效果

    2、当我们滑动到一半的时候,两边的宽度是一致的,3D效果不明显。

    Flutter仿网易怎么实现广告卡片3D翻转效果

    其实这两个问题都很好解决,

    第一个滑动角度问题,我们滑动到90度进行翻过来的时候只需要将角度+180度进行翻转即可。这样就相当于翻了360度,最后自然会回到原来的图片的样子。

    第二个我们需要设置Transform的一个属性..setEntry(3, 2, 0.002),让卡片翻转过程中看起来远小近大的效果。

    我们加上这两个属性再看看效果:

    Flutter仿网易怎么实现广告卡片3D翻转效果

    这样看着是不是效果就好多了。

    这里我只简单了插入了一条广告,如果有多个广告建议用一个Map对象将Key存储起来,因为一个Key只能对应一个组件。

    完整代码

    class ListViewWidgetDemo extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return ListViewState();
      }
    }
    
    class ListViewState extends State<ListViewWidgetDemo> {
      List<NewsListBean> lis = <NewsListBean>[];
    
      late ScrollController _scrollController = ScrollController();
      String imageUrl =
          "https://images.unsplash.com/photo-1451187580459-43490279c0fa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60";
    
      GlobalKey _globalKey = GlobalKey();
    
      double angle = 0;
      double bannerHeight = 200;
    
      @override
      void initState() {
        WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
          _scrollController.addListener(() {
            double appBarHeight = 56;
            double stateHeight = MediaQuery.of(context).padding.top;
            double h = MediaQuery.of(context).size.height; //屏幕高度
    
            RenderBox? renderBox =
                _globalKey.currentContext?.findRenderObject() as RenderBox?;
            double? dy = renderBox?.localToGlobal(Offset.zero).dy;
            // 56 AppBar 高度
            if (dy != null) {
              // 广告距离AppBar Y轴距离
              var bannerY = dy - appBarHeight - stateHeight;
              // 主内容区域高度
              var contentHeight = h - appBarHeight - stateHeight;
              if (bannerY + bannerHeight < contentHeight && bannerY > 0) {
                setState(() {
                  //滑动的距离
                  angle = pi *
                      ((contentHeight - bannerHeight - bannerY) /
                          (contentHeight - bannerHeight));
                  // 前半部分 0-90 后半部分 270-360
                  if (angle >= (pi / 2)) {
                    angle = angle + pi;
                  }
                });
              }
            }
          });
        });
    
        super.initState();
        for (int i = 0; i < 40; i++) {
          lis.add(NewsListBean(
            i.isEven ? 0 : 1,
            "资讯标题$i",
            imageUrl,
          ));
        }
        // 插入广告
        lis.insert(12, NewsListBean(2, "广告", imageUrl));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text("仿网易新闻广告卡片翻转"),
            ),
            body: ListView.builder(
                controller: _scrollController,
                shrinkWrap: true,
                scrollDirection: Axis.vertical,
                itemCount: lis.length,
                itemBuilder: (context, index) {
                  return _listWidget(lis[index]);
                }));
      }
    
      Widget _listWidget(NewsListBean bean) {
        late Widget widget;
        switch (bean.type) {
          case 0:
            widget = Container(
                height: 50,
                padding: EdgeInsetsDirectional.only(start: 20),
                alignment: Alignment.centerLeft,
                color: Colors.blue[200],
                child: Text(
                  bean.title,
                  style: TextStyle(),
                ));
            break;
          case 1:
            widget = Row(
              children: [
                Expanded(
                  child: Container(
                      height: 80,
                      alignment: Alignment.center,
                      color: Colors.red[200],
                      margin: EdgeInsets.all(10),
                      child: Text(bean.title)),
                ),
                Image.network(
                  bean.image,
                  width: 40,
                  height: 40,
                )
              ],
            );
            break;
          case 2:
            widget = Container(
                padding: EdgeInsetsDirectional.only(
                    start: 20, end: 20, top: 30, bottom: 30),
                height: bannerHeight,
                key: _globalKey,
                child: Transform(
                  alignment: Alignment.center, //相对于坐标系原点的对齐方式
                  transform: Matrix4.identity()
                    ..setEntry(3, 2, 0.002)
                    ..rotateX(0)
                    ..rotateY(angle),
                  child: Image.asset(
                    "images/img.png",
                    fit: BoxFit.fill,
                  ),
                ));
            break;
          default:
            widget = SizedBox();
            break;
        }
        return widget;
      }
    }
    class NewsListBean {
      //资讯类型 0:资讯无图 1:资讯有图 2:3d广告
      final int type;
      final bool isFirst;
      final String title;
      final String image;
    
      NewsListBean(this.type, this.title, this.image, {this.isFirst = false});
    }

    关于“Flutter仿网易怎么实现广告卡片3D翻转效果”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI