自定义View显示超大图片

╰+攻爆jí腚メ 2021-09-22 04:58 518阅读 0赞

上一节( 自定义初学5——自定义View显示图片 ) 已经说了如何自定义View显示图片。做android时,加载图片是避免不了的,加载网络图片还需要异步加载,最烦人的就是经常出现OOM,为了避免这样的问题,我们一般这样解决:

  1. 根据图片控件的大小对图片进行压缩显示。
  2. 如果图片数量非常多,则会使用LruCache等缓存机制,将所有图片占据的内容维持在一个范围内。

有时加载图片还会遇到特殊情况——就是单个图片非常巨大,还不允许压缩。那么对于这种需求,该如何做呢?

首先不压缩,按照原图尺寸加载,那么屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性整图加载到内存中,所以肯定是局部加载,而有一个类很好的完成了这个功能,这就是BitmapRegionDecoder

先看下官方的API

Center

写个简单的例子:

public class MainActivity extends Activity {

  1. private ImageView mImageView;

@Override

  1. protected void onCreate(Bundle savedInstanceState) \{
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity\_main);

mImageView = (ImageView) findViewById(R.id.imageView1);

  1. try \{
  2. InputStream inputStream = getAssets().open("dog.jpg");
  3. // 获得图片的宽、高
  4. BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
  5. // tmpOptions.inJustDecodeBounds = true;// 如果设置为true,解码器将返回null
  6. // 设置显示图片的中心区域
  7. BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder
  8. .newInstance(inputStream, false);
  9. BitmapFactory.Options options = new BitmapFactory.Options();
  10. options.inPreferredConfig = Bitmap.Config.ARGB\_8888;

//用给定的Rect和options创建位图

  1. Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(0, 0,
  2. 600, 600), options);
  3. mImageView.setImageBitmap(bitmap);
  4. \} catch (IOException e) \{
  5. e.printStackTrace();
  6. \}
  7. \}

}

效果如下:
Center 1

很明显,BitmapRegionDecoder的功能就是加载图片的部分区域,现在大概原理就清楚了,我们需要定义一个View,然后利用去BitmapRegionDecoder显示一个图片的局部,然后利用手势检测,能够移动图片,好了,开始实现吧。梳理一下我们的View:

  • 提供一个设置图片的入口
  • 重写onTouchEvent,在里面根据用户移动的手势,去更新显示区域的参数
  • 每次更新区域参数后,调用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw

自定义LargeImageView

public class LargeImageView extends View {

  1. private BitmapRegionDecoder mDecoder;
  2. /\*\*
  3. \* 图片的宽度和高度
  4. \*/
  5. private int mImageWidth, mImageHeight;
  6. /\*\*
  7. \* 绘制的区域
  8. \*/
  9. private volatile Rect mRect = new Rect();
  10. private MoveGestureDetector mDetector;
  11. private static final BitmapFactory.Options options = new BitmapFactory.Options();
  12. static \{
  13. options.inPreferredConfig = Bitmap.Config.ARGB\_8888;
  14. \}
  15. public void setInputStream(InputStream is) \{
  16. try \{
  17. mDecoder = BitmapRegionDecoder.newInstance(is, false);
  18. BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
  19. // 获取图片的宽高
  20. tmpOptions.inJustDecodeBounds = true;
  21. BitmapFactory.decodeStream(is, null, tmpOptions);
  22. mImageWidth = tmpOptions.outWidth;
  23. mImageHeight = tmpOptions.outHeight;
  24. requestLayout();
  25. invalidate();
  26. \} catch (IOException e) \{
  27. e.printStackTrace();
  28. \} finally \{
  29. try \{
  30. if (is != null)
  31. is.close();
  32. \} catch (Exception e) \{
  33. \}
  34. \}
  35. \}
  36. public void init() \{
  37. mDetector = new MoveGestureDetector(getContext(),
  38. new MoveGestureDetector.SimpleMoveGestureDetector() \{
  39. @Override
  40. public boolean onMove(MoveGestureDetector detector) \{
  41. int moveX = (int) detector.getMoveX();
  42. int moveY = (int) detector.getMoveY();
  43. if (mImageWidth > getWidth()) \{
  44. mRect.offset(-moveX, 0);
  45. checkWidth();
  46. invalidate();
  47. \}
  48. if (mImageHeight > getHeight()) \{
  49. mRect.offset(0, -moveY);
  50. checkHeight();
  51. invalidate();
  52. \}
  53. return true;
  54. \}
  55. \});
  56. \}
  57. private void checkWidth() \{
  58. Rect rect = mRect;
  59. int imageWidth = mImageWidth;
  60. if (rect.right > imageWidth) \{
  61. rect.right = imageWidth;
  62. rect.left = imageWidth - getWidth();
  63. \}
  64. if (rect.left < 0) \{
  65. rect.left = 0;
  66. rect.right = getWidth();
  67. \}
  68. \}
  69. private void checkHeight() \{
  70. Rect rect = mRect;
  71. int imageHeight = mImageHeight;
  72. if (rect.bottom > imageHeight) \{
  73. rect.bottom = imageHeight;
  74. rect.top = imageHeight - getHeight();
  75. \}
  76. if (rect.top < 0) \{
  77. rect.top = 0;
  78. rect.bottom = getHeight();
  79. \}
  80. \}
  81. public LargeImageView(Context context, AttributeSet attrs) \{
  82. super(context, attrs);
  83. init();
  84. \}
  85. @Override
  86. public boolean onTouchEvent(MotionEvent event) \{
  87. mDetector.onToucEvent(event);
  88. return true;
  89. \}
  90. @Override
  91. protected void onDraw(Canvas canvas) \{
  92. Bitmap bm = mDecoder.decodeRegion(mRect, options);
  93. canvas.drawBitmap(bm, 0, 0, null);
  94. \}
  95. @Override
  96. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) \{
  97. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  98. int width = getMeasuredWidth();
  99. int height = getMeasuredHeight();
  100. int imageWidth = mImageWidth;
  101. int imageHeight = mImageHeight;
  102. // 默认直接显示图片的中心区域,可以自己去调节
  103. mRect.left = imageWidth / 2 - width / 2;
  104. mRect.top = imageHeight / 2 - height / 2;
  105. mRect.right = mRect.left + width;
  106. mRect.bottom = mRect.top + height;
  107. \}

}

手势检测抽象类BaseGestureDetector

public abstract class BaseGestureDetector {

  1. protected boolean mGestureInProgress;
  2. protected MotionEvent mPreMotionEvent;
  3. protected MotionEvent mCurrentMotionEvent;
  4. protected Context mContext;
  5. public BaseGestureDetector(Context context)
  6. \{
  7. mContext = context;
  8. \}
  9. public boolean onToucEvent(MotionEvent event)
  10. \{
  11. if (!mGestureInProgress)
  12. \{
  13. handleStartProgressEvent(event);
  14. \} else
  15. \{
  16. handleInProgressEvent(event);
  17. \}
  18. return true;
  19. \}
  20. protected abstract void handleInProgressEvent(MotionEvent event);
  21. protected abstract void handleStartProgressEvent(MotionEvent event);
  22. protected abstract void updateStateByEvent(MotionEvent event);
  23. protected void resetState()
  24. \{
  25. if (mPreMotionEvent != null)
  26. \{
  27. mPreMotionEvent.recycle();
  28. mPreMotionEvent = null;
  29. \}
  30. if (mCurrentMotionEvent != null)
  31. \{
  32. mCurrentMotionEvent.recycle();
  33. mCurrentMotionEvent = null;
  34. \}
  35. mGestureInProgress = false;
  36. \}

}

手势检测实现类

public class MoveGestureDetector extends BaseGestureDetector {

  1. private PointF mCurrentPointer;
  2. private PointF mPrePointer;
  3. // 用于记录最终结果,并返回
  4. private PointF mExtenalPointer = new PointF();
  5. private OnMoveGestureListener mListenter;
  6. public MoveGestureDetector(Context context, OnMoveGestureListener listener) \{
  7. super(context);
  8. mListenter = listener;
  9. \}
  10. @Override
  11. protected void handleInProgressEvent(MotionEvent event) \{
  12. int actionCode = event.getAction() & MotionEvent.ACTION\_MASK;
  13. switch (actionCode) \{
  14. case MotionEvent.ACTION\_CANCEL:
  15. case MotionEvent.ACTION\_UP:
  16. mListenter.onMoveEnd(this);
  17. resetState();
  18. break;
  19. case MotionEvent.ACTION\_MOVE:
  20. updateStateByEvent(event);
  21. boolean update = mListenter.onMove(this);
  22. if (update) \{
  23. mPreMotionEvent.recycle();
  24. mPreMotionEvent = MotionEvent.obtain(event);
  25. \}
  26. break;
  27. \}
  28. \}
  29. @Override
  30. protected void handleStartProgressEvent(MotionEvent event) \{
  31. int actionCode = event.getAction() & MotionEvent.ACTION\_MASK;
  32. switch (actionCode) \{
  33. case MotionEvent.ACTION\_DOWN:
  34. resetState();// 防止没有接收到CANCEL or UP ,保险起见
  35. mPreMotionEvent = MotionEvent.obtain(event);
  36. updateStateByEvent(event);
  37. break;
  38. case MotionEvent.ACTION\_MOVE:
  39. mGestureInProgress = mListenter.onMoveBegin(this);
  40. break;
  41. \}
  42. \}
  43. protected void updateStateByEvent(MotionEvent event) \{
  44. final MotionEvent prev = mPreMotionEvent;
  45. mPrePointer = caculateFocalPointer(prev);
  46. mCurrentPointer = caculateFocalPointer(event);
  47. boolean mSkipThisMoveEvent = prev.getPointerCount() != event
  48. .getPointerCount();
  49. mExtenalPointer.x = mSkipThisMoveEvent ? 0 : mCurrentPointer.x
  50. - mPrePointer.x;
  51. mExtenalPointer.y = mSkipThisMoveEvent ? 0 : mCurrentPointer.y
  52. - mPrePointer.y;
  53. \}
  54. /\*\*
  55. \* 根据event计算多指中心点
  56. \*
  57. \* @param event
  58. \* @return
  59. \*/
  60. private PointF caculateFocalPointer(MotionEvent event) \{
  61. final int count = event.getPointerCount();
  62. float x = 0, y = 0;
  63. for (int i = 0; i < count; i++) \{
  64. x += event.getX(i);
  65. y += event.getY(i);
  66. \}
  67. x /= count;
  68. y /= count;
  69. return new PointF(x, y);
  70. \}
  71. public float getMoveX() \{
  72. return mExtenalPointer.x;
  73. \}
  74. public float getMoveY() \{
  75. return mExtenalPointer.y;
  76. \}
  77. public interface OnMoveGestureListener \{
  78. public boolean onMoveBegin(MoveGestureDetector detector);
  79. public boolean onMove(MoveGestureDetector detector);
  80. public void onMoveEnd(MoveGestureDetector detector);
  81. \}
  82. public static class SimpleMoveGestureDetector implements
  83. OnMoveGestureListener \{
  84. @Override
  85. public boolean onMoveBegin(MoveGestureDetector detector) \{
  86. return true;
  87. \}
  88. @Override
  89. public boolean onMove(MoveGestureDetector detector) \{
  90. return false;
  91. \}
  92. @Override
  93. public void onMoveEnd(MoveGestureDetector detector) \{
  94. \}
  95. \}

}

用法不说了,看效果吧:

Center 2
这只是简单的实现,我们当然还可以实现更多的功能了,我就抛砖引玉吧

源代码
参考:

http://blog.csdn.net/lmj623565791/article/details/49300989

http://www.itstrike.cn/Question/904dfdaa-5cec-4d3c-bbc3-a7cc2e6172d8.html

http://blog.csdn.net/hahajluzxb/article/details/8158852

发表评论

表情:
评论列表 (有 0 条评论,518人围观)

还没有评论,来说两句吧...

相关阅读