Подтвердить что ты не робот

Пользовательский элемент управления Android с детьми

Я пытаюсь создать пользовательский элемент управления Android, который содержит LinearLayout. Вы можете думать об этом как о расширенном LinearLayout с фантастическими границами, фоном, изображением слева...

Я мог бы все это сделать в XML (отлично работает), но поскольку у меня есть десятки случаев в моем приложении, его трудно поддерживать. Я думал, что было бы лучше иметь что-то вроде этого:

/* Main.xml */
<MyFancyLayout>
    <TextView />   /* what goes inside my control linear layout */
</MyfancyLayout>

Как бы вы к этому подошли? Я бы хотел избежать повторной записи всей линейной компоновки методов onMeasure/onLayout. Это то, что у меня есть на данный момент:

/* MyFancyLayout.xml */
<TableLayout>
    <ImageView />
    <LinearLayout id="container" />   /* where I want the real content to go */
</TableLayout>    

и

/* MyFancyLayout.java */
public class MyFancyLayout extends LinearLayout
{
    public MyFancyLayout(Context context) {
        super(context);
        View.inflate(context, R.layout.my_fancy_layout, this);
    }
}

Как бы вы вставляли пользовательский контент (TextView в main.xml) в нужное место (id = container)?

Ура!

Ромны

----- edit -------

По-прежнему вам не повезло, поэтому я изменил свой дизайн, чтобы использовать более простой макет и решил жить с немного повторяющимся XML. Все еще очень интересно, кто-нибудь знает, как это сделать, хотя!

4b9b3361

Ответ 1

Этот точный вопрос прослушивал меня уже некоторое время, но только сейчас, когда я его решил.

С первого взгляда проблема заключается в том, что декларативный контент (TextView в вашем случае) создается через некоторое время после ctor (где мы обычно раздуваем наши макеты), поэтому он слишком рано имеет как декларативный, так и шаблонный контент под рукой, чтобы подтолкнуть прежнего к последнему.

Я нашел одно такое место, где мы можем манипулировать обоими: это метод onFinishInflate(). Вот как это происходит в моем случае:

    @Override
    protected void onFinishInflate() {
        int index = getChildCount();
        // Collect children declared in XML.
        View[] children = new View[index];
        while(--index >= 0) {
            children[index] = getChildAt(index);
        }
        // Pressumably, wipe out existing content (still holding reference to it).
        this.detachAllViewsFromParent();
        // Inflate new "template".
        final View template = LayoutInflater.from(getContext())
            .inflate(R.layout.labeled_layout, this, true);
        // Obtain reference to a new container within "template".
        final ViewGroup vg = (ViewGroup)template.findViewById(R.id.layout);
        index = children.length;
        // Push declared children into new container.
        while(--index >= 0) {
            vg.addView(children[index]);
        }

        // They suggest to call it no matter what.
        super.onFinishInflate();
    }

Указанный выше labeled_layout.xml не похож на что-то вроде этого:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation             ="vertical"
    android:layout_width            ="fill_parent"
    android:layout_height           ="wrap_content"
    android:layout_marginLeft       ="8dip"
    android:layout_marginTop        ="3dip"
    android:layout_marginBottom     ="3dip"
    android:layout_weight           ="1"
    android:duplicateParentState    ="true">

    <TextView android:id            ="@+id/label"
        android:layout_width        ="fill_parent"
        android:layout_height       ="wrap_content"
        android:singleLine          ="true"
        android:textAppearance      ="?android:attr/textAppearanceMedium"
        android:fadingEdge          ="horizontal"
        android:duplicateParentState="true" />

    <LinearLayout
        android:id                  ="@+id/layout"
        android:layout_width        ="fill_parent"
        android:layout_height       ="wrap_content"
        android:layout_marginLeft   ="8dip"
        android:layout_marginTop    ="3dip" 
        android:duplicateParentState="true" />
</LinearLayout>

Теперь (все еще опуская некоторые подробности) в другом месте мы можем использовать его следующим образом:

        <com.example.widget.LabeledLayout
            android:layout_width    ="fill_parent"
            android:layout_height   ="wrap_content">
            <!-- example content -->
        </com.example.widget.LabeledLayout> 

Ответ 2

Этот подход сэкономит мне много кода!:)

Как следует из объяснения, просто поменяйте содержимое, определенное xml, туда, где вы хотите их внутренне в своем собственном макете, в onFinishInflate(). Пример:

Я беру содержимое, которое я указываю в xml:

<se.jog.custom.ui.Badge ... >
    <ImageView ... />
    <TextView ... />
</se.jog.custom.ui.Badge>

... и переместите их в мой внутренний LinearLayout, называемый contents, где я хочу, чтобы они были:

public class Badge extends LinearLayout {
    //...
    private LinearLayout badge;
    private LinearLayout contents;

    // This way children can be added from xml.
    @Override
    protected void onFinishInflate() {      
        View[] children = detachChildren(); // gets and removes children from parent
        //...
        badge = (LinearLayout) layoutInflater.inflate(R.layout.badge, this);
        contents = (LinearLayout) badge.findViewById(R.id.badge_contents);
        for (int i = 0; i < children.length; i++)
            addView(children[i]); //overridden, se below.
        //...
        super.onFinishInflate();
    }

    // This way children can be added from other code as well.
    @Override
    public void addView(View child) {
        contents.addView(child);
}

В сочетании с пользовательскими атрибутами XML все становится очень удобным.

Ответ 3

Вы можете создать свой класс MyFancyLayout, расширив LinearLayout. Добавьте три конструктора, которые вызывают метод ( "инициализировать" в этом случае), чтобы настроить остальные виды:

public MyFancyLayout(Context context) {
    super(context);
    initialize();
}

public MyFancyLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initialize();
}

public MyFancyLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initialize();
}

Внутри инициализации вы делаете все, что вам нужно, чтобы добавить дополнительные представления. Вы можете получить LayoutInflater и раздуть другой макет:

final LayoutInflater inflator = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflator.inflate(R.layout.somecommonlayout, this);

Или вы можете создавать представления в коде и добавлять их:

        ImageView someImageView = new ImageView(getContext());
        someImageView.setImageDrawable(myDrawable);
        someImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        addView(someImageView);

Если вы собираетесь использовать контекст много, вы можете сохранить ссылку на него в своих конструкторах и использовать это, а не getContext(), чтобы сохранить небольшие накладные расходы.