亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android性能優化大圖的方法

發布時間:2022-08-17 10:33:03 來源:億速云 閱讀:130 作者:iii 欄目:開發技術

本篇內容介紹了“Android性能優化大圖的方法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

引言

在實際的Android項目開發中,圖片是必不可少的元素,幾乎所有的界面都是由圖片構成的;像列表頁、查看大圖頁等,都是需要展示圖片,而且這兩者是有共同點的,列表展示的Item數量多,如果全部加載進來勢必會造成OOM,因此列表頁通常采用分頁加載,加上RecyclerView的復用機制,一般很少會發生OOM。

但是對于大圖查看,通常在外界展示的是一張縮略圖,點開之后放大就是原圖,如果圖片很大,OOM發生也是正常的,因此在加載大圖的時候,可以看下面這張圖

Android性能優化大圖的方法

一張圖片如果很大,在手機屏幕中并不能完全展示,那么其實就沒有必要講圖片完全加載進來,而是可以采用分塊加載的方式,只展示顯示的那一部分,當圖片向上滑動的時候,之前展示的區域內存能夠復用,不需要開辟新的內存空間來承接新的模塊,從而達到了大圖的治理的目的。

1 自定義大圖View

像在微信中點擊查看大圖,查看大圖的組件就是一個自定義View,能夠支持滑動、拖拽、放大等功能,因此我們也可以自定義一個類似于微信的大圖查看器,從中了解圖片加載優化的魅力

1.1 準備工作

class BigView : View{
    constructor(context: Context):super(context){
        initBigView(context)
    }
    constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet){
        initBigView(context)
    }
    private fun initBigView(context: Context) {
    }
}
class BigView : View, GestureDetector.OnGestureListener, View.OnTouchListener {
    //分塊加載
    private lateinit var mRect: Rect
    //內存復用
    private lateinit var mOptions: BitmapFactory.Options
    //手勢
    private lateinit var mGestureDetector: GestureDetector
    //滑動
    private lateinit var mScroller: Scroller
    constructor(context: Context) : super(context) {
        initBigView(context)
    }
    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
        initBigView(context)
    }
    private fun initBigView(context: Context) {
        mRect = Rect()
        mOptions = BitmapFactory.Options()
        mGestureDetector = GestureDetector(context, this)
        mScroller = Scroller(context)
        setOnTouchListener(this)
    }
    override fun onDown(e: MotionEvent?): Boolean {
        return false
    }
    override fun onShowPress(e: MotionEvent?) {
    }
    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        return false
    }
    override fun onScroll(
        e1: MotionEvent?,
        e2: MotionEvent?,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        return false
    }
    override fun onLongPress(e: MotionEvent?) {
    }
    override fun onFling(
        e1: MotionEvent?,
        e2: MotionEvent?,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        return false
    }
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        return false
    }
}

前面我們提到的分塊加載、內存復用、手勢等操作,直接在view初始化時完成,這樣我們前期的準備工作就完成了。

1.2 圖片寬高適配

當我們加載一張圖片的時候,要讓這張圖片完全展示在手機屏幕上不被裁剪,就需要做寬高的適配;如果這張圖片大小是80M,那么為了獲取寬高而將圖片加載到內存中肯定會OOM,那么在圖片加載到內存之前就像獲取圖片的寬高該怎么辦呢?BitmapFactory.Options就提供了這個手段

fun setImageUrl(inputStream: InputStream) {
    //獲取圖片寬高
    mOptions.inJustDecodeBounds = true
    BitmapFactory.decodeStream(inputStream,null,mOptions)
    imageWidth = mOptions.outWidth
    imageHeight = mOptions.outHeight
    mOptions.inJustDecodeBounds = false
    //開啟復用
    mOptions.inMutable = true
    mOptions.inPreferredConfig = Bitmap.Config.RGB_565
    //創建區域解碼器
    try {
        BitmapRegionDecoder.newInstance(inputStream,false)
    }catch (e:Exception){
    }
    requestLayout()
}

當設置inJustDecodeBounds為true(記住要成對出現,使用完成之后需要設置為false),意味著我調用decodeStream方法的時候,不會將圖片的內存加載而是僅僅為了獲取寬高。

然后拿到了圖片的寬高之后呢,調用requestLayout方法,會回調onMeasure方法,這個方法大家就非常熟悉了,能夠拿到view的寬高,從而完成圖片的適配

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    //適配
    viewWidth = measuredWidth
    viewHeight = measuredHeight
    originScale = viewWidth / imageWidth.toFloat()
    mScale = originScale
    //分塊加載首次進入展示的rect
    mRect.left = 0
    mRect.top = 0
    mRect.right = imageWidth
    mRect.bottom = (viewHeight / mScale).toInt()
}

這里設置Rect的right就是圖片的寬度,因為原始圖片的寬度可能比控件的寬度要寬,因此是將控件的寬度與圖片的寬度對比獲取了縮放比,那么Rect的bottom就需要等比縮放

這里的mRect可以看做是這張圖片上的一個滑動窗口,無論是放大還是縮小,只要在屏幕上看到的區域,都可以看做是mRect在這張圖片上來回移動截取的目標區域

1.3 BitmapRegionDecoder

在onMeasure中,我們定義了需要加載的圖片的Rect,這是一塊區域,那么我們通過什么樣的方式能夠將這塊區域的圖片加載出來,就是通過BitmapRegionDecoder區域解碼器。

區域解碼器,顧名思義,能夠在某個區域進行圖片解碼展示

//創建區域解碼器
try {
    BitmapRegionDecoder.newInstance(inputStream,false)
}catch (e:Exception){
}

在傳入圖片流的時候,我們就已經創建了BitmapRegionDecoder,同時將圖片流作為參數構建了解碼器,那么這個解碼器其實已經拿到了整張圖片的資源,因此任意一塊區域,通過BitmapRegionDecoder都能夠解碼展示出來

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    mRegionDecoder ?: return
    //復用bitmap
    mOptions.inBitmap = mutableBitmap
    mutableBitmap = mRegionDecoder?.decodeRegion(mRect, mOptions)
    //畫出bitmap
    val mMatrix = Matrix()
    mMatrix.setScale(mScale, mScale)
    mutableBitmap?.let {
        canvas?.drawBitmap(it, mMatrix, null)
    }
}

首先我們想要進行內存復用,需要調用BitmapFactory.Options的inBitmap,這個參數的含義就是,當我們在某塊區域加載圖片之后,如果圖片上滑那么就需要重新加載,那么這個時候就不會重新開辟一塊內存空間,而是復用之前的這塊區域,所以調用BitmapRegionDecoder的decodeRegion方法,傳入需要展示圖片的區域,就能夠給mutableBitmap賦值,這樣就達成了一塊內存空間,多次復用的效果。

這樣通過壓縮之后,在屏幕中展示了這個長圖的最上邊部分,那么剩下就需要做的是手勢事件的處理。

2 大圖View的手勢事件處理

通過前期的準備工作,我們已經實現了圖片的區域展示,那么接下來關鍵在于,我們通過手勢來查看完整的圖片,對于手勢事件的響應,在onTouch方法中處理。

override fun onTouch(v: View?, event: MotionEvent?): Boolean {
    return mGestureDetector.onTouchEvent(event)
}

2.1 GestureDetector

通常來說,手勢事件的處理都是通過GestureDetector來完成,因此當onTouch方法監聽到手勢事件之后,直接傳給GestureDetector,讓GestureDetector來處理這個事件。

override fun onDown(e: MotionEvent?): Boolean {
    return false
}
override fun onShowPress(e: MotionEvent?) {
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
    return false
}
override fun onScroll(
    e1: MotionEvent?,
    e2: MotionEvent?,
    distanceX: Float,
    distanceY: Float
): Boolean {
    return false
}
override fun onLongPress(e: MotionEvent?) {
}
override fun onFling(
    e1: MotionEvent?,
    e2: MotionEvent?,
    velocityX: Float,
    velocityY: Float
): Boolean {
    return false
}

首先,我們先看下之前注冊的GestureDetector.OnGestureListener監聽器中實現的方法:

(1)onDown

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

當手指按下時,因為滑動的慣性,所以down事件的處理就是如果圖片還在滑動時,按下就停止滑動;

(2)onScroll

那么當你的手指按下之后,可能還會繼續滑動,那么就是會回調到onScroll方法,在這個方法中,主要做滑動的處理

override fun onScroll(
    e1: MotionEvent?,
    e2: MotionEvent?,
    distanceX: Float,
    distanceY: Float
): Boolean {
    mRect.offset(0, distanceY.toInt())
    //邊界case處理
    if (mRect.bottom > imageHeight) {
        mRect.bottom = imageHeight
        mRect.top = imageHeight - (viewHeight / mScale).toInt()
    }
    if (mRect.top < 0) {
        mRect.top = 0
        mRect.bottom = (viewHeight / mScale).toInt()
    }
    postInvalidate()
    return false
}

在onScroll方法中,其實已經對滑動的距離做了計算(這個真的太nice了,不需要我們自己手動計算),因此只需要對mRect展示區域進行變換即可;

但是這里會有兩個邊界case,例如滑動到底部時就不能再滑了,這個時候,mRect的底部很可能都已經超過了圖片的高度,因此需要做邊界的處理,那么滑動到頂部的時候同樣也是需要做判斷。

(3)onFling

慣性滑動。我們在使用列表的時候,我們在滑動的時候,雖然手指的滑動距離很小,但是列表劃出去的距離卻很大,就是因為慣性,所以GestureDetector中對慣性也做了處理。

override fun onFling(
    e1: MotionEvent?,
    e2: MotionEvent?,
    velocityX: Float,
    velocityY: Float
): Boolean {
    mScroller.fling(0, mRect.top, 0, -velocityY.toInt(), 0, 0, 0, imageHeight - viewHeight)
    return false
}
//計算慣性
override fun computeScroll() {
    super.computeScroll()
    if (mScroller.isFinished) {
        return
    }
    if (mScroller.computeScrollOffset()) {
        //正在滑動
        mRect.top = mScroller.currY
        mRect.bottom = mScroller.currY + (viewHeight / mScale).toInt()
        postInvalidate()
    }
}

這個還是比較好理解的,就是設置最大的一個慣性滑動距離,無論怎么滑動,邊界值就是從頂部一劃到底,這個最大的距離就是 imageHeight - viewHeight

設置了慣性滑動的距離,那么在慣性滑動時,也需要實時改變mRect的解碼范圍,需要重寫computeScroll方法,判斷如果是正在滑動(通過 mScroller.computeScrollOffset() 判斷),那么需要改變mRect的位置。

2.2 雙擊放大效果處理

我們在使用app時,雙擊某張圖片或者雙指拉動某張圖片的時候,都會講圖片放大,這也是業內主流的兩種圖片放大的方式。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    //適配
    viewWidth = measuredWidth
    viewHeight = measuredHeight
    //縮放比
    val radio = viewWidth / imageWidth.toFloat()
    //分塊加載首次進入展示的rect
    mRect.left = 0
    mRect.top = 0
    mRect.right = imageWidth
    mRect.bottom = viewHeight
}

我們先看一下不能縮放時,mRect的賦值;那么當我們雙擊放大時,left和top的位置不會變,因為圖片放大了,但是控件的大小不會變,因此left的最大值就是控件的寬度,bottom的最大值就是控件的高度。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    //適配
    viewWidth = measuredWidth
    viewHeight = measuredHeight
    originScale = viewWidth / imageWidth.toFloat()
    mScale = originScale
    //分塊加載首次進入展示的rect
    mRect.left = 0
    mRect.top = 0
    mRect.right = Math.min(imageWidth, viewWidth)
    mRect.bottom = Math.min((viewHeight / mScale).toInt(), viewHeight)
}

這里就將onMeasure進行改造;那么對于雙擊事件的處理,可以使用GestureDetector.OnDoubleTapListener來處理,在onDoubleTap事件中回調。

override fun onDoubleTap(e: MotionEvent?): Boolean {
    if (mScale < originScale * 2) {
        mScale = originScale * 2
    } else {
        mScale = originScale
    }
    postInvalidate()
    return false
}

這里做了縮放就是判斷mScale的值,因為一開始進來不是縮放的場景,因此 mScale = originScale,當雙擊之后,需要將mScale擴大2倍,當重新繪制的時候,Bitmap就放大了2倍。

那么當圖片放大之后,之前橫向不能滑動現在也可以滑動查看圖片,所以需要處理,同時也需要考慮邊界case

override fun onDoubleTap(e: MotionEvent?): Boolean {
    if (mScale < originScale * 2) {
        mScale = originScale * 2
    } else {
        mScale = originScale
    }
    //
    mRect.right = mRect.left + (viewWidth / mScale).toInt()
    mRect.bottom = mRect.top + (viewHeight / mScale).toInt()
    if (mRect.bottom > imageHeight) {
        mRect.bottom = imageHeight
        mRect.top = imageHeight - (viewHeight / mScale).toInt()
    }
    if (mRect.top < 0) {
        mRect.top = 0
        mRect.bottom = (viewHeight / mScale).toInt()
    }
    if(mRect.right > imageWidth){
        mRect.right = imageWidth
        mRect.left = imageWidth - (viewWidth / mScale).toInt()
    }
    if(mRect.left < 0){
        mRect.left = 0
        mRect.right = (viewWidth / mScale).toInt()
    }
    postInvalidate()
    return false
}

當雙擊圖片之后,mRect解碼的區域也隨之改變,因此需要對right和bottom做相應的改變,圖片放大或者縮小,都是在控件寬高的基礎之上

override fun onScroll(
    e1: MotionEvent?,
    e2: MotionEvent?,
    distanceX: Float,
    distanceY: Float
): Boolean {
    mRect.offset(distanceX.toInt(), distanceY.toInt())
    //邊界case處理
    if (mRect.bottom > imageHeight) {
        mRect.bottom = imageHeight
        mRect.top = imageHeight - (viewHeight / mScale).toInt()
    }
    if (mRect.top < 0) {
        mRect.top = 0
        mRect.bottom = (viewHeight / mScale).toInt()
    }
    if(mRect.left < 0){
        mRect.left = 0
        mRect.right = (viewWidth / mScale).toInt()
    }
    if(mRect.right > imageWidth){
        mRect.right = imageWidth
        mRect.left = imageWidth - (viewWidth / mScale).toInt()
    }
    postInvalidate()
    return false
}

因為需要左右滑動,那么onScroll方法也需要做相應的改動,mRect的offset需要加上x軸的偏移量。

2.3 手指放大效果處理

手指縮放,是依賴 ScaleGestureDetector,其實跟GestureDetector的使用方式一致,這里就不做過多的贅述。

mScaleGestureDetector = ScaleGestureDetector(context, ScaleGesture())

在初始化ScaleGestureDetector的時候,需要傳入一個ScaleGesture內部類,集成ScaleGestureDetector.SimpleOnScaleGestureListener,在onScale方法中獲取縮放因子來繪制

inner class ScaleGesture : ScaleGestureDetector.SimpleOnScaleGestureListener() {
    override fun onScale(detector: ScaleGestureDetector?): Boolean {
        var scale = detector?.scaleFactor ?: mScale//可以代替mScale
        if (scale < originScale) {
            scale = originScale
        } else if (scale > originScale * 2) {
            scale = originScale * 2
        }
        //在原先基礎上縮放
        mRect.right = mRect.left + (viewWidth / scale).toInt()
        mRect.bottom = mRect.top + (viewHeight / scale).toInt()
        mScale = scale
        postInvalidate()
        return super.onScale(detector)
    }
}

這里別忘記了別事件傳遞出來,對于邊界case可自行處理

override fun onTouch(v: View?, event: MotionEvent?): Boolean {
    mGestureDetector.onTouchEvent(event)
    mScaleGestureDetector.onTouchEvent(event)
    return true
}

下面附上大圖治理的流程圖

Android性能優化大圖的方法

黃顏色模塊: BitmapFactory.Options配置,避免整張大圖直接加載在內存當中,通過開啟內存復用(inMutable),使用區域解碼器,繪制一塊可見區域

淺黃色模塊: View的繪制流程

“Android性能優化大圖的方法”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

晋城| 涞水县| 赤城县| 阿坝县| 古田县| 安多县| 阿城市| 仙桃市| 监利县| 临澧县| 鄱阳县| 广宗县| 乌苏市| 甘泉县| 沂南县| 元氏县| 饶平县| 宣武区| 胶南市| 宁南县| 阜南县| 达孜县| 涿州市| 瓦房店市| 安岳县| 裕民县| 北川| 阿鲁科尔沁旗| 威信县| 阿巴嘎旗| 广灵县| 西城区| 娄烦县| 宾川县| 汉川市| 南昌市| 报价| 山东| 韶山市| 扎鲁特旗| 布拖县|