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

Основное направление деятельности Android-приложения, которое я сейчас разрабатываю, стало довольно большим. Это главным образом потому, что он содержит TabWidget с 3 вкладками. Каждая вкладка состоит из нескольких компонентов. Действие должно контролировать все эти компоненты одновременно. Итак, я думаю, вы можете представить, что это 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 экземпляра?


person Tom van Zummeren    schedule 25.09.2009    source источник
comment
Мне нравятся вопросы, на которых я чему-то учусь. Спасибо.   -  person Jeremy Logan    schedule 26.09.2009
comment
Недавно я написал об этом запись в блоге: blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3   -  person Tom van Zummeren    schedule 12.10.2009


Ответы (2)


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

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

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

person bhatt4982    schedule 25.09.2009
comment
Большое тебе спасибо! Это именно то, что я искал. Удивительно, как на такой длинный вопрос может быть такой короткий ответ. Отлично! - person Tom van Zummeren; 25.09.2009
comment
Как насчет включения этого макета слияния в альбомную ориентацию? - person Kostadin; 10.07.2012
comment
@Timmmm hahahaha Я задал этот вопрос задолго до того, как появился визуальный редактор :) - person Tom van Zummeren; 21.12.2012

Я думаю, что вы должны использовать имя своего класса в качестве корневого элемента 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);
}

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

person Timmmm    schedule 22.10.2012
comment
Спасибо! Думаю, этот ответ тоже правильный :) Но три года назад, когда я задал этот вопрос, слияние тоже помогло! - person Tom van Zummeren; 21.12.2012
comment
А потом кто-то приходит и пытается использовать ваше пользовательское представление в макете где-нибудь с помощью всего лишь <com.example.views.MyView />, и ваши вызовы setData и onFinishInflate начинают выбрасывать NPE, и вы не понимаете почему. - person Christopher Perry; 09.03.2013
comment
Уловка здесь заключается в том, чтобы использовать ваше настраиваемое представление, а затем в конструкторе раздуть макет, который использует тег слияния в качестве корня. Теперь вы можете использовать его в XML или просто обновив его. Охватываются все основы, что и делает вопрос / принятый ответ вместе. Однако вы больше не можете напрямую обращаться к макету. Теперь он «принадлежит» настраиваемому элементу управления и должен использоваться только им в конструкторе. Но если вы используете этот подход, зачем вам вообще нужно использовать его где-нибудь еще? Ты бы не стал. - person Mark A. Donohoe; 21.07.2017