Android GridLayoutManager Glide批量加载Bitmap绘制Canvas画在RecyclerView,Kotlin(a)

Android GridLayoutManager Glide批量加载Bitmap绘制Canvas画在RecyclerView,Kotlin(a)

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.GridLayoutManager
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
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


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

        const val VIEW_TYPE = 0
        const val GROUP_TYPE = 1

        const val SPAN_COUNT = 16
    }

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

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

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

        rv.setHasFixedSize(true)

        layoutManager.spanSizeLookup = object : SpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                return SPAN_COUNT
            }
        }

        lifecycleScope.launch(Dispatchers.IO) {
            val items = readAllImage(this@MainActivity)
            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.holo_orange_light))
                    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, position)
                }
            }
        }
    }

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

        }
    }

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

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

    private fun sliceDataList(data: ArrayList<MediaData>): ArrayList<AdapterData> {
        var k: Int
        val lists = ArrayList<AdapterData>()
        for (i in data.indices step BatchBitmapView.ROW_SIZE) {
            lists.add(AdapterData(GROUP_TYPE))

            val temp = ArrayList<MediaData>()

            k = 0
            for (j in 0 until BatchBitmapView.ROW_SIZE) {
                k = i + j
                if (k >= data.size) {
                    break
                }
                temp.add(data[k])
            }

            val adapterData = AdapterData(VIEW_TYPE)
            adapterData.mediaData = temp
            lists.add(adapterData)
        }

        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

    companion object {
        const val TAG = "BatchBitmapView"
        const val ROW_SIZE = MainActivity.SPAN_COUNT //一行多少个bitmap
        const val IMAGE_SIZE = 50 //每个小格子图片的尺寸
    }

    constructor(
        ctx: Context,
        attributeSet: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : super(ctx, attributeSet, defStyleAttr) {
        mContext = ctx
    }

    fun setRowBitmapData(rows: ArrayList<MainActivity.MediaData>?, position: Int) {
        Log.d(TAG, "setRowBitmapData $position")

        mData.clear()

        Log.d(TAG, "mTargets.size=${mTargets.size}")
        mTargets.forEach {
            GlideApp.with(context).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(IMAGE_SIZE)
                .load(data.path)
                .into(target)

            mTargets.add(target)
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(mScreenWidth, IMAGE_SIZE)
    }

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

        mData.forEachIndexed { index, dataBean ->
            canvas.save()
            val left = (mScreenWidth / ROW_SIZE) * index
            canvas.drawBitmap(dataBean.bitmap, left.toFloat(), 0f, null)
            canvas.restore()
        }
    }

    data class DataBean(val bitmap: Bitmap)
}

有一个遗留问题,每行加载16张图片,以行为原子单位。后面可以考虑另外一种实现,group分组标签单独占一行,图片可以一大片一大片的占据多行,每行16张。

Android Glide自定义AppCompatImageView切分成若干小格子,每个小格子onDraw绘制Bitmap,Kotlin(1)_android appcompatimageview-CSDN博客文章浏览阅读1.2k次,点赞18次,收藏21次。垂直方向的RecyclerView,每行一个AppCompatImageView,每个AppCompatImageView被均匀切割成n个小格子, 每个小格子通过Glide加载出来Bitmap,然后onDraw绘制整行。//读取所有图片!//路径 uri//图片名称//图片大小= null,const val ROW_SIZE = 16 //一行多少个bitmap。_android appcompatimageviewhttps://zhangphil.blog.csdn.net/article/details/134519527Android GridLayoutManager SpanSizeLookup dynamic set grid cell column count,Kotlin-CSDN博客文章浏览阅读575次,点赞6次,收藏7次。Android RecyclerView的StaggeredGridLayoutManager实现交错排列的子元素分组先看实现的结果如图:设计背景:现在的产品对设计的需求越来越多样化,如附录文章2是典型的联系人分组RecyclerView,子元素排列到一个相同的组,但是有些时候,UI要求把这些元素不是垂直方向的,而是像本文开头的图中所示样式排列,这就需要用StaggeredGridLayoutMa_staggeredgridlayoutmanager。https://blog.csdn.net/zhangphil/article/details/137694645Android RecyclerView性能优化及Glide流畅加载图片丢帧率低的一种8宫格实现,Kotlin-CSDN博客文章浏览阅读690次,点赞26次,收藏11次。【代码】Android Paging 3,kotlin(1)在实际的开发中,虽然Glide解决了快速加载图片的问题,但还有一个问题悬而未决:比如用户的头像,往往用户的头像是从服务器端读出的一个普通矩形图片,但是现在的设计一般要求在APP端的用户头像显示成圆形头像,那么此时虽然Glide可以加载,但加载出来的是一个矩形,如果要Glide_android 毛玻璃圆角。现在结合他人的代码加以修改,给出一个以原始图形中心为原点,修剪图片为头像的工具类,此类可以直接在布局文件中加载使用,比。文章浏览阅读670次。https://blog.csdn.net/zhangphil/article/details/137653692

相关推荐
游戏开发爱好者83 小时前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview
王码码20353 小时前
Flutter for OpenHarmony 实战之基础组件:第三十一篇 Chip 系列组件 — 灵活的标签化交互
android·flutter·交互·harmonyos
黑码哥4 小时前
ViewHolder设计模式深度剖析:iOS开发者掌握Android列表性能优化的实战指南
android·ios·性能优化·跨平台开发·viewholder
亓才孓4 小时前
[JDBC]元数据
android
独行soc4 小时前
2026年渗透测试面试题总结-17(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
金融RPA机器人丨实在智能4 小时前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿4 小时前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
独行soc5 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
王码码20355 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
2501_915106325 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview