Android - 编写自定义(复合)组件

135
我目前正在开发的Android应用程序有一个主活动,它已经变得非常庞大。这主要是因为它包含了一个带有3个选项卡的TabWidget。每个选项卡都有相当多的组件。该活动必须同时控制所有这些组件。因此,你可以想象一下,这个Activity有大约20个字段(几乎每个组件都有一个字段)。此外,它包含了大量的逻辑(点击监听器、填充列表等)。
在组件化框架中,我通常会将所有内容拆分成自定义组件。每个自定义组件都有明确的责任。它将包含其自己的一组组件和所有其他与该组件相关的逻辑。
我试图找出如何实现这一点,我在Android文档中找到了他们所谓的“复合控件”(Compound Control)。 (请参见http://developer.android.com/guide/topics/ui/custom-components.html#compound并滚动到“复合控件”部分)我想创建一个基于XML文件定义视图结构的这样的组件。
文档中说:
请注意,就像使用Activity一样,您可以使用声明性(基于XML的)方法来创建包含的组件,也可以从代码中以编程方式嵌套它们。
好消息是,基于XML的方法正是我想要的!但是它没有说明如何做,除了它“像在Activity中一样”……但我在Activity中所做的就是调用setContentView(...)来从XML填充视图。如果你以LinearLayout为例创建子类,该方法将不可用。
因此,我尝试手动膨胀XML,就像这样:
public class MyCompoundComponent extends LinearLayout {

    public MyCompoundComponent(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.my_layout, this);
    }
}
这个方案有效,但我加载的 XML 有 LinearLayout 声明为根元素。这导致充气的 LinearLayoutMyCompoundComponent 的子级,而后者本身已经是一个 LinearLayout!因此,现在在 MyCompoundComponent 和它实际需要的视图之间多了一个冗余的 LinearLayout。
请问有没有更好的方法来避免实例化多余的 LinearLayout

16
我喜欢那些能让我学到东西的问题。谢谢。 - Jeremy Logan
5
我最近写了一篇关于这个的博客文章: http://blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3/ - Tom van Zummeren
2个回答

104

使用merge标记作为您的XML根节点。

<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Your Layout -->
</merge>

查看这篇文章。


11
非常感谢!这正是我在寻找的。真不可思议,一个如此长的问题可以有如此简短的答案。太好了! - Tom van Zummeren
这个合并布局在横向屏幕中包含怎么样? - Kostadin
2
@Timmmm 哈哈哈哈,我在可视化编辑器问世之前就提出了这个问题 :) - Tom van Zummeren

0

我认为你应该这样做,使用你的类名作为XML根元素:

<com.example.views.MyView xmlns:....
       android:orientation="vertical" etc.>
    <TextView android:id="@+id/text" ... />
</com.example.views.MyView>

然后让您的类从您想要使用的任何布局派生出来。请注意,如果您使用此方法,不要在此处使用布局膨胀器。

public class MyView extends LinearLayout
{
    public ConversationListItem(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public ConversationListItem(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }


    public void setData(String text)
    {
        mTextView.setText(text);
    }

    private TextView mTextView;

    @Override
    protected void onFinishInflate()
    {
        super.onFinishInflate();

        mTextView = (TextView)findViewById(R.id.text);
    }
}

然后您可以像平常一样在XML布局中使用视图。如果您想以编程方式创建视图,则必须自己进行填充:

MyView v = (MyView)inflater.inflate(R.layout.my_view, parent, false);

不幸的是,这并不能让你执行v = new MyView(context),因为似乎没有绕过嵌套布局问题的方法,所以这并不是一个完整的解决方案。你可以在MyView中添加一个像这样的方法,使它看起来更好一些:

public static MyView create(Context context)
{
    return (MyView)LayoutInflater.fromContext(context).inflate(R.layout.my_view, null, false);
}

声明:我可能在胡说八道。


谢谢!我认为这个答案也是正确的 :) 但是三年前,当我问这个问题时,“merge”也可以解决问题! - Tom van Zummeren
8
жңүж—¶еҖҷпјҢеҲ«дәәдјҡеңЁеёғеұҖдёӯдҪҝз”Ё<com.example.views.MyView />жқҘи°ғз”ЁдҪ еҲӣе»әзҡ„иҮӘе®ҡд№үи§ҶеӣҫпјҢдҪҶжҳҜеҰӮжһңдҪ зҡ„setDataе’ҢonFinishInflateж–№жі•жҠӣеҮәдәҶз©әжҢҮй’ҲејӮеёёпјҢ并且дҪ дёҚзҹҘйҒ“еҺҹеӣ ж—¶пјҢиҜ·жіЁж„Ҹиҝҷз§Қжғ…еҶөгҖӮжҲ‘йңҖиҰҒж¶ҰиүІиҝҷж®өеҶ…容并дҪҝе…¶жӣҙеҠ йҖҡдҝ—жҳ“жҮӮпјҢдҪҶдёҚж”№еҸҳеҺҹж„ҸгҖӮ - Christopher Perry
这里的技巧是使用自定义视图,然后在构造函数中填充一个使用合并标记作为根的布局。现在你可以在XML中使用它,或者只需新建它。所有基础都被覆盖了,这正是问题/接受的答案一起做的。但是,你不能直接引用布局了。它现在被自定义控件所拥有,并且只应该在构造函数中使用。但是,如果你正在使用这种方法,为什么还需要在其他地方使用它呢?你不需要。 - Mark A. Donohoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接