Android之探究viewGroup自定義子屬性參數的獲取流程


通常會疑惑,當使用不同的布局方式時,子view得布局屬性就不太一樣,比如當父布局是LinearLayout時,子view就能有效的使用它的一些布局屬性如layout_weight、weightSum、等;當使用的是RelativeLayout時,其子view就能使用屬於它的有效的屬性;當然使用FrameLayout時,其屬性集就少多了,也就常說的FrameLayout是最簡單的布局方式了。

簡單的說我們就是想要實現自定的屬性可以再子view里面使用。

那么我們先來探索下ViewGroup中的LayoutParams吧。

viewGroup也是繼承自view,主要是實現布局容器。

那么它有個內部基類LayoutParams。當然,這個viewGroup中的LayoutParams是所有的FrameLayout、RelativeLayout、、、的LayoutParams的基礎類。當繼承后,子layoutParams就擁有了父親的所有的屬性集合

這個類注釋了,是用於view去告訴他們的父布局他們想咋滴,也就是說這個是子view和父布局的通訊方式吧

現在來看看ViewGroup中的addView的實現流程

public void addView(View child, int index) {
        ...
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
         ....
        }
        addView(child, index, params);
    } 
public void addView(View child, int index, LayoutParams params) {
...
addViewInner(child, index, params, false);
}

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

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

if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
}

通過整個addview的片段流程可以看到。

首先是checkLayoutParams,目的是檢測這個參數是否為空,如果為空的話就給它生成一個普通的LayoutParams;

    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return  p != null;
    }
  protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
   return p;
  }
  public LayoutParams generateLayoutParams(AttributeSet attrs) {
   return new LayoutParams(getContext(), attrs);
  }

可以得到前面兩個方法protected主要是給子類去實現的,第三個方法是公開的。還是有些蹊蹺的。所以要實現布局參數轉換成自定義的參數,這三個方法就顯得尤為重要了。

最后把這個params賦予子view,那么params是誰實現的,子view中的mLayoutParams就是什么類型的。這就是為什么我們在代碼中改變view的LayoutParams時,把它轉換成其他類型了就會報錯的原因。

當然最為容易明白的還是先去研究下FrameLayout吧,因為這個類看下來不過300行代碼。它其中的FrameLayout.LayoutParams中自定義了一個Gravity屬性。

    public static class LayoutParams extends MarginLayoutParams {
     
        public int gravity = -1;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
            gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
            a.recycle();
        }
       
        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(int width, int height, int gravity) {
            super(width, height);
            this.gravity = gravity;
        }
      
        ....
        public LayoutParams(LayoutParams source) {
            super(source);

            this.gravity = source.gravity;
        }
    }

  並且也實現了add流程中所提的三個方法:

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new FrameLayout.LayoutParams(getContext(), attrs);        
    }

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

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

最后FrameLayout才能在OnMeasure或者onLayout方法中對子view 進行大展身手:


 

研究完了原理后,我也實現了一下這個動作,也得到了一些對方法的使用猜測

代碼如下:

/**
 * 自定義測試viewgroup ,探究viewgroup中的addview的流程,已經子view中屬性的獲取
 * Created by taofuxn on 2016/12/27.
 */

public class ViewPropertiesLayout extends ViewGroup {

    public ViewPropertiesLayout(Context context) {
        super(context);
    }

    public ViewPropertiesLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ViewPropertiesLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    @Override
    protected boolean checkLayoutParams(LayoutParams p) {
        boolean b = p instanceof  LayoutParams;
        Log.i("AAA","checkLayoutParams  "+b);
        return b;
    }

    //沒調用,當在xml布局中加載的時候不會調用這個方法
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        Log.i("AAA","generateLayoutParams p");
        return new MyLayoutParam(p);
    }

    //這個方法是通過傳入的屬性集合生成相應的LayouParams .在viewgroup中是找不到調用的地方,我估計是xml解析的時候調用這個方法並賦予屬性集合來生成params.
    //因為通過addview的流程和這里的打印log可以看到是先調用這個方法生成了params,再去checklayout方法,最后再把轉換的這個對象賦予給了子view的params
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        Log.i("AAA","generateLayoutParams attrs");
        return new MyLayoutParam(getContext(),attrs);
    }

    //onmeasure,在viewgroup中是沒有對子view進行測量的處理的,所有在onlayout中是獲取不到子view的寬高
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        for (int i = 0 ; i < getChildCount() ; i++){
            View child = getChildAt(i);
            child.layout(l,t,r,100);
            //將子view的布局參數強行轉換為本布局中自定義的LayoutParams,能這么做的原因是addview里面對子params進行了generate
            MyLayoutParam lp = (MyLayoutParam) child.getLayoutParams();
            child.setBackgroundColor(lp.color);
        }
    }

    public class MyLayoutParam extends LayoutParams{

        private int color ;

        public MyLayoutParam(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs,R.styleable.custom);
            color = a.getColor(R.styleable.custom_layout_bg, Color.CYAN);
            a.recycle();
        }

        public MyLayoutParam(int width, int height) {
            super(width, height);
        }

        public MyLayoutParam(MyLayoutParam source) {
            super(source);
            this.color = source.color ;
        }

        public MyLayoutParam(LayoutParams source){
            super(source);
        }
    }
}
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <com.example.administrator.viewproperties.ViewPropertiesLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:app="http://schemas.android.com/apk/res-auto"
 4     android:id="@+id/activity_main"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7   >
 8 
 9     <TextView
10         android:layout_width="wrap_content"
11         android:layout_height="wrap_content"
12         app:layout_bg="@color/colorAccent"
13         android:textSize="20sp"
14         android:text="Hello World!" />
15 </com.example.administrator.viewproperties.ViewPropertiesLayout>

 

 

這個布局只在xml中應用,所有會調用public 的generateLayoutParams(atters)方法來給子view生成自定義的布局參數MyLayoutParam.那么他的執行順序如下:

只有當子view的layoutparams不是MyLayoutParams的實例時才會調用,generateLayoutParma(p)這個方法生成一個屬於自定義布局屬性

 

這樣的話我的這自定義布局就有了自己布局參數。感覺比自定義view的自定義屬性高大上了很多。

我的探索可能有些錯誤。希望大神盛情指導

 


免責聲明!

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



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