Android优化RecyclerView图片展示:Glide成堆加载批量Bitmap在RecyclerView成片绘制Canvas,Kotlin(b)

Android优化RecyclerView图片展示:Glide成堆加载批量Bitmap在RecyclerView成片绘制Canvas,Kotlin(b)

对 Android GridLayoutManager Glide批量加载Bitmap绘制Canvas画在RecyclerView,Kotlin(a)-CSDN博客 改进,用Glide批量把Bitmap加载出来,然后在RecyclerView成片成堆的绘制Canvas,此种实现是RecyclerView加载多宫格图片展示,卡顿丢帧最低的一种实现,上下滑动流畅。

XML 复制代码
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
XML 复制代码
plugins {
    id 'org.jetbrains.kotlin.kapt'
}
 
 
    implementation 'com.github.bumptech.glide:glide:4.16.0'
    kapt 'com.github.bumptech.glide:compiler:4.16.0'
Kotlin 复制代码
import android.content.Context
import android.util.Log
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator
import com.bumptech.glide.load.engine.executor.GlideExecutor
import com.bumptech.glide.module.AppGlideModule


@GlideModule
class MyGlideModule : AppGlideModule() {

    override fun applyOptions(context: Context, builder: GlideBuilder) {
        super.applyOptions(context, builder)
        builder.setLogLevel(Log.DEBUG)

        val memoryCacheScreens = 200F
        val maxSizeMultiplier = 0.8F

        val calculator = MemorySizeCalculator.Builder(context)
            .setMemoryCacheScreens(memoryCacheScreens)
            .setBitmapPoolScreens(memoryCacheScreens)
            .setMaxSizeMultiplier(maxSizeMultiplier)
            .setLowMemoryMaxSizeMultiplier(maxSizeMultiplier * 0.8F)
            .setArrayPoolSize((1024 * 1024 * memoryCacheScreens).toInt())
            .build()

        builder.setMemorySizeCalculator(calculator)

        val diskCacheSize = 1024 * 1024 * 2000L
        builder.setDiskCache(InternalCacheDiskCacheFactory(context, diskCacheSize))


        val mSourceExecutor = GlideExecutor.newSourceBuilder()
            .setUncaughtThrowableStrategy(GlideExecutor.UncaughtThrowableStrategy.LOG)
            .setThreadCount(4)
            //.setThreadTimeoutMillis(1000) //线程读写超时时间。
            .setName("fly-SourceExecutor")
            .build()

        val mDiskCacheBuilder = GlideExecutor.newDiskCacheBuilder()
            .setThreadCount(1)
            //.setThreadTimeoutMillis(1000) //线程读写超时时间。
            .setName("fly-DiskCacheBuilder")
            .build()

        val mAnimationExecutor = GlideExecutor.newDiskCacheBuilder()
            .setThreadCount(1)
            //.setThreadTimeoutMillis(1000) //线程读写超时时间。
            .setName("fly-AnimationExecutor")
            .build()

        builder.setSourceExecutor(mSourceExecutor)
        builder.setDiskCacheExecutor(mDiskCacheBuilder)
        builder.setAnimationExecutor(mAnimationExecutor)
    }

    override fun isManifestParsingEnabled(): Boolean {
        return false
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.provider.MediaStore
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.imageview.ShapeableImageView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.ceil
import kotlin.math.roundToInt

const val COLUMN_COUNT = 16 // 一行多少个图片。
const val CHUNKED_SIZE = 100 // 一批/一大片总共有多少张图片。

class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG = "fly"

        const val VIEW_TYPE = 0 //图片区域。
        const val GROUP_TYPE = 1 //分组标签。
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val rv = findViewById<RecyclerView>(R.id.rv)
        val layoutManager = LinearLayoutManager(this)
        layoutManager.orientation = LinearLayoutManager.VERTICAL
        rv.layoutManager = layoutManager

        val adapter = MyAdapter(this)
        rv.adapter = adapter

        rv.setHasFixedSize(true)

        lifecycleScope.launch(Dispatchers.IO) {
            val items = readAllImage(this@MainActivity)
            items.reverse()
            val lists: ArrayList<AdapterData> = sliceDataList(items)
            withContext(Dispatchers.Main) {
                adapter.dataChanged(lists)
            }
        }
    }

    class MyAdapter : RecyclerView.Adapter<MyVH> {
        private var mItems = arrayListOf<AdapterData>()
        private var mContext: Context? = null

        constructor(ctx: Context) {
            this.mContext = ctx
        }

        fun dataChanged(items: ArrayList<AdapterData>) {
            this.mItems = items
            notifyDataSetChanged()
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
            return when (viewType) {
                GROUP_TYPE -> {
                    val view = LayoutInflater.from(mContext!!).inflate(android.R.layout.simple_list_item_1, parent, false)
                    view.setBackgroundColor(ContextCompat.getColor(mContext!!, android.R.color.darker_gray))
                    MyVH(view)
                }

                else -> {
                    val view = BatchBitmapView(mContext!!)
                    MyVH(view)
                }
            }
        }

        override fun getItemCount(): Int {
            return mItems.size
        }

        override fun getItemViewType(position: Int): Int {
            return mItems[position].type
        }

        override fun onBindViewHolder(holder: MyVH, position: Int) {
            Log.d(TAG, "onBindViewHolder $position")

            when (getItemViewType(position)) {
                GROUP_TYPE -> {
                    holder.itemView.findViewById<TextView>(android.R.id.text1).text = "$position GROUP"
                }

                else -> {
                    (holder.itemView as BatchBitmapView).setRowBitmapData(mItems[position].mediaData)
                }
            }
        }
    }

    class MyVH : RecyclerView.ViewHolder {
        constructor(itemView: View) : super(itemView) {

        }
    }

    class AdapterData(var type: Int = GROUP_TYPE, data: List<MediaData>? = null) {
        var mediaData: List<MediaData>? = data
    }

    class MediaData(var path: String, var index: Int)

    private fun sliceDataList(data: ArrayList<MediaData>): ArrayList<AdapterData> {
        val lists = ArrayList<AdapterData>()
        val chunks = data.chunked(CHUNKED_SIZE)

        chunks.forEach {
            lists.add(AdapterData(GROUP_TYPE))
            lists.add(AdapterData(VIEW_TYPE, it))
        }

        return lists
    }

    private fun readAllImage(context: Context): ArrayList<MediaData> {
        val photos = ArrayList<MediaData>()

        //读取所有图片
        val cursor = context.contentResolver.query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null
        )

        var index = 0
        while (cursor!!.moveToNext()) {
            //路径 uri
            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))

            //图片名称
            //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
            //图片大小
            //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))

            photos.add(MediaData(path, index++))
        }
        cursor.close()

        return photos
    }
}

class BatchBitmapView : ShapeableImageView {
    private val mData = mutableListOf<DataBean>()
    private val mScreenWidth = resources.displayMetrics.widthPixels
    private val mTargets = mutableListOf<CustomTarget<Bitmap>>()
    private var mContext: Context? = null
    private var mImageSize = 0 //每个小格子图片的尺寸,动态计算而变化。

    companion object {
        const val TAG = "BatchBitmapView"
    }

    constructor(
        ctx: Context,
        attributeSet: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : super(ctx, attributeSet, defStyleAttr) {
        mContext = ctx
        mImageSize = (mScreenWidth.toFloat() / COLUMN_COUNT.toFloat()).roundToInt()
    }

    fun setRowBitmapData(rows: List<MainActivity.MediaData>?) {
        mData.clear()

        Log.d(TAG, "mTargets.size=${mTargets.size}")
        mTargets.forEach {
            GlideApp.with(mContext!!).clear(it) //如果不清除,会发生有些图错放位置。
        }
        mTargets.clear() //mTargets上下滑动列表会越来越大,清空,一直保持ROW_SIZE.

        rows?.forEachIndexed { index, data ->
            val target = object : CustomTarget<Bitmap>() {
                override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
                    val bean = DataBean(resource)
                    mData.add(bean)
                    postInvalidate()
                }

                override fun onLoadCleared(placeholder: Drawable?) {

                }
            }

            GlideApp.with(mContext!!)
                .asBitmap()
                .centerCrop()
                .override(mImageSize)
                .load(data.path)
                .into(target)

            mTargets.add(target)
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val rows = ceil(CHUNKED_SIZE.toFloat() / COLUMN_COUNT.toFloat()).toInt()
        setMeasuredDimension(mScreenWidth, mImageSize * rows)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        mData.forEachIndexed { index, dataBean ->
            //canvas.save()

            val left = mImageSize * (index % COLUMN_COUNT)
            val top = (index / COLUMN_COUNT) * mImageSize
            canvas.drawBitmap(dataBean.bitmap, left.toFloat(), top.toFloat(), null)

            //canvas.restore()
        }
    }

    data class DataBean(val bitmap: Bitmap)
}

Android GridLayoutManager Glide批量加载Bitmap绘制Canvas画在RecyclerView,Kotlin(a)-CSDN博客【代码】Android Paging 3,kotlin(1)在实际的开发中,虽然Glide解决了快速加载图片的问题,但还有一个问题悬而未决:比如用户的头像,往往用户的头像是从服务器端读出的一个普通矩形图片,但是现在的设计一般要求在APP端的用户头像显示成圆形头像,那么此时虽然Glide可以加载,但加载出来的是一个矩形,如果要Glide_android 毛玻璃圆角。现在结合他人的代码加以修改,给出一个以原始图形中心为原点,修剪图片为头像的工具类,此类可以直接在布局文件中加载使用,比。文章浏览阅读670次。https://blog.csdn.net/zhangphil/article/details/137823405

Android RecyclerView性能优化及Glide流畅加载图片丢帧率低的一种8宫格实现,Kotlin-CSDN博客文章浏览阅读695次,点赞26次,收藏11次。【代码】Android Paging 3,kotlin(1)在实际的开发中,虽然Glide解决了快速加载图片的问题,但还有一个问题悬而未决:比如用户的头像,往往用户的头像是从服务器端读出的一个普通矩形图片,但是现在的设计一般要求在APP端的用户头像显示成圆形头像,那么此时虽然Glide可以加载,但加载出来的是一个矩形,如果要Glide_android 毛玻璃圆角。现在结合他人的代码加以修改,给出一个以原始图形中心为原点,修剪图片为头像的工具类,此类可以直接在布局文件中加载使用,比。文章浏览阅读670次。https://blog.csdn.net/zhangphil/article/details/137653692Android GridLayoutManager SpanSizeLookup dynamic set grid cell column count,Kotlin-CSDN博客文章浏览阅读584次,点赞6次,收藏7次。Android RecyclerView的StaggeredGridLayoutManager实现交错排列的子元素分组先看实现的结果如图:设计背景:现在的产品对设计的需求越来越多样化,如附录文章2是典型的联系人分组RecyclerView,子元素排列到一个相同的组,但是有些时候,UI要求把这些元素不是垂直方向的,而是像本文开头的图中所示样式排列,这就需要用StaggeredGridLayoutMa_staggeredgridlayoutmanager。https://blog.csdn.net/zhangphil/article/details/137694645Android Glide load grid RecyclerView scroll smooth, high performance and ,Kotlin-CSDN博客文章浏览阅读709次,点赞18次,收藏13次。【代码】Android Paging 3,kotlin(1)在实际的开发中,虽然Glide解决了快速加载图片的问题,但还有一个问题悬而未决:比如用户的头像,往往用户的头像是从服务器端读出的一个普通矩形图片,但是现在的设计一般要求在APP端的用户头像显示成圆形头像,那么此时虽然Glide可以加载,但加载出来的是一个矩形,如果要Glide_android 毛玻璃圆角。现在结合他人的代码加以修改,给出一个以原始图形中心为原点,修剪图片为头像的工具类,此类可以直接在布局文件中加载使用,比。文章浏览阅读670次。https://blog.csdn.net/zhangphil/article/details/137520793

相关推荐
Android-Flutter6 小时前
Compose 管理状态 详解
android·compose
缓慢更新7 小时前
审批的加签与会签:多人意见怎么合
android·java·数据库·低代码·rxjava
我怀里的猫7 小时前
android-activity-launch-sequence
android
YB13759 小时前
Jetpack Compose 动画rememberInfiniteTransition
android
baidu_2474386112 小时前
Android 自定义竖直的虚线
android
传奇开心果编程13 小时前
【Jetpack Compose娓娓道来】 第25课:ViewModel就是UI界面的大脑
android·kotlin·移动开发·jetpack compose·编程语言理论·编程框架·声明式ui
马立杰13 小时前
PHP 未授权 RCE 应急响应:CVE-2026-109618 CVE-2026-109619 排查与加固实战
android·开发语言·php
NamoAmitabha12813 小时前
安装“编译器环境”(Android Studio)--deepseek--Cocos --Creator3.8.8
android·ide·android studio
聚美智数14 小时前
文档转换 API‑文档转 PDF‑PDF 转文档‑异步文档处理‑文件格式迁移‑转换结果回调推送 API 接口介绍
android·java·pdf
booboosui15 小时前
Android 上的无人回合:只放开 2 类工具,宁可空手
android