Android - Написання користувацького (складового) компонента


132

Додаток Android, який я зараз розробляю, має основну діяльність, яка зросла досить великою. Це головним чином тому, що воно містить aTabWidget 3 вкладки. Кожна вкладка має досить багато компонентів. Діяльність повинна контролювати всі ці компоненти відразу. Тому я думаю, ви можете собі уявити, що ця діяльність має приблизно 20 полів (поле майже для кожного компонента). Крім того, він містить багато логіки (слухачі клацань, логіка для заповнення списків тощо).

Те, що я зазвичай роблю в рамках компонентних рамок, - це розділити все на спеціальні компоненти. Кожен користувацький компонент тоді несе чітку відповідальність. Він міститиме власний набір компонентів та всю іншу логіку, пов'язану з цим компонентом.

Я спробував розібратися, як це можна зробити, і в документації на Android я знайшов щось, що їм подобається називати «Комплексний контроль». (Побачити http://developer.android.com/guide/topics/ui/custom-components.html#compound і перейдіть до розділу «З'єднання управління») Я хотів би створити такий компонент на основі файлу XML , що визначає вид структури.

У документації зазначено:

Зауважте, що так само, як і у випадку діяльності, ви можете використовувати або декларативний (на основі XML) підхід до створення компонентів, що містяться, або ви можете програмно вкладати їх у свій код.

Ну, це гарна новина! Підхід на основі XML - це саме те, що я хочу! Але це не говорить про те, як це зробити, за винятком того, що це "як із діяльністю" ... Але те, що я роблю в рамках діяльності, - це заклик 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>

Перевірте цю статтю.


10
Велике спасибі! Саме це я і шукав. Дивно, як таке довге запитання може мати таку коротку відповідь. Відмінно!
Том ван Зуммерен,

Що з ілюструванням цього макета злиття в пейзажі?
Костадин

2
@Timmmm hahahaha. Я задав це питання задовго до того, як візуальний редактор навіть існував :)
Том ван Зуммерен

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

Відмова від відповідальності: я можу говорити про повне виправлення.


Дякую! Я вважаю, що ця відповідь також є правильною :) Але три роки тому, коли я задав це питання, "злиття" теж зробило трюк!
Том ван Зуммерен

8
І тоді хтось приходить разом і намагається використати ваш власний вигляд у макеті десь із справедливими, <com.example.views.MyView />і ваші setDataта onFinishInflateдзвінки починають кидати NPE, і ви не знаєте чому.
Крістофер Перрі

Трюк тут полягає у використанні власного перегляду, а потім у конструкторі надуйте макет, який використовує тег злиття як корінь. Тепер ви можете використовувати його в XML або просто доповнивши його. Всі основи охоплені, саме це питання / прийнята відповідь роблять разом. Що ви не можете зробити, це більше посилатися на макет безпосередньо. Тепер він "належить" користувальницькому керуванню і ним слід користуватися лише в конструкторі. Але якщо ви використовуєте такий підхід, то навіщо вам його потрібно використовувати деінде? Ви б не зробили.
Марк А. Донохое
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.