Android - Написание пользовательского (составного) компонента

132

Основное направление деятельности Android-приложения, которое я сейчас разрабатываю, стало довольно большим. Это в основном потому, что он содержит TabWidget3 вкладки. Каждая вкладка состоит из нескольких компонентов. Действие должно контролировать все эти компоненты одновременно. Итак, я думаю, вы можете представить, что это Activity имеет около 20 полей (поле почти для каждого компонента). Также он содержит много логики (прослушиватели кликов, логику для заполнения списков и т. Д.).

В компонентных фреймворках я обычно делю все на отдельные компоненты. Тогда каждый пользовательский компонент будет нести четкую ответственность. Он будет содержать собственный набор компонентов и всю остальную логику, связанную с этим компонентом.

Я попытался выяснить, как это можно сделать, и нашел в документации Android кое-что, что они называют «составным элементом управления». (См. 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объявлен как корневой элемент. Это приводит к тому, что раздутое LinearLayoutсущество является дочерним элементом MyCompoundComponentкоторого уже является LinearLayout!! Итак, теперь у нас есть избыточный LinearLayout между ними MyCompoundComponentи те представления, которые ему действительно нужны.

Может ли кто-нибудь предоставить мне лучший способ подойти к этому, избегая дублирования LinearLayoutэкземпляров?

Том ван Цуммерен
источник
14
Я люблю вопросы, из которых я чему-то учусь. Спасибо.
Джереми Логан,
5
Недавно я написал об этом в блоге запись: blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3
Tom van Zummeren

Ответы:

101

Используйте тег слияния в качестве корня XML

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

Проверьте эту статью.

bhatt4982
источник
10
Большое спасибо! Это именно то, что я искал. Удивительно, как на такой длинный вопрос может быть такой короткий ответ. Превосходно!
Том ван Зуммерен,
А как насчет включения этого макета слияния в альбомную ориентацию?
Костадин
2
@Timmmm hahahaha Я задал этот вопрос задолго до того, как появился визуальный редактор :)
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);
}

Отказ от ответственности: я могу говорить полную чушь.

Timmmm
источник
Спасибо! Думаю, этот ответ тоже правильный :) Но три года назад, когда я задал этот вопрос, «слияние» тоже помогло!
Tom van Zummeren
8
А потом кто - то приходит и пытается использовать настраиваемое представление в макете где - то с просто <com.example.views.MyView />и вашими setDataи onFinishInflateзвонки начинают бросать неработающие, и вы не имеете ни малейшего представления , почему.
Christopher Perry
Хитрость здесь в том, чтобы использовать ваше собственное представление, а затем в конструкторе раздуть макет, который использует тег слияния в качестве корня. Теперь вы можете использовать его в XML или просто обновив его. Охвачены все основы, что и делает вопрос / принятый ответ вместе. Однако вы больше не можете напрямую обращаться к макету. Теперь он «принадлежит» настраиваемому элементу управления и должен использоваться только им в конструкторе. Но если вы используете этот подход, зачем вам вообще нужно использовать его где-либо еще? Вы бы не.
Марк А. Донохо