2013-06-27 11 views
10

私の質問: xLayout(または一般的なViewGroup)はXMLから子ビューをいつ追加しますか?そして、「いつ」というのは、UIツールキットの「トラバース」の「通過」の中で、コードのどの時点でですか? xLayoutまたはViewGroupのどのメソッドをオーバーライドする必要がありますか?XMLからLayout/ViewGroupに子ビューが追加されるタイミング

私は最後のGoogle I/Oで"Writing Custom Views For Android"(Adam PowellとRomain Guy)が贈呈したことを見ており、このGoogle+のAdam Powellのコメントを読んでいます。post

答えて

10

子供が追加されたAndroidのソースコードの正確な場所を探してください。

setContentView(R.layout.some_id)は何をしているのですか。

setContentView(int)

コールPhoneWindow#setContentView(int)からPhoneWindowLinkWindowの具体inplementationある:

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

結局mContentParentViewGroup#addView(View, LayoutParams)を呼び出しLayoutInflater#inflate(layoutResID, mContentParent)方法。その間に、子ビュー

私はコンテンツビューをカスタムビューを含むXMLファイルに設定した後に正確に何が起こるか知りたいと思います。コンストラクタを呼び出すには、XMLで宣言された子ビューを実際のビューに "解析/読み込み/膨張/変換"するカスタムビューのコードの一部が必要です。(JohnTubeによるコメント)

Ambiquity:JohnTubeさんのコメントから、彼がカスタムビューがを膨らませているかを知ることで、より興味を持っているようです。これを知るには、LayoutInflaterLinkの動作を見なければなりません。

したがって、Which method of xLayout or ViewGroup should I override ?への回答はViewGroup#addView(View, LayoutParams)です。この時点で、すべての標準/カスタムビューの膨張が既に行われていることに注意してください。カスタムビューの

インフレ:

addView(View, LayoutParams)は親/ルートに呼び出された

LayoutInflaterで、次の方法がある:

注:これにPhoneWindow#setContentView(int)チェーンでコールmLayoutInflater.inflate(layoutResID, mContentParent);。ここでmContentParentDecorViewです。このビューは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(...)が呼び出されます。

このチェックはなぜですか? android.viewに定義されているViewandroid.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); 
} 

これはSurfaceViewTextureViewandroid.viewパッケージで定義された他のビューを扱うでしょう。 TextView, Button etc.の処理方法を知りたい場合は、PhoneLayoutInflaterLinkを参照してください。LayoutInflaterを拡張し、onCreateView(...)を上書きして、android.widgetandroid.webkitが目的のパッケージ名であるかどうかを確認してください。実際にはgetLayoutInflater()というコールはPhoneLayoutInflaterのインスタンスを取得します。このため、LayoutInflaterをサブクラス化すると、LayoutInflaterandroid.viewパッケージからのビューしか扱えないため、最も単純なレイアウトを膨らませることはできません。

とにかく、私は逃げます。この余分なビットは通常のビューで発生します。そのビューにはperiod(.)がありません。カスタムビューにはというピリオドが付きます。これは、LayoutInflaterが2つを区別する方法です。です。

ので、通常のビュー(たとえば、ボタン)の場合には、そのようなandroid.widgetなどprefixは、2番目の引数として渡されます - カスタムビューのために、これはnullになります。 prefixは、その特定のビューのクラスのコンストラクタを取得するためにnameとともに使用されます。カスタムビューでは、nameがすでに完全修飾されているため、このビューは必要ありません。私はこれが便宜のために行われたと思います。そうでなければ、あなたはこの方法であなたのレイアウトの定義されていたであろう:

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

(ただしその法的...)

ビューがサポートライブラリ(例えばから来る理由も、これはある< android.support。 .v4.widget.DrawerLayout ... />)は、完全修飾名を使用する必要があります。

<MyCustomView ../> 

あなたがしなければならないすべてはLayoutInflaterを拡張し、膨張時にチェックされている文字列のリストにあなたのパッケージ名com.my.package.を追加することです:あなたのようにあなたのレイアウトを書きたいなかった場合ところで

、 。これについては、PhoneLayoutInflaterを参照してください。

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が生まれている:createView(...) -

はのは、カスタムおよび定期的なビューの両方のための最終段階で何が起こるか見てみましょう。

+1

素晴らしい回答!私は 'addView(View)'の使い方を探していましたが、関連するものは見つかりませんでした。 'LayoutInflater'の' rInflate'への参照は大きな助けになります!私が探していた正しい呼び出しは 'addView(View、int、LayoutParams)'でした。 – nhaarman

+1

@NiekHaarmanありがとう、Niek。 'addView(View、int、LayoutParams)'は本当に正しい呼び出しです。他の全てのオーバーロードされた 'addView'メソッドはそれに連鎖します。 – Vikram

7

XMLで定義されたViewGroupについて言えば、ビューが膨らんだときにその子が追加されます。これは、LayoutInflaterで明示的に展開したときや、アクティビティのコンテンツビューを設定したときに発生します。

膨張していないViewGroupに自分で子供を追加したい場合は、ビューのコンストラクタでそれを行うことができます(スタブビューを使用している場合は特にそうです)。

EDIT:ビューが拡張されたときに子がどのように追加されるかを確認するには、LayoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)を呼び出してこれが発生します。 android.view.LayoutInflaterのソースはAndroid SDKディストリビューションに含まれています。オンライン版は多くの場所で見つけることができます(例えば、here at GrepCode)。このメソッドは、たとえばActivityの場合はsetContentView(int)を呼び出したとき、またはレイアウトリソースを明示的に展開したときに呼び出されます。

rInflate(parser, root, attrs, false);( "recursive inflate")の呼び出しで子どもが実際に追加されます。これは、inflate()メソッドのいくつかの異なる場所から呼び出すことができます。インフレータがルートタグとして検出した内容によって異なります。自分でコードロジックをトレースすることができます。興味深い点は、子が再帰的に膨らんで追加されるまで、子は親に追加されないということです。

inflateとの両方で使用される他の興味深い方法は、createViewFromTagです。これは、インストール可能なLayoutInflater.Factory(または.Factory2オブジェクト)に依存してビューを作成するか、またはcreateViewを呼び出すことになります。ここで、ビューの2つの引数のコンストラクタ((Context context, AttributeSet attrs))の呼び出しがどのように行われているかを確認できます。

関連する問題