实现原理:使用RecyclerView.ItemDecoration 将图片绘制在RV的canvas中,即可。
1、应用
public static class MainActivity() {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
.....
recyclerView = findViewById(R.id.dy_rv_id);
....
bgDecoration = new ScrollBgRvItemDecoration(this, 0, 0, false, 1800);
recyclerView.addItemDecoration(bgDecoration, 0);
}
//使用网络图片设为RV背景图
private void updateBg() {
Glide.with(getApplicationContext()).load(url).into(new SimpleTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
bgDecoration.setBackground(drawableToBitmap(DynamicRVActivity.this.getApplicationContext(), resource), 0, false, 1500);
if(adapter != null) {
//ItemDecoration的颜色变了,需要通知adapter刷新
adapter.notifyDataSetChanged();
}
}
});
/**
* drawable转bitmap
* @param destWidth 指定destWidth的原因:若直接根据drawable.getIntrinsicWidth生成的bitmap比较模糊,自定义的destWidth按比例生成bitmap是高清的
*/
public static Bitmap drawableToBitmap(Drawable drawable, int destWidth) {
if(drawable == null) return null;
try {
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
destWidth = destWidth == 0 ? w : destWidth;
int destHeight = destWidth * h / w;
Bitmap.Config config =
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(destWidth, destHeight, config);
//注意,下面三行代码要用到,否在在View或者surfaceview里的canvas.drawBitmap会看不到图
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, destWidth, destHeight);
drawable.draw(canvas);
return bitmap;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
2、核心代码:ScrollBgRvItemDecoration
public class ScrollBgRvItemDecoration extends RecyclerView.ItemDecoration {
private static final String TAG = "ScrollBgRvItemDecoration";
private Bitmap mBmp;
private Paint mBmpPaint;
private Paint mColorPaint;
private Rect srcRect;
private Rect desRect;
private int bmpHeight;
private int bmWidth;
SparseArray<Integer> sparseArray;
private int mDefaultBmpRes; //默认背景图片的resId
private int mDefaultColorRes; //默认背景色的resId
private boolean mBmpRepeat; //是否重复画背景图
private int mShowBmpHeight; //图片渲染的高度, 当bmpRepeat=false,才生效
private boolean isDefaultBm = false;
protected Context mContext;
private Integer firstTop;
private int childCount;
private View firstView;
private View preView;
private View nowView;
private Integer lastScrollY;
private Integer nowScrollY;
/**
* RV的滚动背景
* 支持2种样式:
* 1、图片平铺(defaultBmpRes, bmpRepeat为true)
* 2、图片(defaultBmpRes)渲染一次,其他区域用颜色(defaultColorRes)渲染 ==> bmpRepeat为false, 若showBmpHeight < BmpHeight,则裁剪底部图片; 若showBmpHeight >= BmpHeight,则只展示BmpHeight高度
*
* @param defaultBmpRes 默认背景图片的resId
* @param defaultColorRes 默认背景色的resId
* @param bmpRepeat 是否重复画背景图
* @param showBmpHeight 图片渲染的高度, 当bmpRepeat=false,才生效
* */
public ScrollBgRvItemDecoration(Context context, int defaultBmpRes, int defaultColorRes, boolean bmpRepeat, int showBmpHeight) {
this.mContext = context;
this.mDefaultBmpRes = defaultBmpRes;
this.mDefaultColorRes = defaultColorRes == 0 ? R.color.white : defaultColorRes;
this.mBmpRepeat = bmpRepeat;
this.mShowBmpHeight = showBmpHeight;
restoreDefaultBm();
mBmpPaint = new Paint();
mBmpPaint.setAntiAlias(true);
srcRect = new Rect();
desRect = new Rect();
sparseArray = new SparseArray<>();
mColorPaint = new Paint();
mColorPaint.setColor(context.getResources().getColor(this.mDefaultColorRes));
mColorPaint.setAntiAlias(true);
mColorPaint.setStyle(Paint.Style.FILL);
}
private Bitmap getBitmap(int vectorDrawableId) {
Log.i("zql1" , "getBitmap");
try {
Bitmap bitmap;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
Drawable vectorDrawable = mContext.getDrawable(vectorDrawableId);
bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Log.i("zql1","bitmap-width:" + vectorDrawable.getIntrinsicWidth() +"; height:" + vectorDrawable.getIntrinsicHeight()+";size:" + bitmap.getByteCount());
Canvas canvas = new Canvas(bitmap);
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
Log.i("zql1","canvas-width:" + canvas.getWidth() +"; height:" + canvas.getHeight());
vectorDrawable.draw(canvas);
} else {
bitmap = BitmapFactory.decodeResource(mContext.getResources(), vectorDrawableId);
}
return bitmap;
} catch (Exception ex) {
return null;
}
}
public void setBackground(Bitmap bmp, int colorResId, boolean isBmpRepeat, int showBmpHeight) {
if (this.mBmp != null) {
this.mBmp.recycle();
this.mBmp = null;
}
this.mBmp = bmp;
this.mBmpRepeat = isBmpRepeat;
this.mShowBmpHeight = showBmpHeight;
if(mBmp != null) {
bmpHeight = bmp.getHeight();
bmWidth = bmp.getWidth();
}
if(colorResId != 0) {
mColorPaint.setColor(mContext.getResources().getColor(colorResId));
}
clearMap();
}
private synchronized void clearMap() {
sparseArray.clear();
}
private void restoreDefaultBm() {
if (this.mBmp != null) {
this.mBmp.recycle();
this.mBmp = null;
}
if(mDefaultBmpRes == 0) {
return;
}
this.mBmp = getBitmap(mDefaultBmpRes);
if(this.mBmp == null) return;
bmpHeight = mBmp.getHeight();
bmWidth = mBmp.getWidth();
}
@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
super.onDraw(canvas, parent, state);
// try {
if (this.mBmp == null || mShowBmpHeight == 0) {
drawOnlyColor(canvas, parent, state);
return;
}
childCount = parent.getChildCount();
Log.i("zql", "onDraw---childCount:" + childCount);
if (childCount == 0) {
firstTop = 0;
} else {
firstView = parent.getChildAt(0);
int position = parent.getChildAdapterPosition(firstView);
if (sparseArray.size() == 0) {
firstTop = 0;
sparseArray.put(position, 0);
} else {
firstTop = sparseArray.get(position);
}
if (firstTop != null) {
Integer preScrollY = firstTop;
for (int index = 1, nowPos = position + 1; index < childCount; index++, nowPos++) {
Integer nowScroll = sparseArray.get(nowPos);
if (nowScroll == null) {
preView = parent.getChildAt(index - 1);
if (preView == null) {
break;
}
nowScroll = preScrollY + preView.getHeight();
sparseArray.put(nowPos, nowScroll);
}
preScrollY = nowScroll;
}
} else {
int lastIndex = childCount - 1;
int lastPos = position + lastIndex;
lastScrollY = sparseArray.get(lastPos);
for (int index = lastIndex - 1, nowPos = lastPos - 1; index >= 0; index--, nowPos--) {
Integer nowScrollY = sparseArray.get(nowPos);
if (nowScrollY == null) {
if (lastScrollY != null) {
nowView = parent.getChildAt(index);
if (nowView == null) {
break;
}
nowScrollY = lastScrollY - nowView.getHeight();
sparseArray.put(nowPos, nowScrollY);
}
}
lastScrollY = nowScrollY;
}
firstTop = sparseArray.get(position);
}
if (firstTop == null) {
firstTop = 0;
} else {
firstTop -= firstView.getTop();
}
}
Log.i("zql", "firstTop:" + firstTop + "lastScrollY: " + lastScrollY + ";sparseArray:" + new Gson().toJson(sparseArray));
int totalHeight = parent.getHeight();
int totalWidth = parent.getWidth();
float screenRate = (float) totalHeight / totalWidth;
float widthRate = (float) totalWidth / bmWidth;
int bmShowHeightNoRepeat = Math.round(bmWidth * mShowBmpHeight / totalWidth);
int bmShowTotalHeight = Math.round(bmWidth * screenRate);
int bmStart = Math.round(firstTop / widthRate);
int bmTotalEnd = bmStart + bmShowTotalHeight;
int nowStart = bmStart;
int nowPage = floorDiv(nowStart, bmpHeight);
int lastPage = floorDiv(bmTotalEnd, bmpHeight);
int srcStart;
int srcEnd;
int desStart = 0;
int desEnd;
Log.d("zql", "totalHeight/totalWidth:" + totalHeight + "/" + totalWidth + ";screenRate/widthRate:" + screenRate + "/" + widthRate + ";bmWidth:" + bmWidth + "; bmHeight:" + bmpHeight);
Log.d("zql", "bmShowTotalHeight:" + bmShowTotalHeight + ";bmStart:" + bmStart + ";bmTotalEnd:" + bmTotalEnd + "; nowStart:" + nowStart + "; nowPage:" + nowPage + "; lastPage:" + lastPage);
Log.d("zql", "nowPage:" + nowPage + ";lastPage:" + lastPage);
while (nowPage <= lastPage) {
int pageEndHeight = (nowPage + 1) * bmpHeight;
Log.d("zql", "nowPage:" + nowPage + "; bmTotalEnd:" + bmTotalEnd + ";pageEndHeight:" + pageEndHeight);
if (bmTotalEnd < pageEndHeight) {//图片未超出屏幕
srcStart = floorMod(nowStart, bmpHeight);
srcEnd = floorMod(bmTotalEnd, bmpHeight);
desEnd = totalHeight;
nowStart = bmTotalEnd;
} else if (bmTotalEnd == pageEndHeight) {
srcStart = floorMod(nowStart, bmpHeight);
srcEnd = bmpHeight;
desEnd = totalHeight;
nowStart = bmTotalEnd;
} else {
srcStart = floorMod(nowStart, bmpHeight);
srcEnd = bmpHeight;
desEnd = desStart + (int) ((srcEnd - srcStart) * widthRate);
nowStart = pageEndHeight;
}
srcRect.left = 0;
srcRect.top = srcStart;
srcRect.right = bmWidth;
srcRect.bottom = srcEnd;
desRect.left = 0;
desRect.top = desStart;
desRect.right = totalWidth;
desRect.bottom = desEnd;
Log.d("zql", "nowPage:" + nowPage + ";srcRect:[0," + srcStart + "," + bmWidth + "," + srcEnd + "], desRect:[0," + desStart + "," + totalWidth + "," + desEnd + "]");
if (mBmpRepeat) {
//图片循环渲染
canvas.drawBitmap(mBmp, srcRect, desRect, mBmpPaint);
} else {
//图片只渲染一次
if (bmpHeight <= bmShowHeightNoRepeat) {
if (nowPage == 0) {
canvas.drawBitmap(mBmp, srcRect, desRect, mBmpPaint);
} else {
canvas.drawRect(0, desStart, totalWidth, totalHeight, mColorPaint);
}
} else {
if (nowPage == 0) {
if (srcStart > bmShowHeightNoRepeat) {
//纯画color
canvas.drawRect(0, desStart, totalWidth, totalHeight, mColorPaint);
} else if (srcStart < bmShowHeightNoRepeat && srcEnd > bmShowHeightNoRepeat) {
//一半画图,一半画color
srcRect.top = srcStart;
srcRect.bottom = bmShowHeightNoRepeat;
desRect.top = desStart;
desRect.bottom = mShowBmpHeight % totalHeight;
canvas.drawBitmap(mBmp, srcRect, desRect, mBmpPaint);
canvas.drawRect(0, mShowBmpHeight % totalHeight, totalWidth, totalHeight, mColorPaint);
} else {
//只画图
canvas.drawBitmap(mBmp, srcRect, desRect, mBmpPaint);
}
} else {
canvas.drawRect(0, desStart, totalWidth, totalHeight, mColorPaint);
}
}
}
desStart = desEnd;
nowPage++;
}
// } catch (Exception ex) {
// ex.printStackTrace();
// }
}
private void drawOnlyColor(Canvas canvas, RecyclerView parent, RecyclerView.State state){
canvas.drawRect(0, 0, parent.getWidth(), parent.getHeight(), mColorPaint);
}
private int floorDiv(int nowStart, int bmpHeight) {
return (int) Math.floor((double) nowStart / bmpHeight);
}
public int floorMod(int x, int y) {
int r = x - floorDiv(x, y) * y;
return r;
}
}