自定义ViewGroup
一、个人理解,容器的布局执行过程
* 1 父容器调用measureChildren
* 2 子容器调用measure 计算当前容器的宽高和其子容器的宽高
* 3 当前执行的这个子容器触发onMeasure ,设置当前子容器的宽高
* 4 当前容器调用onLayout , 来展示当前容器的显示
二、自定义布局:
1 ViewGroup职责为:计算自己的尺寸,并给childView计算出建议的宽高和测量模式
2 childView:根据父类传递的测量模式和建议宽高,计算自己的尺寸,draw自己
当childView设置其宽高为精确值或者wrap_content(不确定值),
onMeasur
ViewGroup会传测量模式为EXACTLY宽高哪一个模式为EXACTLY则其值就固定为ViewGroup传过来的值,否则为计算值
三、自定义容器中方法的功能
onMeasure方法:
1 得出当前ViewGroup的宽高,和模式
2 如果宽或高的哪一个模式为MeasureSpec.EXACTLY ,则直接赋值
3 如果宽或高哪一个模式不是EXACTLY,则根据计算子类的宽高和Margin
来计算容器的宽或者高
4 调用setMeasuredDimension,设置容器宽和高
onLayout:
根据子控件的宽高和各种margin计算子控件的坐标,完成子控件定位布局
四、三种模式
EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;
AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。
五、测试代码
1) 自定义容器
/** * 个人理解,容器的布局执行过程 * 1 父容器调用measureChildren * 2 子容器调用measure 计算当前容器的宽高和其子容器的宽高 * 3 当前执行的这个子容器触发onMeasure ,设置当前子容器的宽高 * 4 当前容器调用onLayout , 来展示当前容器的显示 */ public class CustomImgContainer extends ViewGroup { public CustomImgContainer(Context context) { super(context); } //attrs 是xml中配置的参数会传到这里 public CustomImgContainer(Context context, AttributeSet attrs) { super(context, attrs); } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } /** * * 根据父类传入的 widthMeasureSpec和heightMeasureSpec计算本类和其子类的宽高 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //这里得到的宽高单位是像素,但xml中配置的单位都是dp,所以数值可能不一样 //1 容器宽和高的模式 2 容器宽和高 int widthMode = MeasureSpec.getMode( widthMeasureSpec); int heightMode = MeasureSpec.getMode( heightMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) ; int sizeHeight = MeasureSpec.getSize( heightMeasureSpec) ; //根据本容器传的两个参数,子控件计会调用measure和onMeasure方法来计算子控件的宽高 measureChildren(widthMeasureSpec, heightMeasureSpec); /** * 如果wrap_content是设置的宽和高 */ int width = 0 ; int height = 0 ; int count = getChildCount(); int cWidth = 0; int cHeight = 0; MarginLayoutParams cParams = null; // 用于计算左边两个childView的高度 int lHeight = 0; // 用于计算右边两个childView的高度,最终高度取二者之间大值 int rHeight = 0; // 用于计算上边两个childView的宽度 int tWidth = 0; // 用于计算下面两个childiew的宽度,最终宽度取二者之间大值 int bWidth = 0; for(int i=0;i<count;i++ ){ View view = getChildAt(i) ; cWidth = view.getMeasuredWidth(); cHeight = view.getMeasuredHeight(); cParams = (MarginLayoutParams) view.getLayoutParams() ; // 上面两个childView if (i == 0 || i == 1) { tWidth += cWidth + cParams.leftMargin + cParams.rightMargin; } if (i == 2 || i == 3) { bWidth += cWidth + cParams.leftMargin + cParams.rightMargin; } if (i == 0 || i == 2) { lHeight += cHeight + cParams.topMargin + cParams.bottomMargin; } if (i == 1 || i == 3) { rHeight += cHeight + cParams.topMargin + cParams.bottomMargin; } } width = Math.max(tWidth, bWidth); height = Math.max(lHeight, rHeight); /** * 如果是wrap_content设置为我们计算的值 ,这个必须设置 * 否则:直接设置为父容器计算的值 * * 计算:如宽度传进来的模式为EXACTLY(容器具体数值或者fill_parent) ,则容器宽度就用当前传进来的数值 * 如模式不是EXACTLY,则容器宽度需要重新计算,本例子中宽度为上面两个控件宽度和或者下面两个控件宽度和中最大的 * * 高度计算同上 */ setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth : width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight : height); if(widthMode == MeasureSpec.EXACTLY){ System.out.println( "EXACTLY"); }else{ System.out.println( "not EXACTLY"); } if(heightMode == MeasureSpec.EXACTLY){ System.out.println( "EXACTLY"); }else{ System.out.println( "not EXACTLY"); } } /** * 对其所有childView进行定位 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int cCount = getChildCount(); int cWidth = 0; int cHeight = 0; MarginLayoutParams cParams = null; /** * 遍历所有childView根据其宽和高,以及margin进行布局 */ for (int i = 0; i < cCount; i++) { View childView = getChildAt(i); cWidth = childView.getMeasuredWidth(); cHeight = childView.getMeasuredHeight(); cParams = (MarginLayoutParams) childView.getLayoutParams(); int cl = 0, ct = 0, cr = 0, cb = 0; switch (i) { case 0: cl = cParams.leftMargin; ct = cParams.topMargin; break; case 1: cl = getWidth() - cWidth - cParams.leftMargin - cParams.rightMargin; ct = cParams.topMargin; break; case 2: cl = cParams.leftMargin; ct = getHeight() - cHeight - cParams.bottomMargin; break; case 3: cl = getWidth() - cWidth - cParams.leftMargin - cParams.rightMargin; ct = getHeight() - cHeight - cParams.bottomMargin; break; } cr = cl + cWidth; cb = cHeight + ct; //根据容器和子控件的尺寸计算出各个控件的具体位置 childView.layout(cl, ct, cr, cb); } } }
2) 所用的xml ,测试的时候容器的宽和高用固定宽高、fill_parent/wrap_content 分别进行测试
<?xml version="1.0" encoding="utf-8"?> <com.example.fragmentdemo1.widget.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#AA333333" > <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#FF4444" android:gravity="center" android:text="0" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#00ff00" android:gravity="center" android:text="1" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#ff0000" android:gravity="center" android:text="2" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#0000ff" android:gravity="center" android:text="3" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> </com.example.fragmentdemo1.widget.CustomImgContainer>
3)Activity 的使用
public class CustomContainActivity extends Activity implements OnClickListener { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_custom_contain ); //DisplayUtil displayUtil = new DisplayUtil(getWindowManager()); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.start: break; default: break; } } }
参考: http://blog.csdn.net/luohai859/article/details/38435827