使用Kotlin高效地开发Android App(三)

640?wx_fmt=jpeg

一. ?、!! 、lateinit 以及 let

Kotlin 的类型系统旨在从我们的代码中消除 NullPointerException。

1.1 ?

Kotlin基于Java的空指针提出了一个空安全的概念,即每个属性默认不可为null。

例如:

 
 
  1. var a: String = "test kotlin"

  2. a = null //编译错误

如果要允许为空,我们需要手动声明一个变量为可空字符串类型,写为String?

 
 
  1. var a: String? = "test kotlin"

  2. a = null //编译成功

1.2 !!

!!是非空断言运算符。将任何值转换为非空类型,若该值为空则抛出异常。

 
 
  1. object Test {

  2.    var s:String?=null

  3.    @JvmStatic

  4.    fun main(args: Array<String>) {

  5.        println(s!!.length)

  6.    }

  7. }

执行上述代码会抛出如下异常。

 
 
  1. Exception in thread "main" kotlin.KotlinNullPointerException

在App快要发布时,我们会进行检查尽量避免使用“!!”,转而考虑使用lateinit或者let函数来代替它。

1.3 lateinit

在某个类中,如果某些成员变量没办法在一开始就初始化,并且又不想使用可空类型(也就是带?的类型)。那么,可以使用lateinit来修饰它。

被lateinit修饰的变量,并不是不初始化,它需要在生命周期流程中进行获取或者初始化。

如果访问未初始化的 lateinit 变量会导致 UninitializedPropertyAccessException。

1.4 let函数

let函数把当前对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return。它看起来有点类似于run函数。

let函数跟run函数的区别是:let函数在函数内可以通过 it 指代该对象。

 
 
  1. /**

  2. * Calls the specified function [block] with `this` value as its argument and returns its result.

  3. */

  4. @kotlin.internal.InlineOnly

  5. public inline fun <T, R> T.let(block: (T) -> R): R {

  6.    contract {

  7.        callsInPlace(block, InvocationKind.EXACTLY_ONCE)

  8.    }

  9.    return block(this)

  10. }

跟?结合使用, let函数可以在对象不为 null 的时候执行函数内的代码,从而避免了空指针异常的出现。

一般是这样使用:

 
 
  1. ?.let {

  2.       ....

  3. }

使用Kotlin高效地开发Android App(二)中,曾经介绍过结合run和apply函数一起使用的方式。其实,里面使用了“!!”是有隐患的。

 
 
  1.        viewModel.email.run {

  2.            if (value!!.isEmpty()) {

  3.                toast(resources.getString(R.string.you_have_not_completed_the_email_address)).show()

  4.                return@onClickRight

  5.            }

  6.            if (!Util.checkEmail(value!!)) {

  7.                toast(resources.getString(R.string.the_email_format_you_have_filled_is_incorrect)).show()

  8.                return@onClickRight

  9.            }

  10.            viewModel

  11.        }.subject.run {

  12.            if (value!!.isEmpty()) {

  13.                toast(resources.getString(R.string.you_have_not_completed_the_feedback_subject)).show()

  14.                return@onClickRight

  15.            }

  16.            viewModel

  17.        }.content.apply {

  18.            if (value!!.isEmpty()) {

  19.                toast(resources.getString(R.string.you_have_not_completed_the_details)).show()

  20.                return@onClickRight

  21.            }

  22.        }

可以使用let函数进行优化,避免出现空指针的情况。

 
 
  1.        viewModel.email.run {

  2.            value?.let {

  3.                if (it.isEmpty()) {

  4.                    toast(string(R.string.you_have_not_completed_the_email_address)).show()

  5.                    return@onClickRight

  6.                }

  7.                if (!Util.checkEmail(it)) {

  8.                    toast(string(R.string.the_email_format_you_have_filled_is_incorrect)).show()

  9.                    return@onClickRight

  10.                }

  11.            }

  12.            viewModel

  13.        }.subject.run {

  14.            value?.let {

  15.                if (it.isEmpty()) {

  16.                    toast(string(R.string.you_have_not_completed_the_feedback_subject)).show()

  17.                    return@onClickRight

  18.                }

  19.            }

  20.            viewModel

  21.        }.content.apply {

  22.            value?.let {

  23.                if (it.isEmpty()) {

  24.                    toast(string(R.string.you_have_not_completed_the_details)).show()

  25.                    return@onClickRight

  26.                }

  27.            }

  28.        }

二.函数的默认参数

在Kotlin中,函数可以拥有默认参数,这样一来就不再需要像Java那样为了默认参数而写一大长串重载函数了。

例如,我们使用RxBinding时,可能会考虑到防止UI控件被重复点击,于是写下了这样的Transformer

 
 
  1.    /**

  2.     * 防止重复点击的Transformer

  3.     */

  4.    @JvmStatic

  5.    fun <T> preventDuplicateClicksTransformer(windowDuration:Long=1000,timeUnit: TimeUnit=TimeUnit.MILLISECONDS): ObservableTransformer<T, T> {

  6.        return ObservableTransformer { upstream ->

  7.            upstream.throttleFirst(windowDuration, timeUnit)

  8.        }

  9.    }

在1秒内不能重复点击某个UI控件,可以这样写,因为使用了默认参数。

 
 
  1.        RxView.clicks(textview)

  2.                .compose(RxJavaUtils.preventDuplicateClicksTransformer())

  3.                .subscribe({

  4.                         ......

  5.                })

三.DSL的使用

去年的时候,我曾经写过一篇关于kotlin dsl的文章——用kotlin来实现dsl风格的编程,使用dsl的方式编写代码个人感觉更加简洁和直观。

在项目中,我对toast以及glide框架尝试使用dsl的方式来封装。之前的用法是使用Kotlin的扩展函数,由于团队的其他成员更偏好链式调用,目前暂时保留了两种写法。

3.1 对glide的封装

glide的扩展函数,可以满足项目中的使用。

 
 
  1. /**

  2. * 占位符矩形

  3. */

  4. fun ImageView.load(url: String?) {

  5.    get(url).placeholder(R.drawable.shape_default_rec_bg)

  6.            .error(R.drawable.shape_default_rec_bg)

  7.            .into(this)

  8. }

  9. /**

  10. * 占位符圆角矩形

  11. */

  12. fun ImageView.loadRound(url: String?, centerCrop: Boolean = false) {

  13.    get(url).placeholder(R.drawable.shape_default_round_bg)

  14.            .error(R.drawable.shape_default_round_bg)

  15.            .transform(RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = centerCrop))

  16.            .into(this)

  17. }

  18. /**

  19. * 占位符圆形

  20. */

  21. fun ImageView.loadCircle(url: Drawable?) {

  22.    get(url).placeholder(R.drawable.shape_default_circle_bg)

  23.            .apply(RequestOptions.circleCropTransform())

  24.            .error(R.drawable.shape_default_circle_bg)

  25.            .into(this)

  26. }

  27. fun ImageView.loadCircle(url: String?) {

  28.    get(url).placeholder(R.drawable.shape_default_circle_bg)

  29.            .apply(RequestOptions.circleCropTransform())

  30.            .error(R.drawable.shape_default_circle_bg)

  31.            .into(this)

  32. }

  33. fun ImageView.get(url: String?): GlideRequest<Drawable> = GlideApp.with(context).load(url)

  34. fun ImageView.get(url: Drawable?): GlideRequest<Drawable> = GlideApp.with(context).load(url)

加载某个图片之后,让它呈现出圆角矩形的效果

 
 
  1. holder.itemView.iv_game.loadRound(image_url)

使用dsl进行封装

 
 
  1. class GlideWrapper {

  2.    var url:String? = null

  3.    var image: ImageView?=null

  4.    var placeholder: Int = R.drawable.shape_default_rec_bg

  5.    var error: Int = R.drawable.shape_default_rec_bg

  6.    var transform: Transformation<Bitmap>? = null

  7. }

  8. fun load(init: GlideWrapper.() -> Unit) {

  9.    val wrap = GlideWrapper()

  10.    wrap.init()

  11.    execute(wrap)

  12. }

  13. private fun execute(wrap:GlideWrapper) {

  14.    wrap.image?.let {

  15.        var request = it.get(wrap.url).placeholder(wrap.placeholder).error(wrap.error)

  16.        if (wrap?.transform!=null) {

  17.            request.transform(wrap.transform!!)

  18.        }

  19.        request.into(it)

  20.    }

  21. }

仍然是加载该图片,让它呈现出圆角矩形的效果

 
 
  1.            load {

  2.                url = image_url

  3.                image = holder.itemView.iv_game

  4.                transform = RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = false)

  5.            }

3.2 对toast的封装

提示信息是任何App必不可少的,在我们的项目中也使用扩展函数对toast进行封装。

 
 
  1. fun Toast.setGravityCenter(): Toast {

  2.    setGravity(Gravity.CENTER, 0, 0)

  3.    return this

  4. }

  5. /**

  6. * 设置Toast字体及背景颜色

  7. * @param messageColor

  8. * @param backgroundColor

  9. * @return

  10. */

  11. fun Toast.setToastColor(@ColorInt messageColor: Int, @ColorInt backgroundColor: Int) {

  12.    val view = view

  13.    if (view != null) {

  14.        val message = view.findViewById(android.R.id.message) as TextView

  15.        message.setBackgroundColor(backgroundColor)

  16.        message.setTextColor(messageColor)

  17.    }

  18. }

  19. /**

  20. * 设置Toast字体及背景

  21. * @param messageColor

  22. * @param background

  23. * @return

  24. */

  25. fun Toast.setBackground(@ColorInt messageColor: Int = Color.WHITE, @DrawableRes background: Int = R.drawable.shape_toast_bg): Toast {

  26.    val view = view

  27.    if (view != null) {

  28.        val message = view.findViewById(android.R.id.message) as TextView

  29.        view.setBackgroundResource(background)

  30.        message.setTextColor(messageColor)

  31.    }

  32.    return this

  33. }

  34. //@SuppressLint("ShowToast")

  35. fun toast(text: CharSequence): Toast = Toast.makeText(App.instance, text, Toast.LENGTH_LONG)

  36.        .setGravityCenter()

  37.        .setBackground()

  38. //需要的地方调用withErrorIcon,默认不要添加

  39. //        .withErrorIcon()

  40. //@SuppressLint("ShowToast")

  41. fun toast(@StringRes res: Int): Toast = Toast.makeText(App.instance, App.instance.resources.getString(res), Toast.LENGTH_LONG)

  42.        .setGravityCenter()

  43.        .setBackground()

  44. //需要的地方调用withErrorIcon,默认不要添加

  45. //        .withErrorIcon()

  46. fun Toast.withErrorIcon(@DrawableRes iconRes: Int = R.drawable.ic_toast_error): Toast {

  47.    val view = view

  48.    if (view != null) {

  49.        val layout = this.view as LinearLayout

  50.        val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)

  51.        val icon = ImageView(getApplicationContext())

  52.        icon.setImageResource(iconRes)

  53.        icon.setPadding(0, 0, Util.dip2px(8f), 0)

  54.        icon.layoutParams = layoutParams

  55.        layout.orientation = LinearLayout.HORIZONTAL

  56.        layout.gravity = Gravity.CENTER_VERTICAL

  57.        layout.addView(icon, 0)

  58.    }

  59.    return this

  60. }

  61. fun Toast.withSuccIcon(@DrawableRes iconRes: Int = R.drawable.ic_right_circle): Toast {

  62.    val view = view

  63.    if (view != null) {

  64.        val layout = this.view as LinearLayout

  65.        val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)

  66.        val icon = ImageView(getApplicationContext())

  67.        icon.setImageResource(iconRes)

  68.        icon.setPadding(0, 0, Util.dip2px(8f), 0)

  69.        icon.layoutParams = layoutParams

  70.        layout.orientation = LinearLayout.HORIZONTAL

  71.        layout.gravity = Gravity.CENTER_VERTICAL

  72.        layout.addView(icon, 0)

  73.    }

  74.    return this

  75. }

要展示一个错误的提示,大致需要这样写。

 
 
  1. toast(resources.getString(R.string.you_have_not_completed_the_email_address)).withErrorIcon().show()

使用dsl进行封装

 
 
  1. class ToastWrapper {

  2.    var text:String? = null

  3.    var res:Int? = null

  4.    var showSuccess:Boolean = false

  5.    var showError:Boolean = false

  6. }

  7. fun toast(init: ToastWrapper.() -> Unit) {

  8.    val wrap = ToastWrapper()

  9.    wrap.init()

  10.    execute(wrap)

  11. }

  12. private fun execute(wrap:ToastWrapper) {

  13.    var taost:Toast?=null

  14.    wrap.text?.let {

  15.        taost = toast(it)

  16.    }

  17.    wrap.res?.let {

  18.        taost = toast(it)

  19.    }

  20.    if (wrap.showSuccess) {

  21.        taost?.withSuccIcon()

  22.    } else if (wrap.showError) {

  23.        taost?.withErrorIcon()

  24.    }

  25.    taost?.show()

  26. }

使用dsl的方式展示同样的错误信息。

 
 
  1.                toast {

  2.                    res = R.string.you_have_not_completed_the_email_address

  3.                    showError = true

  4.                }

总结

目前该系列的文章整理得比较随意,更像是一些常用的tips。

文中的dsl还是结合了扩展函数来使用的,个人认为是进一步的封装。相比起链式调用,我还是比较偏向dsl。

该系列的相关文章:

使用Kotlin高效地开发Android App(二)

使用Kotlin高效地开发Android App(一)


关注【Java与Android技术栈】

更多精彩内容请关注扫码

640?wx_fmt=jpeg


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值