Android:如何避免使用LayoutInflater加倍ViewGroups

时间:2014-09-09 16:33:30

标签: java android xml android-layout layout-inflater

我找到了解决方案

非常感谢cyroxis提出的建设性批评和建议,这些建议使我走上了正确的方向。一开始我认为这不是公认的答案,但经过一番思考后我肯定是。

尽管如此,我想提供一些与我的案例更精确匹配的附加信息。

您可以在此帖子的底部找到解决方案文本

原始问题

我的第一个问题。 :-)让我们开始吧:

我想知道在扩展ViewGroup的自定义类中使用LayoutInflater时,是否有一种简单的方法(通过API,可能是?!)来避免不必要的深层视图层次结构的LinearLayout。

例如

这是一个Android xml布局文件" my_xml_layout.xml"

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="New Text"
        android:id="@+id/textView" />

    <ImageView
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:id="@+id/imageView"
        android:layout_gravity="center_horizontal" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        android:id="@+id/button"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

现在我想将布局封装到自定义视图中,并控制其所有组件(TextView,Button和ImageView)的行为和交互。所以我创建了一个像LinearLayout这样扩展的类

public class MyCustomView extends LinearLayout {

    public MyCustomView(Context context) {
        super(context);
        inflateLayout(context);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        inflateLayout(context);
    }

    public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        inflateLayout(context);
    }

    private void inflateLayout(Context context){
        LayoutInflater.from(context).inflate(R.layout.my_xml_layout, this, true);
    }

...

}

这很好用,现在我可以在MyCustomView内部访问所有子组件,并且可以简单地将MyCustomView作为视图添加到例如活动或片段布局。

但是:现在我的布局层次结构中有两个LinearLayout节点。一个来自布局xml文件,另一个来自MyCustomView父ViewGroup类。

那么我怎么能删除一个冗余的ViewGroups(但保留xml文件的ViewGroup的属性值不变)。

我以前总是这样做的方法是键入大量代码,将xml布局扩展为局部变量,并从中提取每个子组件,只是为了将它再次附加到java ViewGroup。

或者我做错了什么?是否有另一种方法来编写复杂视图的行为,但在xml编辑器中进行设计?

非常感谢您的想法。

谢谢!

马丁

更新#1

来自cyroxis的建议解决方案很遗憾不是解决方案。让我解释一下我的观察结果(抱歉我的英语不好,如果我犯错,请纠正我 - 这有助于提高我的英语技能:-)):

使用完整限定符名称(根据我的经验)仅在使用所有onDraw和onMeasure实现在Java代码中创建完全自定义视图时才有意义。然后,要将自定义视图包含在xml中的布局层次结构中,您必须采用这种方式并在布局文件中使用完整限定符名称。在可定制的xml文件中定义了其他属性后,您可以简单地配置自定义视图,而无需更改Java代码中的单行。

尽管如此,我还是尝试使用完整的限定符来测试我是否遗漏了某些内容,或者我的知识是否落后于Android API的实现功能。但不,我无法找到一个配置,其中使用完整的限定符名称可以帮助我。更糟糕的是,Android Studio在此开发阶段对限定符名称更改非常敏感,并且在启用预览渲染器并且存在语法错误或一些奇怪的依赖循环时经常崩溃。 ; - )

要明确:使用完整的限定符名称封装整个布局结构通常会起作用(就不会崩溃IDE而言)但不会简单地#34;在没有另外调用LayoutInflater的情况下合并它的子视图 - cyroxis想要避免什么。

说到&#34;合并&#34;:合并标签是我发现的最接近我想要的行为。使用合并封装我的布局并在我的自定义视图类中对其进行充气确实可以节省一个级别的布局层次结构,但代价是直观的xml布局工作流 - 因为布局编辑器不再具有ViewGroup信息来设置合并的子视图position(但它在运行时工作)。

让我的立场明确:我希望能够使用Android的标准视图来构成更复杂的视图,然后编排Java代码中的所有子组件,以创建一个我可以重用的新奇视图在其他xml布局文件,活动和片段中。在我看来,在Activity或Fragment中编写组合视图的逻辑并不是一个好的选择,因为它会使代码对于任何进一步的活动或片段而言是多余的。创建静态方法,或者完全松散的类需要一个实例到我的复杂视图的一个,一些或所有布局组件,这样就可以实现易于维护的代码和封装模块。

我将在这里提供另一种解决方法,我现在在几个小时内使用了很长时间。这让我想知道我是否只是少数几个真正被这个问题困扰的人之一?!

更新#2

我做了一些进一步的调查,发现了一些有趣的东西 - 也许它与cyroxis有很多共同之处。溶液:

  1. 我将完整的限定符名称添加到我的组合xml视图的最顶层ViewGroup,指向我的Java类(如cyroxis所说)
  2. 我使用&#34; include&#34;将xml布局添加到我的Fragment的布局xml文件中。指示。

  3. 结果是,渲染器(和我的设备)正确显示合成视图。所以我假设它从我的Java类获取ViewGroup信息(它实际上是一个LinearLayout),因为这是唯一注意到这一点的地方。

    现在:如果是这种情况,我该如何访问不同的子组件?

    this.getChildAt(...) 
    

    对我不起作用。

    第二个观察结果:

    如果我不是通过include-directive添加xml布局,而是直接添加

    <net.martinmajewski.challengeme.views.panels.HintActivationPanel/>
    

    我得到一个没有任何尺寸的不可见节点。 因此,此时Java类中不提供布局属性。

    到目前为止,我的Java类没有什么特别之处。它只是实现了三个构造函数,并且(如上所述)尝试访问子组件。

    解决方案

    先决条件:

    • 您可以创建一个布局xml文件,其中包含要组合成单个复杂视图的所有(标准Android)视图。这是您视图的设计阶段。
    • 您创建了一个相应的Java类文件,该文件扩展了您选择的ViewGroup - 最好是您的xml文件的根ViewGroup类型。这是包含行为和子视图编排的视图逻辑的类。
    • 使用Java类的完整限定符名称替换xml文件中的ViewGroup类型名称。这将两个文件链接在一起(方向:XML - &gt; Java类。说:xml布局知道并且需要Java类,但是反过来说这并不成立。)

    现在,您可以通过将其包含在指令中来(重新)使用不同布局中的视图。按布局我的字面意思是布局xml文件。我希望能够以所见即所得的方式设计我的UI,以便快速进行原型设计和审阅。因此,我希望启用预览渲染器以使用xml文件,而不必在此时写入单个逻辑行。问题来了。当您将撰写的视图放置在“活动”/“片段”的布局中时,它会完全自动地&#34;自动生成&#34;由setContentView或在onCreateView方法中膨胀。所以在这一点上它已经存在并且已经调用了Java类的三个可用公共构造函数之一。由于cyroxis我意识到了这个事实。 Cyroxis建议在Activity内手动充气视图,但这会导致冗余通胀。我认为cyroxis忘记考虑自定义视图已经是父布局的一部分了。

    现在布局已经存在并正确地连接到父而不会加倍任何节点一切都很完美,对吧?

    尚未。

    您的Java类没有对其子组件的引用。 您可以像cyroxis一样执行此操作,但是如果您已经实现了子组件的某些交互而没有任何外部影响,则必须在开始时初始化子视图(保存引用)。 为此,您必须提供在通胀过程后调用的公共初始化方法。此时,您的Java对象最终可以找到子视图的ID并调用findViewById方法,而不会返回空指针。对于getChildAt方法也是如此。

    也许这对你们中的一些人也有帮助。

    再见 马丁

1 个答案:

答案 0 :(得分:0)

不应在扩展类中扩充布局,而应在xml文件中指定完全限定的类名。

<?xml version="1.0" encoding="utf-8"?>
<com.package.MyCustomView xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="New Text"
        android:id="@+id/textView" />

    <ImageView
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:id="@+id/imageView"
        android:layout_gravity="center_horizontal" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        android:id="@+id/button"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

以下是包含更多信息的article

修改

可以将普通的android XML文件视为以编程方式创建视图的简写。上面的XML指示android使用给定的参数创建LinearLayout类型的视图,然后添加具有给定参数的子TextView,依此类推。

这种布局可以双向使用。

  1. 在另一个布局中包含布局
  2. 给布局充气(即使用布局充气机),它将返回LinearLayout类型的对象
  3. 您可以按如下方式创建课程

    public class MyCustomView extends LinearLayout {
    
      // Note: I am not inflating the layout here
    
      public void foo() {
          // Do some foo
      }
    
      public void setTitle(String text) {
          findViewById(R.id.textView).setText(text);
      }
      ....
    
    }
    

    然后使用上面的布局,您可以使用以下代码(比如活动/片段)

     MyCustomView view = (MyCustomView) LayoutInflater.from(context).inflate(R.layout.my_xml_layout, parent, true);
     view.foo(); // Do some foo
     view.setTitle("Hello Word!!!");
    

    这允许您创建自己的自定义“窗口小部件”,其行为与内置系统窗口小部件(TextView,ListView等)非常相似。它只有一个视图组(MyCustomView),您可以添加所需的任何额外行为。