- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
- plugins {
- id 'org.jetbrains.kotlin.kapt'
- }
-
-
- implementation 'com.github.bumptech.glide:glide:4.16.0'
- kapt 'com.github.bumptech.glide:compiler:4.16.0'
- import android.content.Context
- import android.util.Log
- import com.bumptech.glide.GlideBuilder
- import com.bumptech.glide.annotation.GlideModule
- import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
- import com.bumptech.glide.load.engine.cache.MemorySizeCalculator
- import com.bumptech.glide.load.engine.executor.GlideExecutor
- import com.bumptech.glide.module.AppGlideModule
-
-
- @GlideModule
- class MyGlideModule : AppGlideModule() {
-
- override fun applyOptions(context: Context, builder: GlideBuilder) {
- super.applyOptions(context, builder)
- builder.setLogLevel(Log.DEBUG)
-
- val memoryCacheScreens = 200F
- val maxSizeMultiplier = 0.8F
-
- val calculator = MemorySizeCalculator.Builder(context)
- .setMemoryCacheScreens(memoryCacheScreens)
- .setBitmapPoolScreens(memoryCacheScreens)
- .setMaxSizeMultiplier(maxSizeMultiplier)
- .setLowMemoryMaxSizeMultiplier(maxSizeMultiplier * 0.8F)
- .setArrayPoolSize((1024 * 1024 * memoryCacheScreens).toInt())
- .build()
-
- builder.setMemorySizeCalculator(calculator)
-
- val diskCacheSize = 1024 * 1024 * 2000L
- builder.setDiskCache(InternalCacheDiskCacheFactory(context, diskCacheSize))
-
-
- val mSourceExecutor = GlideExecutor.newSourceBuilder()
- .setUncaughtThrowableStrategy(GlideExecutor.UncaughtThrowableStrategy.LOG)
- .setThreadCount(4)
- //.setThreadTimeoutMillis(1000) //线程读写超时时间。
- .setName("fly-SourceExecutor")
- .build()
-
- val mDiskCacheBuilder = GlideExecutor.newDiskCacheBuilder()
- .setThreadCount(1)
- //.setThreadTimeoutMillis(1000) //线程读写超时时间。
- .setName("fly-DiskCacheBuilder")
- .build()
-
- val mAnimationExecutor = GlideExecutor.newDiskCacheBuilder()
- .setThreadCount(1)
- //.setThreadTimeoutMillis(1000) //线程读写超时时间。
- .setName("fly-AnimationExecutor")
- .build()
-
- builder.setSourceExecutor(mSourceExecutor)
- builder.setDiskCacheExecutor(mDiskCacheBuilder)
- builder.setAnimationExecutor(mAnimationExecutor)
- }
-
- override fun isManifestParsingEnabled(): Boolean {
- return false
- }
- }
- import android.content.Context
- import android.graphics.Bitmap
- import android.graphics.Canvas
- import android.graphics.drawable.Drawable
- import android.os.Bundle
- import android.provider.MediaStore
- import android.util.AttributeSet
- import android.util.Log
- import android.view.LayoutInflater
- import android.view.View
- import android.view.ViewGroup
- import android.widget.TextView
- import androidx.appcompat.app.AppCompatActivity
- import androidx.core.content.ContextCompat
- import androidx.lifecycle.lifecycleScope
- import androidx.recyclerview.widget.GridLayoutManager
- import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
- import androidx.recyclerview.widget.LinearLayoutManager
- import androidx.recyclerview.widget.RecyclerView
- import com.bumptech.glide.request.target.CustomTarget
- import com.bumptech.glide.request.transition.Transition
- import com.google.android.material.imageview.ShapeableImageView
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.launch
- import kotlinx.coroutines.withContext
-
-
- class MainActivity : AppCompatActivity() {
- companion object {
- const val TAG = "fly"
-
- const val VIEW_TYPE = 0
- const val GROUP_TYPE = 1
-
- const val SPAN_COUNT = 16
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- val rv = findViewById
(R.id.rv) - val layoutManager = GridLayoutManager(this, SPAN_COUNT)
- layoutManager.orientation = LinearLayoutManager.VERTICAL
- rv.layoutManager = layoutManager
-
- val adapter = MyAdapter(this)
- rv.adapter = adapter
-
- rv.setHasFixedSize(true)
-
- layoutManager.spanSizeLookup = object : SpanSizeLookup() {
- override fun getSpanSize(position: Int): Int {
- return SPAN_COUNT
- }
- }
-
- lifecycleScope.launch(Dispatchers.IO) {
- val items = readAllImage(this@MainActivity)
- val lists: ArrayList
= sliceDataList(items) - withContext(Dispatchers.Main) {
- adapter.dataChanged(lists)
- }
- }
- }
-
- class MyAdapter : RecyclerView.Adapter<MyVH> {
- private var mItems = arrayListOf
() - private var mContext: Context? = null
-
- constructor(ctx: Context) {
- this.mContext = ctx
- }
-
- fun dataChanged(items: ArrayList<AdapterData>) {
- this.mItems = items
- notifyDataSetChanged()
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
- return when (viewType) {
- GROUP_TYPE -> {
- val view = LayoutInflater.from(mContext!!).inflate(android.R.layout.simple_list_item_1, parent, false)
- view.setBackgroundColor(ContextCompat.getColor(mContext!!, android.R.color.holo_orange_light))
- MyVH(view)
- }
-
- else -> {
- val view = BatchBitmapView(mContext!!)
- MyVH(view)
- }
- }
- }
-
- override fun getItemCount(): Int {
- return mItems.size
- }
-
- override fun getItemViewType(position: Int): Int {
- return mItems[position].type
- }
-
- override fun onBindViewHolder(holder: MyVH, position: Int) {
- Log.d(TAG, "onBindViewHolder $position")
-
- when (getItemViewType(position)) {
- GROUP_TYPE -> {
- holder.itemView.findViewById
(android.R.id.text1).text = "$position GROUP" - }
-
- else -> {
- (holder.itemView as BatchBitmapView).setRowBitmapData(mItems[position].mediaData, position)
- }
- }
- }
- }
-
- class MyVH : RecyclerView.ViewHolder {
- constructor(itemView: View) : super(itemView) {
-
- }
- }
-
- class AdapterData(var type: Int = GROUP_TYPE) {
- var mediaData: ArrayList
? = null - }
-
- class MediaData(var path: String, var index: Int)
-
- private fun sliceDataList(data: ArrayList<MediaData>): ArrayList
{ - var k: Int
- val lists = ArrayList
() - for (i in data.indices step BatchBitmapView.ROW_SIZE) {
- lists.add(AdapterData(GROUP_TYPE))
-
- val temp = ArrayList
() -
- k = 0
- for (j in 0 until BatchBitmapView.ROW_SIZE) {
- k = i + j
- if (k >= data.size) {
- break
- }
- temp.add(data[k])
- }
-
- val adapterData = AdapterData(VIEW_TYPE)
- adapterData.mediaData = temp
- lists.add(adapterData)
- }
-
- return lists
- }
-
- private fun readAllImage(context: Context): ArrayList
{ - val photos = ArrayList
() -
- //读取所有图片
- val cursor = context.contentResolver.query(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null
- )
-
- var index = 0
- while (cursor!!.moveToNext()) {
- //路径 uri
- val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
-
- //图片名称
- //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
- //图片大小
- //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))
-
- photos.add(MediaData(path, index++))
- }
- cursor.close()
-
- return photos
- }
- }
-
- class BatchBitmapView : ShapeableImageView {
- private val mData = mutableListOf
() - private val mScreenWidth = resources.displayMetrics.widthPixels
- private val mTargets = mutableListOf
>() - private var mContext: Context? = null
-
- companion object {
- const val TAG = "BatchBitmapView"
- const val ROW_SIZE = MainActivity.SPAN_COUNT //一行多少个bitmap
- const val IMAGE_SIZE = 50 //每个小格子图片的尺寸
- }
-
- constructor(
- ctx: Context,
- attributeSet: AttributeSet? = null,
- defStyleAttr: Int = 0
- ) : super(ctx, attributeSet, defStyleAttr) {
- mContext = ctx
- }
-
- fun setRowBitmapData(rows: ArrayList<MainActivity.MediaData>?, position: Int) {
- Log.d(TAG, "setRowBitmapData $position")
-
- mData.clear()
-
- Log.d(TAG, "mTargets.size=${mTargets.size}")
- mTargets.forEach {
- GlideApp.with(context).clear(it) //如果不清除,会发生有些图错放位置。
- }
- mTargets.clear() //mTargets上下滑动列表会越来越大,清空,一直保持ROW_SIZE.
-
- rows?.forEachIndexed { index, data ->
- val target = object : CustomTarget
() { - override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
- val bean = DataBean(resource)
- mData.add(bean)
- postInvalidate()
- }
-
- override fun onLoadCleared(placeholder: Drawable?) {
-
- }
- }
-
- GlideApp.with(mContext!!)
- .asBitmap()
- .centerCrop()
- .override(IMAGE_SIZE)
- .load(data.path)
- .into(target)
-
- mTargets.add(target)
- }
- }
-
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- setMeasuredDimension(mScreenWidth, IMAGE_SIZE)
- }
-
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
-
- mData.forEachIndexed { index, dataBean ->
- canvas.save()
- val left = (mScreenWidth / ROW_SIZE) * index
- canvas.drawBitmap(dataBean.bitmap, left.toFloat(), 0f, null)
- canvas.restore()
- }
- }
-
- data class DataBean(val bitmap: Bitmap)
- }
有一个遗留问题,每行加载16张图片,以行为原子单位。后面可以考虑另外一种实现,group分组标签单独占一行,图片可以一大片一大片的占据多行,每行16张。
Android Glide自定义AppCompatImageView切分成若干小格子,每个小格子onDraw绘制Bitmap,Kotlin(1)_android appcompatimageview-CSDN博客文章浏览阅读1.2k次,点赞18次,收藏21次。垂直方向的RecyclerView,每行一个AppCompatImageView,每个AppCompatImageView被均匀切割成n个小格子, 每个小格子通过Glide加载出来Bitmap,然后onDraw绘制整行。//读取所有图片!//路径 uri//图片名称//图片大小= null,const val ROW_SIZE = 16 //一行多少个bitmap。_android appcompatimageviewhttps://zhangphil.blog.csdn.net/article/details/134519527Android GridLayoutManager SpanSizeLookup dynamic set grid cell column count,Kotlin-CSDN博客文章浏览阅读575次,点赞6次,收藏7次。Android RecyclerView的StaggeredGridLayoutManager实现交错排列的子元素分组先看实现的结果如图:设计背景:现在的产品对设计的需求越来越多样化,如附录文章2是典型的联系人分组RecyclerView,子元素排列到一个相同的组,但是有些时候,UI要求把这些元素不是垂直方向的,而是像本文开头的图中所示样式排列,这就需要用StaggeredGridLayoutMa_staggeredgridlayoutmanager。
https://blog.csdn.net/zhangphil/article/details/137694645Android RecyclerView性能优化及Glide流畅加载图片丢帧率低的一种8宫格实现,Kotlin-CSDN博客文章浏览阅读690次,点赞26次,收藏11次。【代码】Android Paging 3,kotlin(1)在实际的开发中,虽然Glide解决了快速加载图片的问题,但还有一个问题悬而未决:比如用户的头像,往往用户的头像是从服务器端读出的一个普通矩形图片,但是现在的设计一般要求在APP端的用户头像显示成圆形头像,那么此时虽然Glide可以加载,但加载出来的是一个矩形,如果要Glide_android 毛玻璃圆角。现在结合他人的代码加以修改,给出一个以原始图形中心为原点,修剪图片为头像的工具类,此类可以直接在布局文件中加载使用,比。文章浏览阅读670次。
https://blog.csdn.net/zhangphil/article/details/137653692