码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 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加载多宫格图片展示,卡顿丢帧最低的一种实现,上下滑动流畅。

    1. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    2. <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

    1. plugins {
    2. id 'org.jetbrains.kotlin.kapt'
    3. }
    4. implementation 'com.github.bumptech.glide:glide:4.16.0'
    5. kapt 'com.github.bumptech.glide:compiler:4.16.0'

    1. import android.content.Context
    2. import android.util.Log
    3. import com.bumptech.glide.GlideBuilder
    4. import com.bumptech.glide.annotation.GlideModule
    5. import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
    6. import com.bumptech.glide.load.engine.cache.MemorySizeCalculator
    7. import com.bumptech.glide.load.engine.executor.GlideExecutor
    8. import com.bumptech.glide.module.AppGlideModule
    9. @GlideModule
    10. class MyGlideModule : AppGlideModule() {
    11. override fun applyOptions(context: Context, builder: GlideBuilder) {
    12. super.applyOptions(context, builder)
    13. builder.setLogLevel(Log.DEBUG)
    14. val memoryCacheScreens = 200F
    15. val maxSizeMultiplier = 0.8F
    16. val calculator = MemorySizeCalculator.Builder(context)
    17. .setMemoryCacheScreens(memoryCacheScreens)
    18. .setBitmapPoolScreens(memoryCacheScreens)
    19. .setMaxSizeMultiplier(maxSizeMultiplier)
    20. .setLowMemoryMaxSizeMultiplier(maxSizeMultiplier * 0.8F)
    21. .setArrayPoolSize((1024 * 1024 * memoryCacheScreens).toInt())
    22. .build()
    23. builder.setMemorySizeCalculator(calculator)
    24. val diskCacheSize = 1024 * 1024 * 2000L
    25. builder.setDiskCache(InternalCacheDiskCacheFactory(context, diskCacheSize))
    26. val mSourceExecutor = GlideExecutor.newSourceBuilder()
    27. .setUncaughtThrowableStrategy(GlideExecutor.UncaughtThrowableStrategy.LOG)
    28. .setThreadCount(4)
    29. //.setThreadTimeoutMillis(1000) //线程读写超时时间。
    30. .setName("fly-SourceExecutor")
    31. .build()
    32. val mDiskCacheBuilder = GlideExecutor.newDiskCacheBuilder()
    33. .setThreadCount(1)
    34. //.setThreadTimeoutMillis(1000) //线程读写超时时间。
    35. .setName("fly-DiskCacheBuilder")
    36. .build()
    37. val mAnimationExecutor = GlideExecutor.newDiskCacheBuilder()
    38. .setThreadCount(1)
    39. //.setThreadTimeoutMillis(1000) //线程读写超时时间。
    40. .setName("fly-AnimationExecutor")
    41. .build()
    42. builder.setSourceExecutor(mSourceExecutor)
    43. builder.setDiskCacheExecutor(mDiskCacheBuilder)
    44. builder.setAnimationExecutor(mAnimationExecutor)
    45. }
    46. override fun isManifestParsingEnabled(): Boolean {
    47. return false
    48. }
    49. }

    1. import android.content.Context
    2. import android.graphics.Bitmap
    3. import android.graphics.Canvas
    4. import android.graphics.drawable.Drawable
    5. import android.os.Bundle
    6. import android.provider.MediaStore
    7. import android.util.AttributeSet
    8. import android.util.Log
    9. import android.view.LayoutInflater
    10. import android.view.View
    11. import android.view.ViewGroup
    12. import android.widget.TextView
    13. import androidx.appcompat.app.AppCompatActivity
    14. import androidx.core.content.ContextCompat
    15. import androidx.lifecycle.lifecycleScope
    16. import androidx.recyclerview.widget.LinearLayoutManager
    17. import androidx.recyclerview.widget.RecyclerView
    18. import com.bumptech.glide.request.target.CustomTarget
    19. import com.bumptech.glide.request.transition.Transition
    20. import com.google.android.material.imageview.ShapeableImageView
    21. import kotlinx.coroutines.Dispatchers
    22. import kotlinx.coroutines.launch
    23. import kotlinx.coroutines.withContext
    24. import kotlin.math.ceil
    25. import kotlin.math.roundToInt
    26. const val COLUMN_COUNT = 16 // 一行多少个图片。
    27. const val CHUNKED_SIZE = 100 // 一批/一大片总共有多少张图片。
    28. class MainActivity : AppCompatActivity() {
    29. companion object {
    30. const val TAG = "fly"
    31. const val VIEW_TYPE = 0 //图片区域。
    32. const val GROUP_TYPE = 1 //分组标签。
    33. }
    34. override fun onCreate(savedInstanceState: Bundle?) {
    35. super.onCreate(savedInstanceState)
    36. setContentView(R.layout.activity_main)
    37. val rv = findViewById(R.id.rv)
    38. val layoutManager = LinearLayoutManager(this)
    39. layoutManager.orientation = LinearLayoutManager.VERTICAL
    40. rv.layoutManager = layoutManager
    41. val adapter = MyAdapter(this)
    42. rv.adapter = adapter
    43. rv.setHasFixedSize(true)
    44. lifecycleScope.launch(Dispatchers.IO) {
    45. val items = readAllImage(this@MainActivity)
    46. items.reverse()
    47. val lists: ArrayList = sliceDataList(items)
    48. withContext(Dispatchers.Main) {
    49. adapter.dataChanged(lists)
    50. }
    51. }
    52. }
    53. class MyAdapter : RecyclerView.Adapter<MyVH> {
    54. private var mItems = arrayListOf()
    55. private var mContext: Context? = null
    56. constructor(ctx: Context) {
    57. this.mContext = ctx
    58. }
    59. fun dataChanged(items: ArrayList<AdapterData>) {
    60. this.mItems = items
    61. notifyDataSetChanged()
    62. }
    63. override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
    64. return when (viewType) {
    65. GROUP_TYPE -> {
    66. val view = LayoutInflater.from(mContext!!).inflate(android.R.layout.simple_list_item_1, parent, false)
    67. view.setBackgroundColor(ContextCompat.getColor(mContext!!, android.R.color.darker_gray))
    68. MyVH(view)
    69. }
    70. else -> {
    71. val view = BatchBitmapView(mContext!!)
    72. MyVH(view)
    73. }
    74. }
    75. }
    76. override fun getItemCount(): Int {
    77. return mItems.size
    78. }
    79. override fun getItemViewType(position: Int): Int {
    80. return mItems[position].type
    81. }
    82. override fun onBindViewHolder(holder: MyVH, position: Int) {
    83. Log.d(TAG, "onBindViewHolder $position")
    84. when (getItemViewType(position)) {
    85. GROUP_TYPE -> {
    86. holder.itemView.findViewById(android.R.id.text1).text = "$position GROUP"
    87. }
    88. else -> {
    89. (holder.itemView as BatchBitmapView).setRowBitmapData(mItems[position].mediaData)
    90. }
    91. }
    92. }
    93. }
    94. class MyVH : RecyclerView.ViewHolder {
    95. constructor(itemView: View) : super(itemView) {
    96. }
    97. }
    98. class AdapterData(var type: Int = GROUP_TYPE, data: List? = null) {
    99. var mediaData: List? = data
    100. }
    101. class MediaData(var path: String, var index: Int)
    102. private fun sliceDataList(data: ArrayList<MediaData>): ArrayList {
    103. val lists = ArrayList()
    104. val chunks = data.chunked(CHUNKED_SIZE)
    105. chunks.forEach {
    106. lists.add(AdapterData(GROUP_TYPE))
    107. lists.add(AdapterData(VIEW_TYPE, it))
    108. }
    109. return lists
    110. }
    111. private fun readAllImage(context: Context): ArrayList {
    112. val photos = ArrayList()
    113. //读取所有图片
    114. val cursor = context.contentResolver.query(
    115. MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null
    116. )
    117. var index = 0
    118. while (cursor!!.moveToNext()) {
    119. //路径 uri
    120. val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
    121. //图片名称
    122. //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
    123. //图片大小
    124. //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))
    125. photos.add(MediaData(path, index++))
    126. }
    127. cursor.close()
    128. return photos
    129. }
    130. }
    131. class BatchBitmapView : ShapeableImageView {
    132. private val mData = mutableListOf()
    133. private val mScreenWidth = resources.displayMetrics.widthPixels
    134. private val mTargets = mutableListOf>()
    135. private var mContext: Context? = null
    136. private var mImageSize = 0 //每个小格子图片的尺寸,动态计算而变化。
    137. companion object {
    138. const val TAG = "BatchBitmapView"
    139. }
    140. constructor(
    141. ctx: Context,
    142. attributeSet: AttributeSet? = null,
    143. defStyleAttr: Int = 0
    144. ) : super(ctx, attributeSet, defStyleAttr) {
    145. mContext = ctx
    146. mImageSize = (mScreenWidth.toFloat() / COLUMN_COUNT.toFloat()).roundToInt()
    147. }
    148. fun setRowBitmapData(rows: List<MainActivity.MediaData>?) {
    149. mData.clear()
    150. Log.d(TAG, "mTargets.size=${mTargets.size}")
    151. mTargets.forEach {
    152. GlideApp.with(mContext!!).clear(it) //如果不清除,会发生有些图错放位置。
    153. }
    154. mTargets.clear() //mTargets上下滑动列表会越来越大,清空,一直保持ROW_SIZE.
    155. rows?.forEachIndexed { index, data ->
    156. val target = object : CustomTarget() {
    157. override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
    158. val bean = DataBean(resource)
    159. mData.add(bean)
    160. postInvalidate()
    161. }
    162. override fun onLoadCleared(placeholder: Drawable?) {
    163. }
    164. }
    165. GlideApp.with(mContext!!)
    166. .asBitmap()
    167. .centerCrop()
    168. .override(mImageSize)
    169. .load(data.path)
    170. .into(target)
    171. mTargets.add(target)
    172. }
    173. }
    174. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    175. super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    176. val rows = ceil(CHUNKED_SIZE.toFloat() / COLUMN_COUNT.toFloat()).toInt()
    177. setMeasuredDimension(mScreenWidth, mImageSize * rows)
    178. }
    179. override fun onDraw(canvas: Canvas) {
    180. super.onDraw(canvas)
    181. mData.forEachIndexed { index, dataBean ->
    182. //canvas.save()
    183. val left = mImageSize * (index % COLUMN_COUNT)
    184. val top = (index / COLUMN_COUNT) * mImageSize
    185. canvas.drawBitmap(dataBean.bitmap, left.toFloat(), top.toFloat(), null)
    186. //canvas.restore()
    187. }
    188. }
    189. data class DataBean(val bitmap: Bitmap)
    190. }

    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

  • 相关阅读:
    ElasticSearch安装部署,单节点部署,集群部署
    DoFaker: 一个简单易用的换脸工具
    竞赛 大数据商城人流数据分析与可视化 - python 大数据分析
    开发人员请注意:在 PyPI 上的 Python 包中发现 BlazeStealer 恶意软件
    ElasticJob+Spring Boot简单使用
    xgo: golang基于-toolexec实现猴子补丁
    自动监控网站可用性并发送通知的 Bash 脚本
    红心向阳 百鸟朝凤
    安信可Ai-WB1系列AT指令连接MQTT阿里云物联网平台
    设计模式与应用:备忘录模式
  • 原文地址:https://blog.csdn.net/zhangphil/article/details/137843559
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号