MVVM模式下组件复用的最佳实践及更优方案探讨
Great question! I totally get the frustration of juggling three separate files for a simple reusable component like an empty state view. Let’s go through a few optimized approaches that cut down on boilerplate while keeping your MVVM flow smooth:
1. Data Binding + <include> (No Custom View/Styleable Needed)
This is my go-to for XML-based component reuse—it lets you define all your configurable properties directly in the base layout and pass them in when including the component, no custom view class or <declare-styleable> required.
Step 1: Define the base empty state layout (empty_state.xml)
Add a <data> block to declare the variables you need, then bind them to your views:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="emptyImageRes" type="int" /> <variable name="emptyText" type="String" /> <variable name="onTextClick" type="android.view.View.OnClickListener" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@emptyImageRes" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@emptyText" android:clickable="true" android:onClick="@{onTextClick}" /> </LinearLayout> </layout>
Step 2: Reuse it in any fragment/layout
Use the <include> tag with bind: prefixes to pass your values and bind the ViewModel’s event:
<include layout="@layout/empty_state" bind:emptyImageRes="@drawable/someImage" bind:emptyText="@string/empty_text" bind:onTextClick="@{() -> viewModel.onEmptyStateClicked()}" />
That’s it—only one layout file to maintain, no extra Kotlin/Java classes or styleable declarations.
2. View Binding + Helper Extension (Minimal Boilerplate)
If you prefer View Binding over Data Binding, you can create a lightweight extension function or helper class to configure the component without a full custom view.
Step 1: Keep your base layout (empty_state.xml)
No data block needed—just a regular XML layout with your ImageView and TextView.
Step 2: Create an extension function in Kotlin
fun EmptyStateBinding.setup( imageRes: Int, text: String, onTextClicked: () -> Unit ) { emptyImageView.setImageResource(imageRes) emptyTextView.text = text emptyTextView.setOnClickListener { onTextClicked() } }
Step 3: Initialize in your Fragment
class MyFragment : Fragment() { private val viewModel: MyViewModel by viewModels() private lateinit var binding: MyFragmentBinding private lateinit var emptyStateBinding: EmptyStateBinding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = MyFragmentBinding.inflate(inflater, container, false) emptyStateBinding = EmptyStateBinding.bind(binding.emptyStateContainer) // Configure the empty state emptyStateBinding.setup( imageRes = R.drawable.someImage, text = getString(R.string.empty_text), onTextClicked = { viewModel.onEmptyStateClicked() } ) return binding.root } }
Here you only have two files: the layout and a tiny extension function—way simpler than a full custom view with styleables.
3. Jetpack Compose (For Modern Projects)
If your project is already using Compose, this is the cleanest option. Reusable components are built directly into the framework with zero XML:
@Composable fun EmptyState( imageRes: Int, text: String, onTextClicked: () -> Unit ) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Image( painter = painterResource(id = imageRes), contentDescription = null ) Text( text = text, modifier = Modifier .clickable { onTextClicked() } .padding(top = 16.dp) ) } }
Then reuse it anywhere in your Compose UI:
EmptyState( imageRes = R.drawable.someImage, text = stringResource(R.string.empty_text), onTextClicked = { viewModel.onEmptyStateClicked() } )
No XML, no styleables, just a single composable function that’s fully type-safe.
All these approaches eliminate the need for maintaining both a custom view class and <declare-styleable> file, keeping your codebase lean and easier to maintain.
内容的提问来源于stack exchange,提问作者Ghimpu Lucian Eduard

