Code前端首页关于Code前端联系我们

Kotlin的真实代码提供了对预定义扩展函数的更深入的理解

terry 2年前 (2023-09-25) 阅读数 51 #后端开发

包括以下知识点:函数类型、扩展函数、带有接收者的lambda、apply()、also()、let()、安全调用运算符、操作符号Elvis。

apply()

函数apply()在第一篇文章中已经提到过。这次结合实际代码进行更深入的讲解。

要在 Android 中组合多个动画,您可以使用 AnimatorSet。使用apply()可以在构建复合动画的代码中重复减少对象名称,使得所有语义构建一目了然:

val span = 300
AnimatorSet().apply {
    playTogether(
            ObjectAnimator.ofPropertyValuesHolder(
                    tvTitle,
                    PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            },
            ObjectAnimator.ofPropertyValuesHolder(
                    ivAvatar,
                    PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            }
    )
    start()
}
复制代码

同时为tvTitle和ivAvatar控件中创建透明度和位移动画,并设置动画时间和插值器。代码中没有多个 AnimatorSet 对象和多个 Animator 对象,这一切都归功于 apply()”:.()

  1. 接受 lambda 作为参数。语义是: 将 lambda 应用于对象对象 ,其中 lambda 是一个特殊的 lambda,称为 lambda,带有接收器。这是 kotlin 特有的,在 Java 中没有。 带有接收者的 lambda 函数体不仅可以访问类成员,还可以访问接收者的所有非私有成员。这个特点是其魅力的关键。 (这个特性也非常适合构建DSL,下一篇文章会提到)

    上面的代码中,lambda函数体除了访问之外,还紧跟在apply()外部的。变量 span,还可以访问 playTogether()start()playTogether() 和 动画器的start()。硒。 (可以在这两个函数前面加上this,去掉会更简洁)。

  2. object.apply()的另一个特点是,它在对object对象执行某些操作后返回object对象本身。

所以apply()适合“构造一个对象后,需要调用一些对象方法来准备并最终返回一个对象实例” let() let( )

apply()类似,但由于以下两个区别,应用场景与

  • :
    1. 它接受一个普通的lambda 作为参数。
    2. 它返回一个 lambda 值作为返回值。

    项目中有一个场景:启动一个Fragment,传递bundle类型参数。如果持续时间值不为0,则显示A,否则显示B。三种常用做法爱()

    class FragmentA : Fragment() {
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            
            arguments?.let { arg ->
                //调用对象方法
                arg.getBundle(KEY)?.takeIf { it[DURATION] != 0 }?.let { duration -> 
                    //将对象作为参数传递给另一个函数
                    showA(duration)
                } ?: showB()
            }
        }
    }
    复制代码

    class FragmentA : Fragment() {
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            
            arguments?.let { arg ->
                //调用对象方法
                arg.getBundle(KEY)?.takeIf { it[DURATION] != 0 }?.let { duration -> 
                    //将对象作为参数传递给另一个函数
                    showA(duration)
                } ?: showB()
            }
        }
    }
    复制代码

    class FragmentA : Fragment() {
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            
            arguments?.let { arg ->
                //调用对象方法
                arg.getBundle(KEY)?.takeIf { it[DURATION] != 0 }?.let { duration -> 
                    //将对象作为参数传递给另一个函数
                    showA(duration)
                } ?: showB()
            }
        }
    }
    复制代码

    class FragmentA : Fragment() {
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            
            arguments?.let { arg ->
                //调用对象方法
                arg.getBundle(KEY)?.takeIf { it[DURATION] != 0 }?.let { duration -> 
                    //将对象作为参数传递给另一个函数
                    showA(duration)
                } ?: showB()
            }
        }
    }
    复制代码

    listOf(
        R.drawable.img1,
        R.drawable.img2, 
        R.drawable.img3,
        R.drawable.img4, 
    ).forEach {resId->
        BitmapFactory.decodeResource(
            resources, 
            resId, 
            BitmapFactory.Options().apply {
                inPreferredConfig = Bitmap.Config.ARGB_4444
                inMutable = true
            }
        ).also { 
            //存储逻辑
            bitmap -> imgList.add(bitmap) 
        }.also {
            //显示逻辑
            ivImg.setImageResource(it)   
        }
    }
    复制代码

    我们走吧()

    1. 通常是let() 会和安全电话运营商一起使用吗?,即。语义是: 如果对象不为空,则对该对象执行一些操作。操作可以调用方法,或者将其作为参数传递给另一个函数 apply() 相比之下,因为 apply() 通常用于构造新对象。 (let() 用于现有对象)。新创建的对象不能为空,所以没必要,而且就使用习惯而言,apply()之后的Lambda通常只是调用对象方法,而不传递对象作为另一个函数的参数(虽然这也可以做到,只需传递 this
    2. let( ) 也会与运算符 ♽ 组合? 实现空值处理。当调用let()的对象为空时,lambda中的逻辑将不会被执行。如果需要指定此时要执行的逻辑,可以使用?:
    3. let()嵌套时,明确指定 ambi的lambda参数名。 是
    。在 kotlin 中,如果只有一个 lambda 参数,则可以省略参数声明,使用 it 来引用它。但是当附加 lambda 时,is 的方向不清楚。因此,代码中用arg来表示这是一个Fragment参数,用duration来表示这是一个Bundle中的duration。

  • 除了上述用法之外,还可以使用let()作为变换函数,如 地图() 运算符。因为 let() 将 lambda 值作为返回值。

    有这样一个项目:为某个界面上的所有点击事件添加一个数据埋点。

    当然,你可以将埋入逻辑分散在各个控件的OnClickListener中,但如果你想通过埋入逻辑进行集成控制,可以使用以下解决方案: ❀❀ 点击响应类逻辑

    class OnClickListenerBuilder {
        //'点击响应逻辑'
        var onClickAction: ((View) -> Unit)? = null
    
        //'为点击响应逻辑赋值的函数'
        fun onClick(action: (View) -> Unit) {
            onClickAction = action
        }
    }
    复制代码

    在函数式编程中,将函数视为值。您可以将函数作为值传递,也可以独立声明函数并将其存储在变量中,但最常见的还是直接声明并将其作为参数传递给函数。

    OnClickListenerBuilder定义了一个函数类型为onClickAction的成员变量,类型为() -> 相同单位? 看。 OnClickListener中的函数void onClick(View view)完全相同,即输入View,返回空值。该成员变量的值可以为空,因此在原始函数类型(View) -> Unit之外多了一个括号和一个问号。

    该类的目的是将自定义的点击响应逻辑存储在函数类型变量中,并在发生点击时应用此逻辑。

    1. 为 View 设置点击事件并应用自定义点击响应逻辑
    //'定义扩展函数'
    fun View.setOnDataClickListener(action: OnClickListenerBuilder.() -> Unit) {
        setOnClickListener(
                OnClickListenerBuilder().apply(action).let { builder ->
                    View.OnClickListener { view ->
                        //'埋点逻辑'
                        Log.v(“ttaylor”, “view{$view} is clicked”)
                        //'点击响应逻辑'
                        builder.onClickAction?.invoke(view)
                    }
                }
        )
    }
    
    //'在界面中使用扩展函数为控件设置点击事件'
    btn.setOnDataClickListener {
        onClick {
            Toast.makeText(this@KotlinExample, “btn is click”, Toast.LENGTH_LONG).show()
        }
    }
    复制代码
    • 扩展函数声明扩展函数 setOnDataClickListener() for ❀❀。扩展函数是类的成员函数,但在类体之外定义。这种定义的优点是可以随时随地向类添加功能。

      在扩展函数中,您可以像任何其他类成员函数一样访问类属性和方法(私有和受保护成员除外)。本例中调用setOnClickListener()来控制设置点击事件。

    • 带有接收器的 Lambda 必须首先构建一个实例,以便实现存储在 OnClickListenerBuilder 中的点击响应逻辑。代码中的OnClickListenerBuilder().apply(action)在构建实例时将 lambda 应用于实例。这是一个带有接收器的 lambda,以及接收器 OnClickListenerBuilder。这作为参数传递给扩展函数。当调用 setOnDataClickListener() 时,我们可以轻松地在传入 lambda 中调用 onClick() 方法,因为可以在成员 lambda 中非私有地访问接收者。这样,点击响应逻辑就存储在函数类型变量 onClickAction 中。
    • 当使用map()时使用let()。由于API限制,View.setOnClickListener()的参数必须为View.OnClickListener类型,所以OnClickListener 必须改为View.OnClickListener 。这可以通过调用 let() 轻松完成,因为它返回一个 lambda 值。

      最后,在原始点击事件响应函数中实现嵌入逻辑,并调用存储在函数类型变量中的自定义点击响应逻辑。

    also()

    also()let() 非常相似,唯一的区别是它返回调用者本身而不是 lambda 值。价值。

    apply()相比,它也返回自己的调用者:

    1. 在传递参数方面,apply()和app中的 ly() 和 mb also() 以普通 lambda 传递。所以在lambda函数体中,前者通过this引用调用者,后者通过it引用调用者(如果参数名没有定义,则默认为它) .)
    2. 。换句话说,apply()更多地用于构建新对象并执行操作,而also()更多地用于向现有对象添加操作。

    在项目中,有一个接口应该在初始化时加载一系列图像并将它们存储在列表中:

    listOf(
        R.drawable.img1,
        R.drawable.img2, 
        R.drawable.img3,
        R.drawable.img4, 
    ).forEach {resId->
        BitmapFactory.decodeResource(
            resources, 
            resId, 
            BitmapFactory.Options().apply {
                inPreferredConfig = Bitmap.Config.ARGB_4444
                inMutable = true
            }
        ).also { bitmap -> imgList.add(bitmap) }
    }
    复制代码

    在这种情况下使用let()没有任何问题。但如果你还需要显示解析后的图像,最好使用also()

    listOf(
        R.drawable.img1,
        R.drawable.img2, 
        R.drawable.img3,
        R.drawable.img4, 
    ).forEach {resId->
        BitmapFactory.decodeResource(
            resources, 
            resId, 
            BitmapFactory.Options().apply {
                inPreferredConfig = Bitmap.Config.ARGB_4444
                inMutable = true
            }
        ).also { 
            //存储逻辑
            bitmap -> imgList.add(bitmap) 
        }.also {
            //显示逻辑
            ivImg.setImageResource(it)   
        }
    }
    复制代码

    因为also(),你可以自己调用它。 使用also()来拆分不同类型的逻辑,代码更容易理解和修改。这个例子的逻辑比较简单,只有一句话,所以组合起来并没有什么问题。

    知识点总结

    • 在函数式编程中,函数被视为值。您可以将函数作为值传递,也可以独立声明该函数并将其存储在变量中。
    • 函数类型是一种新类型,它使用 lambda 来描述函数的输入和输出。
    • 扩展功能是可以在类之外添加新功能的功能。在扩展函数体中,可以访问类成员(私有和受保护修饰的成员除外)
    • 使用 lambda 接收者 是一种特殊类型的 lambda,可以访问函数体内的非私有接收者成员。可以理解为一个接收者扩展函数,只不过这个扩展函数没有函数名。
    • apply() also() let()是系统定义的扩展函数。用于简化代码并减少重复的对象名称。
    • ?.被称为安全电话接线员。如果 object?.fun() 中的对象为空,则不会调用 fun()
    • ?: 被称为 Elvis 运算符,为 null 提供标准逻辑,funA() ?: funB(),如果 funA() 返回非 null 值,它将被执行回。该值用作所有表达式的返回值,否则执行 funB() 并使用返回值。

    作者:唐子轩
    链接:https://juejin.im/post/5d061caef265da1b8f1ac00a
    来源:掘金商业转载请联系作者授权。非商业转载请注明出处。

    版权声明

    本文仅代表作者观点,不代表Code前端网立场。
    本文系作者Code前端网发表,如需转载,请注明页面地址。

    发表评论:

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

    热门