Pre-Honeycomb (Android 3),每个Activity都注册为通过布局XML中的onClick标签处理按钮点击:
android:onClick="myClickMethod"
在该方法中,您可以使用view.getId()和switch语句来执行按钮逻辑。
随着蜂窝的引入,我将这些活动分解为片段,这些片段可以在许多不同的活动中重用。按钮的大部分行为是活动独立的,我想代码驻留在Fragments文件中,而不使用旧的(pre 1.6)方法为每个按钮注册OnClickListener。
final Button button = (Button) findViewById(R.id.button_id);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
}
});
问题是,当我的布局被膨胀时,它仍然是接收按钮点击的宿主活动,而不是单个片段。有没有好的方法来解决这两个问题
注册片段以接收按钮点击?
将点击事件从活动传递到它们所属的片段?
下面的解决方案可能是更好的选择。布局在fragment_my.xml中
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="listener"
type="my_package.MyListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/moreTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> listener.onClick()}"
android:text="@string/login"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
片段是这样的
class MyFragment : Fragment(), MyListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return FragmentMyBinding.inflate(
inflater,
container,
false
).apply {
lifecycleOwner = viewLifecycleOwner
listener = this@MyFragment
}.root
}
override fun onClick() {
TODO("Not yet implemented")
}
}
interface MyListener{
fun onClick()
}
当我看到答案时,它们不知何故很古老。最近谷歌引入了DataBinding,它更容易处理onClick或在xml中分配。
下面是一个很好的例子,你可以看到如何处理这个问题:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
</LinearLayout>
</layout>
还有一个关于数据绑定的很好的教程,你可以在这里找到。
你可以这样做:
活动:
Fragment someFragment;
//...onCreate etc instantiating your fragments
public void myClickMethod(View v) {
someFragment.myClickMethod(v);
}
片段:
public void myClickMethod(View v) {
switch(v.getId()) {
// Just like you were doing
}
}
@Ameen希望减少耦合,这样Fragments就可以重用了
接口:
public interface XmlClickable {
void myClickMethod(View v);
}
活动:
XmlClickable someFragment;
//...onCreate, etc. instantiating your fragments casting to your interface.
public void myClickMethod(View v) {
someFragment.myClickMethod(v);
}
片段:
public class SomeFragment implements XmlClickable {
//...onCreateView, etc.
@Override
public void myClickMethod(View v) {
switch(v.getId()){
// Just like you were doing
}
}
尽管我已经发现了一些依赖于数据绑定的不错的答案,但我还没有看到这种方法能达到完全的程度——在支持片段解析的同时允许XML中的无片段布局定义的意义上。
假设启用了数据绑定,我可以提出一个通用的解决方案;有点长,但它肯定有效(有一些注意事项):
步骤1:自定义OnClick实现
这将通过与点击视图相关的上下文(例如按钮)运行片段感知搜索:
// CustomOnClick.kt
@file:JvmName("CustomOnClick")
package com.example
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import java.lang.reflect.Method
fun onClick(view: View, methodName: String) {
resolveOnClickInvocation(view, methodName)?.invoke(view)
}
private data class OnClickInvocation(val obj: Any, val method: Method) {
fun invoke(view: View) {
method.invoke(obj, view)
}
}
private fun resolveOnClickInvocation(view: View, methodName: String): OnClickInvocation? =
searchContexts(view) { context ->
var invocation: OnClickInvocation? = null
if (context is Activity) {
val activity = context as? FragmentActivity
?: throw IllegalStateException("A non-FragmentActivity is not supported (looking up an onClick handler of $view)")
invocation = getTopFragment(activity)?.let { fragment ->
resolveInvocation(fragment, methodName)
}?: resolveInvocation(context, methodName)
}
invocation
}
private fun getTopFragment(activity: FragmentActivity): Fragment? {
val fragments = activity.supportFragmentManager.fragments
return if (fragments.isEmpty()) null else fragments.last()
}
private fun resolveInvocation(target: Any, methodName: String): OnClickInvocation? =
try {
val method = target.javaClass.getMethod(methodName, View::class.java)
OnClickInvocation(target, method)
} catch (e: NoSuchMethodException) {
null
}
private fun <T: Any> searchContexts(view: View, matcher: (context: Context) -> T?): T? {
var context = view.context
while (context != null && context is ContextWrapper) {
val result = matcher(context)
if (result == null) {
context = context.baseContext
} else {
return result
}
}
return null
}
注:松散地基于原来的Android实现(见https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#3025)
步骤2:布局文件中的声明性应用程序
然后,在支持数据绑定的XML中:
<layout>
<data>
<import type="com.example.CustomOnClick"/>
</data>
<Button
android:onClick='@{(v) -> CustomOnClick.onClick(v, "myClickMethod")}'
</Button>
</layout>
警告
假设一个基于FragmentActivity的“现代”实现
只能查找堆栈中“最顶层”(即最后一个)片段的方法(尽管这可以修复,如果需要)