知乎的广告效果一直想写,无奈最近才有时间。 先看效果: canvas
1.图片如何在范围内(单个item范围)上下移动,如窗户通常,后面的图是能够动的,可是窗户是固定的。
2.图片移动的时机确定和recycleView滚动监听item有关,用哪些方法?bash
1.窗户问题首先想到imageView的scaleType属性,而scaleType中只有matrix和center能够在不缩放图片的状况下显示一张大图中的部分,center始终显示在图片中间部分,不符合要求;matrix不指定显示位置,合适。
2.recycleView Item的滚动监听,恰好前段时间在仿写微博视频自动播放时接触过,recycleView提供了一些譬如FindFirstVisibleItemPosition(当前屏幕第一个item的position),FindFirstCompletelyVisibleItemPosition(当前屏幕第一个彻底显示item的position)等方法,能够利用这些方法,把当前的item找到,再利用instanceof关键字比较当前item是否是个人广告item,若是是再想办法让广告图片动起来。ide
继承imageView,只须要重写他的2个方法,onSizeChanged和onDraw。
onSizeChanged用来获得控件高度
onDraw移动广告图片ui
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中说几个点:spa
经过onDraw方法,已经能够实现:一个imageView控件,动态的去移动它的内部图片。这个自定义的imageView就算是完成了。rest
写监听以前想一想如何把recycleView的item与自定义imageView联系起来,经过 canvas.translate(dx,dy)让图片动起来,必需要求出dy: 能够看看效果,只要广告的item有一点不在屏幕内,那么其中的图片是不会移动的,那么咱们广告item有效移动距离就是整个recycleView的高度减去广告item的高度,如图绿色线: 日志
而咱们自定义imageView中图片有效移动距离是整个图片的高度减去窗口的高度,如图绿色线:(红色框就至关于自定义imageView窗口,整张图就是窗后能够translate的图片) code
关系就出来了: 广告item位置 / 广告有效移动距离 = dy / 图片有效移动距离cdn
重写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++) {}
使用的是firstPosition和lastPosition,也就是//第一个显示的item和//最后一个显示的item。 知乎广告的效果是 广告item 彻底显示才会去translate图片,一开始我使用的是first和last,他们正好也是彻底显示的意思,for循环少走一两次挺好的;可是在验证效果的时候发现一个尴尬的问题:recycleView滑动速度过快,会出现广告item不能translate至底部或者顶部的状况;日志监视了一下,缘由就是滑动过快。在代码中的表现是什么呢?自定义imageView中setDy()方法的rate变化异常。
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);方法就能够实现整个效果了。