android再加载大图,长图时,为了减少内存的占用,可以不必一次性把整张图加载到内存中,而是采用加载部分的办法,随着图片的滑动再加载展现的部分。
加载Bitmap时,通常我们使用BitmapFactory,可以从File文件、InPutStreaml流、Byte数组中加载一张图片。
而BitmapRegionDecoder,可以加载图片的一个Rect区域大小的图。得需配置BitmapFactory.Options()中的一些参数。
另外还得处理滑动事件,来保证图片随着屏幕的滑动,而不断的加载。
1.初始化
- private void init(Context context) {
- //图片decode的矩形大小
- mRect = new Rect();
- //canvas绘制时需要的参数
- matrix = new Matrix();
- //处理手势相关的
- mGestureDetector = new GestureDetector(context, this);
- //滑动
- mScroller = new Scroller(context);
- setOnTouchListener(this);
- }
2.设置一个流
1) options.inJustDecodeBounds 设置为true,可以获得图片的宽高,而不必把图片加载到内存。通过options.outWidth; options.outHeight;获得图片的宽高。
- If set to true, the decoder will return null (no bitmap),
- but the out... fields will still be set,
- allowing the caller to query the bitmap without having to
- allocate the memory for its pixels.
-
- public boolean inJustDecodeBounds;
options.inMutable = true,返回一个可变的Bitmap。
- If set, decode methods will always return a mutable Bitmap
- instead of an immutable one.
-
- public boolean inMutable;
创建一个区域解码器BitmapRegionDecoder.newInstance(is, false);
- public void setImage(InputStream is) {
- options = new BitmapFactory.Options();
- //为true则只加载图片的大小,返回一个null值,不会把图片加载到内存
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(is, null, options);
- //得到图片的宽高
- imgWidth = options.outWidth;
- imgHeight = options.outHeight;
- //默认是false,正常加载图片
- options.inJustDecodeBounds = false;
- //返回的一个可变的Bitmap
- options.inMutable = true;
- try {
- //区域解码器,可以从一个图片上解码一个Rect大小的区域
- decoder = BitmapRegionDecoder.newInstance(is, false);
- //重新测量、布局、绘制
- requestLayout();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
3. 在onMeasure中得到View的宽高。 设置Rect的大小,告知BitmapRegionDecoder要加载的图片区域。Rect的初始left和top都是0,right是图片的宽度。
通过View的宽度除以图片的宽度,得到一个缩放因子scale。再用View的高度除以这个scale,得到的就是初始的Rect的bottom值,之所以这计算,就是为了在onDraw时,对加载后的Bitmap按照scale进行缩放,以保证显示的屏幕中的图片大小是View的大小。
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- viewWidth = getMeasuredWidth();
- viewHeight = getMeasuredHeight();
- //设置要加载图片的矩形left、top、right、bottom值
- mRect.left = 0;
- mRect.top = 0;
- //图片加载的矩形右侧大小
- mRect.right = imgWidth;
- //用屏幕宽度除以图片宽度,得到缩放因子。
- // 这个是为了配合onDraw时对画布进行缩放,使画布宽度正好是View的宽度
- mScale = viewWidth / (float) imgWidth;
- //通过缩放因子,计算出矩形底部,保证图片缩放后的高度正好是View的高度
- mRect.bottom = (int) (viewHeight / mScale);
- }
4. onDraw
options.inBitmap = mBitmap; 这个是为了对mBitmap申请内存的复用。
如果设置了inBitmap,在加载内容时,会尝试复用这个bitmap,如果不能使用,则会抛出异常。
- If set, decode methods that take the Options object
- will attempt to reuse this bitmap when loading content.
- If the decode operation cannot use this bitmap,
- the decode method will throw an IllegalArgumentException.
-
- public Bitmap inBitmap;
options.inPreferredConfig 这个可以设置图片的加载格式。默认是ARGB_8888
decoder.decodeRegion(mRect, options) 加载一个mRect大小的bitmap
matrix.setScale(mScale, mScale); 加入缩放因子
canvas.drawBitmap(mBitmap, matrix, null); 绘制,这样就可以显示到屏幕上了。
- @Override
- protected void onDraw(Canvas canvas) {
- if (decoder == null) {
- return;
- }
- //内存复用,decode的时,会复用mBitmap所占用的内存
- options.inBitmap = mBitmap;
- //设置图片解码格式。默认是ARGB_8888
- options.inPreferredConfig = Bitmap.Config.RGB_565;
- //通过区域解码器,decode出一个mRect大小的Bitmap
- mBitmap = decoder.decodeRegion(mRect, options);
- matrix.setScale(mScale, mScale);
- //把bitmap绘制在canvas上。然后显示在屏幕上
- canvas.drawBitmap(mBitmap, matrix, null);
- }
5.滑动事件处理。
- //按下事件
- @Override
- public boolean onDown(MotionEvent e) {
- if (!mScroller.isFinished()) {
- mScroller.forceFinished(true);
- }
- return true;
- }
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- mRect.offset(0, (int) distanceY);
- if (mRect.bottom > imgHeight) {
- mRect.bottom = imgHeight;
- //Img的高度,减去加载一屏的高度,就是此时的top值。
- mRect.top = imgHeight - (int) (viewHeight / mScale);
- }
- if (mRect.top < 0) {
- mRect.top = 0;
- mRect.bottom = (int) (viewHeight / mScale);
- }
- invalidate();
- return false;
- }
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- //maxY: Y轴最大的滑动距离 imgHeight - (int) (viewHeight / mScale)
- //用Img的总高度,减去第一次加载的高度的高度,剩下的就是未加载的,也就是可以滑动的最大距离。
- mScroller.fling(0, mRect.top, 0, (int) -velocityY, 0, 0, 0,
- imgHeight - (int) (viewHeight / mScale));
- return false;
- }
-
- @Override
- public void computeScroll() {
- super.computeScroll();
- if (mScroller.isFinished()) {
- return;
- }
- if (mScroller.computeScrollOffset()) {
- mRect.top = mScroller.getCurrY();
- mRect.bottom = mRect.top + (int) (viewHeight / mScale);
- invalidate();
- }
- }
6. GestureDetector.OnGestureListener 手势监听源码分析。
- public class GestureDetector {
-
- public interface OnGestureListener {
- //按下事件
- boolean onDown(MotionEvent e);
-
- void onShowPress(MotionEvent e);
- //单指点击
-
- boolean onSingleTapUp(MotionEvent e);
- //滑动事件处理
-
- boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
- //长按事件
- void onLongPress(MotionEvent e);
- //手指抬起后,滑动事件处理
- boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
- }
GestureDetector.OnGestureListener 这个就是对事件类型做了一个简单封装,接口中定义的方法都是在onTouchEvent时处理的。
- case MotionEvent.ACTION_DOWN:
- ...
- handled |= mListener.onDown(ev);
- break;
- case MotionEvent.ACTION_MOVE:
- ...
- handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
- break;
- case MotionEvent.ACTION_UP:
- ...
- handled = mListener.onSingleTapUp(ev);
- ...
- handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
- break;