美文网首页
Android 协程加载图片小练习

Android 协程加载图片小练习

作者: 雁过留声_泪落无痕 | 来源:发表于2022-03-17 17:26 被阅读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
    }
    
    模糊显示

    相关文章

      网友评论

          本文标题:Android 协程加载图片小练习

          本文链接:https://www.haomeiwen.com/subject/xxovdrtx.html