Когда дочерние представления добавляются в Layout/ViewGroup из XML

Мой вопрос: я хочу знать, когда xLayout (или ViewGroup в целом) добавляет дочернее представление из XML? И под «когда» я подразумеваю, в какой точке кода, в каком «проходе» «обхода» инструментария пользовательского интерфейса? Какой метод xLayout или ViewGroup следует переопределить?

Я сделал свою домашнюю работу: я посмотрел представленный "Создание пользовательских представлений для Android" ( Адам Пауэлл и Ромен Гай) в последнем I/O Google, и я прочитал комментарии Адама Пауэлла в этом Google+ опубликовать.


person JohnTube    schedule 27.06.2013    source источник


Ответы (2)


Ищите точную точку в исходном коде Android, где добавляются дочерние элементы.

Мы можем посмотреть, что setContentView(R.layout.some_id) делает под капотом.

setContentView(int) вызывает PhoneWindow#setContentView(int) - PhoneWindowСсылка является конкретной реализацией Window:

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    } else {
        mContentParent.removeAllViews();
    }
    mLayoutInflater.inflate(layoutResID, mContentParent);
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

Метод LayoutInflater#inflate(layoutResID, mContentParent) в итоге вызывает ViewGroup#addView(View, LayoutParams) для mContentParent. В промежутках дочерние просмотры

Я хочу знать, что происходит после того, как я задаю представление содержимого XML-файлу, содержащему настраиваемое представление. После конструктора в коде должна быть часть, где пользовательское представление «разбирает/читает/раздувает/преобразовывает» XML-декларированные дочерние представления в фактические представления! (комментарий JohnTube)

Неоднозначность: судя по комментарию JohnTube, его больше интересует, как раздувается пользовательское представление. Чтобы узнать это, нам нужно взглянуть на работу LayoutInflaterСсылка.

Итак, ответ на Which method of xLayout or ViewGroup should I override ? равен ViewGroup#addView(View, LayoutParams). Обратите внимание, что на данный момент инфляция всех обычных/пользовательских представлений уже произошла.

Инфляция пользовательских представлений:

Следующий метод в LayoutInflater - это когда addView(View, LayoutParams) вызывается для родителя/корня:

Примечание. К этому привязывается вызов mLayoutInflater.inflate(layoutResID, mContentParent); в PhoneWindow#setContentView(int). Здесь mContentParent — это DecorView: представление, доступное через getWindow().getDecorView().

// Inflate a new view hierarchy from the specified XML node.
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

// Recursive method used to descend down the xml hierarchy and instantiate views,     
// instantiate their children, and then call onFinishInflate().
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
       boolean finishInflate) throws XmlPullParserException, IOException

Вызов интереса в этом методе (и в рекурсивном rInflate(XmlPullParser, View, AttributeSet, boolean)):

temp = createViewFromTag(root, name, attrs);

Посмотрим, что делает createViewFromTag(...):

View createViewFromTag(View parent, String name, AttributeSet attrs) {
    ....
    ....
    if (view == null) {
        if (-1 == name.indexOf('.')) {
            view = onCreateView(parent, name, attrs);
        } else {
            view = createView(name, null, attrs);
        }
    }
    ....
}

От period(.) зависит, будет ли вызываться onCreateView(...) или createView(...).

Зачем этот чек? Потому что доступ к View, определенному в пакетах android.view, android.widget или android.webkit, осуществляется через имя его класса. Например:

android.widget: Button, TextView etc.

android.view: ViewStub. SurfaceView, TextureView etc.

android.webkit: WebView

Когда встречаются эти представления, вызывается onCreateView(parent, name, attrs). Этот метод фактически связывается с createView(...):

protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

Это касается SurfaceView, TextureView и других представлений, определенных в пакете android.view. Если вам интересно узнать, как обрабатываются TextView, Button etc., посмотрите PhoneLayoutInflaterLink — расширяет LayoutInflater и переопределяет onCreateView(...), чтобы проверить, являются ли android.widget и android.webkit предполагаемыми именами пакетов. На самом деле вызов getLayoutInflater() дает вам экземпляр PhoneLayoutInflater. Вот почему если бы вы создали подкласс LayoutInflater, вы не смогли бы раздуть даже самые простые макеты, потому что LayoutInflater может работать только с представлениями из android.view пакета.

В любом случае, я отвлекся. Этот дополнительный бит происходит для обычных представлений, в определении которых нет period(.). Названия пользовательских представлений делают имеют точку – com.my.package.CustomView. Вот как LayoutInflater различает их.

Таким образом, в случае обычного представления (скажем, кнопки) prefix, например android.widget, будет передано в качестве второго аргумента — для пользовательских представлений это будет null. Затем prefix используется вместе с name для получения конструктора для этого конкретного класса представления. Пользовательским представлениям это не нужно, потому что их name уже полностью определено. Думаю, это сделано для удобства. В противном случае вы бы определяли свои макеты следующим образом:

<android.widget.LinearLayout
    ...
    ... />  

(Хотя это законно...)

Кроме того, именно поэтому представления из библиотеки поддержки (например, ‹android.support.v4.widget.DrawerLayout.../>) должны использовать полные имена.

Кстати, если вы хотите написать свои макеты как:

<MyCustomView ../>

все, что вам нужно сделать, это расширить LayoutInflater и добавить имя вашего пакета com.my.package. в список строк, которые проверяются во время надувания. Обратитесь к PhoneLayoutInflater за помощью в этом.

Посмотрим, что получится на финальном этапе как для кастомных, так и для обычных представлений — createView(...):

public final View createView(String name, String prefix, AttributeSet attrs)
                            throws ClassNotFoundException, InflateException {

    // Try looking for the constructor in cache
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;

    try {
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = mContext.getClassLoader().loadClass(
                 prefix != null ? (prefix + name) : name).asSubclass(View.class);
            ....
            // Get constructor   
            constructor = clazz.getConstructor(mConstructorSignature);
            sConstructorMap.put(name, constructor);
        } else {
            ....
        }

        Object[] args = mConstructorArgs;
        args[1] = attrs;

        // Obtain an instance
        final View view = constructor.newInstance(args);
        ....

        // We finally have a view!
        return view;
    }
    // A bunch of catch blocks: 
        - if the only constructor defined is `CustomView(Context)` - NoSuchMethodException
        - if `com.my.package.CustomView` doesn't extend View - ClassCastException
        - if `com.my.package.CustomView` is not found - ClassNotFoundException

    // All these catch blocks throw the often seen `InflateException`.
}

... родился View.

person Vikram    schedule 24.08.2014
comment
Отличный ответ! Я искал варианты использования addView(View), но не нашел ничего подходящего. Ссылка на rInflate в LayoutInflater очень помогает! Правильный вызов, который я искал, был addView(View, int, LayoutParams). - person nhaarman; 24.08.2014
comment
@NiekHaarman Спасибо, Ниек. addView(View, int, LayoutParams) действительно правильный вызов. Все остальные перегруженные методы addView сводятся к нему. - person Vikram; 25.08.2014

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

Если вы хотите сами добавить дочерние элементы к нерасширенному ViewGroup, вы можете сделать это в конструкторе представления.

РЕДАКТИРОВАТЬ: Если вы хотите увидеть, как добавляются дочерние элементы при раздувании представления, это происходит при вызове LayoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot). Исходный код для android.view.LayoutInflater включен в дистрибутив Android SDK; интерактивные версии можно найти во многих местах (здесь, например, в GrepCode). Этот метод в конечном итоге вызывается, когда, например, вы вызываете setContentView(int) для Activity или когда вы явно увеличиваете ресурс макета.

На самом деле дочерние элементы добавляются при вызове rInflate(parser, root, attrs, false); ("рекурсивное надувание"), который может быть вызван из нескольких разных мест в методе inflate(), в зависимости от того, что инфлятор нашел в качестве корневого тега. Вы можете самостоятельно проследить логику кода. Интересным моментом является то, что дочерний элемент не добавляется к своему родителю до тех пор, пока его собственные дочерние элементы не будут рекурсивно расширены и добавлены к нему.

Другой интересный метод, используемый как inflate, так и rInflate, называется createViewFromTag. Это может полагаться на устанавливаемый объект LayoutInflater.Factory (или .Factory2) для создания представления или может закончиться вызовом createView. Там вы можете увидеть, как выполняется вызов конструктора представления с двумя аргументами ((Context context, AttributeSet attrs)).

person Ted Hopp    schedule 27.06.2013