知乎的廣告效果一直想寫,無奈最近纔有時間。 先看效果: canvas
確定要自定義view了,一個相似imageView的控件,還要給它一個值用來指定廣告圖片的顯示位置。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);方法就能夠實現整個效果了。