使用Kotlin高效地開發Android App(三)

漂亮的氣球.jpg

一. ?、!! 、lateinit 以及 let

Kotlin 的類型系統旨在從咱們的代碼中消除 NullPointerException。android

1.1 ?

Kotlin基於Java的空指針提出了一個空安全的概念,即每一個屬性默認不可爲null。編程

例如:安全

var a: String = "test kotlin"
a = null //編譯錯誤
複製代碼

若是要容許爲空,咱們須要手動聲明一個變量爲可空字符串類型,寫爲String?閉包

var a: String? = "test kotlin"
a = null //編譯成功
複製代碼

1.2 !!

!!是非空斷言運算符。將任何值轉換爲非空類型,若該值爲空則拋出異常。app

object Test {

    var s:String?=null

    @JvmStatic
    fun main(args: Array<String>) {

        println(s!!.length)
    }
}
複製代碼

執行上述代碼會拋出以下異常。框架

Exception in thread "main" kotlin.KotlinNullPointerException
複製代碼

在App快要發佈時,咱們會進行檢查儘可能避免使用「!!」,轉而考慮使用lateinit或者let函數來代替它。ide

1.3 lateinit

在某個類中,若是某些成員變量沒辦法在一開始就初始化,而且又不想使用可空類型(也就是帶?的類型)。那麼,可使用lateinit來修飾它。函數

被lateinit修飾的變量,並非不初始化,它須要在生命週期流程中進行獲取或者初始化。post

若是訪問未初始化的 lateinit 變量會致使 UninitializedPropertyAccessException。字體

1.4 let函數

let函數把當前對象做爲閉包的it參數,返回值是函數裏面最後一行,或者指定return。它看起來有點相似於run函數。

let函數跟run函數的區別是:let函數在函數內能夠經過 it 指代該對象。

/** * Calls the specified function [block] with `this` value as its argument and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
複製代碼

跟?結合使用, let函數能夠在對象不爲 null 的時候執行函數內的代碼,從而避免了空指針異常的出現。

通常是這樣使用:

?.let {
       ....
}
複製代碼

使用Kotlin高效地開發Android App(二)中,曾經介紹過結合run和apply函數一塊兒使用的方式。其實,裏面使用了「!!」是有隱患的。

viewModel.email.run {

            if (value!!.isEmpty()) {
                toast(resources.getString(R.string.you_have_not_completed_the_email_address)).show()
                return@onClickRight
            }
            if (!Util.checkEmail(value!!)) {
                toast(resources.getString(R.string.the_email_format_you_have_filled_is_incorrect)).show()
                return@onClickRight
            }

            viewModel
        }.subject.run {

            if (value!!.isEmpty()) {
                toast(resources.getString(R.string.you_have_not_completed_the_feedback_subject)).show()
                return@onClickRight
            }

            viewModel
        }.content.apply {

            if (value!!.isEmpty()) {
                toast(resources.getString(R.string.you_have_not_completed_the_details)).show()
                return@onClickRight
            }
        }
複製代碼

可使用let函數進行優化,避免出現空指針的狀況。

viewModel.email.run {
            value?.let {
                if (it.isEmpty()) {
                    toast(string(R.string.you_have_not_completed_the_email_address)).show()
                    return@onClickRight
                }
                if (!Util.checkEmail(it)) {
                    toast(string(R.string.the_email_format_you_have_filled_is_incorrect)).show()
                    return@onClickRight
                }
            }

            viewModel
        }.subject.run {
            value?.let {
                if (it.isEmpty()) {
                    toast(string(R.string.you_have_not_completed_the_feedback_subject)).show()
                    return@onClickRight
                }
            }

            viewModel
        }.content.apply {
            value?.let {
                if (it.isEmpty()) {
                    toast(string(R.string.you_have_not_completed_the_details)).show()
                    return@onClickRight
                }
            }
        }
複製代碼

二.函數的默認參數

在Kotlin中,函數能夠擁有默認參數,這樣一來就再也不須要像Java那樣爲了默認參數而寫一大長串重載函數了。

例如,咱們使用RxBinding時,可能會考慮到防止UI控件被重複點擊,因而寫下了這樣的Transformer

/** * 防止重複點擊的Transformer */
    @JvmStatic
    fun <T> preventDuplicateClicksTransformer(windowDuration:Long=1000,timeUnit: TimeUnit=TimeUnit.MILLISECONDS): ObservableTransformer<T, T> {
        return ObservableTransformer { upstream ->
            upstream.throttleFirst(windowDuration, timeUnit)
        }
    }
複製代碼

在1秒內不能重複點擊某個UI控件,能夠這樣寫,由於使用了默認參數。

RxView.clicks(textview)
                .compose(RxJavaUtils.preventDuplicateClicksTransformer())
                .subscribe({
                         ......
                })
複製代碼

三.DSL的使用

去年的時候,我曾經寫過一篇關於kotlin dsl的文章——用kotlin來實現dsl風格的編程,使用dsl的方式編寫代碼我的感受更加簡潔和直觀。

在項目中,我對toast以及glide框架嘗試使用dsl的方式來封裝。以前的用法是使用Kotlin的擴展函數,因爲團隊的其餘成員更偏好鏈式調用,目前暫時保留了兩種寫法。

3.1 對glide的封裝

glide的擴展函數,能夠知足項目中的使用。

/** * 佔位符矩形 */
fun ImageView.load(url: String?) {
    get(url).placeholder(R.drawable.shape_default_rec_bg)
            .error(R.drawable.shape_default_rec_bg)
            .into(this)
}

/** * 佔位符圓角矩形 */
fun ImageView.loadRound(url: String?, centerCrop: Boolean = false) {
    get(url).placeholder(R.drawable.shape_default_round_bg)
            .error(R.drawable.shape_default_round_bg)
            .transform(RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = centerCrop))
            .into(this)
}

/** * 佔位符圓形 */
fun ImageView.loadCircle(url: Drawable?) {
    get(url).placeholder(R.drawable.shape_default_circle_bg)
            .apply(RequestOptions.circleCropTransform())
            .error(R.drawable.shape_default_circle_bg)
            .into(this)
}

fun ImageView.loadCircle(url: String?) {
    get(url).placeholder(R.drawable.shape_default_circle_bg)
            .apply(RequestOptions.circleCropTransform())
            .error(R.drawable.shape_default_circle_bg)
            .into(this)
}

fun ImageView.get(url: String?): GlideRequest<Drawable> = GlideApp.with(context).load(url)
fun ImageView.get(url: Drawable?): GlideRequest<Drawable> = GlideApp.with(context).load(url)
複製代碼

加載某個圖片以後,讓它呈現出圓角矩形的效果

holder.itemView.iv_game.loadRound(image_url)
複製代碼

使用dsl進行封裝

class GlideWrapper {

    var url:String? = null

    var image: ImageView?=null

    var placeholder: Int = R.drawable.shape_default_rec_bg

    var error: Int = R.drawable.shape_default_rec_bg

    var transform: Transformation<Bitmap>? = null

}

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

    val wrap = GlideWrapper()

    wrap.init()

    execute(wrap)
}

private fun execute(wrap:GlideWrapper) {

    wrap.image?.let {

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

        if (wrap?.transform!=null) {

            request.transform(wrap.transform!!)
        }

        request.into(it)

    }

}
複製代碼

仍然是加載該圖片,讓它呈現出圓角矩形的效果

load {
                url = image_url
                image = holder.itemView.iv_game
                transform = RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = false)
            }
複製代碼

3.2 對toast的封裝

提示信息是任何App必不可少的,在咱們的項目中也使用擴展函數對toast進行封裝。

fun Toast.setGravityCenter(): Toast {
    setGravity(Gravity.CENTER, 0, 0)
    return this
}

/** * 設置Toast字體及背景顏色 * @param messageColor * @param backgroundColor * @return */
fun Toast.setToastColor(@ColorInt messageColor: Int, @ColorInt backgroundColor: Int) {
    val view = view
    if (view != null) {
        val message = view.findViewById(android.R.id.message) as TextView
        message.setBackgroundColor(backgroundColor)
        message.setTextColor(messageColor)
    }
}

/** * 設置Toast字體及背景 * @param messageColor * @param background * @return */
fun Toast.setBackground(@ColorInt messageColor: Int = Color.WHITE, @DrawableRes background: Int = R.drawable.shape_toast_bg): Toast {
    val view = view
    if (view != null) {
        val message = view.findViewById(android.R.id.message) as TextView
        view.setBackgroundResource(background)
        message.setTextColor(messageColor)
    }
    return this
}

//@SuppressLint("ShowToast")
fun toast(text: CharSequence): Toast = Toast.makeText(App.instance, text, Toast.LENGTH_LONG)
        .setGravityCenter()
        .setBackground()
//須要的地方調用withErrorIcon,默認不要添加
// .withErrorIcon()


//@SuppressLint("ShowToast")
fun toast(@StringRes res: Int): Toast = Toast.makeText(App.instance, App.instance.resources.getString(res), Toast.LENGTH_LONG)
        .setGravityCenter()
        .setBackground()
//須要的地方調用withErrorIcon,默認不要添加
// .withErrorIcon()

fun Toast.withErrorIcon(@DrawableRes iconRes: Int = R.drawable.ic_toast_error): Toast {
    val view = view
    if (view != null) {
        val layout = this.view as LinearLayout
        val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
        val icon = ImageView(getApplicationContext())
        icon.setImageResource(iconRes)
        icon.setPadding(0, 0, Util.dip2px(8f), 0)
        icon.layoutParams = layoutParams
        layout.orientation = LinearLayout.HORIZONTAL
        layout.gravity = Gravity.CENTER_VERTICAL
        layout.addView(icon, 0)
    }
    return this
}

fun Toast.withSuccIcon(@DrawableRes iconRes: Int = R.drawable.ic_right_circle): Toast {
    val view = view
    if (view != null) {
        val layout = this.view as LinearLayout
        val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
        val icon = ImageView(getApplicationContext())
        icon.setImageResource(iconRes)
        icon.setPadding(0, 0, Util.dip2px(8f), 0)
        icon.layoutParams = layoutParams
        layout.orientation = LinearLayout.HORIZONTAL
        layout.gravity = Gravity.CENTER_VERTICAL
        layout.addView(icon, 0)
    }
    return this
}
複製代碼

要展現一個錯誤的提示,大體須要這樣寫。

toast(resources.getString(R.string.you_have_not_completed_the_email_address)).withErrorIcon().show()
複製代碼

使用dsl進行封裝

class ToastWrapper {

    var text:String? = null

    var res:Int? = null

    var showSuccess:Boolean = false

    var showError:Boolean = false
}

fun toast(init: ToastWrapper.() -> Unit) {
    val wrap = ToastWrapper()

    wrap.init()

    execute(wrap)
}

private fun execute(wrap:ToastWrapper) {

    var taost:Toast?=null

    wrap.text?.let {

        taost = toast(it)
    }

    wrap.res?.let {

        taost = toast(it)
    }

    if (wrap.showSuccess) {

        taost?.withSuccIcon()
    } else if (wrap.showError) {

        taost?.withErrorIcon()
    }

    taost?.show()
}
複製代碼

使用dsl的方式展現一樣的錯誤信息。

toast {

                    res = R.string.you_have_not_completed_the_email_address
                    showError = true
                }
複製代碼

總結

目前該系列的文章整理得比較隨意,更像是一些經常使用的tips。

文中的dsl仍是結合了擴展函數來使用的,我的認爲是進一步的封裝。相比起鏈式調用,我仍是比較偏向dsl。

該系列的相關文章:

使用Kotlin高效地開發Android App(五)完結篇

使用Kotlin高效地開發Android App(四)

使用Kotlin高效地開發Android App(二)

使用Kotlin高效地開發Android App(一)


Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公衆號二維碼並關注,期待與您的共同成長和進步。

相關文章
相關標籤/搜索