import android.net.Uri
open class MyData {
var uri: Uri? = null
var path: String? = null
var lastModified = 0L
var width = 0
var height = 0
var position = -1
var type = -1 //-1未知。1,普通图。2,视频。
constructor(uri: Uri?, path: String?, type: Int = -1) {
this.uri = uri
this.path = path
this.type = type
}
override fun toString(): String {
return "MyData(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.graphics.Canvas
import android.graphics.Color
import android.graphics.Picture
import android.graphics.RectF
import android.graphics.drawable.ColorDrawable
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.graphics.toRect
import androidx.lifecycle.lifecycleScope
import coil3.ImageLoader
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.Disposable
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.toBitmap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MyGridImgView : AppCompatImageView {
companion object {
const val TAG = "fly/MyGridImgView"
}
private var mCtx: Context? = null
private var mImageLoader: ImageLoader? = null
private var mRowHeight: Int = 0 //正方形小格子,高度也就是宽度
private var mRealSize: Int = 0
private var mBmp = mutableListOf<DataBean>()
private var mThumbDisposables = mutableListOf<Disposable?>()
private var mThumbError: Bitmap? = null
constructor(ctx: Context, il: ImageLoader?, screenWidth: Int, thumbError: Bitmap?) : super(ctx) {
mCtx = ctx
mImageLoader = il
mThumbError = thumbError
val h = screenWidth.toFloat() / MainActivity.ROW_SIZE
Log.d(TAG, "小格子实际宽度=$h")
mRowHeight = h.toInt()
scaleType = ScaleType.FIT_START
}
fun setData(data: ArrayList<MyData>) {
clear()
setImageDrawable(ColorDrawable(Color.DKGRAY))
mRealSize = data.size
(mCtx as AppCompatActivity).lifecycleScope.launch(Dispatchers.IO) {
var loadCount = 0
val t = System.currentTimeMillis()
// this for-loop will cost some time
// if no memory cache, for-loop more , time cost more
data.forEachIndexed { _, myData ->
val thumbItem = Item(myData)
val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
val thumbMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(thumbMemoryCacheKey)
// be careful, this block will cost time
// time cost point
if (thumbMemoryCache != null) {
Log.d(TAG, "命中缓存 ${MyCoilManager.INSTANCE.calMemoryCache()}")
loadCount++
refresh(loadCount, thumbMemoryCache.image.toBitmap())
} else {
val req = ImageRequest.Builder(mCtx!!)
.data(thumbItem)
.size(MainActivity.THUMB_SIZE, MainActivity.THUMB_SIZE)
.memoryCacheKey(thumbMemoryCacheKey)
.memoryCachePolicy(CachePolicy.WRITE_ONLY)
.listener(object : ImageRequest.Listener {
override fun onSuccess(request: ImageRequest, result: SuccessResult) {
Log.d(TAG, "onSuccess ${MyCoilManager.INSTANCE.calMemoryCache()}")
loadCount++
refresh(loadCount, result.image.toBitmap())
}
override fun onError(request: ImageRequest, result: ErrorResult) {
Log.e(TAG, "onError")
loadCount++
refresh(loadCount, mThumbError)
}
}).build()
val d = mImageLoader?.enqueue(req)
mThumbDisposables.add(d)
}
}
Log.d(TAG, "forEachIndexed 耗时=${System.currentTimeMillis() - t}")
}
}
private fun refresh(loadCount: Int, bmp: Bitmap?) {
val bean = DataBean(bmp)
mBmp.add(bean)
if (loadCount == mRealSize) {
val jBmp = joinBitmap()
(mCtx as AppCompatActivity).lifecycleScope.launch(Dispatchers.Main) {
setImageBitmap(jBmp)
}
mBmp.clear()
}
}
data class DataBean(var bitmap: Bitmap?)
private fun joinBitmap(): Bitmap {
val bmp = Bitmap.createBitmap(mRowHeight * mRealSize, mRowHeight, Bitmap.Config.RGB_565)
val canvas = Canvas(bmp)
canvas.drawColor(Color.LTGRAY)
val bitmaps = mBmp.toMutableList()
bitmaps.forEachIndexed { idx, dataBean ->
if (dataBean.bitmap != null) {
if (Bitmap.Config.HARDWARE == dataBean.bitmap!!.config) {
Log.d(TAG, "Bitmap.Config.HARDWARE")
dataBean.bitmap = convert(dataBean.bitmap)
}
val w = dataBean.bitmap!!.width
val h = dataBean.bitmap!!.height
val mini = Math.min(w, h)
val left = (w - mini) / 2f
val top = (h - mini) / 2f
val right = (w + mini) / 2f
val bottom = (h + mini) / 2f
val srcRct = RectF(left, top, right, bottom)
val dstRctLeft = idx * mRowHeight.toFloat()
val dstRct = RectF(dstRctLeft, 0f, dstRctLeft + mRowHeight, mRowHeight.toFloat())
canvas.drawBitmap(dataBean.bitmap!!, srcRct.toRect(), dstRct.toRect(), null)
}
}
bitmaps.clear()
return bmp
}
private fun convert(src: Bitmap?): Bitmap {
val w = src?.width
val h = src?.height
val picture = Picture()
val canvas = picture.beginRecording(w!!, h!!)
canvas.drawBitmap(src, 0f, 0f, null)
picture.endRecording()
return Bitmap.createBitmap(picture, w, h, Bitmap.Config.RGB_565)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(mRowHeight * mRealSize, mRowHeight)
}
private fun clear() {
mThumbDisposables.forEach {
it?.dispose()
}
mThumbDisposables.clear()
}
class Item(val data: MyData) {
companion object {
//内存中的标记
const val MEM_THUMB = 0
const val MEM_IMAGE = 1
}
var memory = -1
override fun toString(): String {
return "Item(data=$data, memory=$memory)"
}
}
}
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 MyThumbFetcher(private val ctx: Context, private val item: MyGridImgView.Item, private val options: Options) : Fetcher {
companion object {
const val TAG = "fly/MyThumbFetcher"
}
override suspend fun fetch(): FetchResult {
var bmp: Bitmap? = null
val t = System.currentTimeMillis()
try {
bmp = ctx.contentResolver.loadThumbnail(item.data.uri!!, Size(MainActivity.THUMB_SIZE, MainActivity.THUMB_SIZE), null)
Log.d(TAG, "loadThumbnail time cost=${System.currentTimeMillis() - t} $item ${MyCoilManager.INSTANCE.calMemoryCache()}")
} catch (e: Exception) {
Log.e(TAG, "e=$e Item=$item")
}
return ImageFetchResult(
bmp?.asImage()!!,
true,
dataSource = DataSource.DISK
)
}
class Factory(private val ctx: Context) : Fetcher.Factory<MyGridImgView.Item> {
override fun create(
item: MyGridImgView.Item,
options: Options,
imageLoader: ImageLoader,
): Fetcher {
return MyThumbFetcher(ctx, item, options)
}
}
}