当前位置:   article > 正文

自定义控件使用ViewBinding时控件不可用的原因_kotlin架包中的viewbinding报错

kotlin架包中的viewbinding报错

最近在跟着郭霖老师的第一行代码(第二版)在学习Android开发,在书中常常用到 kotlin-android-extensions 

使用这个插件就可以直接用控件的id来调用控件实例,而不需要通过大量的 findViewById 来获取。

但在最新版本的AndroidStudio中,这个插件已经被弃用了,并且推荐使用 ViewBinding 来写。

这里对于为什么弃用 kotlin-android-extensions 插件不再多赘述,大概原理是这个插件原理通过维护一个哈希表来实现缓存增加了内存开支,另外哈希表的get也并非真正的O(1)方法,在郭霖老师的博客中通过反编译来获取这个插件的原理,对该原因有更加详细的描述。郭霖老师的DLC

ViewBinding能达成和该插件类似的结果,并且只是通过事先的一个 inflate 操作,从而可以用一个View实例binding的属性来调用控件实例。

具体的代码风格来说如下所示:

  1. package com.example.activitytest
  2. import androidx.appcompat.app.AppCompatActivity
  3. import android.os.Bundle
  4. import android.view.View
  5. import android.widget.Button
  6. import android.widget.Toast
  7. import androidx.appcompat.app.AlertDialog
  8. import com.example.activitytest.databinding.ActivityMainBinding
  9. class FirstActivity : AppCompatActivity() {
  10. override fun onCreate(savedInstanceState: Bundle?) {
  11. super.onCreate(savedInstanceState)
  12. val binding = ActivityMainBinding.inflate(layoutInflater)
  13. setContentView(binding.root)
  14. supportActionBar?.hide()
  15. val inputText = binding.editText.text
  16. binding.button.setOnClickListener{
  17. Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show()
  18. binding.image.setImageResource(R.drawable.ic_launcher_foreground)
  19. if (binding.progressBar.visibility == View.VISIBLE) {
  20. binding.progressBar.visibility = View.INVISIBLE
  21. } else {
  22. binding.progressBar.visibility = View.VISIBLE
  23. }
  24. binding.progressBar.progress += 10
  25. AlertDialog.Builder(this).apply {
  26. setTitle("This is a Dialog")
  27. setMessage("Something important")
  28. setCancelable(false)
  29. setPositiveButton("OK") {
  30. dialog, which ->
  31. }
  32. setNegativeButton("Cancel") {
  33. dialog, which ->
  34. }
  35. show()
  36. }
  37. }
  38. }
  39. }

在上面这段代码中,通过一个 ActivityMainBinding.inflate() 语句(对每个xml文件改为驼峰书写加上Binding均可获得对应binding类)可以获得该View实例 binding ,后续便可通过 binding.editText 来获取xml中的输入框的属性,其他控件同理。

看一下 inflate() 语句具体再做些什么,我们通过看他的定义,如下:

  1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
  2. final Resources res = getContext().getResources();
  3. if (DEBUG) {
  4. Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
  5. + Integer.toHexString(resource) + ")");
  6. }
  7. View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
  8. if (view != null) {
  9. return view;
  10. }
  11. XmlResourceParser parser = res.getLayout(resource);
  12. try {
  13. return inflate(parser, root, attachToRoot);
  14. } finally {
  15. parser.close();
  16. }
  17. }

我们可以看到这里需要传参一个resource的id,一个ViewGroup的根(父)view,以及一个表示是否加入到根(父)view的布尔变量。

然后获取View实例的关键点在于 try 中的 inflate() 方法,我们再去看看这个方法:

  1. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
  2. synchronized (mConstructorArgs) {
  3. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
  4. final Context inflaterContext = mContext;
  5. final AttributeSet attrs = Xml.asAttributeSet(parser);
  6. Context lastContext = (Context) mConstructorArgs[0];
  7. mConstructorArgs[0] = inflaterContext;
  8. View result = root;
  9. if (root != null && root.getViewRootImpl() != null) {
  10. root.getViewRootImpl().notifyRendererOfExpensiveFrame();
  11. }
  12. try {
  13. advanceToRootNode(parser);
  14. final String name = parser.getName();
  15. if (DEBUG) {
  16. System.out.println("**************************");
  17. System.out.println("Creating root view: "
  18. + name);
  19. System.out.println("**************************");
  20. }
  21. if (TAG_MERGE.equals(name)) {
  22. if (root == null || !attachToRoot) {
  23. throw new InflateException("<merge /> can be used only with a valid "
  24. + "ViewGroup root and attachToRoot=true");
  25. }
  26. rInflate(parser, root, inflaterContext, attrs, false);
  27. } else {
  28. // Temp is the root view that was found in the xml
  29. final View temp = createViewFromTag(root, name, inflaterContext, attrs);
  30. if (root == null && temp != null && temp.getViewRootImpl() != null) {
  31. temp.getViewRootImpl().notifyRendererOfExpensiveFrame();
  32. }
  33. ViewGroup.LayoutParams params = null;
  34. if (root != null) {
  35. if (DEBUG) {
  36. System.out.println("Creating params from root: " +
  37. root);
  38. }
  39. // Create layout params that match root, if supplied
  40. params = root.generateLayoutParams(attrs);
  41. if (!attachToRoot) {
  42. // Set the layout params for temp if we are not
  43. // attaching. (If we are, we use addView, below)
  44. temp.setLayoutParams(params);
  45. }
  46. }
  47. if (DEBUG) {
  48. System.out.println("-----> start inflating children");
  49. }
  50. // Inflate all children under temp against its context.
  51. rInflateChildren(parser, temp, attrs, true);
  52. if (DEBUG) {
  53. System.out.println("-----> done inflating children");
  54. }
  55. // We are supposed to attach all the views we found (int temp)
  56. // to root. Do that now.
  57. if (root != null && attachToRoot) {
  58. root.addView(temp, params);
  59. }
  60. // Decide whether to return the root that was passed in or the
  61. // top view found in xml.
  62. if (root == null || !attachToRoot) {
  63. result = temp;
  64. }
  65. }
  66. } catch (XmlPullParserException e) {
  67. final InflateException ie = new InflateException(e.getMessage(), e);
  68. ie.setStackTrace(EMPTY_STACK_TRACE);
  69. throw ie;
  70. } catch (Exception e) {
  71. final InflateException ie = new InflateException(
  72. getParserStateDescription(inflaterContext, attrs)
  73. + ": " + e.getMessage(), e);
  74. ie.setStackTrace(EMPTY_STACK_TRACE);
  75. throw ie;
  76. } finally {
  77. // Don't retain static reference on context.
  78. mConstructorArgs[0] = lastContext;
  79. mConstructorArgs[1] = null;
  80. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  81. }
  82. return result;
  83. }
  84. }

这里虽然比较长,但是核心代码部分还是非常清晰的:

当root不为空时,根据resource的最外层view在xml中定义的的属性,创建布局参数params。如果attachToRoot=False,则为temp设置布局参数params。

而当root不为空而且attachToRoot=True时,将temp添加到root中,并使用上面创建的布局参数params。

当root为空时候或者attachToRoot=False,将temp赋值给result。

最后,将result返回。

后续是我遇到的问题,我在自定义titleBar控件(第一行代码第四章中的实例)并在MainActivity中引用时候发生了控件不可用(按钮失效)的问题。

  1. package com.example.activitytest
  2. import android.app.Activity
  3. import android.content.Context
  4. import android.util.AttributeSet
  5. import android.view.LayoutInflater
  6. import android.widget.LinearLayout
  7. import android.widget.Toast
  8. import com.example.activitytest.databinding.TitleBinding
  9. class TitleLayout(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs){
  10. init {
  11. LayoutInflater.from(context).inflate(R.layout.title, this)
  12. val binding = TitleBinding.inflate(LayoutInflater.from(context), this, true)
  13. binding.titleBack.setOnClickListener {
  14. val activity = context as Activity
  15. activity.finish()
  16. }
  17. binding.titleEdit.setOnClickListener {
  18. Toast.makeText(context, "You clicked the edit button.", Toast.LENGTH_SHORT).show()
  19. }
  20. }
  21. }

我在不断修改初始化代码块的前两行代码(Binding部分),偶然在修改为如下代码时发现控件运行正常了:

LayoutInflater.from(context).inflate(R.layout.title, this, false)

回归源码去思考为什么这个第三个参数 attachToRoot 采用缺省值(True)会造成这样的结果,两种代码相差了一句代码:

root.addView(temp, params);

也就是说在root不为空时,我把先前的 params 参数传入 rootView 中则会造成未给该组件分配参数的问题,所以需要 attachToRoot 传参 False ,至此问题解决了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/319966
推荐阅读
相关标签
  

闽ICP备14008679号