【发布时间】:2014-11-03 04:16:29
【问题描述】:
我找到了解决办法
非常感谢 cyroxis 的建设性批评和建议,使我朝着正确的方向前进。一开始我认为这不是公认的答案,但经过一番思考后我确定它是。
不过,我想提供一些更符合我的情况的附加信息。
您可以在本文底部找到解决方案文本
原始问题
我在这里的第一个问题。 :-) 让我们开始吧:
我想知道是否有一种简单的方法(通过 API,也许?!)在扩展 ViewGroup 的自定义类中使用 LayoutInflater 时避免不必要的深层视图层次结构,例如线性布局。
例如
这是一个 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 作为视图添加到例如Activity 或 Fragment 布局。
但是:现在我的布局层次结构中有两个 LinearLayout 节点。一个来自布局 xml 文件,另一个来自 MyCustomView 父 ViewGroup 类。
那么,我怎样才能删除其中一个冗余的 ViewGroup(但保持 xml 文件的 ViewGroup 的属性值不变)。
以前我总是实现这一点的方法是输入大量代码,将 xml 布局膨胀到一个局部变量中,并从中提取每个子组件,然后再次将其附加到 java ViewGroup。
还是我做的事情完全错了?除了在 xml 编辑器中进行设计之外,还有其他方法可以对复杂视图的行为进行编程吗?
非常感谢您的想法。
谢谢!
马丁
更新 #1
遗憾的是,cyroxis 建议的解决方案不是解决方案。让我解释一下我的观察(对不起我的英语不好,如果我犯了错误请纠正我 - 这有助于我提高我的英语技能:-)):
使用完整的限定符名称(根据我的经验)仅在您在 Java 代码中使用所有 onDraw 和 onMeasure 实现创建完全自定义的视图时才有意义。然后要将您的自定义视图包含在 xml 中的布局层次结构中,您必须采用这种方式并在布局文件中使用完整的限定符名称。在可样式化的 xml 文件中还定义了其他属性后,您可以简单地配置自定义视图,而无需更改 Java 代码中的任何一行。
尽管如此,我还是尝试使用完整的限定符来测试我是否遗漏了某些内容,或者我的知识是否落后于 Android API 的已实现功能。但是不,我找不到使用完整限定符名称对我有帮助的配置。更糟糕的是,Android Studio 在这个开发阶段对限定符名称的更改非常敏感,并且在启用预览渲染器并且存在语法错误或一些奇怪的依赖循环时经常崩溃。 ;-)
要明确:用完整的限定符名称封装整个布局结构通常可以工作(就不会使 IDE 崩溃而言),但不会“简单地”合并它的子视图而无需再次调用 LayoutInflater - cyroxis 想要避免。
说到“合并”:合并标签是我发现的最接近我想要的行为方式的东西。用合并封装我的布局并在我的自定义视图类中膨胀它确实节省了一层布局层次结构,但以直观的 xml 布局工作流为代价——因为布局编辑器不再有 ViewGroup 信息来设置合并的子视图位置(但它在运行时工作)。
为了表明我的立场:我希望能够使用 Android 的标准视图来组成更复杂的视图,然后在 Java 代码中编排所有子组件以创建一个可以在其他 xml 布局中重用的精美新视图文件、活动和片段。在我看来,在 Activity 或 Fragment 中编写组合视图的逻辑并不是一个好的选择,因为它使代码对于任何进一步的 Activity 或 Fragment 都是多余的。创建静态方法或完全松散的类,这些类需要我的复杂视图的一个、部分或全部布局组件的实例,这符合易于维护的代码和封装模块的想法。
我将在几个小时后提供另一个我已经使用了很长时间的解决方法。这让我想知道我是否是少数几个真正被这个问题困扰的人之一?!
更新 #2
我做了一些进一步的调查,发现了一些有趣的东西——也许它与 cyroxis 的解决方案有很多共同点:
- 我将完整的限定符名称添加到我的组合 xml 视图的最顶层 ViewGroup 中,指向我的 Java 类(如 cyroxis 所说)
-
我使用“include”指令将 xml 布局添加到我的 Fragment 的布局 xml 文件中。
结果是,渲染器(和我的设备)正确地显示了组合视图。所以我假设它从我的 Java 类中获取 ViewGroup 信息(它实际上是一个 LinearLayout),因为这是唯一需要注意的地方。
现在:如果是这种情况,我该如何访问不同的子组件?
this.getChildAt(...)
对我不起作用。
第二个观察:
如果我不是通过包含指令添加 xml 布局,而是直接使用
<net.martinmajewski.challengeme.views.panels.HintActivationPanel/>
我得到一个没有任何维度的不可见节点。 因此,此时 Java 类中的布局属性不可用。
到目前为止,我的 Java 类没有什么特别之处。它只是实现了三个构造函数,并且(如上所述)尝试访问子组件。
解决方案
先决条件:
- 您创建了一个布局 xml 文件,其中包含要组合成单个复杂视图的所有(标准 Android)视图。这是您视图的设计阶段。
- 您创建一个相应的 Java 类文件来扩展您选择的 ViewGroup - 最好是您的 xml 文件的根 ViewGroup 类型。这是保存视图行为和子视图编排逻辑的类。
- 您将 xml 文件中的 ViewGroup 类型名称替换为 Java 类的完整限定符名称。这将两个文件链接在一起(方向:XML -> Java 类。说:xml 布局知道并需要 Java 类,但反过来就不成立)。
现在您可以(重新)在不同的布局中使用您的视图,方法是将其包含在指令中。布局我的字面意思是布局 xml 文件。我希望能够以所见即所得的方式设计我的 UI,以便快速进行原型设计和审查。所以我想启用预览渲染器来使用 xml 文件,而无需在此时编写单个逻辑行。问题来了。当您将组合视图放置在 Activity / 片段的布局中时,它会被 setContentView 或 onCreateView 方法完全“自动”膨胀。所以此时它已经存在,并且它调用了 Java 类的三个可用公共构造函数之一。多亏了cyroxis,我才意识到了这个事实。 Cyroxis 建议在 Activity 中手动膨胀视图,但这会导致多余的膨胀。我认为 cyroxis 只是忘记考虑到自定义视图已经是父布局的一部分。
所以现在布局已经存在并且正确地连接到父节点没有加倍任何节点一切都是完美的,对吧?
还没有。
您的 Java 类没有对其子组件的引用。 您可以像 cyroxis 一样执行此操作,但是如果您已经实现了子组件的一些交互而没有任何外部影响,则必须在开始时初始化子视图(保存引用)。 为此,您必须提供一个在膨胀过程之后调用的公共初始化方法。此时,您的 Java 对象终于可以找到子视图的 id 并调用 findViewById 方法,而不会返回空指针。 getChildAt 方法也是如此。
也许这对你们中的一些人也有帮助。
再见 马丁
【问题讨论】:
-
“简单地”合并它的子视图是什么意思?您有两个不同视图组的原因是因为您正在扩展的 LinerLayout 已经在扩展 xml 布局。您的代码导致它膨胀了两次。
-
这个怎么解释?我的(上面显示的)代码的问题是(根据我的理解),Java 类是一个 ViewGroup(扩展 LinearLayout),它被连接到视图层次结构中并且它用另一个 ViewGroup 膨胀 xml 文件(在这种情况下也是一个 LinearLayout - 也可能是一个不同的 VG)并将其连接到 Java 类 ViewGroup。所以在这种情况下,我有两个 LL 节点。但是:我希望能够让我的 Java 类 VG 充当父 VG 来代替 xml 的父 VG。每个子组件都应附加到 Java 的 VG。你明白我的意思吗?
-
我下面的建议就是这样做的,我会添加更多关于如何使用它的信息。
标签: java android xml android-layout layout-inflater