Bitmap如何处理大图,如何预防OOM

图片加载有一种情况就是,单个图片巨大,不允许压缩。比如显示世界地图、清明上河图等。那么对于这种需求,该如何做呢?
首先不压缩,由于图片尺寸大,屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性将整张图加载到内存中,所以肯定是局部加载,那么就需要用到一个类:BitmapRegionDecoder,既然屏幕显示不完,那么就需要添加一个上下左右拖动的手势,让用户可以拖动查看
(1)初始化变量

        mOptions = BitmapFactory.Options()
        //滑动器
        mScroller = Scroller(context)
        //缩放器
        mMatrix = Matrix()
        //手势识别
        mGestureDetector = GestureDetector(context, this)
        mScaleGestureDetector = ScaleGestureDetector(context, this)

(2)设置需要加载的图片

    fun setBigImg(stream: InputStream) {
    
    
        //设为true,在decode时会返回null,通过此设置可以去查询一个bitmap的属性,比如Bitmap的长宽而不占用内存
        mOptions!!.inJustDecodeBounds = true
        BitmapFactory.decodeStream(stream, null, mOptions)
        mWidth = mOptions!!.outWidth.toFloat()
        mHeight = mOptions!!.outHeight.toFloat()
        mOptions!!.inPreferredConfig = Bitmap.Config.RGB_565 //降低内存消耗
        mOptions!!.inJustDecodeBounds = false
        try {
    
    
            mRegionDecoder = BitmapRegionDecoder.newInstance(stream, false) //区域解码器
        } catch (e: IOException) {
    
    
            e.printStackTrace()
        }
        requestLayout()
    }

(3)重写onSizeChanged获取view的宽高,计算缩放值,当视图大小发生更改时会调用此方法,第一次调用是在onMeasure之后

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    
    
        super.onSizeChanged(w, h, oldw, oldh)
        mViewWidth = w.toFloat()
        mViewHeight = h.toFloat()
        mRect.top = 0
        mRect.left = 0
        mRect.right = mViewWidth.toInt()
        mRect.bottom = mViewHeight.toInt()
        mScale = (mViewWidth / mWidth)
        mCurrentScale = mScale
    }

(4)绘制
通过区域解码器解码一个矩形的区域,返回一个bitmap对象

    override fun onDraw(canvas: Canvas?) {
    
    
        super.onDraw(canvas)
        mOptions!!.inBitmap = mBitmap //复用内存,保证内存的使用一直是矩形的这块区域
        mBitmap = mRegionDecoder!!.decodeRegion(mRect, mOptions)
        mMatrix!!.setScale(mCurrentScale, mCurrentScale)
        canvas!!.drawBitmap(mBitmap!!, mMatrix!!, null)
    }

(5)事件分发,交给两个手势检测器处理

    override fun onTouchEvent(event: MotionEvent?): Boolean {
    
     
        mGestureDetector!!.onTouchEvent(event)
        mScaleGestureDetector!!.onTouchEvent(event)
        return true
    }

(6)当手指按下时,如果图片正在滑动,则停止

    override fun onDown(p0: MotionEvent?): Boolean {
    
    
        if (!mScroller!!.isFinished) {
    
     
            mScroller!!.forceFinished(true)
        }
        return true
    }

(7)根据手指的移动参数,来移动矩形绘制区域,注意处理各个边界点

    override fun onScroll(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
    
    
        mRect.offset(p2.toInt(), p3.toInt())
        if (mRect.left < 0) {
    
    
            mRect.left = 0
            mRect.right = (mViewWidth / mCurrentScale).toInt()
        }
        if (mRect.right > mWidth) {
    
    
            mRect.right = mWidth.toInt()
            mRect.left = (mWidth - mViewWidth / mCurrentScale).toInt()
        }
        if (mRect.top < 0) {
    
    
            mRect.top = 0
            mRect.bottom = (mViewHeight / mCurrentScale).toInt()
        }
        if (mRect.bottom > mHeight) {
    
    
            mRect.bottom = mHeight.toInt()
            mRect.top = (mHeight - mViewHeight / mCurrentScale).toInt()
        }
        invalidate()
        return false
    }

(8)调用滑动器Scroller处理手指离开之后的惯性滑动,惯性滑动的距离在View的computeScroll中计算

    override fun onFling(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
    
    
        mScroller!!.fling(
            mRect.left,
            mRect.top,
            (-p2).toInt(),
            (-p3).toInt(),
            0,
            mWidth.toInt(),
            0,
            mHeight.toInt()
        )
        return false
    }
    override fun computeScroll() {
    
    
        super.computeScroll()
        if (!mScroller!!.isFinished && mScroller!!.computeScrollOffset()) {
    
    
            if (mRect.top + mViewHeight / mCurrentScale < mHeight) {
    
    
                mRect.top = mScroller!!.currY
                mRect.bottom = (mRect.top + mViewHeight / mCurrentScale).toInt()
            }
            if (mRect.bottom > mHeight) {
    
    
                mRect.top = (mHeight - mViewHeight / mCurrentScale).toInt()
                mRect.bottom = mHeight.toInt()
            }
            invalidate()
        }
    }

(9)处理双击事件

    override fun onDoubleTap(p0: MotionEvent?): Boolean {
    
    
        mCurrentScale = if (mCurrentScale > mScale) mScale else mScale * mMultiple
        mRect.right = mRect.left + (mViewWidth / mCurrentScale).toInt()
        mRect.bottom = mRect.top + (mViewHeight / mCurrentScale).toInt()
        //处理边界
        if (mRect.left < 0) {
    
    
            mRect.left = 0
            mRect.right = (mViewWidth / mCurrentScale).toInt()
        }
        if (mRect.right > mWidth) {
    
    
            mRect.right = mWidth.toInt()
            mRect.left = (mWidth - mViewWidth / mCurrentScale).toInt()
        }
        if (mRect.top < 0) {
    
    
            mRect.top = 0
            mRect.bottom = (mViewHeight / mCurrentScale).toInt()
        }
        if (mRect.bottom > mHeight) {
    
    
            mRect.bottom = mHeight.toInt()
            mRect.top = (mHeight - mViewHeight / mCurrentScale).toInt()
        }
        invalidate()
        return true
    }

(10)处理缩放事件

    override fun onScale(p0: ScaleGestureDetector?): Boolean {
    
    
        val scaleFactor = p0!!.scaleFactor //获取比例因子
        mCurrentScale *= scaleFactor
        if (mCurrentScale > mScale * mMultiple) {
    
    
            mCurrentScale = mScale * mMultiple
        } else if (mCurrentScale <= mScale) {
    
    
            mCurrentScale = mScale
        }
        mRect.right = (mRect.left + mViewWidth / mCurrentScale).toInt()
        mRect.bottom = (mRect.top + mViewHeight / mCurrentScale).toInt()
        invalidate()
        return true
    }

最后在布局文件中引入并使用即可,大图放在了assets上

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".BigImgActivity">

    <com.mytest.newcode.BigImgView
        android:id="@+id/img"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
        val inputStream = assets.open("qmsht.png")
        img.setBigImg(inputStream)

整个View的代码如下:

class BigImgView : View, GestureDetector.OnGestureListener,
    ScaleGestureDetector.OnScaleGestureListener, GestureDetector.OnDoubleTapListener {
    
    
    private var mOptions: BitmapFactory.Options? = null
    private var mScroller: Scroller? = null
    private var mMatrix: Matrix? = null
    private var mGestureDetector: GestureDetector? = null
    private var mScaleGestureDetector: ScaleGestureDetector? = null
    private var mRegionDecoder: BitmapRegionDecoder? = null
    private var mBitmap: Bitmap? = null
    private var mViewWidth = 0f
    private var mViewHeight = 0f
    private var mWidth = 0f
    private var mHeight = 0f
    private var mScale = 1f
    private var mCurrentScale = 1f
    private var mMultiple = 3 //放大倍数
    private val mRect = Rect()

    constructor(context: Context?) : super(context) {
    
    
        init()
    }

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
    
    
        init()
    }

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
    
    
        init()
    }

    private fun init() {
    
    
        mOptions = BitmapFactory.Options()
        //滑动器
        mScroller = Scroller(context)
        //缩放器
        mMatrix = Matrix()
        //手势识别
        mGestureDetector = GestureDetector(context, this)
        mScaleGestureDetector = ScaleGestureDetector(context, this)
    }

    fun setBigImg(stream: InputStream) {
    
    
        //设为true,在decode时会返回null,通过此设置可以去查询一个bitmap的属性,比如Bitmap的长宽而不占用内存
        mOptions!!.inJustDecodeBounds = true
        BitmapFactory.decodeStream(stream, null, mOptions)
        mWidth = mOptions!!.outWidth.toFloat()
        mHeight = mOptions!!.outHeight.toFloat()
        mOptions!!.inPreferredConfig = Bitmap.Config.RGB_565 //降低内存消耗
        mOptions!!.inJustDecodeBounds = false
        try {
    
    
            mRegionDecoder = BitmapRegionDecoder.newInstance(stream, false)
        } catch (e: IOException) {
    
    
            e.printStackTrace()
        }
        requestLayout()
    }

    //当视图大小发生更改时会调用此方法,第一次调用是在onMeasure之后
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    
    
        super.onSizeChanged(w, h, oldw, oldh)
        mViewWidth = w.toFloat()
        mViewHeight = h.toFloat()
        mRect.top = 0
        mRect.left = 0
        mRect.right = mViewWidth.toInt()
        mRect.bottom = mViewHeight.toInt()
        mScale = (mViewWidth / mWidth)
        mCurrentScale = mScale
    }

    override fun onDraw(canvas: Canvas?) {
    
    
        super.onDraw(canvas)
        mOptions!!.inBitmap = mBitmap //复用内存,保证内存的使用一直是矩形的这块区域
        mBitmap = mRegionDecoder!!.decodeRegion(mRect, mOptions)
        mMatrix!!.setScale(mCurrentScale, mCurrentScale)
        canvas!!.drawBitmap(mBitmap!!, mMatrix!!, null)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
    
    
        mGestureDetector!!.onTouchEvent(event)
        mScaleGestureDetector!!.onTouchEvent(event)
        return true
    }

    override fun onDown(p0: MotionEvent?): Boolean {
    
    
        if (!mScroller!!.isFinished) {
    
     //如何正在滑动,先停止
            mScroller!!.forceFinished(true)
        }
        return true
    }

    override fun onShowPress(p0: MotionEvent?) {
    
    }

    override fun onSingleTapUp(p0: MotionEvent?): Boolean {
    
    
        return false
    }

    override fun onScroll(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
    
    
        mRect.offset(p2.toInt(), p3.toInt())
        if (mRect.left < 0) {
    
    
            mRect.left = 0
            mRect.right = (mViewWidth / mCurrentScale).toInt()
        }
        if (mRect.right > mWidth) {
    
    
            mRect.right = mWidth.toInt()
            mRect.left = (mWidth - mViewWidth / mCurrentScale).toInt()
        }
        if (mRect.top < 0) {
    
    
            mRect.top = 0
            mRect.bottom = (mViewHeight / mCurrentScale).toInt()
        }
        if (mRect.bottom > mHeight) {
    
    
            mRect.bottom = mHeight.toInt()
            mRect.top = (mHeight - mViewHeight / mCurrentScale).toInt()
        }
        invalidate()
        return false
    }

    override fun onLongPress(p0: MotionEvent?) {
    
    }

    override fun onFling(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
    
    
        mScroller!!.fling(
            mRect.left,
            mRect.top,
            (-p2).toInt(),
            (-p3).toInt(),
            0,
            mWidth.toInt(),
            0,
            mHeight.toInt()
        )
        return false
    }

    //计算惯性移动的距离
    override fun computeScroll() {
    
    
        super.computeScroll()
        if (!mScroller!!.isFinished && mScroller!!.computeScrollOffset()) {
    
    
            if (mRect.top + mViewHeight / mCurrentScale < mHeight) {
    
    
                mRect.top = mScroller!!.currY
                mRect.bottom = (mRect.top + mViewHeight / mCurrentScale).toInt()
            }
            if (mRect.bottom > mHeight) {
    
    
                mRect.top = (mHeight - mViewHeight / mCurrentScale).toInt()
                mRect.bottom = mHeight.toInt()
            }
            invalidate()
        }
    }

    //处理手指缩放事件
    override fun onScale(p0: ScaleGestureDetector?): Boolean {
    
    
        val scaleFactor = p0!!.scaleFactor //获取比例因子
        mCurrentScale *= scaleFactor
        if (mCurrentScale > mScale * mMultiple) {
    
    
            mCurrentScale = mScale * mMultiple
        } else if (mCurrentScale <= mScale) {
    
    
            mCurrentScale = mScale
        }
        mRect.right = (mRect.left + mViewWidth / mCurrentScale).toInt()
        mRect.bottom = (mRect.top + mViewHeight / mCurrentScale).toInt()
        invalidate()
        return true
    }

    override fun onScaleBegin(p0: ScaleGestureDetector?): Boolean {
    
    
        return true
    }

    override fun onScaleEnd(p0: ScaleGestureDetector?) {
    
    }

    override fun onSingleTapConfirmed(p0: MotionEvent?): Boolean {
    
    
        return false
    }

    //处理双击事件
    override fun onDoubleTap(p0: MotionEvent?): Boolean {
    
    
        mCurrentScale = if (mCurrentScale > mScale) mScale else mScale * mMultiple
        mRect.right = mRect.left + (mViewWidth / mCurrentScale).toInt()
        mRect.bottom = mRect.top + (mViewHeight / mCurrentScale).toInt()
        //处理边界
        if (mRect.left < 0) {
    
    
            mRect.left = 0
            mRect.right = (mViewWidth / mCurrentScale).toInt()
        }
        if (mRect.right > mWidth) {
    
    
            mRect.right = mWidth.toInt()
            mRect.left = (mWidth - mViewWidth / mCurrentScale).toInt()
        }
        if (mRect.top < 0) {
    
    
            mRect.top = 0
            mRect.bottom = (mViewHeight / mCurrentScale).toInt()
        }
        if (mRect.bottom > mHeight) {
    
    
            mRect.bottom = mHeight.toInt()
            mRect.top = (mHeight - mViewHeight / mCurrentScale).toInt()
        }
        invalidate()
        return true
    }

    override fun onDoubleTapEvent(p0: MotionEvent?): Boolean {
    
    
        return false
    }
    
}

猜你喜欢

转载自blog.csdn.net/qq_45485851/article/details/109055402