安卓自定义ViewGroup起步

一.自定义View和自定义ViewGroup有什么区别

        自定义View是就是一个控件,比如说Textview,RadioButton等都是一个控

件,控件内部不能再放控件

        自定义ViewGroup是自定义了一个容器,这个容器内可以房一下控件,比

如说像RelativeLayout.LinearLayout,FrameLayout等在其内部可以放textview,Button等控件


二.自定义ViewGroup的步骤:

      第一步  需要添加构造方法    在构造方法内进行一些初始化的操作 (必须)

      第二步 需要重写onMeasure()方法   这个方法根据子控件的数量和大小

来测量获取自身(容器)的大小

      第三步 需要重写onLayout()方法  这个方法是用来确定子控件相对于父控件的位置 (非必须)

       第四步需要重写generateDefaultLayoutParams()    这个方法是给子控件调用的   

用来确定子控件的LayoutParams参数类型(这个下边会具体说) (必须)

      第五步 需要重写onDraw方法  这个方法是用来画控件的  (非必须)

      第六步 需要重写onTouchEvent()和onDispatchTouchEvent()两个方法   这两个方法

分别是触摸事件和事件分发    默认是父控件将事件传递给子控件   但如果父控件和子控件

都需要响应事件,那就需要进行事件分发 (非必须)

      与自定义控件对比,多了 第三步第四步


三.做一个简单的DEMO

     1.先看下效果图


看着很简单  通过一个相对布局也能实现  但是这里主要是探讨自定义ViewGroup

要求:

      这个容器中放五个Textview,分别位于左上 右上  中 左下 右下

    2.新建一个类DefindViewGroup,继承ViewGroup

    3.添加构造方法

	public DefindViewGroup(Context context) {
		this(context, null);
	}

	public DefindViewGroup(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public DefindViewGroup(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

注意 这里的三个构造方法相关介绍参考这里

4.重写onMeasure()方法

<span style="white-space:pre">	</span>@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		// 父控件传进来的宽度和高度以及对应的测量模式
		int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
		int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
		int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
		int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

		// 测量出所有的childView的宽和高
		measureChildren(widthMeasureSpec, heightMeasureSpec);

		// 如果当前ViewGroup的宽高为wrap_content的情况
		int ownWidth = 0;// 测量自身的 宽度
		int ownHeight = 0;// 测量自身的高度

		// 获取子view的个数
		int childCount = getChildCount();
		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			// 得到LayoutParams
			MarginLayoutParams lp = (MarginLayoutParams) child
					.getLayoutParams();
			// 测量子View的宽和高
			int childHeight = child.getMeasuredHeight() + lp.topMargin
					+ lp.bottomMargin;
			heightMap.put(i, childHeight);
			// 子View占据的宽度
			int childWidth = child.getMeasuredWidth() + lp.leftMargin
					+ lp.rightMargin;
			widthMap.put(i, childWidth);
			// 子控件wrap_content 计算出实际高度
			if (i == 1) {
				ownHeight += heightMap.get(0) > childHeight ? heightMap.get(0)
						: childHeight;
			}
			if (i == 2) {
				ownHeight += childHeight;

			}
			if (i == 4) {// 获取总高度
				ownHeight += heightMap.get(3) > childHeight ? heightMap.get(3)
						: childHeight;
			}
			// wrap_content 计算出实际宽度
			if (i == 3) {
				ownWidth += widthMap.get(0) > childWidth ? widthMap.get(0)
						: childWidth;
			}
			if (i == 2) {
				ownWidth += childWidth;

			}
			if (i == 4) {// 获取总高度
				ownWidth += widthMap.get(1) > childWidth ? widthMap.get(1)
						: childWidth;
			}
		}
		// 将宽度和高度设置上去
		setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth
				: ownWidth, modeHeight == MeasureSpec.EXACTLY ? sizeHeight
				: ownHeight);
	}
注意:a.View的三种测量模式 参考这里

b.在获取控件的测量值之前一定要先调用这句话

<span style="white-space:pre">	</span>// 测量出所有的childView的宽和高
	measureChildren(widthMeasureSpec, heightMeasureSpec);
调用这个方法回去测量每个子控件的宽和高,如果拿到的child.getMeasuredWidth()和child.getMeasuredHeight()
的值均为0

c.super.onMeasure(widthMeasureSpec, heightMeasureSpec);  这句话一定要放在首句  ,不然这个方法

 写了跟没写一样  最终还是父类的值

5.重写onLayout()方法

<span style="white-space:pre">	</span>@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int childCount = getChildCount();
		// 下边这个四个变量来确定子控件相对父控件的位置 分别是左上右下 我是这样理解的 left和top组合在一起刚好是子控件左上角点的坐标
		// right和bottom组合在一起刚好是右下角点相对于父控件的坐标
		int left = 0;
		int top = 0;
		int right = 0;
		int bottom = 0;
		for (int i = 0; i < childCount; i++) {
			View childView = getChildAt(i);
			MarginLayoutParams childParams = (MarginLayoutParams) childView
					.getLayoutParams();
			switch (i) {
			case 0:// 左上
				left = childParams.leftMargin;
				top = childParams.topMargin;
				break;
			case 1:// 右上
				left = getWidth() - widthMap.get(i);
				top = childParams.topMargin;
				break;
			case 2:// 中
				left = getWidth() / 2 - childView.getMeasuredWidth() / 2;
				top = getMeasuredHeight() / 2 - childView.getMeasuredHeight()
						/ 2;
				break;
			case 3:// 左下
				left = childParams.leftMargin;
				top = getHeight() - heightMap.get(i);
				break;
			case 4:// 右下
				left = getWidth() - widthMap.get(i);
				top = getHeight() - heightMap.get(i);
				break;
			}
			right = left + childView.getMeasuredWidth();
			bottom = top + childView.getMeasuredHeight();
			Log.e("TAG", "left=" + left + "----right=" + right + "----top="
					+ top + "---bottom" + bottom);
			childView.layout(left, top, right, bottom);
		}
	}
根据计算的位置给子控件设置上去


6.重写generateDefaultLayoutParams()

<span style="white-space:pre">	</span>/**
	 * generateDefaultLayoutParams这三个方法形成了重构 主要是给子控件调用的
	 * 子控件需要根据这个方法来确定LayoutParams的类型 子控件设置外边距(Margin)的时候,其类型是到父类类型的LayoutParams
	 * 每个容器类的控件都有自身类型的LayoutParams 如LinearLayout.LayoutParams
	 * RelativeLayout.LayoutParams ViewGroup.LayoutParams等等 也正是这个原因
	 * 我们在代码中动态设置子控件的外边距的时候,必须获取父类的LayoutParams类型,例如: 1、获取按钮的LayoutParams
	 * LinearLayout.LayoutParams layoutParams =
	 * (LinearLayout.LayoutParams)button.getLayoutParams();
	 * 2、在LayoutParams中设置margin
	 * layoutParams.setMargins(100,20,10,5);//4个参数按顺序分别是左上右下
	 * 3、把这个LayoutParams设置给按钮 button.setLayoutParams(layoutParams); 因此
	 * 这个类集成的是ViewGroup 必须返回的是ViewGroup.MarginLayoutParams
	 */
	@Override
	protected LayoutParams generateDefaultLayoutParams() {
		return new MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
				ViewGroup.LayoutParams.MATCH_PARENT);
	}

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

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


7.使用这个自定义ViewGroup

<com.example.dedindviewgroup.DefindViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#e0e0e0"
    tools:context="${relativePackage}.${activityClass}" >

    <TextView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_margin="5dp"
        android:background="#111111"
        android:gravity="center"
        android:text="金"
        android:textColor="#FF6600" />

    <TextView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_margin="5dp"
        android:background="#333333"
        android:gravity="center"
        android:text="木"
        android:textColor="#FF6600" />

    <TextView
         android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_margin="5dp"
        android:background="#555555"
        android:gravity="center"
        android:text="水"
        android:textColor="#FF6600" />

    <TextView
         android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_margin="5dp"
        android:background="#777777"
        android:gravity="center"
        android:text="火"
        android:textColor="#FF6600" />

    <TextView
         android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_margin="5dp"
        android:background="#999999"
        android:gravity="center"
        android:text="土"
        android:textColor="#FF6600" />

</com.example.dedindviewgroup.DefindViewGroup>


8.运行看看效果 



点击这里下载源码












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值