包含具有自定义属性的布局

时间:2012-06-29 16:07:09

标签: android android-layout

我制作复杂的布局,并使用“include”作为我的自定义组件,例如

<include layout="@layout/topbar"/>

topbar定义如下:

<?xml version="1.0" encoding="utf-8"?>
<my.package.TopBarLayout
 ... a lot of code

现在,我想将我的自定义属性传递给“topbar”,就像这样

<include layout="@layout/topbar" txt:trName="@string/contacts"/>

但我没有结果。我从that page了解到我不能设置任何属性,而是id,height和width。

那么,我如何将我的自定义属性传递给包含,以及如何使其工作?

7 个答案:

答案 0 :(得分:61)

我知道这是一个老问题,但我发现它现在可以归功于Data Binding。

首先,您需要在项目中启用Data Binding

然后将数据绑定添加到要包含的布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <variable name="title" type="java.lang.String"/>
</data>
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/screen_header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:gravity="center">

...

<TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_centerInParent="true"
           android:textSize="20sp"
           android:textStyle="bold"
           android:text="@{title}"/>

...

</RelativeLayout>
</layout>

最后,将变量从主布局传递到包含的布局,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    ...
</data>    
...
xmlns:app="http://schemas.android.com/apk/res-auto"
...
<include layout="@layout/included_layout"
            android:id="@+id/title"
            app:title="@{@string/title}"/>
...
</layout>

答案 1 :(得分:9)

除了包含标记上的布局参数,可见性或ID之外,其他属性是不可能的。这包括自定义属性。

您可以通过查看第705行附近的LayoutInflater.parseInclude方法的来源来验证这一点: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/view/LayoutInflater.java#640

inflater仅将ID和可见性属性应用于包含的布局。

答案 2 :(得分:8)

我今天遇到了这个问题。无论它有什么价值,我认为有一个直截了当的工作。不是为include标记添加属性,而是为include创建自定义包装器视图,并为其添加属性。然后,从包装器中执行include。让包装器类实现提取属性并传递给它的单个子节点,这是包含布局的根视图。

所以,假设我们为一个名为SingleSettingWrapper的包装器声明了一些自定义属性 -

<declare-styleable name="SingleSettingWrapper">
    <attr name="labelText" format="string"/>
</declare-styleable>

然后,我们创建两个自定义视图类 - 一个用于包装器(SingleSettingWrapper),另一个用于子包(SingleSettingChild) -

<!-- You will never end up including this wrapper - it will be pasted where ever you wanted to include. But since the bulk of the XML is in the child, that's ok -->
<com.something.SingleSettingWrapper
    android:id="@+id/wrapper"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    custom:labelText="@string/my_label_string">

    <!-- Include the child layout -->
    <include layout="@layout/setting_single_item"/>

</com.something.SingleSettingWrapper>

对于孩子,我们可以在那里放置任何我们想要的复杂布局。我只是把一些基本的东西,但实际上你可以包括任何东西 -

<com.something.SingleSettingItem
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <RelativeLayout >
        <!-- add whatever custom stuff here -->
        <!-- in this example there would be a text view for the label and maybe a bunch of other stuff -->
        <!-- blah blah blah -->
    </RelativeLayout>
</com.something.SingleSettingItem>

对于包装器(这是键),我们在构造函数中读取所有自定义属性。然后,我们覆盖onViewAdded()并将这些自定义属性传递给我们的孩子。

public class SingleSettingWrapper extends FrameLayout 
{
    private String mLabel;

    public SingleSettingWrapper(Context context, AttributeSet attrs)
    {
        super(context, attrs);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
                                                             R.styleable.SingleSettingWrapper,
                                                             0, 0);

        mLabel = a.getString(R.styleable.SingleSettingWrapper_labelText);
        a.recycle();
    }

    public void onViewAdded(View child)
    {
        super.onViewAdded(child);
        if (!(child instanceof SingleSettingItem))
            return;

       ((TextView)child.findViewById(R.id.setting_single_label)).setText(mLabel);
        /*
        Or, alternatively, call a custom method on the child implementation -
        ((SingleSettingItem)child)setLabel(mLabel);
        */
    }
}

或者,您也可以实现子进程并让它从包装器接收消息并自行修改(而不是像上面那样让包装器修改子进程)。

public class SingleSettingItem extends LinearLayout
{
    public SingleSettingItem(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    public void setLabel(String l)
    {
        // set the string into the resource here if desired, for example
    }
}

在一天结束时,您希望<include>布局的每个XML文件将包含大约7行XML for wrapper + include而不是单个include包含您想要的,但是如果包含的视图包含数百行,您仍然可以更好。例如 -

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

    <!-- this is the beginning of your custom attribute include -->
    <com.something.SingleSettingWrapper
        android:id="@+id/my_wrapper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        custom:labelText="@string/auto_lock_heading">
        <include layout="@layout/setting_single_item"/>
    </com.something.SingleSettingWrapper>
    <!-- this is the end of your custom attribute include -->
</LinearLayout>

在实践中,这似乎工作得很好,并且设置起来相对简单。我希望它有所帮助。

答案 3 :(得分:6)

不幸的是,我唯一能做的就是我也无法在include标签上设置自定义属性,并将它们传递给包含的布局。

此时可能无法实现。

答案 4 :(得分:1)

答案 5 :(得分:0)

您必须在root xml元素中包含自定义命名空间。 如果你的包名是com.example.test你的xml shold是这样的:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:txt="http://schemas.android.com/apk/res/com.example.test" />

一个很好的教程是:http://blog.infidian.com/2008/05/02/android-tutorial-42-passing-custom-variables-via-xml-resource-files/

答案 6 :(得分:0)

我有同样的问题。在访问此主题后,我最终使用View的{​​{1}}方法在setTag()期间向每个View附加了标识信息,然后onCreate()稍后检索它的方法。