最近公司项目要求实现一个效果,RecyclerView的背景是一张很长的图片,在RecyclerView元素滑动的同时,背景图也要跟随滑动,如果背景图滑出屏幕或者没有占满屏幕,则空出的部分显示界面的背景色
接到需求的第一反应:我屮艸芔茻 这个实现不了,老妹儿请换个方式,但产品妹子有一种力量不能抵挡,它永不言败生来倔强,在深思熟虑了一秒后严词拒绝了我的要求,啊!颜面扫地
当时流眼泪了,捂着眼说停停,我说小妹子你不讲武德。。。。但没关系啊,两分多钟以后,就屈服了,来!实现!
老铁们,还是那句话,只有你想不到的,没有我老八。。。呸,错了,我老张做不到的。。今天就给大家表演。。。呸,错了,是实现一个背景能滑动的RecyclerView,干就完了 奥利给
首先构思一下思路:
1.因为背景图要跟着内容一起滑动,所以肯定要监听RecyclerView的滑动
2.因为图片很长,所以需要截取,而Canvas的drawBitmap方法的第二个参数正好可以截取Bitmap的绘制区域
3.当RecyclerView滑动时,我们获取到RecyclerView的滑动区域,不断改变Bitmap的绘制区域绘制到RecyclerView的背景上就可以了
思路有了看下实现:
在网上找了几张老张的新晋女神龄龄子的高清无码大图使用windows画图工具拼成了一张大图准备开整,这里图片整的歪扭七八对黄龄女士有些不尊重,但是不管了,实现需求要紧
定义一个类BackgroundRecyclerView继承自RecyclerView,代码如下:
public class BackgroundRecyclerView extends RecyclerView {
private int bitmapWidth; // 图片的总宽度
private int bitmapHeight; // 图片的总高度
private Rect dstRect; // 图片位置Rect;
private Rect srcRect; // 图片展示Rect
private int scrollY; // RecyclerView的滚动距离
private float ratio; // 图片缩放比
private float drawHeight; // 需要绘制的高度
private Bitmap bitmap; // 图片Bitmap
private Paint paint; // 绘制图片的画笔
public BackgroundRecyclerView(@NonNull Context context) {
super(context);
init();
}
public BackgroundRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public BackgroundRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// 设置RecyclerView滚动监听
this.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
scrollY += dy;
}
});
try {
InputStream stream = getResources().getAssets().open("yello_zero_combine_1.jpg");
bitmap = BitmapFactory.decodeStream(stream);
bitmapWidth = bitmap.getWidth();
bitmapHeight = bitmap.getHeight();
} catch (IOException e) {
e.printStackTrace();
}
srcRect = new Rect();
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
int width = MeasureSpec.getSize(widthSpec);
int height = MeasureSpec.getSize(heightSpec);
dstRect = new Rect(0, 0, width, height);
// 因为背景图片要铺满背景,因此需要一个缩放比例
ratio = width / (float) bitmapWidth;
// 根据缩放比计算实际要draw的高度
drawHeight = bitmapHeight * ratio;
}
@Override
public void onDraw(Canvas canvas) {
if (bitmap != null && scrollY < drawHeight) {
Log.d("zyl", "computeVerticalScrollOffset = " + scrollY);
Log.d("zyl", "canvasWidth = " + canvas.getWidth() + " " + "canvasHeight = " + canvas.getHeight());
srcRect.left = 0;
srcRect.top = (int)(scrollY / ratio);
srcRect.right = bitmapWidth;
srcRect.bottom = (int)((canvas.getHeight() + scrollY) / ratio);
canvas.drawBitmap(bitmap, srcRect, dstRect, paint);
}
super.onDraw(canvas);
}
}
但是这种方法有个问题,就是背景图尺寸有可能很大,全部加载进内存有可能造成因为加载超大图造成的oom
怎么解决这个问题呢?回顾一下加载超大图的方案:
我们在解决加载超大图问题的时候,有一个方案是使用BitmapRegionDecoder分块加载,将图片截取出一定尺寸的区域加载到Bitmap中,在滚动的时候变换展示区域
这个方案用来解决这个问题刚好合适
废话不多说,看下实现:
package com.dafasoft.backgroundrecyclerview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import java.io.IOException;
import java.io.InputStream;
/**
* @author zhangyulong
* @date 2020/12/1.
* Description:
*/
public class BackgroundRecyclerView extends RecyclerView {
private InputStream inputStream; // 图片的inputStream
private BitmapRegionDecoder bitmapRegionDecoder; // 用来解析图片的部分区域
private int bitmapWidth; // 图片的总宽度
private int bitmapHeight; // 图片的总高度
private Rect regionRect; // 图片资源Rect, 配合BitmapRegionDecoder使用截取图片
private Rect dstRect; // 图片位置Rect;
private Rect srcRect; // 图片展示Rect
private int scrollY; // RecyclerView的滚动距离
private float ratio; // 图片缩放比
private float drawHeight; // 需要绘制的高度
private Bitmap bitmap; // 从inputstream中使用BitmapRegionDecoder截取的bitmap
BitmapFactory.Options options; // BitmapFactory参数
private Paint paint; // 绘制图片的画笔
public BackgroundRecyclerView(@NonNull Context context) {
super(context);
init();
}
public BackgroundRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public BackgroundRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// 设置RecyclerView滚动监听
this.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
scrollY += dy;
}
});
try {
// 从Assets中解析图片到inputStream
inputStream = getResources().getAssets().open("yello_zero_combine_1.jpg");
} catch (IOException e) {
e.printStackTrace();
}
if (inputStream != null) {
options = new BitmapFactory.Options();
// 只decode图片属性
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, options);
// 获取图片原始宽高
bitmapHeight = options.outHeight;
bitmapWidth = options.outWidth;
// 关闭只读取图片属性选项
options.inJustDecodeBounds = false;
// 开启复用内存,节省内存占用
options.inMutable = true;
// 指定复用内存的bitmap
options.inBitmap = bitmap;
try {
// 初始化BitmapRegionDecoder
bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
} catch (IOException e) {
e.printStackTrace();
}
// 初始化几个Rect
srcRect = new Rect();
dstRect = new Rect();
regionRect = new Rect();
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
int width = MeasureSpec.getSize(widthSpec);
int height = MeasureSpec.getSize(heightSpec);
dstRect = new Rect(0, 0, width, height);
// 因为背景图片要铺满背景,因此需要一个缩放比例
ratio = width / (float) bitmapWidth;
// 根据缩放比计算实际要draw的高度
drawHeight = bitmapHeight * ratio;
// 初始化bitmapRegionDecoder得出的bitmap所占Rect的大小
srcRect = new Rect(0, 0, bitmapWidth, (int) (height / ratio));
}
@Override
public void onDraw(Canvas canvas) {
if (bitmapRegionDecoder == null) {
super.onDraw(canvas);
return;
}
if (scrollY < drawHeight) {
Log.d("zyl", "computeVerticalScrollOffset = " + scrollY);
Log.d("zyl", "canvasWidth = " + canvas.getWidth() + " " + "canvasHeight = " + canvas.getHeight());
// 计算截取图片Rect的ltrb
regionRect.left = 0;
regionRect.top = (int)(scrollY / ratio);
regionRect.right = bitmapWidth;
regionRect.bottom = (int)((canvas.getHeight() + scrollY) / ratio);
// 从原始图片的InputStream中截取Bitmap
bitmap = bitmapRegionDecoder.decodeRegion(regionRect, options);
// 绘制该Bitmap 保证Bitmap正好铺满背景
canvas.drawBitmap(bitmap, srcRect, dstRect, paint);
}
super.onDraw(canvas);
}
}
因为这是一个Demo工程,我直接将长图放到了assets文件夹中,在实际项目中长图是从网络加载的,但思路都是一样的,这里不多赘述
用网上比较装13的话讲:不多说,懂的都懂
效果图:
![](https://img.haomeiwen.com/i3112838/9acb9d510f4ab4d3.gif)
网友评论