hei*_*erg 29 android android-viewbinding
我开始使用 ViewBinding。在搜索了一些示例或建议后,如何将 ViewBinding 与抽象基类一起使用,该抽象基类应该处理预期出现在每个孩子的布局中的视图的相同逻辑,我最终在这里发布了这个问题。
场景:
我有一个基类public abstract class BaseFragment。有多个 Fragment 扩展了这个基类。这些 Fragment 具有从基类实现(使用 "old" findViewById())处理的公共视图。例如,每个片段的布局都应该包含一个 ID 为 text_title 的 TextView。这是从BaseFragment's处理它的方式onViewCreated():
TextView title = view.findViewById(R.id.text_title);
// Do something with the view from the base class
Run Code Online (Sandbox Code Playgroud)
现在 ViewBinding-API 为每个子片段生成绑定类。我可以使用绑定引用视图。但是我不能使用基类中的具体绑定。即使在基类中引入泛型,也有太多类型的片段绑定,我现在放弃了这个解决方案。
从抽象基类处理绑定视图的推荐方法是什么?是否有最佳实践?没有在 API 中找到内置机制来优雅地处理这种情况。
当期望子片段包含公共视图时,我可以提供抽象方法,这些方法从片段的具体绑定中返回视图,并使它们可以从基类访问。(例如protected abstract TextView getTitleView();)。但这是一个优势而不是使用findViewById()吗?你怎么认为?任何其他(更好的)解决方案?请让我们开始一些讨论。
Che*_*pta 27
嗨,我创建了一篇博文,其中深入介绍了视图绑定,还包括组合模式/委托模式来实现视图绑定以及使用链接中的继承签出
结帐以获取完整的代码BaseActivity和BaseFragment用法
/*
* In Activity
* source : https://chetangupta.net/viewbinding/
* Author : ChetanGupta.net
*/
abstract class ViewBindingActivity<VB : ViewBinding> : AppCompatActivity() {
private var _binding: ViewBinding? = null
abstract val bindingInflater: (LayoutInflater) -> VB
@Suppress("UNCHECKED_CAST")
protected val binding: VB
get() = _binding as VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = bindingInflater.invoke(layoutInflater)
setContentView(requireNotNull(_binding).root)
setup()
}
abstract fun setup()
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
Run Code Online (Sandbox Code Playgroud)
/*
* In Fragment
* source : https://chetangupta.net/viewbinding/
* Author : ChetanGupta.net
*/
abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
private var _binding: ViewBinding? = null
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
@Suppress("UNCHECKED_CAST")
protected val binding: VB
get() = _binding as VB
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bindingInflater.invoke(inflater, container, false)
return requireNotNull(_binding).root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
}
abstract fun setup()
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Run Code Online (Sandbox Code Playgroud)
对于用法,提前模式和反模式签出博客Androidbites|ViewBinding
Mat*_*tky 17
这是我的完整示例BaseViewBindingFragment:
abstract属性或功能,fun createBindingInstance,其中VB使用泛型类型参数package app.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import java.lang.reflect.ParameterizedType
/**
* Base application `Fragment` class with overridden [onCreateView] that inflates the view
* based on the [VB] type argument and set the [binding] property.
*
* @param VB The type of the View Binding class.
*/
open class BaseViewBindingFragment<VB : ViewBinding> : Fragment() {
/** The view binding instance. */
protected var binding: VB? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
createBindingInstance(inflater, container).also { binding = it }.root
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
/** Creates new [VB] instance using reflection. */
@Suppress("UNCHECKED_CAST")
protected open fun createBindingInstance(inflater: LayoutInflater, container: ViewGroup?): VB {
val vbType = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
val vbClass = vbType as Class<VB>
val method = vbClass.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
// Call VB.inflate(inflater, container, false) Java static method
return method.invoke(null, inflater, container, false) as VB
}
}
Run Code Online (Sandbox Code Playgroud)
使用minifyEnabled true,要保留生成的ViewBinding类,请将此规则添加到您的ProGuard文件中:
-keepclassmembers class * implements androidx.viewbinding.ViewBinding {
*;
}
Run Code Online (Sandbox Code Playgroud)
小智 10
基类将像这样
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity(){
protected lateinit var binding : VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = inflateLayout(layoutInflater)
setContentView(binding.root)
}
abstract fun inflateLayout(layoutInflater: LayoutInflater) : VB
}
Run Code Online (Sandbox Code Playgroud)
现在在您想要使用的活动中
class MainActivity : BaseActivity<ActivityMainBinding>(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.tvName.text="ankit"
}
override fun inflateLayout(layoutInflater: LayoutInflater) = ActivityMainBinding.inflate(layoutInflater)
}
Run Code Online (Sandbox Code Playgroud)
现在在 onCreate 中只需根据使用情况使用绑定
我找到了适用于我的具体场景的解决方案,我不想与您分享。
请注意,这不是对如何ViewBinding工作的解释。
我在下面创建了一些伪代码与您分享。(使用DialogFragments该显示从我的解决方案迁移AlertDialog)。我希望它几乎正确适应 Fragments ( onCreateView()vs. onCreateDialog())。我让它以这种方式工作。
想象一下,我们有一个抽象BaseFragment类FragmentA和两个扩展类and FragmentB。
首先看看我们所有的布局。请注意,我将布局的可重用部分移到了一个单独的文件中,该文件稍后将从具体片段的布局中包含在内。特定视图保留在其片段的布局中。对于这种情况,使用常用的布局很重要。
fragment_a.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- FragmentA-specific views -->
<EditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/edit_name">
<!-- Include the common layout -->
<include
layout="@layout/common_layout.xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
Run Code Online (Sandbox Code Playgroud)
fragment_b.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- FragmentB-specific, differs from FragmentA -->
<TextView
android:id="@+id/text_explain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/explain" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/text_explain">
<!-- Include the common layout -->
<include
layout="@layout/common_layout.xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
Run Code Online (Sandbox Code Playgroud)
common_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.widget.RelativeLayout">
<Button
android:id="@+id/button_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/up"/>
<Button
android:id="@+id/button_down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button_up"
android:text="@string/down" />
</merge>
Run Code Online (Sandbox Code Playgroud)
接下来是片段类。首先是我们的BaseFragment实现。
onCreateView()是绑定膨胀的地方。我们能够CommonLayoutBinding根据common_layout.xml包含的片段的绑定来绑定。我定义了一个抽象方法,onCreateViewBinding()调用onCreateView()它返回VewBindingfromFragmentA和FragmentB。这样我就可以确保在需要创建CommonLayoutBinding.
接下来,我可以CommonLayoutBinding通过调用commonBinding = CommonLayoutBinding.bind(binding.getRoot());. 请注意,来自具体片段绑定的根视图被传递给bind()。
getCommonBinding()允许提供对CommonLayoutBinding来自扩展片段的访问。我们可以更严格:BaseFragment应该提供访问该绑定的具体方法,而不是将其公开给它的子类。
private CommonLayoutBinding commonBinding; // common_layout.xml
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Make sure to create the concrete binding while it's required to
// create the commonBinding from it
ViewBinding binding = onCreateViewBinding(inflater);
// We're using the concrete layout of the child class to create our
// commonly used binding
commonBinding = CommonLayoutBinding.bind(binding.getRoot());
// ...
return binding.getRoot();
}
// Makes shure to create the concrete binding class from child-classes before
// the commonBinding can be bound
@NonNull
protected abstract ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container);
// Allows child-classes to access the commonBinding to access common
// used views
protected CommonLayoutBinding getCommonBinding() {
return commonBinding;
}
Run Code Online (Sandbox Code Playgroud)
现在看看其中一个子类,FragmentA. 从onCreateViewBinding()我们创建我们的绑定,就像我们从onCreateView(). 原则上它仍然是从onCreateVIew(). 如上所述,此绑定从基类中使用。我正在使用getCommonBinding()能够从common_layout.xml. BaseFragment现在,每个子类都可以从ViewBinding.
这样我就可以将基于公共视图的所有逻辑上移到基类。
private FragmentABinding binding; // fragment_a.xml
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Make sure commonBinding is present before calling super.onCreateView()
// (onCreateViewBinding() needs to deliver a result!)
View view = super.onCreateView(inflater, container, savedInstanceState);
binding.editName.setText("Test");
// ...
CommonLayoutBinding commonBinding = getCommonBinding();
commonBinding.buttonUp.setOnClickListener(v -> {
// Handle onClick-event...
});
// ...
return view;
}
// This comes from the base class and makes sure we have the required
// binding-instance, see BaseFragment
@Override
protected ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container) {
binding = FragmentABinding.inflate(inflater, container, false);
return binding;
}
Run Code Online (Sandbox Code Playgroud)
优点:
<include />缺点:
<included />布局会导致许多 Binding 类,那么没有什么可取的CommonLayoutBinding)。每个子项 ( FragmentA, FragmentB)不仅有一个绑定类,它提供对视图层次结构中所有视图的访问如果视图无法移动到通用布局中怎么办?
我对如何作为最佳实践解决这个问题非常感兴趣!让我们考虑一下:在具体的ViewBinding. 我们可以引入一个接口来提供对常用视图的访问。从片段中,我们将绑定包装在这些包装类中。另一方面,这将导致每个 ViewBinding 类型的许多包装器。但是我们可以BaseFragment使用抽象方法(泛型)来提供这些包装器。BaseFragment然后可以使用定义的接口方法访问视图或处理它们。你怎么认为?
结论:
也许这只是 ViewBinding 的实际限制,即一种布局需要拥有自己的 Binding 类。如果您在无法共享布局并且需要在每个布局中声明重复的情况下找到了一个好的解决方案,请告诉我。
我不知道这是否是最佳实践,或者是否有更好的解决方案。但是直到这是我的用例的唯一已知解决方案之前,这似乎是一个好的开始!
我创建了这个抽象类作为基础;
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
private var _binding: VB? = null
val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = inflateViewBinding(inflater, container)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
abstract fun inflateViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
Run Code Online (Sandbox Code Playgroud)
}
用法;
class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textViewTitle.text = ""
}
override fun inflateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {
return FragmentHomeBinding.inflate(inflater, container, false)
}
Run Code Online (Sandbox Code Playgroud)
}
| 归档时间: |
|
| 查看次数: |
8657 次 |
| 最近记录: |