Android Coil3阶梯preload批量Bitmap拼接扁平宽图,Kotlin
<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" />
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")
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.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil3.ImageLoader
import coil3.memory.MemoryCache
import coil3.request.ImageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
companion object {
const val THUMB_WIDTH = 150
const val THUMB_HEIGHT = 150
const val ROW_SIZE = 16
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 = LinearLayoutManager(this)
layoutManager.orientation = LinearLayoutManager.VERTICAL
val imageLoader = MyCoilManager.INSTANCE.getImageLoader(this)
val adapter = MyAdapter(this, imageLoader)
rv.adapter = adapter
rv.layoutManager = layoutManager
rv.setItemViewCacheSize(ROW_SIZE * 10)
rv.recycledViewPool.setMaxRecycledViews(0, ROW_SIZE * 10)
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)
val total = lists.size
Log.d(TAG, "总数量=$total")
lists.shuffle()
val sliceLists = sliceDataList(lists)
lifecycleScope.launch(Dispatchers.Main) {
adapter.dataChanged(sliceLists)
}
val probability = 0.8f
lists.forEachIndexed { idx, myData ->
if (idx in 500..2000) {
Log.d(TAG, "$idx/$total preload")
preload(imageLoader, myData)
} else if (2000 < idx && Math.random() <= probability) {
Log.d(TAG, "$idx/$total preload")
preload(imageLoader, myData)
}
}
}
}
private fun preload(imageLoader: ImageLoader, myData: MyData) {
val thumbItem = Item(uri = myData.uri, path = myData.path)
thumbItem.type = Item.THUMB
val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
val thumbMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(thumbMemoryCacheKey)
if (thumbMemoryCache == null) {
val thumbReq = ImageRequest.Builder(this)
.data(thumbItem)
.size(THUMB_WIDTH, THUMB_HEIGHT)
.memoryCacheKey(thumbMemoryCacheKey)
.build()
imageLoader.enqueue(thumbReq)
}
}
class MyData(var path: String, var uri: Uri)
private fun sliceDataList(data: ArrayList<MyData>): ArrayList<ArrayList<MyData>> {
var k: Int
val lists = ArrayList<ArrayList<MyData>>()
for (i in data.indices step ROW_SIZE) {
val temp = ArrayList<MyData>()
k = 0
for (j in 0 until ROW_SIZE) {
k = i + j
if (k >= data.size) {
break
}
temp.add(data[k])
}
lists.add(temp)
}
return lists
}
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
}
}
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)"
}
}
import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil3.ImageLoader
import com.appdemo.MainActivity.MyData
class MyAdapter : RecyclerView.Adapter<MyAdapter.ImageHolder> {
private var mCtx: Context? = null
private var mImageLoader: ImageLoader? = null
private var mItems = ArrayList<ArrayList<MyData>>()
companion object {
const val TAG = "fly/ImageAdapter"
}
constructor(ctx: Context, il: ImageLoader?) : super() {
mCtx = ctx
mImageLoader = il
}
fun dataChanged(items: ArrayList<ArrayList<MyData>>) {
this.mItems = items
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
val view = MyImgView(mCtx!!, mImageLoader)
return ImageHolder(view)
}
override fun onBindViewHolder(holder: ImageHolder, position: Int) {
holder.image.setData(mItems[position])
}
override fun getItemCount(): Int {
return mItems.size
}
class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var image = itemView as MyImgView
}
}
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)
}
}
import android.content.Context
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 java.io.File
class MyCoilManager {
companion object {
const val TAG = "fly/MyCoilManager"
val INSTANCE by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MyCoilManager() }
}
private var mImageLoader: ImageLoader? = null
private var memoryCacheMaxSize = 0L
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())
.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()
memoryCacheMaxSize = memoryCache.maxSize
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
}
fun getMemoryCache(key: MemoryCache.Key): MemoryCache.Value? {
return mImageLoader?.memoryCache?.get(key)
}
fun calMemoryCache(): String {
val sz = mImageLoader?.memoryCache?.size
return "${sz?.toFloat()!! / memoryCacheMaxSize.toFloat()} , $sz / $memoryCacheMaxSize"
}
}
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.RectF
import android.util.Log
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.graphics.toRect
import coil3.ImageLoader
import coil3.memory.MemoryCache
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.toBitmap
import com.appdemo.MainActivity.MyData
class MyImgView : AppCompatImageView {
companion object {
const val TAG = "fly/MyImgView"
//整数相除,精度损失的平衡因子
const val BALANCE_FACTOR = 1
}
private var mCtx: Context? = null
private var mImageLoader: ImageLoader? = null
private var mHeight: Int = 0
private var mRealSize: Int = 0
private var mBmp = mutableListOf<DataBean>()
constructor(ctx: Context, il: ImageLoader?) : super(ctx) {
mCtx = ctx
mImageLoader = il
mHeight = resources.displayMetrics.widthPixels / MainActivity.ROW_SIZE + BALANCE_FACTOR
scaleType = ScaleType.CENTER_CROP
}
fun setData(data: ArrayList<MyData>) {
mRealSize = data.size
var loadCount = 0
data.forEachIndexed { _, myData ->
val thumbItem = Item(uri = myData.uri, path = myData.path)
thumbItem.type = Item.THUMB
val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
val thumbMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(thumbMemoryCacheKey)
if (thumbMemoryCache == null) {
val thumbReq = ImageRequest.Builder(mCtx!!)
.data(thumbItem)
.size(MainActivity.THUMB_WIDTH, MainActivity.THUMB_HEIGHT)
.memoryCacheKey(thumbMemoryCacheKey)
.listener(object : ImageRequest.Listener {
override fun onSuccess(request: ImageRequest, result: SuccessResult) {
loadCount++
refresh(loadCount, result.image.toBitmap())
}
override fun onCancel(request: ImageRequest) {
Log.w(TAG, "onCancel")
loadCount++
refresh(loadCount, null)
}
override fun onError(request: ImageRequest, result: ErrorResult) {
Log.e(TAG, "onError")
loadCount++
refresh(loadCount, null)
}
}).build()
Log.d(TAG, "开始加载...")
mImageLoader?.enqueue(thumbReq)
} else {
Log.d(TAG, "命中缓存 ${MyCoilManager.INSTANCE.calMemoryCache()}")
loadCount++
refresh(loadCount, thumbMemoryCache.image.toBitmap())
}
}
}
private fun refresh(loadCount: Int, bmp: Bitmap?) {
val bean = DataBean(bmp)
mBmp.add(bean)
if (loadCount == mRealSize) {
val jBmp = joinBitmap()
this@MyImgView.setImageBitmap(jBmp)
mBmp.clear()
}
}
data class DataBean(val bitmap: Bitmap?)
private fun joinBitmap(): Bitmap {
val bmp = Bitmap.createBitmap(mHeight * mRealSize, mHeight, Bitmap.Config.RGB_565)
val canvas = Canvas(bmp)
canvas.drawColor(Color.LTGRAY)
mBmp.forEachIndexed { idx, dataBean ->
if (dataBean.bitmap != null) {
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 * mHeight.toFloat()
val dstRct = RectF(dstRctLeft, 0f, dstRctLeft + mHeight, mHeight.toFloat())
canvas.drawBitmap(dataBean.bitmap, srcRct.toRect(), dstRct.toRect(), null)
}
}
return bmp
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(mHeight * mRealSize, mHeight)
}
}
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 ${MyCoilManager.INSTANCE.calMemoryCache()}")
} 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空白宽平大Bitmap循环基于Rect小格子drawBitmap若干小Bitmap,Kotlin-CSDN博客文章浏览阅读731次,点赞13次,收藏11次。Android拼接合并图片生成长图代码实现合并两张图片,以第一张图片的宽度为标准,如果被合并的第二张图片宽度和第一张不同,那么就以第一张图片的宽度为准线,对第二张图片进行缩放。Android拼接合并图片生成长图代码实现合并两张图片,以第一张图片的宽度为标准,如果被合并的第二张图片宽度和第一张不同,那么就以第一张图片的宽度为准线,对第二张图片进行缩放。基础上,把剪切的区域从矩形Rect变为圆形的Path,当手指在上面的ImageView移动时候,下面同等大小对应的坐标区域显示“剪切”出来的圆形图。https://zhangphil.blog.csdn.net/article/details/144293203Android Glide批量加载Bitmap,拼接组装大Bitmap,更新单个AppCompatImageView,Kotlin(3)_kotlin glide批量加载网络图片为bitmap-CSDN博客文章浏览阅读590次,点赞5次,收藏4次。(2)即便显示出来,因为绘制是按照ROW_SIZE绘满一行,导致实际不满一行的位置显示为灰色占位颜色块。(1)当最后一行不满ROW_SIZE时候,根本就不会显示。_kotlin glide批量加载网络图片为bitmap
https://blog.csdn.net/zhangphil/article/details/144120564Android Glide批量加载Bitmap,拼接组装大Bitmap,更新单个AppCompatImageView,Kotlin(2)_kotlin 批量加载网络图片为bitmap-CSDN博客文章浏览阅读558次,点赞3次,收藏6次。本文介绍了如何在Android应用中使用Glide库将AppCompatImageView分割成小格子,并在每个格子上异步加载Bitmap并利用Canvas进行绘制,以提高性能。Android Glide自定义AppCompatImageView切分成若干小格子,每个小格子onDraw绘制Bitmap,Kotlin(1)_android appcompatimageview-CSDN博客。_kotlin 批量加载网络图片为bitmap
https://blog.csdn.net/zhangphil/article/details/144087919