如何优雅的在 Activity、Fragment 之间传递参数

3,632 阅读2分钟

前言

对于 Kotlin 早就有所耳闻,而且 Google 粑粑也大力推行 Kotlin 了,作为一个 Android coder 还是要紧跟粑粑的步伐的。恰巧遇上公司项目大改版,于是就决定将 Java 替换成 Kotlin,并用 MVP+Retrofit+RxJava框架进行重构,毕竟,以前的代码太难维护了。在重构的过程中,发现以前那种获取 intent 参数的方式是在是有点难受,就在想有没有更好的方式可以代替,恰巧看待委托属性。。。

改革开放前参数获取方式

在 Java 中,我们是这样获取传递的参数的:

Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String name = bundle.getString("name");

在 Kotlin 中,我们是这样获取传递的参数的:

val bundle = intent.extras
val name = bundle.getString("name")

我们发现获取传递的参数,无论是 Java 还是 Kotlin,都需要在 onCreate()或者是其他地方对参数进行获取并且赋值,如果是传递的参数很多,那我们写的重复的代码是很多的。在 Java 中,我们可以使用注解的方式为参数进行赋值,那在 Kotlin 中有没有注解的方式呢?其实并不需要注解,Kotlin 本身支持拓展函数和委托属性,我们可以利用这两个特性实现参数的绑定,绑定后,再也不用在 onCreate()里为参数赋值啦!!!

委托属性

在讲如何利用拓展函数和委托属性实现参数绑定之前,我们还是需要先了解一下这两个特性,拓展函数较为简单,这里就不说了,我们就说一下委托属性。

class Example {
    var p: String by Delegate()
}

这就是一个简单的委托属性,例子中把属性 p 的 set 和 get 方法委托给了 Delegate。

语法是: val/var <属性名>: <类型> by <表达式>。在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。

我们来看看这个 Delegate。

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

如果类型是 val,则需提供 getValue 方法,如果是 var 类型,则对应的还需提供一个 setValue 的方法。第一个参数是自身的一个对象,而第二个参数则保存了对象的一个描述。当我们访问委托属性时,将调用委托的 getValue 方法。关于委托属性更详细的描述请访问委托属性

利用拓展函数及委托属性实现参数绑定

Extensions.kt

fun <U, T> Activity.bindExtra(key: String) = BindLoader<U, T>(key)

fun <U, T> Fragment.bindArgument(key: String) = BindLoader<U, T>(key)

fun <U, T> android.app.Fragment.bindArgument(key: String) = BindLoader<U, T>(key)

private class IntentDelegate<in U, out T>(private val key: String) : ReadOnlyProperty<U, T> {
    override fun getValue(thisRef: U, property: KProperty<*>): T {
        @Suppress("UNCHECKED_CAST")
        return when (thisRef) {
            is Fragment -> thisRef.arguments?.get(key) as T
            is android.app.Fragment -> thisRef.arguments?.get(key) as T
            else -> (thisRef as Activity).intent?.extras?.get(key) as T
        }
    }

}

class BindLoader<in U, out T>(private val key: String) {
    operator fun provideDelegate(thisRef: U, prop: KProperty<*>): ReadOnlyProperty<U, T> {
        // 创建委托
        return IntentDelegate(key)
    }

}

这里对 Activity、android.app.Fragment、android.support.v4.app.Fragment进行了拓展,主要实现逻辑都在 IntentDelegate 里面,通过 thisRef 判断类型并执行相应获取参数的方法,最后完成赋值。

如何使用

在我们需要绑定的参数后进行参数绑定就可以了,非常的简洁方便,简直清爽的不要不要的。使用的时候直接调用参数就可以了,有值则返回相应的参数值,无则返回空,和原先一样。

在 Activity 中使用:

private val id: Int by bindExtra("id")
private val name: String by bindExtra("name")

在 Fragment 中使用:

private val person: Person by bindArgument("person")

检验

show.gif

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.onClick {
            startActivity<SecondActivity>(
                    "id" to 9547,
                    "name" to "WG"
            )
        }
    }
}
class SecondActivity : AppCompatActivity() {
    private val id: Int by bindExtra("id")
    private val name: String by bindExtra("name")
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        textView.text = "get from MainActivity id = ${id} name = ${name}"

        button2.onClick {
            val transition = supportFragmentManager.beginTransaction()
            val tfg = TFragment.newInstance(Person("赵日天", "男", 24))
            transition.replace(R.id.fragment, tfg)
            transition.commit()
        }
    }
}
open class TFragment : Fragment() {
    private val person: Person by bindArgument("person")

    companion object {
        fun newInstance(p: Person): TFragment {
            val fg = TFragment()
            val bundle = Bundle()
            bundle.putParcelable("person", p)
            fg.arguments = bundle
            return fg
        }
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater?.inflate(R.layout.fragment_t, container, false)
    }

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        textView2.text = "get from SecondActivity msg = ${person.toString()}"
    }

}

拓展

众所周知,Anko 可以说是一个 很 Cool 的 Android 的 Kotlin 拓展库了,结合 Anko,可以很简便的实现Activity 的携参跳转与获取。 喏,还是那样,Activity 携参跳转我们这样写:

val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("id", 5)
startActivity(intent)

有了 Anko,我们只需简单的一句话就可以搞定,并且支持 map 的方式填充参数:

startActivity<SecondActivity>(
                    "id" to 5,
                    "name" to "WG"
            )

更多详情请访问Anko

后记

对于 Kotlin,本人接触的时间尚短,仍在不断学习的过程中,如果上述有描述不正确的地方,欢迎指正,共同进步。BTW,点一下小心心呗~~~n(≧▽≦)n