Android Glide, first start based on loadThumbnail, Kotlin(二)
Android Glide, first start based on loadThumbnail, Kotlin(一)中有个小问题,通过loadThumbnail()采集到的缩略图真的就是整张图片的完整缩略图,直接放在正方形小格子里面,明显看到左右或者上下有空隙,因此,现在对这一情况改善,对loadThumbnail采集到的缩略图拉伸成标准的正方形图。
Kotlin
import android.content.ContentValues
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "glide-fly"
const val SIZE = 450
const val VIEW_TYPE = 0
const val DATE_TYPE = 1
const val SPAN_COUNT = 6
const val PAD_SIZE = 1
var FIRST_START = true
}
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) {
if ((holder as MyVH).itemView is MyIV) {
GlideApp.with(holder.itemView.context).clear(holder.itemView)
}
}
})
*/
val layoutManager = MyGridLayoutManager(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 MyGridLayoutManager : GridLayoutManager {
constructor(ctx: Context, cnt: Int) : super(ctx, cnt) {
}
override fun getExtraLayoutSpace(state: RecyclerView.State?): Int {
return 1000
}
}
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?
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
}
private fun getImageUri(context: Context, filePath: String): Uri? {
val cursor = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Images.Media._ID),
MediaStore.Images.Media.DATA + "=? ",
arrayOf(filePath),
null
)
return if (cursor != null && cursor.moveToFirst()) {
val id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
val baseUri = Uri.parse("content://media/external/images/media")
Uri.withAppendedPath(baseUri, "" + id)
} else {
val values = ContentValues()
values.put(MediaStore.Images.Media.DATA, filePath)
context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
}
override fun onBindViewHolder(holder: MyVH, position: Int) {
val type = getItemViewType(position)
if (type == VIEW_TYPE) {
val path = mItems[holder.adapterPosition].path
val miv = holder.itemView as MyIV
if (position > 50) {
FIRST_START = false
}
if (FIRST_START && position < 50) {
miv.setImageDrawable(mPlaceholder)
val uri = getImageUri(mContext!!, path!!)
QuickLoader.Instance().start(mContext!!, uri!!, miv)
} else {
GlideApp.with(mContext!!)
.asBitmap()
.load(path)
.centerCrop()
.override(SIZE)
.placeholder(mPlaceholder)
.error(mError)
.into(miv)
}
} 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(PAD_SIZE)
}
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
}
}
Kotlin
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.RectF
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import android.util.Size
import androidx.appcompat.app.AppCompatActivity
import com.pkg.name0220.MainActivity.Companion.SIZE
import java.util.concurrent.Executors
import kotlin.math.min
class QuickLoader {
private var mHandler: MsgHandler? = null
companion object {
const val THREAD_NUMBER = 4
const val WHAT = 0xf01
private val inst by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { QuickLoader() }
fun Instance() = inst
/**
* 把采集到的缩略图(可能是瘦长的,也可能是矮平的)拉伸为宽高为标准MainActivity.SIZE的Bitmap。
* 注意:Bitmap的API函数createScaledBitmap()相对耗时更多。
*/
fun scale(srcBmp: Bitmap): Bitmap {
val t = System.currentTimeMillis()
val bmp = Bitmap.createBitmap(SIZE, SIZE, Bitmap.Config.ARGB_8888)
val c = Canvas(bmp)
val width: Int = srcBmp.width
val height: Int = srcBmp.height
val bmpCenterX: Float = width / 2f
val bmpCenterY: Float = height / 2f
val minVal = min(width, height)
val srcRectF = RectF(
bmpCenterX - minVal / 2,
bmpCenterY - minVal / 2,
bmpCenterX + minVal / 2,
bmpCenterY + minVal / 2
)
val dstRectF = RectF(0f, 0f, SIZE.toFloat(), SIZE.toFloat())
val matrix = Matrix()
//把bitmap中心区域的那一块放到目标的dstRectF里面。
matrix.setRectToRect(srcRectF, dstRectF, Matrix.ScaleToFit.CENTER)
c.drawBitmap(srcBmp, matrix, null)
Log.d("耗时", "scale=${System.currentTimeMillis() - t}")
return bmp
}
}
private constructor() {
Log.d(MainActivity.TAG, "QuickLoader 初始化")
mHandler = MsgHandler()
}
fun start(ctx: Context, uri: Uri, miv: MainActivity.MyIV) {
val msg = mHandler?.obtainMessage(WHAT)
msg?.obj = LoadTask(ctx, uri, miv)
mHandler?.sendMessage(msg!!)
}
private class MsgHandler : Handler {
private var mExecutorService = Executors.newFixedThreadPool(THREAD_NUMBER)
constructor() : super(Looper.getMainLooper()) {
Log.d(MainActivity.TAG, "MsgHandler 初始化")
}
override fun handleMessage(msg: Message) {
mExecutorService.execute(msg.obj as LoadTask)
}
fun destroy() {
removeMessages(WHAT)
mExecutorService.shutdownNow()
mExecutorService = null
}
}
class LoadTask(private val ctx: Context, private val uri: Uri, private val miv: MainActivity.MyIV) : Runnable {
override fun run() {
Log.d(MainActivity.TAG, "run $uri")
val t = System.currentTimeMillis()
val bmp = ctx.contentResolver?.loadThumbnail(uri, Size(MainActivity.SIZE, MainActivity.SIZE), null)
Log.d(MainActivity.TAG, "LoadTask 耗时:${System.currentTimeMillis() - t} $uri")
(ctx as AppCompatActivity).runOnUiThread {
miv.setImageBitmap(scale(bmp!!))
}
}
}
fun destroy() {
mHandler?.destroy()
mHandler = null
}
}
Android用setRectToRect实现Bitmap基于Matrix矩阵scale缩放RectF动画,Kotlin(二)_android bitmap 动画-CSDN博客文章浏览阅读770次,点赞4次,收藏5次。【代码】Android用setRectToRect实现Bitmap基于Matrix矩阵scale缩放RectF动画,Kotlin(二)_android bitmap 动画https://blog.csdn.net/zhangphil/article/details/135992780Android用setRectToRect实现Bitmap基于Matrix矩阵scale缩放RectF动画,Kotlin(一)_android matrix setrecttorect-CSDN博客文章浏览阅读1.2k次,点赞14次,收藏8次。基于Matrix,控制Bitmap的setRectToRect的目标RectF的宽高。从很小的宽高开始,不断迭代增加setRectToRect的目标RectF的宽高,每次迭代加上一定时延,实现Matrix基础上的动画。文章浏览阅读180次。【代码】Android矩阵setRectToRect裁剪Bitmap原图Matrix放大,mapRect标记中心区域,Kotlin。Android矩阵setRectToRect裁剪Bitmap原图Matrix放大,mapRect标记中心区域,Kotlin-CSDN博客。_android matrix setrecttorecthttps://blog.csdn.net/zhangphil/article/details/135980821Android矩阵Matrix变换setRectToRect,Kotlin_android rect 矩阵-CSDN博客文章浏览阅读709次,点赞7次,收藏8次。Android拼接合并图片生成长图代码实现合并两张图片,以第一张图片的宽度为标准,如果被合并的第二张图片宽度和第一张不同,那么就以第一张图片的宽度为准线,对第二张图片进行缩放。Android拼接合并图片生成长图代码实现合并两张图片,以第一张图片的宽度为标准,如果被合并的第二张图片宽度和第一张不同,那么就以第一张图片的宽度为准线,对第二张图片进行缩放。基础上,把剪切的区域从矩形Rect变为圆形的Path,当手指在上面的ImageView移动时候,下面同等大小对应的坐标区域显示"剪切"出来的圆形图。_android rect 矩阵https://blog.csdn.net/zhangphil/article/details/135913218