Flutter仿网易实现广告卡片3D翻转效果
作者:老李code 发布时间:2021-07-23 22:38:05
前言
在逛网易新闻时,发现列表中的广告在你滑动的时候会有一个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翻转的效果,实现这种效果可能也有其他更好的方式,本篇文章只提供了一个实现思路。
来源:https://juejin.cn/post/7090856555817467935


猜你喜欢
- 本节作为主要讲解Spring Data的环境搭建JPA Spring Data :致力于减少数据访问层(DAO)的开发量。开发者唯一要做的就
- 本文实例为大家分享了Android Studio实现帧动画的具体代码,供大家参考,具体内容如下按一定的顺序播放静态的图片1、几张联系的图片2
- 记得在前面的文章中,我带大家一起从源码的角度分析了Android中View的事件分发机制,相信阅读过的朋友对View的事件分发已经有比较深刻
- AudioSource 组件参考属性属性说明Clip音频资源Volume音量大小Mute是否静音Loop是否循环Play on load加载
- Servlet:当然,在servlet中,一般跳转都发生在doGet, doPost等方法里面。1) redirect 方式response
- 修改FeginCilent定义的服务名到指定服务通过覆盖类来修改对应的服务名,这里将所有的FeginClient对应的服务名都修改好。pac
- 也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗。在进入Java NIO编程之前,我们今天先来讨论一些比较
- 一、介绍SharpZipLib是一个完全由C#编写的ZIP,GZIP,Tar和BZIP2 Library,可以方便的支持这几种格式的压缩和解
- 表关联上一篇介绍了JPA的简单使用,这一篇介绍JPA在表关联上的使用一对一配置参数JPA对于数据实体一对一映射使用的是@OneToOne注解
- 本文实例讲述了C#递归实现回文判断算法,分享给大家供大家参考。具体实现方法如下:static void Main(string[] args
- 1. 算法分析根据概率将奖品划分区间,每个区间代表一个奖品,然后抽取 随机数,反查落在那个区间上,即为所抽取的奖品。2. 代码核心
- Java-Web获取客户端真实IP:发生的场景:服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等情况,在进行客户端IP限
- 这里介绍一个简易的音乐播放器,供大家参考,具体内容如下效果图如下:但是,由于这是一个简易版的音乐播放器,所播放的音乐只有一首,且被写死,但,
- 在"C#中,什么时候用yield return"中,我们了解到:使用yield return返回集合,不是一次性加载到内
- Android Fragment的回退栈点开之后按一次回退键只返回一次MainActivity 类public class Ma
- 带着问题 往下看 (namesrv)我们在写组件的时候 怎么管理version如果现在让你 维护一个 各个jar包公用的属性System.e
- 本文实例讲述了Java针对封装数组的简单复杂度分析方法。分享给大家供大家参考,具体如下:完成了数组的封装之后我们还需对其进行复杂度分析:此处
- 本文实例讲述了java实现图片写入高清字体及带边框的方法。分享给大家供大家参考。具体实现方法如下:Graphics2D g2=image.c
- 表达式目录树表达式目录树:语法树,或者说是一种数据结构1.表达式目录树Expression:System.Linq.Expressions;
- 我们使用Jmeter测试同学的网站时,就会出现网站无法访问,403等错误。An error occurred.Sorry, the page