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

相关推荐
懒人村杂货铺2 小时前
Android BLE 扫描完整实战
android
TeleostNaCl5 小时前
如何安装 Google 通用的驱动以便使用 ADB 和 Fastboot 调试(Bootloader)设备
android·经验分享·adb·android studio·android-studio·android runtime
fatiaozhang95275 小时前
中国移动浪潮云电脑CD1000-系统全分区备份包-可瑞芯微工具刷机-可救砖
android·网络·电脑·电视盒子·刷机固件·机顶盒刷机
低调小一6 小时前
Swift 语法学习指南 - 与 Kotlin 对比
微信·kotlin·swift
2501_915918416 小时前
iOS 开发全流程实战 基于 uni-app 的 iOS 应用开发、打包、测试与上架流程详解
android·ios·小程序·https·uni-app·iphone·webview
lichong9516 小时前
【混合开发】vue+Android、iPhone、鸿蒙、win、macOS、Linux之dist打包发布在Android工程asserts里
android·vue.js·iphone
Android出海7 小时前
Android 15重磅升级:16KB内存页机制详解与适配指南
android·人工智能·新媒体运营·产品运营·内容运营
一只修仙的猿7 小时前
毕业三年后,我离职了
android·面试
编程乐学7 小时前
安卓非原创--基于Android Studio 实现的新闻App
android·ide·android studio·移动端开发·安卓大作业·新闻app
雅雅姐8 小时前
Android14 init.rc中on boot阶段操作4
android