Android Jetpack 之KTX扩展库初使用

作者:王俊

image

自谷歌2018年发布支持Android开发的KTX扩展库以来,KTX扩展库主要是对Android原始的一些api进行了扩展,旨在帮助开发者更为简洁、通顺和优雅地使用 Kotlin进行开发Android程序。

添加依赖

implementation 'androidx.core:core-ktx:1.0.1'

ktx 对哪些内容进行了扩展

image

简单使用

AnimatorKt

animations 相关扩展库

  1. Animation listener 事件的监听

一般情况下我们需要对一个属性动画进行监听的话,写法不管在美观上还是在代码量上都会有点繁琐。但是如果我们使用ktx的话会简洁清爽很多

        val animator = ObjectAnimator.ofFloat(TextView(this), "translationX", 0f, 500f)

        //kotlin 普通写法
        animator.addListener(object :Animator.AnimatorListener{
            override fun onAnimationRepeat(animation: Animator?) {
            }

            override fun onAnimationEnd(animation: Animator?) {
            }

            override fun onAnimationCancel(animation: Animator?) {
            }

            override fun onAnimationStart(animation: Animator?) {
            }

        })

        //ktx 写法
        animator.addListener {
            handleAnimator(it)
        }

另外我们还可以接收annimation动画的多个callbacks,对于想添加几个callback完全可以根据自己的使用场景来实现, 这样对于原本就不需要太多callback来说,我们的代码量就减少了很多,也很清爽。

        //ktx 写法
        animator.addListener(
            onStart = {},
            onEnd = {},
            onCancel = {},
            onRepeat = {}
        )
  1. 对animation个别event的事件的监听

除了addListener 里面对 onStart 、onEnd 、 onCancel 、onRepeat 自由的添加监听外,ktx 还增加了对Pause 事件的监听:

        // ktx 对pause的监听
        animator.addPauseListener {
            handleAnimator(it)
        }
        
        
        //或者
        animator.addPauseListener(
            onPause = {},
            onResume = {}
        )

ktx 监听单个event:

        animator.doOnStart { handleAnimator(it) }
        
        animator.doOnEnd { handleAnimator(it) }
        
        animator.doOnPause { handleAnimator(it) }

        animator.doOnCancel { handleAnimator(it) }
        
        animator.doOnRepeat { handleAnimator(it) }
        
        animator.doOnResume { handleAnimator(it) }

TransitionsKt 转场动画

Transitions转场动画也像animator 那样,我们可以通过使用addListener调用转场动画的回调监听。具体的思路实现可以参考AnimatorKt

        val slide = Slide(Gravity.LEFT)
        slide.duration = 1000
        slide.interpolator = FastOutSlowInInterpolator()

        //kotlin 普通写法
        slide.addListener(object : Transition.TransitionListener {
            override fun onTransitionEnd(transition: Transition?) {

            }

            override fun onTransitionResume(transition: Transition?) {
            }

            override fun onTransitionPause(transition: Transition?) {
            }

            override fun onTransitionCancel(transition: Transition?) {
            }

            override fun onTransitionStart(transition: Transition?) {
            }
        })

        //ktx
        slide.addListener {
            handleTransition(it)
        }

        slide.addListener(
            onEnd = {},
            onStart = {},
            onCancel = {},
            onResume = {},
            onPause = {}
        )

        slide.doOnCancel { }
        slide.doOnEnd { }
        slide.doOnPause { }
        slide.doOnResume { }
        slide.doOnStart { }

OS

ktx 为Android OS 包提供了一系列的扩展支持,以下仅列举我们常见的一些写法。

  • 比如我们最常见的handler扩展:
        //kotlin 普通写法
        handler.postAtTime({
            Log.d("kotlin 正常写法 ", "我被执行了")
        },2000L)

        //ktx
        handler.postAtTime(uptimeMillis = 200L) {
            Log.d("os extensions ", "我被执行了")
        }
  • 创建bundle实例传递参数:
        //kotlin 普通写法
        val bundle = Bundle()
        bundle.putString("hello","大王叫我来巡山")

        //ktx
        val bundle = bundleOf("hello" to "大王叫我来巡山")

        val persitableBundle = persistableBundleOf("hello" to "大王叫我来巡山")

Utils

Utils 包主要是针对files 、arrays 以及其他的数据类型的扩展支持。

  • AtomicFiles
    AtomicFile是Android API17中引入的对文件进行原子操作的帮助类(原子是指在对整个文件操作时,要么不操作,要么操作成功。如果操作失败,不会影响文件内容)
        // AtomicFiles
        val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
        val file = File("${directory.absolutePath}.png")
        val atomicFile = AtomicFile(file)
        val readBytes = atomicFile.readBytes()
        val text = atomicFile.readText(charset = Charset.defaultCharset())
        atomicFile.tryWrite {
            //TODO 执行写入操作
        }
        atomicFile.writeBytes(readBytes)
        atomicFile.writeText("大王叫我来巡山", charset = Charset.defaultCharset())
  • Arrays
    对于LongSparseArray,SparseArray,SparseBooleanArray,SparseIntArray,SparseLongArray类型(我们平时可能使用SparseBooleanArray 比较多),我们可以这样使用:
        //SparseLongArray
        val array = SparseBooleanArray()
        for (i in 1..8) {
            array[i] = i % 2 == 0
        }
        val size = array.size
        array.contains(8)
        array.containsKey(8)
        array.containsValue(true)
        array.forEach { key, value -> Log.d("SparseLongArray", "key=${key},value=${value}") }
        array.getOrDefault(key = 8, defaultValue = false)
        array.getOrElse(key = 8, defaultValue = {
            false
        })
        array.isEmpty()
        array.isNotEmpty()
        val keyIterator = array.keyIterator()
        val valueIterator = array.valueIterator()
        val anotherArray = SparseBooleanArray()
        anotherArray[99] = true
        array.plus(anotherArray)
        array.putAll(anotherArray)
        array.remove(key = 8, value = false)
        array.set(key = 6, value = true)

Resources

当我们自定义view的使用使用到自定义属性必须用到TypedArray 类,ktx 在简化了TypedArray使用方式的同时,如果对于某个属性必须强制使用的话可以考虑用这个扩展( Retrieve the xxx value for the attribute at [index] or throws [IllegalArgumentException])

class CustomerView : View {

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)

    constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {
        val typedArray = context.theme.obtainStyledAttributes(attributeSet, R.styleable.CustomerView, defStyle, 0)

        //kotlin 普通写法
        val tempColorAttr = typedArray.getColor(R.styleable.CustomerView_testColor, Color.BLACK)

        //ktx
        val colorAttr = typedArray.getBooleanOrThrow(R.styleable.CustomerView_testColor)
        val booleanAttr = typedArray.getColorOrThrow(R.styleable.CustomerView_testBoolean)

        typedArray.recycle()
    }
}

Text 富文本

我们在开发中使用富文本的场景很多,而ktx对于这部分也提供了一些很方便的扩展功能,比如我们常见的SpannableStringBuilder等类。

例如我们在SpannableStringBuilder中append加粗的文本:


        val tempText = "大王叫我来巡山"
        val builder = SpannableStringBuilder()

        //kotlin 普通写法
        builder.append(tempText)
            .setSpan(StyleSpan(Typeface.BOLD), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        tvContent.text = builder

        //ktx
        builder.bold { append(tempText) }
        tvContent.text = builder
        
        //当然你也可以这样
        builder.bold { italic { underline { append("我渴望世界和平") } } }
        tvContent.text = builder

当然我们可以这样去设置文本的背景色和设置Span属性:

        builder.backgroundColor(color = Color.RED) {
            // builder action
            append("大王叫我来巡山")
        }
        builder.inSpans(span = StyleSpan(Typeface.BOLD_ITALIC)) {
            append("猴子派来的逗比")
        }

最后还有一个buildSpannedString扩展方法,这样可以直接构建SpannableStringBuilder实例,写起来更加方便。

        tvContent.text = buildSpannedString { bold { append("hello world!!!") } }

Net

ktx 对net包扩展主要是URI 这块的扩展

String to Uri

        val myUriString = "ezbuy://android.home"
        //kotlin 普通写法
        val uri = Uri.parse(myUriString)

        // ktx
        val uri = myUriString.toUri()

Content

主要针对ContentValues、Context、SharedPreferences 等的扩展

  • Context
    比如我们需要获取SystemService服务:
        //kotlin 普通写法
        val manager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

        //ktx 
        val alarmManager = getSystemService<AlarmManager>()
  • Styled Attributes
    比如我们自定义View里获取typedArray并设置attributes时:
        //kotlin 普通写法
        val typedArray = context.theme.obtainStyledAttributes(attributeSet, R.styleable.CustomerView, defStyle, 0)
        val tempColorAttr = typedArray.getColor(R.styleable.CustomerView_testColor, Color.BLACK)
        
        //ktx
        context.withStyledAttributes(set = attributeSet,attrs = R.styleable.CustomerView,defStyleAttr = defStyle,defStyleRes = 0){
            //TypedArray actions
            val tempColorAttr = getColor(R.styleable.CustomerView_testColor, Color.BLACK)
        }
  • SharedPreferences
        val sharedPreferences = getSharedPreferences("data", Context.MODE_PRIVATE)
        //kotlin 普通写法
        sharedPreferences.edit()
            .putString("leon", "大王叫我来巡山")
            .apply()

        //ktx
        sharedPreferences.edit {
            putString("leon", "大王叫我来巡山")
            putBoolean("male", true)
        }
  • ContentValues
        //kotlin 普通写法
        val contentValues  =  ContentValues()
        contentValues.put("key","大王叫我来巡山")
        //ktx
        val contentValues2 = contentValuesOf("key1" to "大王叫我来巡山",
            "key2" to "猴子派来的逗比")

Graphics

ktx 对Graphics包的相关类做了比较多的扩展,比如我们常见的Bitmap、Canvas、Color、Rect、Path、Shader等。

  • 常见的位图转换:
        val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)

        val adaptiveIcon = bitmap.toAdaptiveIcon()
        val icon = bitmap.toIcon()

        val resources = ImageView(this).resources
        val bitmapDrawable = bitmap.toDrawable(resources)

        val color = resources.getColor(R.color.colorAccent)
        val colorDrawable = color.toDrawable()

        val myUri = "ezbuy://android.home".toUri()
        val icon2 = myUri.toIcon()
        
        val drawable = resources.getDrawable(R.drawable.ic_launcher_background)
        val bitmap2 = drawable.toBitmap(width = 100, height = 100, config = Bitmap.Config.ARGB_8888)
  • bitmap 常见的扩展操作
        val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
        //set canvas
        bitmap.applyCanvas {
            val paint = Paint()
            paint.color = Color.BLUE
            paint.style = Paint.Style.STROKE
            paint.strokeWidth = 10F
            drawText("大王叫我来巡山", 0F, 0F, paint)
        }

        //get location color
        val colorInt = bitmap.get(100, 100)

        //create new bitmap
        val bitmap1 = bitmap.scale(100, 100, filter = true)
        
        // set location color
        bitmap.set(100, 100, Color.RED)
  • Canvas
        val canvas = Canvas()
        //旋转
        canvas.withRotation(90F, 0F, 0F) {
            // canvas actions
        }
        //保存Canvas的状态
        canvas.withSave {
            // canvas actions
        }

        //缩放
        canvas.withScale(0F, 0F, 100F, 100F) {
            // canvas actions
        }

        //位移
        canvas.withTranslation(100F, 100F) {
            // canvas actions
        }
  • Color
        // 解构
        val (r, g, b, a) = Color.RED
        // 合并两个颜色
        var color = Color.RED
        color += Color.BLACK
        //透明度
        val alpha = color.alpha
  • Rect
val rect = someRect and anotherRect
val (left, top, right, bottom) = someRect
someRect.contains(somePoint)
val region = someRect - anotherRect
val rect = someRect - someInt
val rect = someRect - somePoint
val rect = someRect or someRect
val rect = someRect + someRect
val rect = someRect + someInt
val rect = someRect + somePoint
val rectF = someRect.toRectF()
val region = someRect.toRegion()
val region = someRect xor someRect
  • Path
val path = somePath and anotherPath
val path = somePath.flatten(error = 1f)
val path = somePath - anotherPath
val path = somePath or anotherPath
val path = somePath + anotherPath
val path = somePath xor anotherPath

Views

  • layout 回调监听
        //kotlin 普通写法
        view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
            override fun onLayoutChange(
                v: View?,
                left: Int,
                top: Int,
                right: Int,
                bottom: Int,
                oldLeft: Int,
                oldTop: Int,
                oldRight: Int,
                oldBottom: Int
            ) {
                //callback
                view.removeOnLayoutChangeListener(this)
                view.width// 获取宽度
                view.height// 获取高度
            }
        })

        //ktx
        view.doOnLayout {
            //callback
            it.apply {
                removeOnLayoutChangeListener(this)
                width// 获取宽度
                height// 获取高度
            }
        }
        view.doOnNextLayout {
            //callback
            it.apply {
                removeOnLayoutChangeListener(this)
                width// 获取宽度
                height// 获取高度
            }
        }

  • postDelayed 写法
        //kotlin 普通写法
        view.postDelayed({
            Log.d("View", "postDelayed")
        }, 500)

        //ktx
        view.postDelayed(delayInMillis = 500) {
            Log.d("View", "postDelayed")
        }

  • postOnAnimationDelayed 同上
        //kotlin 普通写法
        view.postOnAnimationDelayed({
            Log.d("View", "postOnAnimationDelayed")
        }, 500)

        //ktx
        view.postOnAnimationDelayed(delayInMillis = 500) {
            Log.d("View", "postOnAnimationDelayed")
        }
  • padding
    view 的padding 属性设置也变得清晰明了很多
        //kotlin 普通写法
        view.setPadding(10, 10, 10, 10)
        view.setPaddingRelative(10, 10, 10, 10)

        //ktx
        view.setPadding(10) // view.setPadding(10, 10, 10, 10)
        view.updatePadding(left = 16, right = 11, top = 19, bottom = 20) //view.setPadding(16, 11, 19, 20)
        view.updatePaddingRelative(start = 10, end = 10, top = 10, bottom = 10)

ViewGroup

ViewGroup 常见的方法扩展

  • 检查ViewGroup 是否包含某个View
        val viewGroup = LinearLayout(this)
        //kotlin 普通写法
        viewGroup.indexOfChild(view)
        //ktx
        val doesContain = viewGroup.contains(view)
  • 遍历子View
        //kotlin 普通写法
        for (index in 0 until viewGroup.size) {
            handleChildView(view = viewGroup.getChildAt(index))
        }

        //ktx
        viewGroup.forEach {
            handleChildView(view = it)
        }

        viewGroup.forEachIndexed { index, view ->
            handleChildView(index, view)
        }
  • MutableIterator 迭代器
        val viewGroupIterator = viewGroup.iterator()
  • 其他操作符
        viewGroup.isEmpty()
        viewGroup.isNotEmpty()
        viewGroup.size
        // 移除一个View
        viewGroup -= view
        // 添加一个View
        viewGroup += view

总结

总体使用情况来说利用kotlin扩展函数简化一些api,体验还是很好的,代码看起来简洁、清爽。但是从前几个迭代版本中了解到废弃和新增了不少,该扩展库在不断的稳定和完善中;另外常见的扩展库还是比较少的,我们相信ktx在不断的迭代更新中提供更多简洁的api。

参考资料

https://android.github.io/android-ktx/core-ktx/index.html

https://developer.android.com/kotlin/ktx

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容