图片加载有一种情况就是,单个图片巨大,不允许压缩。比如显示世界地图、清明上河图等。那么对于这种需求,该如何做呢?
首先不压缩,由于图片尺寸大,屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性将整张图加载到内存中,所以肯定是局部加载,那么就需要用到一个类: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
}
}