前言

在逛网易新闻时,发现列表中的广告在你滑动的时候会有一个3D旋转的交互引你的注意,不得不说这些产品为了让用户看广告花样百出,那么今天我们就用Flutter也实现这么一个效果。

先看下网易新闻的效果:

OK,先说了我看到这个效果的思路:首先我们看到这个广告卡片在从底部向上滑的时候在完全滑入到显示屏区域内开始3D旋转,到这个卡片顶部到达列表顶部时翻转结束,那我们主要还是需要计算这个广告卡片距离列表底部的距离和距离列表顶部的距离,有了这两个距离,那么我们就可以根据Transform进行Y轴翻转180°就好了。

实现思路

1、获取各种距离

看图:

思路: 如上图,状态栏高度和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));
     
    });
  }
}

效果:

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

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

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

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

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

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

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

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

这里我只简单了插入了一条广告,如果有多个广告建议用一个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});
}

小结

通过本篇文章我们可以学习到,获取组件的坐标以及3D翻转的效果,实现这种效果可能也有其他更好的方式,本篇文章只提供了一个实现思路。

到此这篇关于Flutter仿网易实现广告卡片3D翻转效果的文章就介绍到这了,更多相关Flutter广告卡片翻转内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Flutter仿网易实现广告卡片3D翻转效果的更多相关文章

  1. ios – 当我在xCode 5中验证我的应用程序时,获取错误的广告标识符[IDFA]错误

    在验证我的应用程序时,我收到错误消息“广告标识符使用不当.您的应用包含广告标识符[IDFA]API,但您尚未遵守iOS中的限制广告跟踪设置.”我在广告标识符的准备上传页面上检查了“是”.我在我的应用程序中使用revmob广告和flurry分析(COCOS2D-X项目).如何解决这个问题,我已经尝试了很多但没有成功.我使用下面的代码进入appdelegate但没有运气.解决方法这个IDFA问题今天仍

  2. iOS上的自定义BLE广告

    有没有使用私有API或越狱的替代品?解决方法我对iOS的体验是,如果它没有在API中公开,除了越狱之外没有办法解决.对于蓝牙低功耗,API处于GAP/GATT级别,而较低级别暴露的则很少.广告是LL(链接层)功能.为了说明访问受限制的限制:扫描BLE设备时,您将无法访问广告商的MAC地址iOS.在Android中你有它.

  3. ios – 蓝牙LE,scanForPeripheralsWithServices在后台增加速度

    我在iPhone5S上使用蓝牙LE,我做了以下工作:>我有一个蓝牙外设,我配置它在所有三个蓝牙广告频道(37,38和39)上宣布每20秒.>我已经配置了我的应用程序与UIBacgroundModes=蓝牙中央在Info.plist>我已经启动了一个scanForperipheralsWithServices,如下所示码:目前的状态是:>在前台模式下,当我启动外围设备时(一秒钟内),应用程序会迅速收

  4. 2014年4月/ iOS 7 – 有没有办法跟踪iOS应用的转换,以便在不使用IDFA的情况下将内容下载到不同的广告系列来源?

    解决方法它现在似乎Apple特别允许使用IDFA跟踪安装.更新我的应用程序时,我看到:选择第二个选项没有出错,应用程序已获批准.

  5. 使用swift集成移动广告聚合平台

    OverridefuncviewDidLoad(){super.viewDidLoad()bannerView.adUnitId=“ca-app-pub-706657930853688714815911455”bannerView.rootViewController=selfself.view.addSubViewVarrequest:GADRequest=GADRequest()request.testDevices={“”}bannerView.loadRequest}Overridefuncdid

  6. Flutter中文教程-Cookbook

    Flutter中文网的Cookbook中包含了在编写Flutter应用程序时常见问题及示例。设计基础使用主题共享颜色和字体样式Images显示来自网上的图片用占位符淡入图片使用缓存图Lists创建一个基本list创建一个水平list使用长列表创建不同类型子项的List创建一个gridList处理手势处理点击添加Material触摸水波效果实现滑动关闭导航导航到新页面并返回给新页面传值从新页面返回数据给上一个页面网络从网上获取数据进行认证请求使用WebSockets

  7. swift – 为什么我的facebook插页式广告不会显示在我的应用中?

    他们肯定也能在这里提供帮助.thispage之后的某个地方我相信你可以找到适当的现场支持作为付费广告客户.

  8. 设置Android隐私政策

    我在我的Android应用程序中使用AdMob.我是否需要在我的应用程序中创建隐私政策,以告知用户Google使用的Cookie?如果是,我需要把它放在哪里?我必须在GooglePlay上的说明中写出来吗?或者在第一次运行时我必须显示片段窗口吗?我阅读了Google文档,但我并不清楚.https://support.google.com/googleplay/android-developer/answer/2519872?hl=en解决方法澄清任何发现此问题的人:答案是肯定的,如果您使用的是AdMob,

  9. android – 使用AdMob的Google Play服务.检查可用性?

    解决方法GooglePlay服务包含两个组件.一个是链接到应用程序的jar,另一个是系统的一部分.AdMob广告显示组件位于jar中,而其他播放服务位于系统中.如果使用GooglePlay服务构建应用,您将能够展示AdMob广告,但您将无法获得其他一些GooglePlay服务功能.所以,是的,当您的应用程序链接到库时,某些GooglePlay服务类将包含在您的APK中.也可以使用旧的AdMobSDK来显示AdMob广告.

  10. android – 阻止“后退”按钮关闭插页式广告

    我正在使用AdMob将插页式广告添加到我的Android应用中.我想知道是否有办法阻止用户通过按“后退”按钮关闭它们.我知道AdMob正在将广告加载到其他活动中,因此我无法使用我的活动的onKeyDown()来执行此操作.此外,我不确定它是否被认为是一种好的做法,如果它值得做的话.谢谢!

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部