2013-06-27 36 views
10

Câu hỏi của tôi là: Tôi muốn biết khi nào một xLayout (hoặc ViewGroup nói chung) thêm một khung nhìn con từ XML? Và bởi "khi" tôi có nghĩa là tại những gì điểm của mã, trong những gì "vượt qua" của "traversal" của bộ công cụ giao diện người dùng? Phương pháp xLayout hoặc ViewGroup nào tôi nên ghi đè?Khi nào chế độ xem con được thêm vào Layout/ViewGroup từ XML

Tôi đã thực hiện bài tập về nhà: Tôi đã xem "Writing Custom Views For Android" được trình bày (bởi Adam Powell và Romain Guy) trong Google I/O cuối cùng và tôi đã đọc nhận xét của Adam Powell về Google+ post này.

Trả lời

10

Tìm kiếm điểm chính xác trong mã nguồn của Android nơi trẻ em được thêm vào.

Chúng ta có thể xem những gì setContentView(R.layout.some_id) đang hoạt động dưới mui xe.

setContentView(int) gọi PhoneWindow#setContentView(int)-PhoneWindowLink là một inplementation cụ thể của 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(); 
    } 
} 

Phương pháp LayoutInflater#inflate(layoutResID, mContentParent) cuối cùng gọi ViewGroup#addView(View, LayoutParams) trên mContentParent. Ở giữa, chế độ xem con

Tôi muốn biết điều gì xảy ra chính xác sau khi tôi đặt chế độ xem nội dung thành tệp XML chứa chế độ xem tùy chỉnh. Afer constructor có phải là một phần trong đoạn mã nơi khung nhìn tùy chỉnh "parse/read/inflate/convert" các khung nhìn con được khai báo XML thành các khung nhìn thực tế!(Comment bởi JohnTube)

Ambiquity: Từ bình luận JohnTube của, có vẻ như ông là quan tâm nhiều hơn trong biết làm thế nào một cái nhìn tùy chỉnh là thổi phồng. Để biết điều này, chúng ta sẽ phải xem xét các hoạt động của LayoutInflaterLink.

Vì vậy, câu trả lời cho Which method of xLayout or ViewGroup should I override ?ViewGroup#addView(View, LayoutParams). Lưu ý rằng, tại thời điểm này, lạm phát của tất cả các Chế độ xem thông thường/tùy chỉnh đã diễn ra.

Lạm phát của quan điểm tùy chỉnh:

Các phương pháp sau đây trong LayoutInflater là nơi addView(View, LayoutParams) được kêu gọi phụ huynh/root:

Lưu ý: Các cuộc gọi mLayoutInflater.inflate(layoutResID, mContentParent); trong PhoneWindow#setContentView(int) chuỗi này. Ở đây mContentParentDecorView: chế độ xem có thể truy cập qua 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 

Cuộc gọi quan tâm trong phương pháp này (và trong đệ quy rInflate(XmlPullParser, View, AttributeSet, boolean)) là:

temp = createViewFromTag(root, name, attrs); 

Hãy xem những gì createViewFromTag(...) được thực hiện:

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

Các period(.) quyết định liệu onCreateView(...) hoặc createView(...) được gọi.

Tại sao lại chọn séc này? Bởi vì View được xác định trong các gói android.view, android.widget hoặc android.webkit được truy cập thông qua tên lớp của nó. Ví dụ:

android.widget: Button, TextView etc. 

android.view: ViewStub. SurfaceView, TextureView etc. 

android.webkit: WebView 

Khi gặp phải những quan điểm này, onCreateView(parent, name, attrs) được gọi. Phương pháp này thực sự chuỗi để createView(...):

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

này sẽ đối phó với SurfaceView, TextureView và quan điểm khác quy định tại android.view gói. Nếu bạn muốn biết cách TextView, Button etc. được xử lý, hãy xem PhoneLayoutInflaterLink - nó mở rộng LayoutInflater và ghi đè onCreateView(...) để kiểm tra xem android.widgetandroid.webkit là tên gói dự kiến ​​hay không. Thực tế, cuộc gọi getLayoutInflater() giúp bạn có một phiên bản PhoneLayoutInflater. Đây là lý do tại sao nếu bạn đã phân loại LayoutInflater, bạn thậm chí không thể thổi phồng bố cục đơn giản nhất - bởi vì LayoutInflater chỉ có thể xử lý lượt xem từ gói android.view.

Dù sao thì tôi cũng đang đi tiêu. Bit phụ này xảy ra cho Chế độ xem thông thường - không có số period(.) trong định nghĩa của chúng. Chế độ xem tùy chỉnh do có một khoảng thời gian trong tên của chúng - com.my.package.CustomView. Đây là cách số LayoutInflater phân biệt giữa hai số.

Vì vậy, trong trường hợp có chế độ xem thông thường (ví dụ: Nút), prefix chẳng hạn như android.widget sẽ được chuyển làm đối số thứ hai - cho chế độ xem tùy chỉnh, điều này sẽ là null. Sau đó, prefix được sử dụng cùng với name để có được hàm tạo cho lớp của chế độ xem cụ thể đó. Chế độ xem tùy chỉnh không cần điều này vì name của chúng tôi đã đủ điều kiện. Tôi đoán điều này đã được thực hiện để thuận tiện. Khác, bạn có thể đã được xác định bố trí theo cách này:

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

(pháp lý của nó mặc dù ...)

Ngoài ra, đây là lý do tại sao lượt xem đến từ một thư viện hỗ trợ (ví dụ < android.support. .v4.widget.DrawerLayout ... />) phải sử dụng tên đầy đủ.

Bằng cách này, nếu bạn đã muốn viết bố trí của bạn như:

<MyCustomView ../> 

tất cả các bạn phải làm là để mở rộng LayoutInflater và thêm tên gói của bạn com.my.package. vào danh sách các chuỗi được kiểm tra trong thời lạm phát . Kiểm tra PhoneLayoutInflater để được trợ giúp về vấn đề này.

Hãy xem điều gì xảy ra trong giai đoạn cuối cùng cho xem cả hai tùy chỉnh và thường xuyên - 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`. 
} 

... một View được sinh ra.

+1

Câu trả lời hay! Tôi đã tìm kiếm tập quán của 'addView (View)', nhưng không tìm thấy bất cứ điều gì có liên quan. Tham chiếu đến 'rInflate' trong' LayoutInflater' là một trợ giúp lớn! Cuộc gọi bên phải mà tôi đang tìm kiếm là 'addView (View, int, LayoutParams)'. – nhaarman

+1

@NiekHaarman Cảm ơn, Niek. 'addView (View, int, LayoutParams)' thực sự là cuộc gọi đúng. Tất cả các chuỗi phương thức 'addView' đã bị quá tải khác. – Vikram

7

Nếu bạn đang nói về một ViewGroup được xác định trong XML, đó là trẻ em được thêm khi lượt xem được tăng cao. Điều này có thể xảy ra khi bạn thổi phồng một cách rõ ràng với LayoutInflater hoặc khi bạn đặt chế độ xem nội dung của một hoạt động. (Có thể có vài lần khác nữa, đặc biệt nếu bạn đang sử dụng chế độ xem sơ khai.)

Nếu bạn muốn tự thêm trẻ vào ViewGroup, bạn có thể làm điều đó trong hàm tạo của khung nhìn.

EDIT: Nếu bạn muốn xem cách trẻ em được thêm khi lượt xem tăng cao, điều này xảy ra trong cuộc gọi đến LayoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot). Nguồn cho android.view.LayoutInflater được bao gồm trong bản phân phối Android SDK; các phiên bản trực tuyến có thể được tìm thấy ở nhiều nơi (ví dụ: here at GrepCode). Phương pháp này kết thúc khi được gọi khi, ví dụ, bạn gọi setContentView(int) cho một Activity hoặc khi bạn thổi phồng một cách rõ ràng tài nguyên bố cục.

Trẻ em thực sự được thêm vào trong cuộc gọi tới rInflate(parser, root, attrs, false); ("thổi phồng đệ quy"), có thể được gọi từ một vài địa điểm khác nhau theo phương pháp inflate(), tùy thuộc vào mức độ phát hiện được tìm thấy dưới dạng thẻ gốc. Bạn có thể tự mình theo dõi mã logic. Một điểm thú vị là một đứa trẻ không được thêm vào cha mẹ của nó cho đến khi con cái của nó đã được tăng lên đệ quy và thêm vào nó.

Phương pháp thú vị khác, được sử dụng bởi cả hai inflaterInflate, là createViewFromTag. Điều này có thể dựa trên một đối tượng có thể cài đặt LayoutInflater.Factory (hoặc .Factory2) để tạo chế độ xem hoặc có thể kết thúc bằng cách gọi createView. Ở đó bạn có thể thấy cách gọi hàm tạo hai đối số của khung nhìn ((Context context, AttributeSet attrs)) được thực hiện.