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


132

我当前正在开发的Android应用程序的主要活动已经变得非常大。这主要是因为它包含TabWidget带有3个标签的。每个选项卡都有很多组件。活动必须立即控制所有这些组件。因此,我想您可以想象这个Activity有20个字段(几乎每个组件都有一个字段)。它还包含很多逻辑(单击侦听器,填充列表的逻辑等)。

我通常在基于组件的框架中所做的就是将所有内容拆分为自定义组件。每个自定义组件将承担明确的责任。它包含它自己的一组组件以及与该组件相关的所有其他逻辑。

我试图弄清楚如何做到这一点,并且在Android文档中找到了他们喜欢称之为“复合控件”的东西。(请参阅http://developer.android.com/guide/topics/ui/custom-components.html#compound并滚动到“复合控件”部分)我想基于定义了视图结构。

在文档中说:

请注意,就像使用Activity一样,您可以使用声明式(基于XML)方法来创建所包含的组件,也可以从代码中以编程方式嵌套它们。

好吧,这是个好消息!基于XML的方法正是我想要的!但是它没有说如何去做,除了它“像一个Activity”一样。但是我在Activity中要做的是调用setContentView(...)以从XML扩展视图。如果您例如使用subclass,则该方法不可用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声明为根元素这一事实外,此方法都有效。这导致膨胀LinearLayout的孩子MyCompoundComponent本身已经是一个孩子LinearLayout!因此,现在我们之间有了一个多余的LinearLayout MyCompoundComponent及其实际需要的视图。

有人可以为我提供一种更好的方法来解决此问题,避免冗余LinearLayout实例化吗?


14
我喜欢我从中学到的问题。谢谢。
杰里米·洛根

5
我最近写了一个与此有关的博客文章:blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3
Tom van Zummeren 2009年

Answers:


101

使用合并标签作为XML根

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

检查本文。


10
非常感谢你!那正是我想要的。这么长的问题怎么能这么短的答案令人惊讶。优秀的!
Tom van Zummeren 09年

如何在景观中排除此合并布局呢?
Kostadin

2
@Timmmm哈哈哈哈我早在视觉编辑器还没有出现时就问了这个问题:)
Tom van Zummeren 2012年

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);
}

免责声明:我可能正在谈论完整的胡说八道。


谢谢!我认为这个答案也是正确的:)但是三年前,当我问这个问题时,“合并”也成功了!
Tom van Zummeren

8
然后有人走来,并尝试使用在布局的地方自定义视图只<com.example.views.MyView />和你setDataonFinishInflate通话开始扔NPE上,而你不知道为什么。
2013年

这里的窍门是使用您的自定义视图,然后在构造函数中为使用合并标记作为根的布局充气。现在,您可以在XML中使用它,也可以仅对其进行更新。涵盖了所有基础,这正是问题/接受的答案一起做的事情。但是,您不能做的就是直接参考布局。现在,它已由自定义控件“拥有”,并且仅应在构造函数中使用。但是,如果您使用的是这种方法,为什么仍然需要在其他任何地方使用它呢?你不会的
马克·A·多诺霍
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.