Android 协程加载图片小练习
2022-03-17 本文已影响0人
雁过留声_泪落无痕
背景
看 kotlin 协程相关的文章时,看到了扔物线的一篇文章,Kotlin 协程的挂起好神奇好难懂?今天我把它的皮给扒了
文章末尾有个小练习题:
使用协程下载一张图,并行进行两次切割
一次切成大小相同的 4 份,取其中的第一份
一次切成大小相同的 9 份,取其中的最后一份
得到结果后,将它们展示在两个 ImageView 上。
这里进行一个简单的实现。
依赖
def lifecycle_version = "2.4.1"
// implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation("io.coil-kt:coil:2.0.0-rc01")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
这里用到了 coil,也是将协程用到炉火纯青的一个图片库
实现
class MainActivity : AppCompatActivity() {
companion object {
private const val IMAGE =
"https://images.unsplash.com/photo-1550979068-47f8ec0c92d0?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjU4MjM5fQ"
}
private lateinit var mBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBinding.root)
go()
}
private fun go() {
lifecycleScope.launch {
val context = this@MainActivity
val request = ImageRequest.Builder(context).data(IMAGE).build()
val result = context.imageLoader.execute(request)
if (result is SuccessResult) {
showToast("fetch image success.")
// split image to 4 parts
val firstList = async {
splitImage(result.drawable as BitmapDrawable, 2, 2)
}
// split image to 9 parts
val secondList = async {
splitImage(result.drawable as BitmapDrawable, 3, 3)
}
mBinding.first.setImageBitmap(firstList.await()[0])
mBinding.second.setImageBitmap(secondList.await()[8])
} else {
showToast("fetch image error: ${(result as ErrorResult).throwable.message}")
}
}
}
private suspend fun splitImage(
drawable: BitmapDrawable,
horizontalParts: Int,
verticalParts: Int
): List<Bitmap> {
if (horizontalParts <= 0 || verticalParts <= 0) {
return listOf()
}
val width = drawable.intrinsicWidth / horizontalParts
val height = drawable.intrinsicHeight / verticalParts
val list = mutableListOf<Bitmap>()
for (i in 0 until horizontalParts) {
for (j in 0 until verticalParts) {
withContext(Dispatchers.Default) {
Bitmap.createBitmap(drawable.bitmap, i * width, j * height, width, height).let {
list.add(it)
}
}
}
}
return list
}
private fun showToast(msg: String) {
Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
}
}
效果
原图 效果图改进1
上面的写法中,虽然也是并行处理的,但是 second 要显示出来,必须要 first 先显示完成,因为 await 是会挂起的;假设 first 操作很慢,而 second 操作很快,会导致即使 second 已经完成了操作但是还是显示不出来的问题。
而两个操作本质上没有相关性,于是考虑改为单独处理并单独显示,如下:
private fun goPro() {
lifecycleScope.launch {
val context = this@MainActivity
val request = ImageRequest.Builder(context).data(IMAGE).build()
val result = context.imageLoader.execute(request)
if (result is SuccessResult) {
showToast("fetch image success.")
// split image to 4 parts
launch {
val list = splitImage(result.drawable as BitmapDrawable, 2, 2)
mBinding.first.setImageBitmap(list[0])
}
// split image to 9 parts
launch {
val list = splitImage(result.drawable as BitmapDrawable, 3, 3)
mBinding.second.setImageBitmap(list[8])
}
} else {
showToast("fetch image error: ${(result as ErrorResult).throwable.message}")
}
}
}
改进2
为了看上去更直观,添加了模糊效果,题目中要求提取的部分图片不模糊,其余部分模糊化显示;整个图片用的 RecyclerView 装载,这里在 Adapter 中动态设置了图片的尺寸:
private fun goPro() {
lifecycleScope.launch {
val context = this@MainActivity
val request = ImageRequest.Builder(context)
.data(IMAGE)
// 禁止使用 Hardware Bitmap,否则模糊处理会失败
.allowHardware(false)
.build()
val result = context.imageLoader.execute(request)
if (result is SuccessResult) {
showToast("fetch image success.")
// split image to 4 parts
launch {
val list = splitImage(result.drawable as BitmapDrawable, 2, 2)
mFirstAdapter.setData(
blurList(this@MainActivity, list, 0),
result.drawable.intrinsicWidth / 6,
result.drawable.intrinsicHeight / 6
)
}
// split image to 9 parts
launch {
val list = splitImage(result.drawable as BitmapDrawable, 3, 3)
mSecondAdapter.setData(
blurList(this@MainActivity, list, 8), result.drawable.intrinsicWidth / 9,
result.drawable.intrinsicHeight / 9
)
}
} else {
showToast("fetch image error: ${(result as ErrorResult).throwable.message}")
}
}
}
private suspend fun blurList(context: Context, list: List<Bitmap>, exclude: Int): List<Bitmap> {
if (exclude < 0 || exclude >= list.size) {
return list
}
val blurList = mutableListOf<Bitmap>()
withContext(Dispatchers.Default) {
list.withIndex().forEach {
if (it.index != exclude) {
val blurBitmap = BlurUtil.myBlur(context, it.value, 1.0f / 8, 8)
blurList.add(blurBitmap)
} else {
blurList.add(it.value)
}
}
}
return blurList
}
模糊显示