前言: 公司產品需要新增懸浮廣告條的功能,要求是可以迴圈滾動,並且點選相應的浮條會跳轉到相應的介面,在實現這個功能的時候遇到一些坑,幸運的是最後從這些坑中爬了出來。這篇文章的主要內容就是介紹功能的實現以及爬坑的經驗。
效果展示
在文章開始前,先看下最後實現的效果,最終的效果如下圖
需求分析
我們已經知道了產品的需求,下面要做的就是分析這個需求應該怎樣實現,首先我們要實現的功能就是讓廣告條迴圈滾動,看最終的效果圖可以發現,滾動的方向是由下往上滾動,平時我們見的banner圖都是左右滾動的,如果是左右滾動的就好辦了,可以通過ViewPager
來實現。但是這個上下滾動的應該怎麼實現呢?首先,想到的是利用ViewFlipper
這個系統控制元件,但是這個控制元件只能滿足迴圈滾動這個功能,我們還有一個需求是點選不同的浮條跳轉不同的內容呢!這個功能ViewFlipper
就無法滿足了。既要迴圈滾動又要每個浮條有相應的點選事件,自然的就想到了RecyclerView
,下面就利用RecyclerView來實現這些需求。
功能實現
RecyclerView的使用相信大家都會的,但是這裡有個問題是怎樣讓RecyclerView迴圈滾動?再把問題細分一下,首先就是怎樣讓RecyclerView自己滾動,然後是怎樣實現裡面的內容迴圈。
動起來吧!RecyclerView
怎樣讓RecyclerView自己滾動呢?通過查官方的Api,發現RecyclerView的LayoutManager中有這樣一個方法
這個方法的說明是
使用提供的SmoothScroller開始平滑滾動。
好了,現在我們知道了這個方法的作用是讓RecyclerView平滑滾動的,既然是讓RecyclerView平滑滾動,那麼我們肯定要告訴startSmoothScroll
方法,RecyclerView怎樣滾動,如,滾動的方向、距離、速度等。上面的方法說明也說了根據提供的SmoothScroller
滾動,因此這裡我們要實現SmoothScroller
類來制定一些滾動的規則,檢視原始碼可以發現SmoothScroller
是抽象類,而官方文件中說它的已知的直接實現類是LinearSmoothScroller,所以這裡直接例項化LinearSmoothScroller
,重寫相應的方法即可。具體程式碼如下
mSmoothScroller = new LinearSmoothScroller(this) {
@Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 3f / (displayMetrics.density);
}
};
複製程式碼
可以看到這裡重寫了兩個方法。getVerticalSnapPreference
這個方法是制定對齊的規則,就是RecyclerView裡面的item頂部或底部與RecyclerView的對齊方式,這裡有三種對齊方式,以下是官方文件中對這三種對齊方式的具體說明
再看下calculateSpeedPerPixel
這個方法,這個方法是用來計算滾動的速度的,返回值是滾動一個畫素花費的毫秒數。displayMetrics.density
這個是1dp對應的畫素密度,就是1dp等於多少畫素。
注:SmoothScroller
是將目標item滾動到RecyclerView中,即讓目標item在RecyclerView中可見。
已經設定好滾動的規則了,下面要做的就是讓RecyclerView中的item滾動,並且迴圈滾動。
實現RecyclerView的迴圈滾動
在實現迴圈滾動之前,看下實現滾動的程式碼,如下
private void startAuto() {
if (mAutoTask != null && !mAutoTask.isDisposed()) {
mAutoTask.dispose();
}
mAutoTask = Observable.interval(1, 2, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) {
mSmoothScroller.setTargetPosition(aLong.intValue());
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager!=null)
layoutManager.startSmoothScroll(mSmoothScroller);
}
});
}
複製程式碼
從這段程式碼中可以看到,用RxJava中的interval
方法實現了一個迴圈數字遞增定時器,時間間隔是2s。mSmoothScroller.setTargetPosition(aLong.intValue());
這句程式碼就是設定哪個item出現在RecyclerView中。layoutManager.startSmoothScroll(mSmoothScroller);
這句程式碼實際上呼叫的就是LinearSmoothScroller
類中的start
方法,這段程式碼實現的功能就是每隔兩秒,就讓設定的目標item平滑滾動到RecyclerView中。
現在已經開始滾動了,那麼怎麼讓目標item重複出現呢?其實這很簡單,就是將itemCount設定成無限大,具體程式碼如下
@Override
public int getItemCount() {
return Integer.MAX_VALUE;
}
@Override
public void onBindViewHolder(@NonNull final AdViewHolder holder, int position) {
if (mDynamicAdsDetails.size() != 0) {
String media1 = mDynamicAdsDetails.get(position % mDynamicAdsDetails.size());
Picasso.get()
.load(media1)
.error(R.mipmap.ic_launcher)
.placeholder(R.mipmap.ic_launcher)
.into( holder.ivFlipperItem);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//控制點選頻率
if ((System.currentTimeMillis() - between) / 1000 < 1) {
return;
}
between = System.currentTimeMillis();
Toast.makeText(mContext,"點選了第"+holder.getAdapterPosition() % mDynamicAdsDetails.size()+"個",Toast.LENGTH_SHORT).show();
}
});
}
複製程式碼
注:將itemCount設定成無限大後,取列表中的值時,不能直接根據相應的position
來取值了,應該這樣取mDynamicAdsDetails.get(position % mDynamicAdsDetails.size())
。
這樣就能實現迴圈滾動了。
開始爬坑
圖片錯亂問題
這樣實現看起來顯然沒問題,但是專案跑起來後,卻發現圖片竟然不是迴圈顯示的,而是偶爾會一張圖片出現多次,然後才是下一張圖片。分析了一下原因,認為是RecyclerView的複用問題,圖片非同步請求的結果還沒有返回回來,複用了上次的控制元件,所以就出現一個圖片顯示多次的問題了。解決方法就是給ImageView設定Tag,具體程式碼如下
if (mDynamicAdsDetails.size() != 0) {
String media1 = mDynamicAdsDetails.get(position % mDynamicAdsDetails.size());
holder.ivFlipperItem.setTag(media1);
Picasso.get()
.load(media1)
.error(R.mipmap.ic_launcher)
.placeholder(R.mipmap.ic_launcher)
.into(new com.squareup.picasso.Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
if (mDynamicAdsDetails.get(holder.getAdapterPosition() % mDynamicAdsDetails.size()).equals(holder.ivFlipperItem.getTag())) {
holder.ivFlipperItem.setScaleType(ImageView.ScaleType.FIT_XY);
holder.ivFlipperItem.setImageBitmap(bitmap);
}
}
@Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
}
複製程式碼
滾動問題
先看下滑動RecyclerView時出現的問題,如圖
可以發現,雖然可以滑動RecyclerView,但是滑動過後,item會回退回來,然後繼續上次的位置開始滾動。這個問題解決方法有兩種
- 記住手動滑動到的item的未知,然後在
interval
方法中把滑動的位置設定為目標位置。 - 禁止RecyclerView的滑動。
因為需求沒有可以滑動的這個功能,所以這裡採用方法2,禁止RecyclerView的滑動,詳細程式碼如下
public class AutoScrollRecyclerView extends RecyclerView {
private int mState;
private OnScrollListener mScrollListener;
public AutoScrollRecyclerView(Context context) {
this(context,null);
}
public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
return true;
case MotionEvent.ACTION_MOVE:
return false;
case MotionEvent.ACTION_POINTER_UP:
return false;
}
return true;
}
}
複製程式碼
這裡重寫了RecyclerVieiew的onTouchEvent
方法,當滑動式返回false
,不消費滑動的動作。
但是,這麼做之後會有新的問題,就是當圖片在滾動時,我們點選圖片,圖片會暫停住,這裡採用的解決方法是監聽RecyclerView 的滾動狀態,只有當RecyclerView滑動停止時,才不攔截事件,否則就攔截事件。具體程式碼如下
public class AutoScrollRecyclerView extends RecyclerView {
private int mState;
private OnScrollListener mScrollListener;
public AutoScrollRecyclerView(Context context) {
this(context,null);
}
public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
mState = newState;
}
};
//新增RecyclerView的滑動監聽
addOnScrollListener(mScrollListener);
}
//判斷是否攔截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
return mState != 0;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
return mState == 0;
case MotionEvent.ACTION_MOVE:
return false;
case MotionEvent.ACTION_POINTER_UP:
return false;
}
return true;
}
}
複製程式碼
好了,這樣就解決了滑動RecyclerView出現的問題。
自定義廣告樣式
廣告的樣式是可以自己定義的,不僅僅是圖片,還可以實現圖文混排等,只需要修改layout檔案即可,這裡為了方便就直接在layout中放了一張圖片。
結束語
實現的功能挺簡單的,但是如果對RecyclerView滑動的方法不熟悉的話,實現起來還是有點難度的,還有就是我們在編寫程式碼的時候不僅要實現功能,還有注意對一些細節的處理,如果細節處理的不好,是很影響使用者體驗的。一些細節方面的問題是很考驗技能的,當然對技能提升的幫助也是很大的。
最後,當然是放出原始碼了,點選這裡獲取原始碼
轉載請註明出處:www.wizardev.cn