Android RecyclerView GridLayoutManager,Glide load Photo,classification by date tag, Kotlin
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.view.setPadding
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.RecyclerListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "fly"
const val SIZE = 500
const val VIEW_TYPE = 0
const val DATE_TYPE = 1
const val SPAN_COUNT = 12
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rv: RecyclerView = findViewById(R.id.rv)
rv.setHasFixedSize(true)
rv.setItemViewCacheSize(SPAN_COUNT * 10)
rv.setRecyclerListener(object : RecyclerListener {
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
Log.d(TAG, "onViewRecycled ${holder.adapterPosition}")
GlideApp.with(holder.itemView.context).clear((holder as MyVH).itemView)
}
})
val layoutManager = GridLayoutManager(this, SPAN_COUNT)
layoutManager.orientation = LinearLayoutManager.VERTICAL
rv.layoutManager = layoutManager
val adapter = MyAdapter(this)
rv.adapter = adapter
lifecycleScope.launch(Dispatchers.IO) {
val items = readAllImage(this@MainActivity)
items.sortByDescending {
it.dateModified
}
val lists = items.distinctBy {
it.dateString
}
lists.forEach { it_lists ->
val idx = items.indexOfFirst {
it_lists.dateString == it.dateString
}
val data = MyData()
data.type = DATE_TYPE
data.dateString = it_lists.dateString
items.add(idx, data) //不要直接加 it_Lists,这里面涉及到List的深拷贝/浅拷贝问题。
}
withContext(Dispatchers.Main) {
adapter.dataChanged(items)
}
}
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (adapter.getItemViewType(position) == DATE_TYPE) {
//group,标题
SPAN_COUNT
} else {
//单个小格子
1
}
}
}
}
class MyAdapter : RecyclerView.Adapter<MyVH> {
private var mItems = arrayListOf<MyData>()
private var mContext: Context? = null
private var mPlaceholder: Drawable? = null
private var mError: Drawable? = null
constructor(ctx: Context) {
mContext = ctx
mPlaceholder = ContextCompat.getDrawable(mContext!!, android.R.drawable.ic_menu_gallery)
mError = ContextCompat.getDrawable(mContext!!, android.R.drawable.stat_notify_error)
}
fun dataChanged(items: ArrayList<MyData>) {
this.mItems = items
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
var v: View? = null
if (viewType == VIEW_TYPE) {
v = MyIV(mContext!!)
} else {
v = LayoutInflater.from(mContext!!).inflate(android.R.layout.simple_list_item_1, null)
v.setBackgroundColor(Color.LTGRAY)
}
return MyVH(v!!)
}
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 ${holder.adapterPosition}")
val type = getItemViewType(position)
if (type == VIEW_TYPE) {
val path = mItems[holder.adapterPosition].path
GlideApp.with(mContext!!)
.asBitmap()
.load(path)
.centerCrop()
.override(SIZE)
.error(mError)
.placeholder(mPlaceholder)
.into(holder.itemView as ImageView)
} else if (type == DATE_TYPE) {
holder.itemView.findViewById<TextView>(android.R.id.text1).text = "${mItems[position].dateString}"
}
}
}
class MyVH : RecyclerView.ViewHolder {
constructor(itemView: View) : super(itemView) {
}
}
class MyIV : AppCompatImageView {
constructor(ctx: Context) : super(ctx) {
setPadding(1)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val w = MeasureSpec.getSize(widthMeasureSpec)
val h = MeasureSpec.getSize(heightMeasureSpec)
val size = Math.max(w, h) //取w,h的最大值。
setMeasuredDimension(size, size) //使得ImageView为正方形。
}
}
class MyData {
var type = VIEW_TYPE
var dateModified: Long? = 0L
var dateString: String? = null
var path: String? = null
var index: Int? = null
override fun toString(): String {
return "MyData(type=$type, dateModified=$dateModified, dateString=$dateString, path=$path, index=$index)"
}
}
private fun readAllImage(context: Context): ArrayList<MyData> {
val photos = ArrayList<MyData>()
//读取所有图片
val cursor = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null
)
var index = 0
val sdf = SimpleDateFormat("yyyy-MM-dd")
while (cursor!!.moveToNext()) {
//路径 uri
val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
if (TextUtils.isEmpty(path)) {
continue
}
val dateModified = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED))
//图片名称
//val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
//图片大小
//val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))
val dateStr = sdf.format(dateModified?.toLong()!! * 1000)
val data = MyData()
data.type = VIEW_TYPE
data.path = path
data.dateModified = dateModified.toLong()
data.dateString = dateStr
data.index = index++
photos.add(data)
}
cursor.close()
return photos
}
}
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 mSourceBuilder = GlideExecutor.newSourceBuilder()
.setUncaughtThrowableStrategy(GlideExecutor.UncaughtThrowableStrategy.LOG)
.setThreadCount(2)
//.setThreadTimeoutMillis(1000) //线程读写超时时间。
.setName("fly-Source")
.build()
val mDiskCacheBuilder = GlideExecutor.newDiskCacheBuilder()
.setThreadCount(1)
//.setThreadTimeoutMillis(1000) //线程读写超时时间。
.setName("fly-DiskCache")
.build()
val mAnimationBuilder = GlideExecutor.newAnimationBuilder()
.setThreadCount(1)
//.setThreadTimeoutMillis(1000) //线程读写超时时间。
.setName("fly-Animation")
.build()
builder.setSourceExecutor(mSourceBuilder)
builder.setDiskCacheExecutor(mDiskCacheBuilder)
builder.setAnimationExecutor(mAnimationBuilder)
}
override fun isManifestParsingEnabled(): Boolean {
return false
}
}