安卓冷知識:LayoutParams


安卓的布局有很多種,每種都有對應的LayoutParams類,那么它們之間到底是什么關系?

為什么在編寫Layout的XML文件時,有的layout_前綴屬性有用有的沒有用?

一句話道出LayoutParams的本質:LayoutParams是Layout提供給其中的Children使用的。我們來看一段不用XML文件創建布局的代碼。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LinearLayout ll = new LinearLayout(this);
        ll.setOrientation(LinearLayout.VERTICAL);

        TextView tv = new TextView(this);
        tv.setText("I am a text view, haha");
        tv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        ll.addView(tv);

        setContentView(ll);
    }

可見LayoutParams的對象是設置在Layout中的Child View上的,而不是設置在自己上。就是說View想要放到某種Layout中,就必須設置這個Layout規定的LayoutParams。

每一種Layout實現不同的布局方式都有其獨特的參數設置,例如LinearLayout.LayoutParams的weight屬性,用來設置Child View之間的大小比例,如果把這個設置給FrameLayout是沒有意義的,因為FrameLayout並不會排列Child View而是會都“擠在一起”。所以在XML文件里設置View的屬性的時候,有的layout屬性是無效的,也是沒有意義的。

如果仔細看上面那段代碼,會發現有個問題,設置TextView的LayoutParams是ViewGroup.LayoutParams,而不是LinearLayout.LayoutParams。查看源代碼可以看到LinearLayout.LayoutParams是ViewGroup.LayoutParams的子類,那么會不會產生運行時錯誤?下面通過分析源代碼找到這個問題的答案。

使Layout與View發生關系的語句是LinearLayout.addView(),先找到LinearLayout.java(frameworks/base/core/java/android/widget/LinearLayout.java)發現沒有override這個方法,到父類ViewGroup(frameworks/base/core/java/android/view/ViewGroup.java)中尋找addView。

    public void addView(View child) {
        addView(child, -1);
    }
    public void addView(View child, int index) {
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            // ......
        }
        addView(child, index, params);
    }
    public void addView(View child, int index, LayoutParams params) {
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }

 

addView有N個重載(overload)的版本,注意第二個是直接獲取Child View的LayoutParams,這里沒有經過處理,最后傳遞給了addViewInner()方法,再來看addViewInner()方法的代碼:

    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        // ...

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }
        
        // ...
    }

發現里面有這樣兩個調用checkLayoutParams()和generateLayoutParams()可能會修改或替換傳進來的params,找到這兩個函數發現ViewGroup中的實現幾乎是空的:

    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return  p != null;
    }
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return p;
    }

讓我們回到LinearLayout里看看有沒有override這兩個方法:

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LinearLayout.LayoutParams;
    }
    @Override
    public LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LinearLayout.LayoutParams(p);
    }

checkLayoutParams做的事情是檢查這個LayoutParam對象是否是LinearLayout.LayoutParams的實例,按照剛開始寫的代碼運行到這里就返回false了,於是執行generateLayoutParams,通過ViewGroup.LAyoutParams創建一個新的LinearLayout.LayoutParams。這樣就保證了使用的一定是LinearLayout.LayoutParams或者其子類。再看一下LinearLayout.LayoutParams的構造函數:

        public float weight;
        public int gravity = -1;
        public LayoutParams(ViewGroup.LayoutParams p) {
            super(p);
        }
        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }
        public LayoutParams(LayoutParams source) {
            super(source);

            this.weight = source.weight;
            this.gravity = source.gravity;
        }

顯然應該調用的是第一個構造函數,然后weight和gravity的值取默認值。為什么要定義第一個和第二個構造函數?因為LinearLayout.LayoutParams繼承自ViewGroup.MarginLayoutParams,而ViewGroup.MarginLayoutParams繼承自ViewGroup.LayoutParams。可見二者是LinearLayout.LayoutParams繼承樹上的所有祖先類。添加這兩個構造方法使能通過祖先類對象構造自己,配合上面的check和generate使不適配的LayoutParams變得可用。

根據以上分析,可以得出一個小結論,使用代碼構建界面的時候,可以隨意使用ViewGroup.LayoutParams或者ViewGroup.MarginLayoutParams,因為所有的Layout中的LayoutParams都繼承自他們,但是也會付出一點點代價就是會新創建一個對象。不過畢竟靠代碼構建界面的場景很少,一般用XML就可以了,這也算是個冷知識點吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM