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


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



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

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


先看下官方的API

自定义View显示超大图片_自定义




写个简单的例子:

public class MainActivity extends Activity {
 
 
    private ImageView mImageView;
 
@Override
 
 
    protected void onCreate(Bundle savedInstanceState) {
 
 
        super.onCreate(savedInstanceState);
 
 
        setContentView(R.layout.activity_main);
 
mImageView = (ImageView) findViewById(R.id.imageView1);
 
 
        try {
 
 
            InputStream inputStream = getAssets().open("dog.jpg");
 
 
            // 获得图片的宽、高
 
 
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
 
 
            // tmpOptions.inJustDecodeBounds = true;// 如果设置为true,解码器将返回null
 
 
            // 设置显示图片的中心区域
 
 
            BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder
 
 
                    .newInstance(inputStream, false);
 
 
            BitmapFactory.Options options = new BitmapFactory.Options();
 
 
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
 
 
//用给定的Rect和options创建位图
 
 
            Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(0, 0,
 
 
                    600, 600), options);
 
 
            mImageView.setImageBitmap(bitmap);
 
 

 
 
        } catch (IOException e) {
 
 
            e.printStackTrace();
 
 
        }
 
 

 
 
    }
 
 
}

效果如下:

自定义View显示超大图片_自定义_02




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


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




自定义LargeImageView


public class LargeImageView extends View {
 
  

 
  
    private BitmapRegionDecoder mDecoder;
 
  
    /**
 
  
     * 图片的宽度和高度
 
  
     */
 
  
    private int mImageWidth, mImageHeight;
 
  
    /**
 
  
     * 绘制的区域
 
  
     */
 
  
    private volatile Rect mRect = new Rect();
 
  

 
  
    private MoveGestureDetector mDetector;
 
  

 
  
    private static final BitmapFactory.Options options = new BitmapFactory.Options();
 
  

 
  
    static {
 
  
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
 
  
    }
 
  

 
  
    public void setInputStream(InputStream is) {
 
  
        try {
 
  
            mDecoder = BitmapRegionDecoder.newInstance(is, false);
 
  
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
 
  
            // 获取图片的宽高
 
  
            tmpOptions.inJustDecodeBounds = true;
 
  
            BitmapFactory.decodeStream(is, null, tmpOptions);
 
  
            mImageWidth = tmpOptions.outWidth;
 
  
            mImageHeight = tmpOptions.outHeight;
 
  

 
  
            requestLayout();
 
  
            invalidate();
 
  
        } catch (IOException e) {
 
  
            e.printStackTrace();
 
  
        } finally {
 
  

 
  
            try {
 
  
                if (is != null)
 
  
                    is.close();
 
  
            } catch (Exception e) {
 
  
            }
 
  
        }
 
  
    }
 
  

 
  
    public void init() {
 
  
        mDetector = new MoveGestureDetector(getContext(),
 
  
                new MoveGestureDetector.SimpleMoveGestureDetector() {
 
  
                    @Override
 
  
                    public boolean onMove(MoveGestureDetector detector) {
 
  
                        int moveX = (int) detector.getMoveX();
 
  
                        int moveY = (int) detector.getMoveY();
 
  

 
  
                        if (mImageWidth > getWidth()) {
 
  
                            mRect.offset(-moveX, 0);
 
  
                            checkWidth();
 
  
                            invalidate();
 
  
                        }
 
  
                        if (mImageHeight > getHeight()) {
 
  
                            mRect.offset(0, -moveY);
 
  
                            checkHeight();
 
  
                            invalidate();
 
  
                        }
 
  

 
  
                        return true;
 
  
                    }
 
  
                });
 
  
    }
 
  

 
  
    private void checkWidth() {
 
  

 
  
        Rect rect = mRect;
 
  
        int imageWidth = mImageWidth;
 
  

 
  
        if (rect.right > imageWidth) {
 
  
            rect.right = imageWidth;
 
  
            rect.left = imageWidth - getWidth();
 
  
        }
 
  

 
  
        if (rect.left < 0) {
 
  
            rect.left = 0;
 
  
            rect.right = getWidth();
 
  
        }
 
  
    }
 
  

 
  
    private void checkHeight() {
 
  

 
  
        Rect rect = mRect;
 
  
        int imageHeight = mImageHeight;
 
  

 
  
        if (rect.bottom > imageHeight) {
 
  
            rect.bottom = imageHeight;
 
  
            rect.top = imageHeight - getHeight();
 
  
        }
 
  

 
  
        if (rect.top < 0) {
 
  
            rect.top = 0;
 
  
            rect.bottom = getHeight();
 
  
        }
 
  
    }
 
  

 
  
    public LargeImageView(Context context, AttributeSet attrs) {
 
  
        super(context, attrs);
 
  
        init();
 
  
    }
 
  

 
  
    @Override
 
  
    public boolean onTouchEvent(MotionEvent event) {
 
  
        mDetector.onToucEvent(event);
 
  
        return true;
 
  
    }
 
  

 
  
    @Override
 
  
    protected void onDraw(Canvas canvas) {
 
  
        Bitmap bm = mDecoder.decodeRegion(mRect, options);
 
  
        canvas.drawBitmap(bm, 0, 0, null);
 
  
    }
 
  

 
  
    @Override
 
  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 
  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
  

 
  
        int width = getMeasuredWidth();
 
  
        int height = getMeasuredHeight();
 
  

 
  
        int imageWidth = mImageWidth;
 
  
        int imageHeight = mImageHeight;
 
  

 
  
        // 默认直接显示图片的中心区域,可以自己去调节
 
  
        mRect.left = imageWidth / 2 - width / 2;
 
  
        mRect.top = imageHeight / 2 - height / 2;
 
  
        mRect.right = mRect.left + width;
 
  
        mRect.bottom = mRect.top + height;
 
  

 
  
    }
 
  
}

手势检测抽象类BaseGestureDetector

public abstract class BaseGestureDetector {
 
  

 
  
     protected boolean mGestureInProgress;
 
  

 
  
        protected MotionEvent mPreMotionEvent;
 
  
        protected MotionEvent mCurrentMotionEvent;
 
  

 
  
        protected Context mContext;
 
  

 
  
        public BaseGestureDetector(Context context)
 
  
        {
 
  
            mContext = context;
 
  
        }
 
  

 
  

 
  
        public boolean onToucEvent(MotionEvent event)
 
  
        {
 
  

 
  
            if (!mGestureInProgress)
 
  
            {
 
  
                handleStartProgressEvent(event);
 
  
            } else
 
  
            {
 
  
                handleInProgressEvent(event);
 
  
            }
 
  

 
  
            return true;
 
  

 
  
        }
 
  

 
  
        protected abstract void handleInProgressEvent(MotionEvent event);
 
  

 
  
        protected abstract void handleStartProgressEvent(MotionEvent event);
 
  

 
  
        protected abstract void updateStateByEvent(MotionEvent event);
 
  

 
  
        protected void resetState()
 
  
        {
 
  
            if (mPreMotionEvent != null)
 
  
            {
 
  
                mPreMotionEvent.recycle();
 
  
                mPreMotionEvent = null;
 
  
            }
 
  
            if (mCurrentMotionEvent != null)
 
  
            {
 
  
                mCurrentMotionEvent.recycle();
 
  
                mCurrentMotionEvent = null;
 
  
            }
 
  
            mGestureInProgress = false;
 
  
        }
 
  

 
  
}

手势检测实现类

public class MoveGestureDetector extends BaseGestureDetector {
 
  

 
  
    private PointF mCurrentPointer;
 
  
    private PointF mPrePointer;
 
  

 
  
    // 用于记录最终结果,并返回
 
  
    private PointF mExtenalPointer = new PointF();
 
  

 
  
    private OnMoveGestureListener mListenter;
 
  

 
  
    public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
 
  
        super(context);
 
  
        mListenter = listener;
 
  
    }
 
  

 
  
    @Override
 
  
    protected void handleInProgressEvent(MotionEvent event) {
 
  
        int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
 
  
        switch (actionCode) {
 
  
        case MotionEvent.ACTION_CANCEL:
 
  
        case MotionEvent.ACTION_UP:
 
  
            mListenter.onMoveEnd(this);
 
  
            resetState();
 
  
            break;
 
  
        case MotionEvent.ACTION_MOVE:
 
  
            updateStateByEvent(event);
 
  
            boolean update = mListenter.onMove(this);
 
  
            if (update) {
 
  
                mPreMotionEvent.recycle();
 
  
                mPreMotionEvent = MotionEvent.obtain(event);
 
  
            }
 
  
            break;
 
  

 
  
        }
 
  
    }
 
  

 
  
    @Override
 
  
    protected void handleStartProgressEvent(MotionEvent event) {
 
  
        int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
 
  
        switch (actionCode) {
 
  
        case MotionEvent.ACTION_DOWN:
 
  
            resetState();// 防止没有接收到CANCEL or UP ,保险起见
 
  
            mPreMotionEvent = MotionEvent.obtain(event);
 
  
            updateStateByEvent(event);
 
  
            break;
 
  
        case MotionEvent.ACTION_MOVE:
 
  
            mGestureInProgress = mListenter.onMoveBegin(this);
 
  
            break;
 
  
        }
 
  

 
  
    }
 
  

 
  
    protected void updateStateByEvent(MotionEvent event) {
 
  
        final MotionEvent prev = mPreMotionEvent;
 
  

 
  
        mPrePointer = caculateFocalPointer(prev);
 
  
        mCurrentPointer = caculateFocalPointer(event);
 
  

 
  
        boolean mSkipThisMoveEvent = prev.getPointerCount() != event
 
  
                .getPointerCount();
 
  

 
  
        mExtenalPointer.x = mSkipThisMoveEvent ? 0 : mCurrentPointer.x
 
  
                - mPrePointer.x;
 
  
        mExtenalPointer.y = mSkipThisMoveEvent ? 0 : mCurrentPointer.y
 
  
                - mPrePointer.y;
 
  

 
  
    }
 
  

 
  
    /**
 
  
     * 根据event计算多指中心点
 
  
     * 
 
  
     * @param event
 
  
     * @return
 
  
     */
 
  
    private PointF caculateFocalPointer(MotionEvent event) {
 
  
        final int count = event.getPointerCount();
 
  
        float x = 0, y = 0;
 
  
        for (int i = 0; i < count; i++) {
 
  
            x += event.getX(i);
 
  
            y += event.getY(i);
 
  
        }
 
  

 
  
        x /= count;
 
  
        y /= count;
 
  

 
  
        return new PointF(x, y);
 
  
    }
 
  

 
  
    public float getMoveX() {
 
  
        return mExtenalPointer.x;
 
  

 
  
    }
 
  

 
  
    public float getMoveY() {
 
  
        return mExtenalPointer.y;
 
  
    }
 
  

 
  
    public interface OnMoveGestureListener {
 
  
        public boolean onMoveBegin(MoveGestureDetector detector);
 
  

 
  
        public boolean onMove(MoveGestureDetector detector);
 
  

 
  
        public void onMoveEnd(MoveGestureDetector detector);
 
  
    }
 
  

 
  
    public static class SimpleMoveGestureDetector implements
 
  
            OnMoveGestureListener {
 
  

 
  
        @Override
 
  
        public boolean onMoveBegin(MoveGestureDetector detector) {
 
  
            return true;
 
  
        }
 
  

 
  
        @Override
 
  
        public boolean onMove(MoveGestureDetector detector) {
 
  
            return false;
 
  
        }
 
  

 
  
        @Override
 
  
        public void onMoveEnd(MoveGestureDetector detector) {
 
  
        }
 
  
    }
 
  
}


用法不说了,看效果吧:



自定义View显示超大图片_android开发_03


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


源代码 参考:

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