Android 知乎广告效果实现代码
作者:达峰a 发布时间:2022-12-02 08:50:12
知乎的广告效果一直想写,无奈最近才有时间。
先看效果:
肯定要自定义view了,一个类似imageView的控件,还要给它一个值用来指定广告图片的显示位置。
问题:
1.图片如何在范围内(单个item范围)上下移动,如窗户一般,后面的图是可以动的,但是窗户是固定的。
2.图片移动的时机肯定和recycleView滚动监听item有关,用哪些方法?
解决:
1.窗户问题首先想到imageView的scaleType属性,而scaleType中只有matrix和center可以在不缩放图片的情况下显示一张大图中的部分,center始终显示在图片中间部分,不符合要求,matrix不指定显示位置。
2.recycleView Item的滚动监听,刚好前段时间在仿写微博视频自动播放时接触过,recycleView提供了一些譬如FindFirstVisibleItemPosition(当前屏幕第一个item的position),FindFirstCompletelyVisibleItemPosition(当前屏幕第一个完全显示item的position)等方法,可以利用这些方法,把当前的item找到,再利用instanceof关键字比较当前item是不是我的广告item,如果是再想办法让广告图片动起来。
步骤:
1.自定义一个广告imageView,把他变成窗户:
继承imageView,只需要重写他的2个方法,onSizeChanged和onDraw。
onSizeChanged用来得到控件高度
onDraw移动广告图片
int itemHeight = 0; //自定义imageView高度
private float rate = 1; //初始化显示比率
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
itemHeight = h; //广告item的高度
}
@Override
protected void onDraw(Canvas canvas) {
Drawable drawable = getDrawable();
if (drawable == null) {
return;
}
int w = getWidth();
int h = (int) (getWidth() * 1.0f / drawable.getIntrinsicWidth() * drawable.getIntrinsicHeight());
drawable.setBounds(0, 0, w, h);//设置图片显示的绝对范围
int maxDy = h - itemHeight; //图片可以移动的最大距离为(图片有效移动距离): (0 ~ -maxDy)
canvas.save();
canvas.translate(0, -rate * maxDy);
super.onDraw(canvas);
canvas.restore();
}
public void setDy(int itemDy, int rvheight) {
int allHeight = rvheight - itemHeight; //有效滑动高度(广告有效移动距离)
rate = itemDy * 1f / allHeight;
if (rate <= 0) {
rate = 0;
}
if (rate >= 1) {
rate = 1;
}
invalidate();
}
setDy方法可以先不管。
onDraw中说几个点:
super.onDraw(canvas)代码中的位置
super.onDraw(canvas)是实现原本imageView逻辑的地方,涉及自定义view绘制先后问题;假如我用canvas画了一个圆,画圆代码写在super之前: 这个圆会先绘制出来,再走super,就会出现imageView把圆挡住的情况,画圆代码写在super之后:
先走super再画圆,圆就在imageView的上面。参考上面代码中的super位置,先把图片的位置通过 canvas.translate方法移动之后,再利用super原本逻辑绘制出图片,就实现图片在窗口中移动的效果了。 (此番解释只针对继承已有的imageview,textview等,如果是继承View,super位置就很随意了,因为super是个空实现)
drawable.setBounds(l,t,r,b)方法
这个方法给图片设定一个绝对位置范围~(或者说相对屏幕的显示范围)~,上面代码中的范围计算~(参数r,b)~其实就是 整个屏幕除开状态栏导航栏以外的范围~(recycleView的范围)~。 int w = getWidth()算出图片可以显示的最大宽度,再通过最大宽度 / 图片原本宽度 = 最大高度 / 图片原本高度 计算出最大高度 h。也就是int h = ....这一句。
通过onDraw方法,已经可以实现:一个imageView控件,动态的去移动它的内部图片。这个自定义的imageView就算是完成了。
2.获取recycleView监听以及位置计算
写监听之前想想如何把recycleView的item与自定义imageView联系起来,通过 canvas.translate(dx,dy)
让图片动起来,必须要求出dy:
可以看看效果,只要广告的item有一点不在屏幕内,那么其中的图片是不会移动的,那么我们广告item有效移动距离就是整个recycleView的高度减去广告item的高度,如图绿色线:
而我们自定义imageView中图片有效移动距离是整个图片的高度减去窗口的高度,如图绿色线:(红色框就相当于自定义imageView窗口,整张图就是窗后可以translate的图片)
关系就出来了: 广告item位置 / 广告有效移动距离 = dy / 图片有效移动距离
重写RecyclerView.OnScrollListener
中的onScrolled
方法,我们要得到:广告item位置 和 广告有效移动距离
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int first = layoutManager.findFirstCompletelyVisibleItemPosition(); //第一个完全显示的item
int last = layoutManager.findLastCompletelyVisibleItemPosition(); //最后一个完全显示的item
int firstPosition = layoutManager.findFirstVisibleItemPosition(); //第一个显示的item
int lastPosition = layoutManager.findLastVisibleItemPosition(); //最后一个显示的item
//循环遍历当前屏幕中显示的所有item
for (int i = firstPosition; i <= lastPosition; i++) {
RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(i);
//找出屏幕中的广告item
if (viewHolder instanceof TxRecycleAdapter.ZhiHuHolder) {
TxRecycleAdapter.ZhiHuHolder zhiHuHolder = (TxRecycleAdapter.ZhiHuHolder) viewHolder;
View itemView = zhiHuHolder.itemView;
//获取到广告item的位置 (item的顶部 与 recycleView顶部的距离)
int top = itemView.getTop();
//获取recycleView的高度
int height = recyclerView.getHeight();
//调用自定义imageView中的方法,实现图片的移动
zhiHuHolder.adImageView.setDy(top, height);
}
}
}
int top = itemView.getTop(); top = 广告item位置;
广告有效移动距离 = recycleView的高度 - 广告item的高度,这一点的实现放在了自定义imageView的setDy方法中。
注意方法中的for循环
for (int i = firstPosition; i <= lastPosition; i++) {}
rate等于1图片刚好显示在 顶部
rate等于0图片刚好显示在 底部
rate从0~1:
滑动慢 rate可能是这么变化的:0.05, 0.10,0.15,0.20 .....,0.80,0.85,0.90,0.95,1.0。
滑动快 rate可能是这么变化的:0.3,0.6,0.9。
压根就不会等于1或者等于0,那图片的translate位置肯定就不对了。
出现这个问题我试过很多方法,比如速度跟踪类(VelocityTracker)计算速度,当速度大了再根据滑动方向直接置顶或者置底,获取广告item可见性置顶或者置底.....等等。有些方法可能有点用,但是太麻烦了,最后直接在for循环中用firstPosition和lastPosition,这样,虽然会出现rate = - 0.2 这样的负值,但是你只要给个判断就可以了:
if (rate <= 0){
rate = 0;
}
if (rate >= 1) {
rate = 1;
}
刚已经通过recycleView的监听得到了广告item位置 与 广告有效移动距离,而 图片有效移动距离呢,它在自定义imageView中的onDraw方法得到:
int maxDy = h - itemHeight;
//图片可以移动的最大距离为(图片有效移动距离): (0 ~ -maxDy)
最后,调用canvas.translate(0, -rate * maxDy);方法就可以实现整个效果了。
来源:https://juejin.im/post/5a6546bb6fb9a01cac18480a


猜你喜欢
- 先讲一下java中的反射:反射就是将类别的各个组成部分进行剖析,可以得到每个组成部分,就可以对每一部分进行操作反射机制应用场景:逆向代码、动
- 动态规划的基本思想是将待求解问题分解成若干个子问题,先求解子问题,并将这些子问题的解保存起来,如果以后在求解较大子问题的时候需要用到这些子问
- 在项目中有事需要对值为NULL的对象中Field不做序列化输入配置方式如下:[配置类型]:源码包中的枚举类:public static en
- 本文主要实现在自定义的ListView布局中加入CheckBox控件,通过判断用户是否选中CheckBox来对ListView的选中项进行相
- 本文实例讲述了C#获取每个年,月,周的起始日期和结束日期的方法。分享给大家供大家参考,具体如下:我们在写程序的时候往往要计算出年,月,周的开
- Eclipse 开发java 出现Failed to create the Java Virtual Machine错误解决办法一直用Ecl
- 前言本文章主要从spring security安全认证登录内部调用流程来流程分析登录过程。一、登录时序图时序原图二、配置与代码1.引入库po
- 目录前言准备工作Nacos安装及使用入门准备三个SpringBoot服务,引入Nacos及Kafka业务解读Nacos配置创建配置读取配置监
- 最长公共子序列(Longest Common Subsequence)定义:两个或多个已知数列的子序列集合中最长的就是最长公共子序列。其实说
- 使用Post添加数据到数据库出现方块乱码解决方法,在web.xml里最前面添加过滤器,代码如下,放在最前面,因为有优先级,要首先拦截<
- UI编程通常都会伴随事件处理,Android也不例外,它提供了两种方式的事件处理:基于回调的事件处理和基于 * 的事件处理。对于基于 * 的
- 项目演示演示中只用一个用户登录,只是为了测试功能,实际使用中是根据数据库表内数据来决定的。1 创建工程完成配置1 ieda新建maven项目
- 第一:写Cookies Response.Cookies["UserName"].Value="Guest&q
- java.lang.NoSuchMethodException: com.sun.proxy.$Proxy58.list错误解决办法玩web
- 面试题1:谈一下你对 Nginx 的理解Nginx 是一款自由的、开源的、高性能的 HTTP 服务器和反向代理服务器;同时也是一个 IMAP
- 摘要:手把手教你使用 Java AWT 创建一个简易计算器。一、关于AWTAWT (抽象窗口工具包)是一个有助于构建 GUI 的 API (
- 一、File流概念JAVA中针对文件的读写操作设置了一系列的流,其中主要有FileInputStream,FileOutputStream,
- 1. 前言ResultSetMetaData 叫元数据,是数据库 列对象,以列为单位封装为对象。元数据,指的是其包含列名,列值,列类型,列长
- C#的特性record 类型、模式匹配、init 属性一、record 类型record ,我还是用原词吧,我知道有翻译为“记录类型”的说法
- SpringCloud 整合ribbon的时候出现了这个问题java.lang.IllegalStateException: No inst