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

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

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

Ответ 1

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

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

setContentView(int) calls 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 файл, содержащий пользовательский вид. Afer конструктор должен быть частью кода, в котором пользовательский вид "разбора/чтения/раздувания/преобразования" объявленных в виде XML дочерних представлений к реальным представлениям! (комментарий JohnTube)

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

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

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

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

Примечание: вызов 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., посмотрите PhoneLayoutInflater Ссылка - он расширяет LayoutInflater и переопределяет onCreateView(...) чтобы проверить, являются ли android.widget и android.webkit именами предполагаемых пакетов. Фактически, вызов getLayoutInflater() получает экземпляр PhoneLayoutInflater. Вот почему, если вы были подклассом LayoutInflater, вы даже не могли бы раздувать простейшие макеты, потому что LayoutInflater может обрабатывать только представления из пакета android.view.

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

Итак, в случае обычного вида (например, Button) в качестве второго аргумента будет передан 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 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`.
}

... a View.

Ответ 2

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

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

EDIT: если вы хотите увидеть, как дети добавляются при раздутии представления, это происходит при вызове 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)).