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.launchclass MainActivity : AppCompatActivity() {companion object {const val THUMB_WIDTH = 150const val THUMB_HEIGHT = 150const val ROW_SIZE = 16const 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.VERTICALval imageLoader = MyCoilManager.INSTANCE.getImageLoader(this)val adapter = MyAdapter(this, imageLoader)rv.adapter = adapterrv.layoutManager = layoutManagerrv.setItemViewCacheSize(ROW_SIZE * 10)rv.recycledViewPool.setMaxRecycledViews(0, ROW_SIZE * 10)val ctx = thislifecycleScope.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.sizeLog.d(TAG, "總數量=$total")lists.shuffle()val sliceLists = sliceDataList(lists)lifecycleScope.launch(Dispatchers.Main) {adapter.dataChanged(sliceLists)}val probability = 0.8flists.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.THUMBval 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: Intval lists = ArrayList<ArrayList<MyData>>()for (i in data.indices step ROW_SIZE) {val temp = ArrayList<MyData>()k = 0for (j in 0 until ROW_SIZE) {k = i + jif (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>()//讀取視頻Videoval 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.Uriclass Item {companion object {const val THUMB = 0const val IMG = 1}var uri: Uri? = nullvar path: String? = nullvar lastModified = 0Lvar width = 0var height = 0var position = -1var type = -1 //0,縮略圖。 1,正圖image。-1,未知。constructor(uri: Uri, path: String) {this.uri = urithis.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.MyDataclass MyAdapter : RecyclerView.Adapter<MyAdapter.ImageHolder> {private var mCtx: Context? = nullprivate var mImageLoader: ImageLoader? = nullprivate var mItems = ArrayList<ArrayList<MyData>>()companion object {const val TAG = "fly/ImageAdapter"}constructor(ctx: Context, il: ImageLoader?) : super() {mCtx = ctxmImageLoader = il}fun dataChanged(items: ArrayList<ArrayList<MyData>>) {this.mItems = itemsnotifyDataSetChanged()}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.SingletonImageLoaderclass 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.Fileclass MyCoilManager {companion object {const val TAG = "fly/MyCoilManager"val INSTANCE by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MyCoilManager() }}private var mImageLoader: ImageLoader? = nullprivate var memoryCacheMaxSize = 0Lfun 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.maxSizereturn 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?.sizereturn "${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.MyDataclass MyImgView : AppCompatImageView {companion object {const val TAG = "fly/MyImgView"//整數相除,精度損失的平衡因子const val BALANCE_FACTOR = 1}private var mCtx: Context? = nullprivate var mImageLoader: ImageLoader? = nullprivate var mHeight: Int = 0private var mRealSize: Int = 0private var mBmp = mutableListOf<DataBean>()constructor(ctx: Context, il: ImageLoader?) : super(ctx) {mCtx = ctxmImageLoader = ilmHeight = resources.displayMetrics.widthPixels / MainActivity.ROW_SIZE + BALANCE_FACTORscaleType = ScaleType.CENTER_CROP}fun setData(data: ArrayList<MyData>) {mRealSize = data.sizevar loadCount = 0data.forEachIndexed { _, myData ->val thumbItem = Item(uri = myData.uri, path = myData.path)thumbItem.type = Item.THUMBval 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.widthval h = dataBean.bitmap.heightval mini = Math.min(w, h)val left = (w - mini) / 2fval top = (h - mini) / 2fval right = (w + mini) / 2fval bottom = (h + mini) / 2fval 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? = nullval 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 Coil3縮略圖、默認占位圖placeholder、error加載錯誤顯示,Kotlin(5)_coil android-CSDN博客文章瀏覽閱讀972次,點贊18次,收藏18次。遺留問題,配置的disk cache似乎沒有work,指定的磁盤緩存文件路徑生成是生成了,但是app跑起來運行后(圖正常顯示),里面是空的。遺留問題,配置的disk cache似乎沒有work,指定的磁盤緩存文件路徑生成是生成了,但是app跑起來運行后(圖正常顯示),里面是空的。遺留問題,配置的disk cache似乎沒有work,指定的磁盤緩存文件路徑生成是生成了,但是app跑起來運行后(圖正常顯示),里面是空的。2、現在分別使用縮略圖內存緩存和正圖內存緩存,感覺應該可以合并,只使用一套內存緩存。_coil androidhttps://blog.csdn.net/zhangphil/article/details/146079600
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