ViewGroup 自定义演示
来源:互联网 发布:页面加载执行js方法 编辑:程序博客网 时间:2024/06/06 02:30
第一部分:利用系统属性自定义ViewGroup
1、ViewGroup的职责是啥?
ViewGroup相当于一个放置View的容器,ViewGroup的职能为:给childView计算出建议的宽和高和测量模式 ;决定childView的位置;为什么只是建议的宽和高,而不是直接确定呢,因为childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。
2、View的职责是啥?
View的职责,根据测量模式和ViewGroup给出的建议的宽和高,计算出自己的宽和高;同时还有个更重要的职责是:在ViewGroup为其指定的区域内绘制自己的形态。
总结: 根据ViewGroup传人的测量值和模式,View对自己宽高进行确定(onMeasure中完成),然后在onDraw中完成对自己的绘制。
ViewGroup需要给View传入view的测量值和模式(onMeasure中完成),而且对于此ViewGroup的父布局,自己也需要在onMeasure中完成对自己宽和高的确定。此外,需要在onLayout中完成对其childView的位置的指定。
举例1: 定义一个ViewGroup,内部可以传入0到4个childView,分别依次显示在左上角,右上角,左下角,右下角。利用系统的 MarginLayoutParams,因为只需要ViewGroup能够支持margin即可
public class ViewGroupTest1 extends ViewGroup { public ViewGroupTest1(Context context) { super(context); } public ViewGroupTest1(Context context, AttributeSet attrs) { super(context, attrs); } public ViewGroupTest1(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } //该方法是用来设置ViewGroup 布局参数 指定了其LayoutParams为MarginLayoutParams @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(),attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //对子view 进行测量 measureChildren(widthMeasureSpec,heightMeasureSpec); /** * 如果ViewGroup是wrap_content时,需要对ViewGroup采用自定义的测试方式进行测量它的宽和高 */ int width = 0; int height = 0; 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; int count = getChildCount(); for (int i=0;i<count;i++){ View viewChildren = getChildAt(i); cWidth = viewChildren.getMeasuredWidth(); cHeight = viewChildren.getMeasuredHeight(); cParams = (MarginLayoutParams) viewChildren.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); boolean isWidthExActly = widthMode == MeasureSpec.EXACTLY; boolean isHeightWxactly = heightMode == MeasureSpec.EXACTLY; Log.i("niuniu " , " widthMode " + isWidthExActly + " widthSize " + widthSize + " width " +width); Log.i("niuniu " , " heightMode " + isHeightWxactly + " heightSize " + heightSize + " height " +height); //将得到的宽高 通过setMeasuredDimension方法设置进去,完成测量工作. setMeasuredDimension(widthMode == MeasureSpec.EXACTLY? widthSize:width,heightMode == MeasureSpec.EXACTLY?heightSize:height); } @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,计算childView在GruopView的位置(l, t, r, b) 并使用layout进行布局 */ for (int i = 0; i < cCount; i++) { View childView = getChildAt(i); cWidth = childView.getMeasuredWidth(); cHeight = childView.getMeasuredHeight(); cParams = (MarginLayoutParams) childView.getLayoutParams();// Log.i("niuniu", " cParams.leftMargin :" +cParams.leftMargin + " cParams.topMargin " +// cParams.topMargin + " cParams.rightMargin: " +cParams.rightMargin + " cParams.bottomMargin " + cParams.bottomMargin); 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.rightMargin; ct = cParams.topMargin; break; case 2: cl = cParams.leftMargin; ct = getHeight() - cHeight - cParams.bottomMargin; break; case 3: cl = getWidth() - cWidth- cParams.rightMargin; ct = getHeight() - cHeight - cParams.bottomMargin; break; } cr = cl + cWidth; cb = cHeight + ct; childView.layout(cl, ct, cr, cb); } }}//activity_main.xml 中引用 <com.example.nft.myapplication.ViewGroupTest1 android:layout_width="match_parent" android:layout_height="500dp" android:id="@+id/viewGruop1" android:layout_marginTop="15dp"> <TextView android:layout_width="200dp" android:layout_height="200dp" android:layout_marginLeft="20dp" android:layout_marginBottom="800dp" android:textSize="50dp" android:text="1" android:background="#FF4444" android:gravity="center" android:textStyle="bold"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:textSize="50dp" android:text="2" android:background="#00ff00" android:gravity="center" android:textStyle="bold"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="45dp" android:textSize="50dp" android:text="3" android:background="#0044ff" android:gravity="center" android:textStyle="bold"/> <TextView android:layout_width="100dp" android:layout_height="100dp" android:layout_marginHorizontal="50dp" android:textSize="50dp" android:text="4" android:background="#ff6600" android:gravity="center" android:textStyle="bold"/> </com.example.nft.myapplication.ViewGroupTest1>
对子view进行测量 也可以使用measureChild方法
int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); }
而使用measureChildren() 方法来简化上面的代码,这个方法将自动遍历所有子view并让它们测量自己,还可以忽略那些visibility 设置为gone的子view,因此它支持visibility gone标志.
第二部分 自定义ViewGroup 定义自己的属性
当使用不同的布局方式时,子view得布局属性就不太一样,比如当父布局是LinearLayout时,子view可以使用父布局属性如layout_weight、weightSum、layout_gravity等;当使用的是RelativeLayout时,其子view就能使用属于父布局的有效属性layout_centerInParent等;因此不同的布局容器,有不同的布局属性, 当需要我们的自定义容器需要定义自己的布局属性时,就必须使用LayoutParams来实现.
先来简单看看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); }}
首先是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); }
比如 FrameLayout.LayoutParams中就自定义了一个Gravity属性,FrameLayout实现了addView的这三个方法
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; }}
@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); }
举例2 自定义Group中添加layout_bg,layout_orientation 属性,供子view来使用.
public class ViewGroupTest2 extends ViewGroup { private int orientation = 0; public ViewGroupTest2(Context context) { super(context); } public ViewGroupTest2(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.ViewGroupTest2); orientation = typedArray.getInt(R.styleable.ViewGroupTest2_layout_orientation,0); typedArray.recycle(); } public ViewGroupTest2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec,heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; if(widthMode == MeasureSpec.EXACTLY){ width = widthSize; } else{ width = widthSize+500; } if (heightMode == MeasureSpec.EXACTLY){ height = heightSize; }else { height = heightSize+600; } setMeasuredDimension(width,height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); int width ; int height ; int distance = 0; MyLayoutParams params = null; for (int i = 0;i<count;i++){ View view = getChildAt(i); params = (MyLayoutParams) view.getLayoutParams(); view.setBackgroundColor(params.color); width = view.getMeasuredWidth(); height = view.getMeasuredHeight(); int cl = 0, ct = 0, cr = 0, cb = 0; if(orientation == 0){ // 水平一次排列 cl = distance; ct = 80; } else { //垂直依次排列 ct = distance; cl = 80; } cr = cl + width; cb = height + ct; view.layout(cl, ct, cr, cb); // 计算下一个子view的左边距离 或者顶部距离 if (orientation == 0){ distance += width +20; } else { distance += height+40; } } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { Log.i("niuniu", " generateLayoutParams attrs "); return new MyLayoutParams(getContext(),attrs); } @Override protected LayoutParams generateLayoutParams(LayoutParams p) { Log.i("niuniu", " generateLayoutParams p "); return new MyLayoutParams(p); } @Override protected boolean checkLayoutParams(LayoutParams p) { boolean params = p instanceof LayoutParams; Log.i("niuniu", " checkLayoutParams params " + params); return params; }}//创建自己的LayoutParams 并获取父容器所支持的属性public class MyLayoutParams extends ViewGroup.LayoutParams { public int color ; public MyLayoutParams(Context c, AttributeSet attrs) { super(c, attrs); Log.i("niuniu", " MyLayoutParams attrs "); TypedArray ta = c.obtainStyledAttributes(attrs,R.styleable.MyParams); color = ta.getColor(R.styleable.MyParams_layout_bg, Color.DKGRAY); ta.recycle(); } public MyLayoutParams(int width, int height) { super(width, height); } public MyLayoutParams(ViewGroup.LayoutParams source) { super(source); } public MyLayoutParams(MyLayoutParams source) { super(source); Log.i("niuniu", " MyLayoutParams source "); this.color = source.color; }}activity_main.xml中引入该父容器 <com.example.nft.myapplication.ViewGroupTest2 xmlns:viewGroupTest2 = "http://schemas.android.com/apk/res/com.example.nft.myapplication" android:layout_width="wrap_content" android:layout_height="800dp" android:id="@+id/viewGroup2" android:layout_marginTop="15dp" viewGroupTest2:layout_orientation = "horital" > <TextView android:layout_width="50dp" android:layout_height="50dp" android:textStyle="bold" android:text="1" android:textSize="24dp" viewGroupTest2:layout_bg="#FF4444"/> <TextView android:layout_width="100dp" android:layout_height="100dp" android:textStyle="bold" android:text="2" android:textSize="24dp" viewGroupTest2:layout_bg="#bb6cc0"/> <TextView android:layout_width="50dp" android:layout_height="50dp" android:textStyle="bold" android:text="3" android:textSize="24dp" viewGroupTest2:layout_bg="#66ff00"/> <TextView android:layout_width="100dp" android:layout_height="100dp" android:textStyle="bold" android:text="4" android:textSize="24dp" viewGroupTest2:layout_bg="#6f60f0"/> </com.example.nft.myapplication.ViewGroupTest2>
log 输出:
generateLayoutParams attrs ;
MyLayoutParams attrs ;
checkLayoutParams params true
总结 :
在xml中引入这个ViewGroupTest2 布局,会调用public 的generateLayoutParams(atters)方法来给子view生成自定义的布局参数MyLayoutParam.
- ViewGroup 自定义演示
- Android自定义ViewGroup的基本流程及用法演示
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义viewGroup
- 自定义viewgroup
- 自定义viewgroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 自定义ViewGroup
- 机器学习之决策树 Decision Tree(一)
- 网络编程
- 我自己收藏的web前端网站(持续更新中...)
- LOJ6008 「网络流 24 题
- ztree 总结
- ViewGroup 自定义演示
- 【Scikit-Learn 中文文档】寻求帮助
- java类加载器
- 无穷小微积分是ZFC大花园的一颗奇葩
- thinkphp 缩略图
- 初探JavaScript魅力(2)
- 混淆矩阵
- Java 中关于this、static、final 关键字的使用
- 75. 寻找峰值