本文源码适用于原始图片高度大于宽度的场景,且图片宽度小于或略大于屏幕宽度的场景。界面水平方向不能滑动,垂直方向可以滑动查看完整大图。
原理:根据用户的滑动位移,裁剪大图的一部分,然后绘制出来;考虑到BitmapRegionDecoder.decodeRegion裁剪耗时,我们只decodeRegion出上次没有decode过的图片区域。SurfaceView控件可以在后台线程中执行图片裁剪绘制操作,可以避免主线程阻塞。
public class BigImageView1 extends SurfaceView implements SurfaceHolder.Callback{
private static final String IMAGE_NAME = "11.jpg";
private Rect rect; //当前要绘制的图片裁剪区域
private Rect lastRect; //上一次绘制的图片裁剪区域
private Rect diffRect; //绘制区域差
private Bitmap lastBmp; //缓存上次从图片中裁剪的bitmap
private BitmapRegionDecoder decoder;
private BitmapFactory.Options options;
private int imageWidth, imageHeight; //原始图片宽高
private GestureDetector detector; //手势检测
//图片宽度和View宽度的缩放比
private float scale = 1;
private SurfaceHolder holder;
private DecoderThread thread; //绘图线程
public BigImageView1(Context context) {
super(context);
}
public BigImageView1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public BigImageView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public BigImageView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init(){
holder = getHolder();
getHolder().addCallback(this);
lastRect = new Rect(0, 0, 0, 0);
diffRect = new Rect(0, 0, 0, 0);
rect = new Rect();
detector = new GestureDetector(getContext(), onGestureListener);
try {
InputStream inputStream = getContext().getAssets().open(IMAGE_NAME);
decoder = BitmapRegionDecoder.newInstance(inputStream, false);
options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, options);
imageWidth = options.outWidth;
imageHeight = options.outHeight;
//初始化缩放尺度
scale = imageWidth * 1.0f / Constants.SCREEN_WIDTH;
if(scale < 1){
scale = 1;
}
options.inJustDecodeBounds = false;
}catch (Exception e){
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
scale = imageWidth * 1.0f / getWidth() ;
if(scale < 1){
scale = 1;
}
rect.left = 0;
rect.top = 0;
rect.right = imageWidth;
rect.bottom = (int)(getHeight() * scale);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
GestureDetector.OnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// mTranslationY = distanceY + mTranslationY;
//判断如果是顶部的下拉动作,则直接忽略不处理
if(rect.top <= 0 && distanceY < 0){
return true;
}
//判断如果是底部的上拉动作,则直接忽略不处理
if(rect.bottom >= imageHeight && distanceY > 0){
return true;
}
//用户手指滑动时,重定位图片绘制区域rect坐标
synchronized (BigImageView1.this) {
rect.offset(0, (int) Math.floor(distanceY * scale));
//处理rect下边界大于图片尺寸的情况
if (rect.bottom > imageHeight) {
rect.top = imageHeight - (int) Math.floor(getHeight() * scale);
rect.bottom = imageHeight;
}
//处理rect上边界小于0的情况
if (rect.top < 0) {
rect.top = 0;
rect.bottom = (int) Math.floor(getHeight() * scale);
}
}
return true;
}
};
@Override
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event);
return true;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new DecoderThread();
thread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//结束线程
if(thread != null){
thread.isRun =false;
}
}
/**
* 后台线程循环绘图
*/
class DecoderThread extends Thread {
//线程运行标示变量
public boolean isRun = true;
@Override
public void run() {
super.run();
boolean flag = false;
float distanceY = 0;
while(isRun){
synchronized (BigImageView1.this) {
// 用户没有滑动界面,绘制的图片区域无变化,走if分支
if (lastRect.bottom == rect.bottom && lastRect.top == rect.top) {
flag = true;
}else{
distanceY = rect.top - lastRect.top;
//比较本次用户滑动后,比上次图片裁剪区域多出的部分,存放在diffrect中
if(lastRect.bottom < rect.bottom){
diffRect.top = lastRect.bottom;
diffRect.bottom = rect.bottom;
diffRect.left = rect.left;
diffRect.right = rect.right;
}else{
diffRect.top = rect.top;
diffRect.bottom = lastRect.top;
diffRect.left = rect.left;
diffRect.right = rect.right;
}
//缓存图片裁剪区域
lastRect.top = rect.top;
lastRect.bottom = rect.bottom;
lastRect.right = rect.right;
lastRect.left = rect.left;
}
}
if(flag){
flag = false;
try {
//sleep 1ms,减少cpu消耗
Thread.sleep(1);
} catch (Exception e) {
}
continue;
}
Canvas canvas = holder.lockCanvas();
Bitmap bm = decoder.decodeRegion(diffRect, options);
//本次绘制的Bitmap = 上次绘制的图片中仍在可视区域的一部分Bitmap + 本次滑动位移产生的图片裁剪Bitmap
if(lastBmp == null){
lastBmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.RGB_565);
Canvas _canvas = new Canvas(lastBmp);
_canvas.drawBitmap(bm, 0, 0, null);
bm.recycle();
bm = null;
}else{
//将需要显示的图片区域分为两部分,上次已经显示的bitmap
Bitmap temp = Bitmap.createBitmap(lastBmp.getWidth(), lastBmp.getHeight(), Bitmap.Config.RGB_565);
Canvas _canvas = new Canvas(temp);
if(distanceY <= 0) {
_canvas.drawBitmap(lastBmp, new Rect(0, 0, lastBmp.getWidth(), lastBmp.getHeight() - diffRect.height()),
new RectF(0, diffRect.height(), lastBmp.getWidth(), lastBmp.getHeight()), null);
_canvas.drawBitmap(bm, 0, 0, null);
}else{
_canvas.drawBitmap(lastBmp, new Rect(0, diffRect.height(), lastBmp.getWidth(), lastBmp.getHeight()),
new RectF(0, 0, lastBmp.getWidth(), lastBmp.getHeight() - diffRect.height()), null);
_canvas.drawBitmap(bm, 0, lastBmp.getHeight() - diffRect.height(), null);
}
lastBmp.recycle();
lastBmp = null;
lastBmp = temp;
bm.recycle();
bm = null;
}
//执行绘制操作
if(imageWidth > getWidth()) {
Bitmap bm1 = Bitmap.createScaledBitmap(lastBmp, getWidth(), getHeight(), false);
canvas.drawBitmap(bm1, 0, 0, null);
bm1.recycle();
bm1 = null;
}else{
//水平居中绘制图片
int diff = (int)((getWidth() - imageWidth) / 2.0);
canvas.drawBitmap(lastBmp, diff, 0, null);
}
holder.unlockCanvasAndPost(canvas);
}
}
}
}