android 自定義View 三 ---- LayoutInflater

說到自定義view就不得提到LayoutInflater,雖然我們在代碼中可以直接用new方法構造出各種View,然後再添加各種屬性去控制View的大小和位置等佈局,但是這是很複雜繁瑣的,細節優化更麻煩困難,面對複雜佈局,用代碼構造更顯得無力。這時我們必須藉助於LayoutInflater這個神器了。

LayoutInflater的作用就是能夠將value/layout目錄下的xml佈局文件,實例化成相應的View。它的好處是顯而易見的,xml佈局文件我們可以可視化構造。要使用LayoutInflater,首先要得到LayoutInflater的實例對象,有下面三種方法:

方法一:

在activity裏調用:

LayoutInflater inflater = getLayoutInflater();

方法二:

LayoutInflater inflater = LayoutInflater.from(mContext);

方法三:
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
如果查看android源碼可以知道前面兩個方法最後都是調用到第三個方法。有了LayoutInflater對象就可以調用它
的inflate方法把xml佈局文件變成view了。

下面我們就通過一個非常簡單的小例子,來更加直觀地看一下LayoutInflater的用法。比如說當前項目MainActivity對應的佈局文件叫做activity_main.xml,代碼如下所示:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

代碼很簡單,就只有一個RelativeLayout,裏面什麼控件都不放,我們再建一個layout_textview.xml,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!!!"
    android:textSize="18sp"
    android:padding="10dp"
    android:gravity="center"
    android:textColor="@color/white"
    android:background="@color/colorAccent"/></span>

代碼也很簡單,就一個TextView,現在我們看看用layoutInflater把這個textview加載到MainActivity的佈局裏,代碼如下:

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.root_layout);
        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        TextView textView = (TextView) inflater.inflate(R.layout.layout_textview,null);
        mainLayout.addView(textView);
    }
這裏使用了LayoutInflater的inflate(int resource, ViewGroup root),進入步看看源碼實際上是調用了:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
從上面可以看出來,inflate方法實際有三個參數:

resource :  int型,將被實例化的佈局文件

root :  ViewGroup型,根佈局或父佈局,如果這個參數不爲空,則傳入的viewgroup是將被實例化的View的根佈局。
attachToRoot : boolean型,是否依附到傳入的根佈局上 


先不多說,我們先看看上面的代碼的效果:


通過inflater.inflate(R.layout.layout_textview,null)方法,說明確實把TextView實例化出來了,當想我們修改下TextView的大小,佈局文件做如下修改:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:text="Hello World!!!"
    android:textSize="18sp"
    android:padding="10dp"
    android:gravity="center"
    android:textColor="@color/white"
    android:background="@color/colorAccent"/>
我們修改了layout_width把wrap_content改成了match_parent,讓寬度充滿全屏,也增加了layout_height的高度,再運行,看看效果:


OH,NO!竟然沒有變化,不是我們設想的寬度會充滿全屏,我們設置的layout_width和layout_height沒有起到一點作用。回到上面inflate方法,它有三個參數,前面root參數我們傳進去的是null,我們修改下這個參數,看看是不是它的問題,我們將activity代碼裏的inflater.inflate(R.layout.layout_textview,null)改成:inflater.inflate(R.layout.layout_textview,mainLayout,false); 再運行,效果如下:



哈哈,終於達到了我們想要的效果,這說明使用inflate時,傳入的佈局文件“最外層”的layout_height和layout_width是要有root佈局才能生效,這樣也說明其實layout_height和layout_width是跟父佈局有關,xml佈局裏也並沒有設置view的height和width兩個屬性。

再回來看看inflate的第三個參數attachToRoot,它其實代表實例化view後,是否將這個view依附添加到傳入的第二個參數的root父佈局裏。即如果爲true,省去了我們手動再調用addView()方法了。

下面我們分析下inflate的源碼:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        //生成xml解析器
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            
         final Context inflaterContext = mContext;
         final AttributeSet attrs = Xml.asAttributeSet(parser);//獲取xml裏面屬性
         View result = root;

         final String name = parser.getName(); //得到佈局xml裏的根佈局的名稱,這裏是textview
              
         if (TAG_MERGE.equals(name)) {//如果是merge佈局文件
              if (root == null || !attachToRoot) { //這裏attachToRoot必須爲true
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
              }
              rInflate(parser, root, inflaterContext, attrs, false);
         } else {
              //這裏先生成上面傳入的佈局文件裏的根View,我們上面只傳入了一個TextView
              // Temp is the root view that was found in the xml
              final View temp = createViewFromTag(root, name, inflaterContext, attrs);

              ViewGroup.LayoutParams params = null;
              if (root != null) {
                  //如果root不爲空,這裏會根據root生成layoutParams
                  params = root.generateLayoutParams(attrs);
                  if (!attachToRoot) {
                        //如果attachToRoot爲false,將上面生成的params賦值給生成的View
                        temp.setLayoutParams(params);
                  }
               }
               //上面生成了根View,再生成它的子View
               rInflateChildren(parser, temp, attrs, true);

               if (root != null && attachToRoot) {
                   root.addView(temp, params); //加入的傳入的root佈局裏
               }

               if (root == null || !attachToRoot) {
                    result = temp;
               }
            return result;
        }
    }
從上面源碼可以看出inflate()方法裏會根據傳入的root父佈局生成相應的layoutParams,然後在賦值給新的View.比如root佈局是RelativeLayout,則會生成RelativeLayout.LayoutParams屬性,那麼我們再修改下我們的佈局文件驗證下我們的分析:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:text="Hello World!!!"
    android:textSize="18sp"
    android:padding="10dp"
    android:gravity="center"
    android:layout_alignParentBottom="true"
    android:textColor="@color/white"
    android:background="@color/colorAccent"/>
我們這次加入了layout_alignParentBottom參數,這個參數只有在RelativeLayout裏纔有用,在我們這裏看看有沒有作用:


這裏起到了作用,說明確實是根據父控件生成了相應的LayoutParams。順便提下我們在activity裏面setContentView設置佈局文件,最終也是調用到了LayoutInflater類的inflate方法。

下面總結下:

inflate方法有三個參數:

resource :  int型, 傳入將被實例化的佈局文件

root :     ViewGroup型,根佈局或父佈局,如果這個參數不爲空,則會爲生成的view加上root相應的layoutParams參數
attachToRoot : boolean型,是否依附到傳入的root根佈局上 



更多精彩Android技術可以關注我們的微信公衆號,掃一掃下方的二維碼或搜索關注公共號: Android老鳥

                                                
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章