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

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

XML 复制代码
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
XML 复制代码
    implementation("io.coil-kt.coil3:coil:3.1.0")
    implementation("io.coil-kt.coil3:coil-gif:3.1.0")
    implementation("io.coil-kt.coil3:coil-core:3.1.0")
Kotlin 复制代码
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil3.imageLoader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


class MainActivity : AppCompatActivity() {
    companion object {
        const val SPAN_COUNT = 8

        const val THUMB_WIDTH = 20
        const val THUMB_HEIGHT = 20

        const val IMAGE_SIZE = 400

        const val TAG = "fly/MainActivity"
    }

    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

        val adapter = ImageAdapter(this, application.imageLoader)

        rv.adapter = adapter
        rv.layoutManager = layoutManager

        rv.setItemViewCacheSize(SPAN_COUNT * 2)
        rv.recycledViewPool.setMaxRecycledViews(0, SPAN_COUNT * 2)

        val ctx = this
        lifecycleScope.launch(Dispatchers.IO) {
            val imgList = readAllImage(ctx)
            val videoList = readAllVideo(ctx)

            Log.d(TAG, "readAllImage size=${imgList.size}")
            Log.d(TAG, "readAllVideo size=${videoList.size}")

            val lists = arrayListOf<MyData>()
            lists.addAll(imgList)
            lists.addAll(videoList)
            lists.shuffle()

            lifecycleScope.launch(Dispatchers.Main) {
                adapter.dataChanged(lists)
            }
        }
    }

    class MyData(var path: String, var uri: Uri)

    private fun readAllImage(ctx: Context): ArrayList<MyData> {
        val photos = ArrayList<MyData>()

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

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

            val id = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
            val imageUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(id))

            //名称
            //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))

            //大小
            //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))

            photos.add(MyData(path, imageUri))
        }
        cursor.close()

        return photos
    }

    private fun readAllVideo(context: Context): ArrayList<MyData> {
        val videos = ArrayList<MyData>()

        //读取视频Video
        val cursor = context.contentResolver.query(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            null,
            null,
            null,
            null
        )

        while (cursor!!.moveToNext()) {
            //路径
            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA))

            val id = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
            val videoUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(id))

            //名称
            //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME))

            //大小
            //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE))

            videos.add(MyData(path, videoUri))
        }
        cursor.close()

        return videos
    }
}
Kotlin 复制代码
import android.app.Application
import android.util.Log
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader


class MyApp : Application(), SingletonImageLoader.Factory {
    companion object {
        const val TAG = "fly/MyApp"
    }

    override fun newImageLoader(context: PlatformContext): ImageLoader {
        Log.d(TAG, "newImageLoader")
        return MyCoilManager.INSTANCE().getImageLoader(this)
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import android.os.Environment
import android.util.Log
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.disk.directory
import coil3.gif.AnimatedImageDecoder
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.bitmapConfig
import java.io.File

class MyCoilManager {
    companion object {
        const val TAG = "fly/MyCoilManager"

        private val single by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MyCoilManager() }
        fun INSTANCE() = single
    }

    private var mImageLoader: ImageLoader? = null

    fun getImageLoader(ctx: Context): ImageLoader {
        if (mImageLoader != null) {
            Log.w(TAG, "ImageLoader已经初始化")
            return mImageLoader!!
        }

        Log.d(TAG, "初始化ImageLoader")
        //初始化加载器。
        mImageLoader = ImageLoader.Builder(ctx)
            .memoryCachePolicy(CachePolicy.ENABLED)
            .memoryCache(initMemoryCache())
            .diskCachePolicy(CachePolicy.ENABLED)
            .diskCache(initDiskCache())
            .bitmapConfig(Bitmap.Config.ARGB_8888)
            .components {
                add(AnimatedImageDecoder.Factory())
                add(ThumbFetcher.Factory(ctx))
            }.build()

        Log.d(TAG, "memoryCache.maxSize=${mImageLoader!!.memoryCache?.maxSize}")

        return mImageLoader!!
    }


    private fun initMemoryCache(): MemoryCache {
        //内存缓存。
        val memoryCache = MemoryCache.Builder()
            .maxSizeBytes(1024 * 1024 * 1024 * 2L) //2GB
            .build()

        return memoryCache
    }

    private fun initDiskCache(): DiskCache {
        //磁盘缓存。
        val diskCacheFolder = Environment.getExternalStorageDirectory()
        val diskCacheName = "coil_disk_cache"

        val cacheFolder = File(diskCacheFolder, diskCacheName)
        if (cacheFolder.exists()) {
            Log.d(TAG, "${cacheFolder.absolutePath} exists")
        } else {
            if (cacheFolder.mkdir()) {
                Log.d(TAG, "${cacheFolder.absolutePath} create OK")
            } else {
                Log.e(TAG, "${cacheFolder.absolutePath} create fail")
            }
        }

        val diskCache = DiskCache.Builder()
            .maxSizeBytes(1024 * 1024 * 1024 * 2L) //2GB
            .directory(cacheFolder)
            .build()

        Log.d(TAG, "cache folder = ${diskCache.directory.toFile().absolutePath}")

        return diskCache
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatImageView
import androidx.recyclerview.widget.RecyclerView
import coil3.Image
import coil3.ImageLoader
import coil3.asImage
import coil3.memory.MemoryCache
import coil3.request.Disposable
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.target
import coil3.toBitmap


class ImageAdapter : RecyclerView.Adapter<ImageHolder> {
    private var mCtx: Context? = null
    private var mImageLoader: ImageLoader? = null
    private var mViewSize = 0
    private var mPlaceholderImage: Image? = null
    private var mErrorBmp: Bitmap? = null

    companion object {
        const val TAG = "fly/ImageAdapter"
    }

    constructor(ctx: Context, il: ImageLoader?) : super() {
        mCtx = ctx
        mImageLoader = il

        mViewSize = mCtx!!.resources.displayMetrics.widthPixels / MainActivity.SPAN_COUNT

        mPlaceholderImage = BitmapFactory.decodeResource(mCtx!!.resources, android.R.drawable.ic_menu_gallery).asImage()
        mErrorBmp = BitmapFactory.decodeResource(mCtx!!.resources, android.R.drawable.stat_notify_error)
    }

    private var mItems = ArrayList<MainActivity.MyData>()

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

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
        val view = MyIV(mCtx!!, mViewSize)
        return ImageHolder(view)
    }

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

    override fun onBindViewHolder(holder: ImageHolder, position: Int) {
        val data = mItems[position]
        loadItemWithThumbForImage(data, holder.image)
    }

    private fun loadItemWithThumbForImage(data: MainActivity.MyData, myIv: MyIV) {
        val thumbItem = Item(uri = data.uri, path = data.path)
        thumbItem.type = Item.THUMB
        val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
        val thumbMemoryCache = getMemoryCache(thumbMemoryCacheKey)

        val imageItem = Item(uri = data.uri, path = data.path)
        imageItem.type = Item.IMG
        val imageMemoryCacheKey = MemoryCache.Key(imageItem.toString())
        val imageMemoryCache = getMemoryCache(imageMemoryCacheKey)

        var isHighQualityLoaded = false
        var thumbDisposable: Disposable? = null

        //首次加载,没有正图缓存,也没有缩略图缓存。
        if (thumbMemoryCache == null && imageMemoryCache == null) {
            val thumbReq = ImageRequest.Builder(mCtx!!)
                .data(thumbItem)
                .memoryCacheKey(thumbMemoryCacheKey)
                .listener(object : ImageRequest.Listener {
                    override fun onSuccess(request: ImageRequest, result: SuccessResult) {
                        if (!isHighQualityLoaded) {
                            myIv.setImageBitmap(result.image.toBitmap())
                        }
                    }
                }).build()

            thumbDisposable = mImageLoader?.enqueue(thumbReq)
        }

        var imgPlaceholder = mPlaceholderImage
        if (thumbMemoryCache != null) {
            imgPlaceholder = thumbMemoryCache.image
        }

        //无论如何,都要加载正图。
        val imageReq = ImageRequest.Builder(mCtx!!)
            .data(data.uri)
            .memoryCacheKey(imageMemoryCacheKey)
            .size(MainActivity.IMAGE_SIZE)
            .target(myIv)
            .placeholder(imgPlaceholder)
            .error(mErrorBmp!!.asImage())
            .listener(object : ImageRequest.Listener {
                override fun onSuccess(request: ImageRequest, result: SuccessResult) {
                    isHighQualityLoaded = true
                    thumbDisposable?.dispose()

                    Log.d(TAG, "image onSuccess ${result.dataSource} $imageItem ${calMemoryCache()}")
                }
            }).build()

        mImageLoader?.enqueue(imageReq)
    }

    private fun getMemoryCache(key: MemoryCache.Key): MemoryCache.Value? {
        return mImageLoader?.memoryCache?.get(key)
    }

    private fun calMemoryCache(): String {
        return "${mImageLoader?.memoryCache?.size} / ${mImageLoader?.memoryCache?.maxSize}"
    }
}

class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    var image = itemView as MyIV
}

class MyIV : AppCompatImageView {
    companion object {
        const val TAG = "fly/MyIV"
    }

    private var mSize = 0
    private var mCtx: Context? = null

    constructor(ctx: Context, size: Int) : super(ctx) {
        mCtx = ctx
        mSize = size
        scaleType = ScaleType.CENTER_CROP
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(mSize, mSize)
    }
}
Kotlin 复制代码
import android.net.Uri

class Item {
    companion object {
        const val THUMB = 0
        const val IMG = 1
    }

    var uri: Uri? = null
    var path: String? = null
    var lastModified = 0L
    var width = 0
    var height = 0

    var position = -1
    var type = -1  //0,缩略图。 1,正图image。-1,未知。

    constructor(uri: Uri, path: String) {
        this.uri = uri
        this.path = path
    }

    override fun toString(): String {
        return "Item(uri=$uri, path=$path, lastModified=$lastModified, width=$width, height=$height, position=$position, type=$type)"
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import android.util.Size
import coil3.ImageLoader
import coil3.asImage
import coil3.decode.DataSource
import coil3.fetch.FetchResult
import coil3.fetch.Fetcher
import coil3.fetch.ImageFetchResult
import coil3.request.Options

/**
 * 例如 FileUriFetcher
 */
class ThumbFetcher(private val ctx: Context, private val thumbItem: Item, private val options: Options) : Fetcher {
    companion object {
        const val TAG = "fly/ThumbFetcher"
    }

    override suspend fun fetch(): FetchResult {
        var bmp: Bitmap? = null
        val t = System.currentTimeMillis()
        try {
            bmp = ctx.contentResolver.loadThumbnail(thumbItem.uri!!, Size(MainActivity.THUMB_WIDTH, MainActivity.THUMB_HEIGHT), null)
            Log.d(TAG, "loadThumbnail time cost=${System.currentTimeMillis() - t} $thumbItem")
        } catch (e: Exception) {
            Log.e(TAG, "e=$e ThumbItem=$thumbItem")
        }

        return ImageFetchResult(
            bmp?.asImage()!!,
            true,
            dataSource = DataSource.DISK
        )
    }

    class Factory(private val ctx: Context) : Fetcher.Factory<Item> {
        override fun create(
            data: Item,
            options: Options,
            imageLoader: ImageLoader,
        ): Fetcher {
            return ThumbFetcher(ctx, data, options)
        }
    }
}

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

相关推荐
alexhilton5 小时前
Android的Agent优先时代:构建时vs运行时
android·kotlin·android jetpack
Cutecat_6 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
2601_961765297 小时前
【分享】PlayerPro媒体音乐播放器 完整专业版
android·媒体
JohnnyDeng949 小时前
【Android】Android 包体积优化:R8/ProGuard 深度配置全攻略
android·性能优化·kotlin·jetpack
故渊at9 小时前
第九板块:Android 多媒体体系 | 第二十四篇:Camera Service 与 HAL3 成像流水线
android·camera·多媒体体系·hal3
Jinkxs12 小时前
Python基础 - 初识内置函数 Python自带的便捷工具
android·java·python
私人珍藏库13 小时前
【Android】VLLO-韩国热门手机剪辑APP
android·app·工具·软件·多功能
Cloud_Shy61813 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第六章 Item 40 - 43)
android·开发语言·人工智能·笔记·python·学习方法
AFinalStone14 小时前
Android12 U盘插拔链路源码全解析(五):Framework层(下) StorageManagerService
android·frameworks
林九生15 小时前
【实用技巧】MySQL 绿色版一键路径更新脚本详解 —— update_path.bat 深度解析
android·数据库·mysql