Android Coil3缩略图、默认占位图placeholder、error加载错误体现,Kotlin(5)

[复制链接]
发表于 2025-10-20 06:57:43 | 显示全部楼层 |阅读模式
Android Coil3缩略图、默认占位图placeholder、error加载错误体现,Kotlin(5)



  1.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  2.     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  3.     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
  4.     <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
  5.     <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
复制代码



  1.     implementation("io.coil-kt.coil3:coil:3.1.0")
  2.     implementation("io.coil-kt.coil3:coil-gif:3.1.0")
  3.     implementation("io.coil-kt.coil3:coil-core:3.1.0")
复制代码



  1. import android.content.ContentUris
  2. import android.content.Context
  3. import android.net.Uri
  4. import android.os.Bundle
  5. import android.provider.MediaStore
  6. import android.util.Log
  7. import androidx.appcompat.app.AppCompatActivity
  8. import androidx.lifecycle.lifecycleScope
  9. import androidx.recyclerview.widget.GridLayoutManager
  10. import androidx.recyclerview.widget.LinearLayoutManager
  11. import androidx.recyclerview.widget.RecyclerView
  12. import coil3.imageLoader
  13. import kotlinx.coroutines.Dispatchers
  14. import kotlinx.coroutines.launch
  15. class MainActivity : AppCompatActivity() {
  16.     companion object {
  17.         const val SPAN_COUNT = 8
  18.         const val THUMB_WIDTH = 20
  19.         const val THUMB_HEIGHT = 20
  20.         const val IMAGE_SIZE = 400
  21.         const val TAG = "fly/MainActivity"
  22.     }
  23.     override fun onCreate(savedInstanceState: Bundle?) {
  24.         super.onCreate(savedInstanceState)
  25.         setContentView(R.layout.activity_main)
  26.         val rv = findViewById<RecyclerView>(R.id.rv)
  27.         val layoutManager = GridLayoutManager(this, SPAN_COUNT)
  28.         layoutManager.orientation = LinearLayoutManager.VERTICAL
  29.         val adapter = ImageAdapter(this, application.imageLoader)
  30.         rv.adapter = adapter
  31.         rv.layoutManager = layoutManager
  32.         rv.setItemViewCacheSize(SPAN_COUNT * 2)
  33.         rv.recycledViewPool.setMaxRecycledViews(0, SPAN_COUNT * 2)
  34.         val ctx = this
  35.         lifecycleScope.launch(Dispatchers.IO) {
  36.             val imgList = readAllImage(ctx)
  37.             val videoList = readAllVideo(ctx)
  38.             Log.d(TAG, "readAllImage size=${imgList.size}")
  39.             Log.d(TAG, "readAllVideo size=${videoList.size}")
  40.             val lists = arrayListOf<MyData>()
  41.             lists.addAll(imgList)
  42.             lists.addAll(videoList)
  43.             lists.shuffle()
  44.             lifecycleScope.launch(Dispatchers.Main) {
  45.                 adapter.dataChanged(lists)
  46.             }
  47.         }
  48.     }
  49.     class MyData(var path: String, var uri: Uri)
  50.     private fun readAllImage(ctx: Context): ArrayList<MyData> {
  51.         val photos = ArrayList<MyData>()
  52.         //读取所有图
  53.         val cursor = ctx.contentResolver.query(
  54.             MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null
  55.         )
  56.         while (cursor!!.moveToNext()) {
  57.             //路径
  58.             val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
  59.             val id = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
  60.             val imageUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(id))
  61.             //名称
  62.             //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
  63.             //大小
  64.             //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))
  65.             photos.add(MyData(path, imageUri))
  66.         }
  67.         cursor.close()
  68.         return photos
  69.     }
  70.     private fun readAllVideo(context: Context): ArrayList<MyData> {
  71.         val videos = ArrayList<MyData>()
  72.         //读取视频Video
  73.         val cursor = context.contentResolver.query(
  74.             MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
  75.             null,
  76.             null,
  77.             null,
  78.             null
  79.         )
  80.         while (cursor!!.moveToNext()) {
  81.             //路径
  82.             val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA))
  83.             val id = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
  84.             val videoUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(id))
  85.             //名称
  86.             //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME))
  87.             //大小
  88.             //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE))
  89.             videos.add(MyData(path, videoUri))
  90.         }
  91.         cursor.close()
  92.         return videos
  93.     }
  94. }
复制代码



  1. import android.app.Application
  2. import android.util.Log
  3. import coil3.ImageLoader
  4. import coil3.PlatformContext
  5. import coil3.SingletonImageLoader
  6. class MyApp : Application(), SingletonImageLoader.Factory {
  7.     companion object {
  8.         const val TAG = "fly/MyApp"
  9.     }
  10.     override fun newImageLoader(context: PlatformContext): ImageLoader {
  11.         Log.d(TAG, "newImageLoader")
  12.         return MyCoilManager.INSTANCE().getImageLoader(this)
  13.     }
  14. }
复制代码


  1. import android.content.Context
  2. import android.graphics.Bitmap
  3. import android.os.Environment
  4. import android.util.Log
  5. import coil3.ImageLoader
  6. import coil3.disk.DiskCache
  7. import coil3.disk.directory
  8. import coil3.gif.AnimatedImageDecoder
  9. import coil3.memory.MemoryCache
  10. import coil3.request.CachePolicy
  11. import coil3.request.bitmapConfig
  12. import java.io.File
  13. class MyCoilManager {
  14.     companion object {
  15.         const val TAG = "fly/MyCoilManager"
  16.         private val single by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MyCoilManager() }
  17.         fun INSTANCE() = single
  18.     }
  19.     private var mImageLoader: ImageLoader? = null
  20.     fun getImageLoader(ctx: Context): ImageLoader {
  21.         if (mImageLoader != null) {
  22.             Log.w(TAG, "ImageLoader已经初始化")
  23.             return mImageLoader!!
  24.         }
  25.         Log.d(TAG, "初始化ImageLoader")
  26.         //初始化加载器。
  27.         mImageLoader = ImageLoader.Builder(ctx)
  28.             .memoryCachePolicy(CachePolicy.ENABLED)
  29.             .memoryCache(initMemoryCache())
  30.             .diskCachePolicy(CachePolicy.ENABLED)
  31.             .diskCache(initDiskCache())
  32.             .bitmapConfig(Bitmap.Config.ARGB_8888)
  33.             .components {
  34.                 add(AnimatedImageDecoder.Factory())
  35.                 add(ThumbFetcher.Factory(ctx))
  36.             }.build()
  37.         Log.d(TAG, "memoryCache.maxSize=${mImageLoader!!.memoryCache?.maxSize}")
  38.         return mImageLoader!!
  39.     }
  40.     private fun initMemoryCache(): MemoryCache {
  41.         //内存缓存。
  42.         val memoryCache = MemoryCache.Builder()
  43.             .maxSizeBytes(1024 * 1024 * 1024 * 2L) //2GB
  44.             .build()
  45.         return memoryCache
  46.     }
  47.     private fun initDiskCache(): DiskCache {
  48.         //磁盘缓存。
  49.         val diskCacheFolder = Environment.getExternalStorageDirectory()
  50.         val diskCacheName = "coil_disk_cache"
  51.         val cacheFolder = File(diskCacheFolder, diskCacheName)
  52.         if (cacheFolder.exists()) {
  53.             Log.d(TAG, "${cacheFolder.absolutePath} exists")
  54.         } else {
  55.             if (cacheFolder.mkdir()) {
  56.                 Log.d(TAG, "${cacheFolder.absolutePath} create OK")
  57.             } else {
  58.                 Log.e(TAG, "${cacheFolder.absolutePath} create fail")
  59.             }
  60.         }
  61.         val diskCache = DiskCache.Builder()
  62.             .maxSizeBytes(1024 * 1024 * 1024 * 2L) //2GB
  63.             .directory(cacheFolder)
  64.             .build()
  65.         Log.d(TAG, "cache folder = ${diskCache.directory.toFile().absolutePath}")
  66.         return diskCache
  67.     }
  68. }
复制代码



  1. import android.content.Context
  2. import android.graphics.Bitmap
  3. import android.graphics.BitmapFactory
  4. import android.util.Log
  5. import android.view.View
  6. import android.view.ViewGroup
  7. import androidx.appcompat.widget.AppCompatImageView
  8. import androidx.recyclerview.widget.RecyclerView
  9. import coil3.Image
  10. import coil3.ImageLoader
  11. import coil3.asImage
  12. import coil3.memory.MemoryCache
  13. import coil3.request.Disposable
  14. import coil3.request.ImageRequest
  15. import coil3.request.SuccessResult
  16. import coil3.request.target
  17. import coil3.toBitmap
  18. class ImageAdapter : RecyclerView.Adapter<ImageHolder> {
  19.     private var mCtx: Context? = null
  20.     private var mImageLoader: ImageLoader? = null
  21.     private var mViewSize = 0
  22.     private var mPlaceholderImage: Image? = null
  23.     private var mErrorBmp: Bitmap? = null
  24.     companion object {
  25.         const val TAG = "fly/ImageAdapter"
  26.     }
  27.     constructor(ctx: Context, il: ImageLoader?) : super() {
  28.         mCtx = ctx
  29.         mImageLoader = il
  30.         mViewSize = mCtx!!.resources.displayMetrics.widthPixels / MainActivity.SPAN_COUNT
  31.         mPlaceholderImage = BitmapFactory.decodeResource(mCtx!!.resources, android.R.drawable.ic_menu_gallery).asImage()
  32.         mErrorBmp = BitmapFactory.decodeResource(mCtx!!.resources, android.R.drawable.stat_notify_error)
  33.     }
  34.     private var mItems = ArrayList<MainActivity.MyData>()
  35.     fun dataChanged(items: ArrayList<MainActivity.MyData>) {
  36.         this.mItems = items
  37.         notifyDataSetChanged()
  38.     }
  39.     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
  40.         val view = MyIV(mCtx!!, mViewSize)
  41.         return ImageHolder(view)
  42.     }
  43.     override fun getItemCount(): Int {
  44.         return mItems.size
  45.     }
  46.     override fun onBindViewHolder(holder: ImageHolder, position: Int) {
  47.         val data = mItems[position]
  48.         loadItemWithThumbForImage(data, holder.image)
  49.     }
  50.     private fun loadItemWithThumbForImage(data: MainActivity.MyData, myIv: MyIV) {
  51.         val thumbItem = Item(uri = data.uri, path = data.path)
  52.         thumbItem.type = Item.THUMB
  53.         val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
  54.         val thumbMemoryCache = getMemoryCache(thumbMemoryCacheKey)
  55.         val imageItem = Item(uri = data.uri, path = data.path)
  56.         imageItem.type = Item.IMG
  57.         val imageMemoryCacheKey = MemoryCache.Key(imageItem.toString())
  58.         val imageMemoryCache = getMemoryCache(imageMemoryCacheKey)
  59.         var isHighQualityLoaded = false
  60.         var thumbDisposable: Disposable? = null
  61.         //首次加载,没有正图缓存,也没有缩略图缓存。
  62.         if (thumbMemoryCache == null && imageMemoryCache == null) {
  63.             val thumbReq = ImageRequest.Builder(mCtx!!)
  64.                 .data(thumbItem)
  65.                 .memoryCacheKey(thumbMemoryCacheKey)
  66.                 .listener(object : ImageRequest.Listener {
  67.                     override fun onSuccess(request: ImageRequest, result: SuccessResult) {
  68.                         if (!isHighQualityLoaded) {
  69.                             myIv.setImageBitmap(result.image.toBitmap())
  70.                         }
  71.                     }
  72.                 }).build()
  73.             thumbDisposable = mImageLoader?.enqueue(thumbReq)
  74.         }
  75.         var imgPlaceholder = mPlaceholderImage
  76.         if (thumbMemoryCache != null) {
  77.             imgPlaceholder = thumbMemoryCache.image
  78.         }
  79.         //无论如何,都要加载正图。
  80.         val imageReq = ImageRequest.Builder(mCtx!!)
  81.             .data(data.uri)
  82.             .memoryCacheKey(imageMemoryCacheKey)
  83.             .size(MainActivity.IMAGE_SIZE)
  84.             .target(myIv)
  85.             .placeholder(imgPlaceholder)
  86.             .error(mErrorBmp!!.asImage())
  87.             .listener(object : ImageRequest.Listener {
  88.                 override fun onSuccess(request: ImageRequest, result: SuccessResult) {
  89.                     isHighQualityLoaded = true
  90.                     thumbDisposable?.dispose()
  91.                     Log.d(TAG, "image onSuccess ${result.dataSource} $imageItem ${calMemoryCache()}")
  92.                 }
  93.             }).build()
  94.         mImageLoader?.enqueue(imageReq)
  95.     }
  96.     private fun getMemoryCache(key: MemoryCache.Key): MemoryCache.Value? {
  97.         return mImageLoader?.memoryCache?.get(key)
  98.     }
  99.     private fun calMemoryCache(): String {
  100.         return "${mImageLoader?.memoryCache?.size} / ${mImageLoader?.memoryCache?.maxSize}"
  101.     }
  102. }
  103. class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
  104.     var image = itemView as MyIV
  105. }
  106. class MyIV : AppCompatImageView {
  107.     companion object {
  108.         const val TAG = "fly/MyIV"
  109.     }
  110.     private var mSize = 0
  111.     private var mCtx: Context? = null
  112.     constructor(ctx: Context, size: Int) : super(ctx) {
  113.         mCtx = ctx
  114.         mSize = size
  115.         scaleType = ScaleType.CENTER_CROP
  116.     }
  117.     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  118.         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
  119.         setMeasuredDimension(mSize, mSize)
  120.     }
  121. }
复制代码




  1. import android.net.Uri
  2. class Item {
  3.     companion object {
  4.         const val THUMB = 0
  5.         const val IMG = 1
  6.     }
  7.     var uri: Uri? = null
  8.     var path: String? = null
  9.     var lastModified = 0L
  10.     var width = 0
  11.     var height = 0
  12.     var position = -1
  13.     var type = -1  //0,缩略图。 1,正图image。-1,未知。
  14.     constructor(uri: Uri, path: String) {
  15.         this.uri = uri
  16.         this.path = path
  17.     }
  18.     override fun toString(): String {
  19.         return "Item(uri=$uri, path=$path, lastModified=$lastModified, width=$width, height=$height, position=$position, type=$type)"
  20.     }
  21. }
复制代码




  1. import android.content.Context
  2. import android.graphics.Bitmap
  3. import android.util.Log
  4. import android.util.Size
  5. import coil3.ImageLoader
  6. import coil3.asImage
  7. import coil3.decode.DataSource
  8. import coil3.fetch.FetchResult
  9. import coil3.fetch.Fetcher
  10. import coil3.fetch.ImageFetchResult
  11. import coil3.request.Options
  12. /**
  13. * 例如 FileUriFetcher
  14. */
  15. class ThumbFetcher(private val ctx: Context, private val thumbItem: Item, private val options: Options) : Fetcher {
  16.     companion object {
  17.         const val TAG = "fly/ThumbFetcher"
  18.     }
  19.     override suspend fun fetch(): FetchResult {
  20.         var bmp: Bitmap? = null
  21.         val t = System.currentTimeMillis()
  22.         try {
  23.             bmp = ctx.contentResolver.loadThumbnail(thumbItem.uri!!, Size(MainActivity.THUMB_WIDTH, MainActivity.THUMB_HEIGHT), null)
  24.             Log.d(TAG, "loadThumbnail time cost=${System.currentTimeMillis() - t} $thumbItem")
  25.         } catch (e: Exception) {
  26.             Log.e(TAG, "e=$e ThumbItem=$thumbItem")
  27.         }
  28.         return ImageFetchResult(
  29.             bmp?.asImage()!!,
  30.             true,
  31.             dataSource = DataSource.DISK
  32.         )
  33.     }
  34.     class Factory(private val ctx: Context) : Fetcher.Factory<Item> {
  35.         override fun create(
  36.             data: Item,
  37.             options: Options,
  38.             imageLoader: ImageLoader,
  39.         ): Fetcher {
  40.             return ThumbFetcher(ctx, data, options)
  41.         }
  42.     }
  43. }
复制代码





Android Coil3缩略图、默认占位图placeholder、error加载错误体现,Kotlin(4)_android coil 图片加载框架,设置占位图后,图片的比例不正确-CSDN博客文章欣赏阅读786次,点赞19次,收藏10次。遗留标题,设置的disk cache似乎没有work,指定的磁盘缓存文件路径天生是天生了,但是app跑起来运行后(图正常体现),内里是空的。遗留标题,设置的disk cache似乎没有work,指定的磁盘缓存文件路径天生是天生了,但是app跑起来运行后(图正常体现),内里是空的。遗留标题,设置的disk cache似乎没有work,指定的磁盘缓存文件路径天生是天生了,但是app跑起来运行后(图正常体现),内里是空的。2、现在分别利用缩略图内存缓存和正图内存缓存,感觉应该可以归并,只利用一套内存缓存。_android coil 图片加载框架,设置占位图后,图片的比例不正确
https://blog.csdn.net/zhangphil/article/details/145832225

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表