Android 知乎廣告效果實(shí)現(xiàn)代碼
知乎的廣告效果一直想寫(xiě),無(wú)奈最近才有時(shí)間。
先看效果:
肯定要自定義view了,一個(gè)類似imageView的控件,還要給它一個(gè)值用來(lái)指定廣告圖片的顯示位置。
問(wèn)題:
1.圖片如何在范圍內(nèi)(單個(gè)item范圍)上下移動(dòng),如窗戶一般,后面的圖是可以動(dòng)的,但是窗戶是固定的。
2.圖片移動(dòng)的時(shí)機(jī)肯定和recycleView滾動(dòng)監(jiān)聽(tīng)item有關(guān),用哪些方法?
解決:
1.窗戶問(wèn)題首先想到imageView的scaleType屬性,而scaleType中只有matrix和center可以在不縮放圖片的情況下顯示一張大圖中的部分,center始終顯示在圖片中間部分,不符合要求,matrix不指定顯示位置。
2.recycleView Item的滾動(dòng)監(jiān)聽(tīng),剛好前段時(shí)間在仿寫(xiě)微博視頻自動(dòng)播放時(shí)接觸過(guò),recycleView提供了一些譬如FindFirstVisibleItemPosition(當(dāng)前屏幕第一個(gè)item的position),F(xiàn)indFirstCompletelyVisibleItemPosition(當(dāng)前屏幕第一個(gè)完全顯示item的position)等方法,可以利用這些方法,把當(dāng)前的item找到,再利用instanceof關(guān)鍵字比較當(dāng)前item是不是我的廣告item,如果是再想辦法讓廣告圖片動(dòng)起來(lái)。
步驟:
1.自定義一個(gè)廣告imageView,把他變成窗戶:
繼承imageView,只需要重寫(xiě)他的2個(gè)方法,onSizeChanged和onDraw。
onSizeChanged用來(lái)得到控件高度
onDraw移動(dòng)廣告圖片
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);//設(shè)置圖片顯示的絕對(duì)范圍 int maxDy = h - itemHeight; //圖片可以移動(dòng)的最大距離為(圖片有效移動(dòng)距離): (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; //有效滑動(dòng)高度(廣告有效移動(dòng)距離) rate = itemDy * 1f / allHeight; if (rate <= 0) { rate = 0; } if (rate >= 1) { rate = 1; } invalidate(); }
setDy方法可以先不管。
onDraw中說(shuō)幾個(gè)點(diǎn):
super.onDraw(canvas)代碼中的位置
super.onDraw(canvas)是實(shí)現(xiàn)原本imageView邏輯的地方,涉及自定義view繪制先后問(wèn)題;假如我用canvas畫(huà)了一個(gè)圓,畫(huà)圓代碼寫(xiě)在super之前: 這個(gè)圓會(huì)先繪制出來(lái),再走super,就會(huì)出現(xiàn)imageView把圓擋住的情況,畫(huà)圓代碼寫(xiě)在super之后:
先走super再畫(huà)圓,圓就在imageView的上面。參考上面代碼中的super位置,先把圖片的位置通過(guò) canvas.translate方法移動(dòng)之后,再利用super原本邏輯繪制出圖片,就實(shí)現(xiàn)圖片在窗口中移動(dòng)的效果了。 (此番解釋只針對(duì)繼承已有的imageview,textview等,如果是繼承View,super位置就很隨意了,因?yàn)閟uper是個(gè)空實(shí)現(xiàn))
drawable.setBounds(l,t,r,b)方法
這個(gè)方法給圖片設(shè)定一個(gè)絕對(duì)位置范圍~(或者說(shuō)相對(duì)屏幕的顯示范圍)~,上面代碼中的范圍計(jì)算~(參數(shù)r,b)~其實(shí)就是 整個(gè)屏幕除開(kāi)狀態(tài)欄導(dǎo)航欄以外的范圍~(recycleView的范圍)~。 int w = getWidth()算出圖片可以顯示的最大寬度,再通過(guò)最大寬度 / 圖片原本寬度 = 最大高度 / 圖片原本高度 計(jì)算出最大高度 h。也就是int h = ....這一句。
通過(guò)onDraw方法,已經(jīng)可以實(shí)現(xiàn):一個(gè)imageView控件,動(dòng)態(tài)的去移動(dòng)它的內(nèi)部圖片。這個(gè)自定義的imageView就算是完成了。
2.獲取recycleView監(jiān)聽(tīng)以及位置計(jì)算
寫(xiě)監(jiān)聽(tīng)之前想想如何把recycleView的item與自定義imageView聯(lián)系起來(lái),通過(guò) canvas.translate(dx,dy)
讓圖片動(dòng)起來(lái),必須要求出dy:
可以看看效果,只要廣告的item有一點(diǎn)不在屏幕內(nèi),那么其中的圖片是不會(huì)移動(dòng)的,那么我們廣告item有效移動(dòng)距離就是整個(gè)recycleView的高度減去廣告item的高度,如圖綠色線:
而我們自定義imageView中圖片有效移動(dòng)距離是整個(gè)圖片的高度減去窗口的高度,如圖綠色線:(紅色框就相當(dāng)于自定義imageView窗口,整張圖就是窗后可以translate的圖片)
關(guān)系就出來(lái)了: 廣告item位置 / 廣告有效移動(dòng)距離 = dy / 圖片有效移動(dòng)距離
重寫(xiě)RecyclerView.OnScrollListener
中的onScrolled
方法,我們要得到:廣告item位置 和 廣告有效移動(dòng)距離
@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int first = layoutManager.findFirstCompletelyVisibleItemPosition(); //第一個(gè)完全顯示的item int last = layoutManager.findLastCompletelyVisibleItemPosition(); //最后一個(gè)完全顯示的item int firstPosition = layoutManager.findFirstVisibleItemPosition(); //第一個(gè)顯示的item int lastPosition = layoutManager.findLastVisibleItemPosition(); //最后一個(gè)顯示的item //循環(huán)遍歷當(dāng)前屏幕中顯示的所有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(); //調(diào)用自定義imageView中的方法,實(shí)現(xiàn)圖片的移動(dòng) zhiHuHolder.adImageView.setDy(top, height); } } }
int top = itemView.getTop(); top = 廣告item位置;
廣告有效移動(dòng)距離 = recycleView的高度 - 廣告item的高度,這一點(diǎn)的實(shí)現(xiàn)放在了自定義imageView的setDy方法中。
注意方法中的for循環(huán)
for (int i = firstPosition; i <= lastPosition; i++) {}
rate等于1圖片剛好顯示在 頂部
rate等于0圖片剛好顯示在 底部
rate從0~1:
滑動(dòng)慢 rate可能是這么變化的:0.05, 0.10,0.15,0.20 .....,0.80,0.85,0.90,0.95,1.0。
滑動(dòng)快 rate可能是這么變化的:0.3,0.6,0.9。
壓根就不會(huì)等于1或者等于0,那圖片的translate位置肯定就不對(duì)了。
出現(xiàn)這個(gè)問(wèn)題我試過(guò)很多方法,比如速度跟蹤類(VelocityTracker)計(jì)算速度,當(dāng)速度大了再根據(jù)滑動(dòng)方向直接置頂或者置底,獲取廣告item可見(jiàn)性置頂或者置底.....等等。有些方法可能有點(diǎn)用,但是太麻煩了,最后直接在for循環(huán)中用firstPosition和lastPosition,這樣,雖然會(huì)出現(xiàn)rate = - 0.2 這樣的負(fù)值,但是你只要給個(gè)判斷就可以了:
if (rate <= 0){ rate = 0; } if (rate >= 1) { rate = 1; }
剛已經(jīng)通過(guò)recycleView的監(jiān)聽(tīng)得到了廣告item位置 與 廣告有效移動(dòng)距離,而 圖片有效移動(dòng)距離呢,它在自定義imageView中的onDraw方法得到:
int maxDy = h - itemHeight;
//圖片可以移動(dòng)的最大距離為(圖片有效移動(dòng)距離): (0 ~ -maxDy)
最后,調(diào)用canvas.translate(0, -rate * maxDy);方法就可以實(shí)現(xiàn)整個(gè)效果了。
- Android使用ViewPager實(shí)現(xiàn)滾動(dòng)廣告
- Android 實(shí)現(xiàn)廣告歡迎界面(倒計(jì)時(shí))
- Android開(kāi)發(fā)實(shí)現(xiàn)廣告無(wú)限循環(huán)功能示例
- Android滾動(dòng)條廣告實(shí)現(xiàn)代碼示例
- Android ViewPager實(shí)現(xiàn)無(wú)限循環(huán)輪播廣告位Banner效果
- Android 應(yīng)用啟動(dòng)歡迎界面廣告的實(shí)現(xiàn)實(shí)例
- Android仿淘寶頭條向上滾動(dòng)廣告條ViewFlipper
- Kotlin FrameLayout與ViewPager2控件實(shí)現(xiàn)滾動(dòng)廣告欄方法
相關(guān)文章
Android設(shè)備上非root的抓包實(shí)現(xiàn)方法(Tcpdump方法)
通常我們?cè)贏ndroid應(yīng)用中執(zhí)行某個(gè)命令時(shí)會(huì)使用“Runtime.getRuntime().exec("命令路徑")”這種方式,但是當(dāng)我們執(zhí)行抓包操作時(shí),使用這條命令無(wú)論如何都不行,通過(guò)下面代碼打印結(jié)果發(fā)現(xiàn),該命令一定要在root權(quán)限下才能執(zhí)行,具體實(shí)現(xiàn)思路,請(qǐng)參考本教程2016-11-11Android使用socket創(chuàng)建簡(jiǎn)單TCP連接的方法
這篇文章主要介紹了Android使用socket創(chuàng)建簡(jiǎn)單TCP連接的方法,結(jié)合實(shí)例形式詳細(xì)分析了Android使用socket創(chuàng)建TCP連接的具體步驟與實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-04-04Android開(kāi)發(fā)中匿名設(shè)備標(biāo)識(shí)符OAID使用及初始化
這篇文章主要為大家介紹了Android開(kāi)發(fā)中匿名設(shè)備標(biāo)識(shí)符OAID使用及初始化,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04Kotlin協(xié)程開(kāi)發(fā)之Flow的融合與Channel容量及溢出策略介紹
這篇文章主要介紹了Kotlin協(xié)程:Flow的融合、Channel容量、溢出策略,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09Android獲取短信驗(yàn)證碼的實(shí)現(xiàn)方法
為了保護(hù)用戶信息的安全性,開(kāi)始使用通過(guò)服務(wù)器向用戶發(fā)送驗(yàn)證碼的方式,接下來(lái)通過(guò)本文給大家介紹android獲取短信驗(yàn)證碼的實(shí)現(xiàn)方法,非常不錯(cuò),感興趣的朋友一起看看吧2016-09-09關(guān)于android連續(xù)點(diǎn)擊出現(xiàn)多個(gè)Activity界面的解決方法
這篇文章主要介紹了關(guān)于android連續(xù)點(diǎn)擊出現(xiàn)多個(gè)Activity界面的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03Android數(shù)據(jù)持久化之Preferences機(jī)制詳解
這篇文章主要介紹了Android數(shù)據(jù)持久化之Preferences機(jī)制,較為詳細(xì)的分析了Android數(shù)據(jù)持久化的概念、Preferences機(jī)制的原理與相關(guān)實(shí)現(xiàn)、使用技巧,需要的朋友可以參考下2017-05-05Android內(nèi)存泄漏檢測(cè)工具LeakCanary
在Android的性能優(yōu)化中,內(nèi)存優(yōu)化是必不可少的點(diǎn),而內(nèi)存優(yōu)化最重要的一點(diǎn)就是解決內(nèi)存泄漏的問(wèn)題,在Android的內(nèi)存泄漏分析工具也不少,比如PC端的有:AndroidStudio自帶的Android?Profiler、MAT等工具;手機(jī)端也有,就是我們今天要介紹的LeakCanary2023-04-04