精华内容
参与话题
问答
  • ViewGroup的使用

    千次阅读 2018-05-14 08:38:15
    View类 1,android.view类是个最基本的UI类,android上很多UI组件都是继承此类而实现的。 2,这些UI组件包括Button(按钮), TextView(文本框),CheckBox(选...ViewGroup类 1,上面所说的UI组件是放置到一个专...

    View类

    1,android.view类是个最基本的UI类,android上很多UI组件都是继承此类而实现的。

    2,这些UI组件包括Button(按钮), TextView(文本框),CheckBox(选字框)等等。

    3,那么这些UI组件应该怎么放置呢?

    ViewGroup类

    1,上面所说的UI组件是放置到一个专门的view容器中,这个View容器就是ViewGroup。

    2,ViewGroup的作用就是对添加进它的View组件进行布局。

    3,可惜,android.view.ViewGoup类是抽象类,不能直接使用它。

    ViewGroup类的实现

    对ViewGoup的实现的类有很多,它们有着各自的布局特点,比如LinearLayout类是把它包括的元素按照水平和竖直的方向排列。

    Android关于ViewGroup的解释还是比较清楚的,通过这个我们可以看出几点:

    1ViewGroup是一个容器,而这个容器是继承与View的。

    2ViewGroup是一个基类,并且是Layout和一些View组件的基类。

    1onLayout方法

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

    }

    在我们继承ViewGroup时会在除了构造函数之外提供这个方法,我们可以看到,在ViewGroup的源代码中方法是这样定义的,也就是父类没有提供方法的内容,需要我们自己实现。

    当View要为所有子对象分配大小和位置时,调用此方法

    2、addView方法

    public void addView(View child) {
            addView(child, -1);
    }

    这个方法是用来想View容器中添加组件用的。我们可以使用这个方法想这个ViewGroup中添加组件。

    3、getChildAt方法

    复制代码
    public View getChildAt(int index) {
            try {
                return mChildren[index];
            } catch (IndexOutOfBoundsException ex) {
                return ;
            }
    }
    复制代码

     这个方法用来返回指定位置的View。

     注意:ViewGroup中的View是从0开始计数的。

    简单测试:

    创建一个MyViewGroup继承ViewGroup


    1. public class MyViewGroup extends ViewGroup {  
    2.   
    3.     public MyViewGroup(Context context) {  
    4.         super(context);  
    5.         myAddView();  
    6.     }  
    7.   
    8.     @Override  
    9.     protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {  
    10.          View v = getChildAt(0);  
    11.          v.layout(arg1,arg2,arg3,arg4);  
    12.     }  
    13.   
    14.     @Override  
    15.     public void addView(View child) {  
    16.         super.addView(child);  
    17.     }  
    18.   
    19.      public void myAddView(){  
    20.             ImageView mIcon = new ImageView(this.getContext());  
    21.             mIcon.setImageResource(R.drawable.guid_image);  
    22.             addView(mIcon);  
    23.         }  
    24. }  
    public class MyViewGroup extends ViewGroup {
    
    
    public MyViewGroup(Context context) {
        super(context);
        myAddView();
    }
    
    @Override
    protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
         View v = getChildAt(0);
         v.layout(arg1,arg2,arg3,arg4);
    }
    
    @Override
    public void addView(View child) {
        super.addView(child);
    }
    
     public void myAddView(){
            ImageView mIcon = new ImageView(this.getContext());
            mIcon.setImageResource(R.drawable.guid_image);
            addView(mIcon);
        }
    

    }

    mainActivity中只要:


    1. @Override  
    2. protected void onCreate(Bundle savedInstanceState) {  
    3.     super.onCreate(savedInstanceState);  
    4.     this.setContentView(new MyViewGroup(this));  
    5. }  
     @Override 
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(new MyViewGroup(this));
    }




    展开全文
  • Android 手把手教您自定义ViewGroup(一)

    万次阅读 多人点赞 2014-08-02 09:26:27
    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38339817 , 本文出自:【张鸿洋的博客】...说白了,就是教大家如何自定义ViewGroup,如果你对自定义ViewGroup还不是很了解,或者正想学习如何自

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38339817 , 本文出自:【张鸿洋的博客】

    最近由于工作的变动,导致的博客的更新计划有点被打乱,希望可以尽快脉动回来~

    今天给大家带来一篇自定义ViewGroup的教程,说白了,就是教大家如何自定义ViewGroup,如果你对自定义ViewGroup还不是很了解,或者正想学习如何自定义,那么你可以好好看看这篇博客。

    1、概述

    在写代码之前,我必须得问几个问题:

    1、ViewGroup的职责是啥?

    ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性,都是为用于告诉容器的),我们的宽度(layout_width)、高度(layout_height)、对齐方式(layout_gravity)等;当然还有margin等;于是乎,ViewGroup的职能为:给childView计算出建议的宽和高和测量模式 ;决定childView的位置;为什么只是建议的宽和高,而不是直接确定呢,别忘了childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。

    2、View的职责是啥?

    View的职责,根据测量模式和ViewGroup给出的建议的宽和高,计算出自己的宽和高;同时还有个更重要的职责是:在ViewGroup为其指定的区域内绘制自己的形态。

    3、ViewGroup和LayoutParams之间的关系?

    大家可以回忆一下,当在LinearLayout中写childView的时候,可以写layout_gravity,layout_weight属性;在RelativeLayout中的childView有layout_centerInParent属性,却没有layout_gravity,layout_weight,这是为什么呢?这是因为每个ViewGroup需要指定一个LayoutParams,用于确定支持childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看LinearLayout的源码,会发现其内部定义了LinearLayout.LayoutParams,在此类中,你可以发现weight和gravity的身影。

    2、View的3种测量模式

    上面提到了ViewGroup会为childView指定测量模式,下面简单介绍下三种测量模式:

    EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;

    AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;

    UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。

    注:上面的每一行都有一个一般,意思上述不是绝对的,对于childView的mode的设置还会和ViewGroup的测量mode有一定的关系;当然了,这是第一篇自定义ViewGroup,而且绝大部分情况都是上面的规则,所以为了通俗易懂,暂不深入讨论其他内容。

    3、从API角度进行浅析

    上面叙述了ViewGroup和View的职责,下面从API角度进行浅析。

    View的根据ViewGroup传人的测量值和模式,对自己宽高进行确定(onMeasure中完成),然后在onDraw中完成对自己的绘制。

    ViewGroup需要给View传入view的测量值和模式(onMeasure中完成),而且对于此ViewGroup的父布局,自己也需要在onMeasure中完成对自己宽和高的确定。此外,需要在onLayout中完成对其childView的位置的指定。

    4、完整的例子

    需求:我们定义一个ViewGroup,内部可以传入0到4个childView,分别依次显示在左上角,右上角,左下角,右下角。

    1、决定该ViewGroup的LayoutParams

    对于我们这个例子,我们只需要ViewGroup能够支持margin即可,那么我们直接使用系统的MarginLayoutParams

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

    重写父类的该方法,返回MarginLayoutParams的实例,这样就为我们的ViewGroup指定了其LayoutParams为MarginLayoutParams。

    2、onMeasure

    在onMeasure中计算childView的测量值以及模式,以及设置自己的宽和高:

    /**
    	 * 计算所有ChildView的宽度和高度 然后根据ChildView的计算结果,设置自己的宽和高
    	 */
    	@Override
    	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    	{
    		/**
    		 * 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
    		 */
    		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    		int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
    		int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
    
    
    		// 计算出所有的childView的宽和高
    		measureChildren(widthMeasureSpec, heightMeasureSpec);
    		/**
    		 * 记录如果是wrap_content是设置的宽和高
    		 */
    		int width = 0;
    		int height = 0;
    
    		int cCount = 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;
    
    		/**
    		 * 根据childView计算的出的宽和高,以及设置的margin计算容器的宽和高,主要用于容器是warp_content时
    		 */
    		for (int i = 0; i < cCount; i++)
    		{
    			View childView = getChildAt(i);
    			cWidth = childView.getMeasuredWidth();
    			cHeight = childView.getMeasuredHeight();
    			cParams = (MarginLayoutParams) childView.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设置为我们计算的值
    		 * 否则:直接设置为父容器计算的值
    		 */
    		setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
    				: width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
    				: height);
    	}

    10-14行,获取该ViewGroup父容器为其设置的计算模式和尺寸,大多情况下,只要不是wrap_content,父容器都能正确的计算其尺寸。所以我们自己需要计算如果设置为wrap_content时的宽和高,如何计算呢?那就是通过其childView的宽和高来进行计算。

    17行,通过ViewGroup的measureChildren方法为其所有的孩子设置宽和高,此行执行完成后,childView的宽和高都已经正确的计算过了

    43-71行,根据childView的宽和高,以及margin,计算ViewGroup在wrap_content时的宽和高。

    80-82行,如果宽高属性值为wrap_content,则设置为43-71行中计算的值,否则为其父容器传入的宽和高。

    3、onLayout对其所有childView进行定位(设置childView的绘制区域)

    // abstract method in viewgroup
    	@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);
    		}
    
    	}

    代码比较容易懂:遍历所有的childView,根据childView的宽和高以及margin,然后分别将0,1,2,3位置的childView依次设置到左上、右上、左下、右下的位置。

    如果是第一个View(index=0) :则childView.layout(cl, ct, cr, cb); cl为childView的leftMargin , ct 为topMargin , cr 为cl+ cWidth , cb为 ct + cHeight

    如果是第二个View(index=1) :则childView.layout(cl, ct, cr, cb); 

    cl为getWidth() - cWidth - cParams.leftMargin- cParams.rightMargin;

    ct 为topMargin , cr 为cl+ cWidth , cb为 ct + cHeight

    剩下两个类似~

    这样就完成了,我们的ViewGroup代码的编写,下面我们进行测试,分别设置宽高为固定值,wrap_content,match_parent

    5、测试结果

    布局1:

    <com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="200dp"
        android:layout_height="200dp"
        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.zhy_custom_viewgroup.CustomImgContainer>

    ViewGroup宽和高设置为固定值

    效果图:



    布局2:

    <com.example.zhy_custom_viewgroup.CustomImgContainer 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="#AA333333" >
    
        <TextView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:background="#E5ED05"
            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.zhy_custom_viewgroup.CustomImgContainer>
    ViewGroup的宽和高设置为wrap_content
    效果图:



    布局3:

    <com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#AA333333" >
    
        <TextView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:background="#E5ED05"
            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="150dp"
            android:layout_height="150dp"
            android:background="#0000ff"
            android:gravity="center"
            android:text="3"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
    
    </com.example.zhy_custom_viewgroup.CustomImgContainer>

    ViewGroup的宽和高设置为match_parent



    可以看到无论ViewGroup的宽和高的值如何定义,我们的需求都实现了预期的效果~~

    好了,通过这篇教程,希望大家已经能够初步掌握自定义ViewGroup的步骤,大家可以尝试自定义ViewGroup去实现LinearLayout的效果~~

    最后说明下,此为第一篇ViewGroup的教程,以后还会进一步的探讨如何更好的自定义ViewGroup~~~


    如果你觉得这篇文章对你有用,那么赞一个或者留个言吧~




    源码点击下载




    展开全文
  • 自定义ViewGroup之自定义布局的实现

    千次阅读 2018-07-11 21:33:30
    图片预览 1. 分析 1. 自定义简易FrameLayout 分别左上,右上,左下,右下4个子View 2. 自定义简易LinearLayout,实现横向和纵向布局 ...1. 自定义ViewGroup主要是复写onMeasure测量每一个子View的宽高,复写onLayo...

    图片预览
    这里写图片描述

    1. 分析

    1. 自定义简易FrameLayout 分别左上,右上,左下,右下4个子View
    2. 自定义简易LinearLayout,实现横向和纵向布局
    3. 自定义简易RelativeLayout,实现layout_alignParentXXX方法
    

    2. 实现原理

    1. 自定义ViewGroup主要是复写onMeasure测量每一个子View的宽高,复写onLayout计算每一个子View的上下左右间距
    2. 自定义一些属性值
    3. 复写generateLayoutParams,计算margin值
    

    3. 简易FrameLayout实现

    1. onMeasure
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    //分别获取宽高的测量模式
       int widthMode = MeasureSpec.getMode(widthMeasureSpec);
       int heightMode = MeasureSpec.getMode(heightMeasureSpec);
       int widthSize = MeasureSpec.getSize(widthMeasureSpec);
       int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
       //记录如果是wrap_content时设置的宽和高
       int width, height;
       //左右的高度
       int lHeight = 0,rHeight = 0;
       //上下的宽度
       int tWidth = 0, bWidth = 0;
    
       //测量所有子孩子的宽高
       measureChildren(widthMeasureSpec,heightMeasureSpec);
    
       for (int i = 0; i < getChildCount(); i++) {
    
           View childView = getChildAt(i);
           int childWidth = childView.getMeasuredWidth();
           int childHeight = childView.getMeasuredHeight();
           MarginLayoutParams mParams = (MarginLayoutParams) childView.getLayoutParams();
    
           if(i ==0 || i == 1){
               tWidth += childWidth + mParams.leftMargin + mParams.rightMargin;
           }
    
           if(i == 2 || i == 3){
               bWidth += childWidth + mParams.leftMargin + mParams.rightMargin;
           }
    
           if(i == 0 || i== 2){
               lHeight += childHeight + mParams.topMargin + mParams.bottomMargin;
           }
    
           if(i == 1 || i == 3){
               rHeight += childHeight + mParams.topMargin + mParams.bottomMargin;
           }
       }
    
    //wrap_content 时取宽高的最大值
       width = Math.max(tWidth,bWidth);
       height = Math.max(lHeight,rHeight);
    
       int measureWidth  = widthMode == MeasureSpec.EXACTLY ? widthSize : width;
       int measureHeight  = heightMode == MeasureSpec.EXACTLY ? heightSize : height;
       setMeasuredDimension(measureWidth,measureHeight);
    }
    
    2. onLayout 需要注意的一个地方是最好计算一下父View的pading值
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
        for (int i = 0; i < getChildCount(); i++) {
    
            View childView = getChildAt(i);
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();
            MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams();
            int childLeft = 0,childTop = 0,childRight = 0,childBottom = 0;
            switch (i){
                case 0:
                    childLeft = cParams.leftMargin;
                    childTop = cParams.topMargin+ getPaddingTop();
                    break;
                case 1:
                    childLeft = getMeasuredWidth() - childWidth - cParams.rightMargin;
                    childTop = cParams.topMargin + getPaddingTop();
                    break;
                case 2:
                    childLeft = cParams.leftMargin;
                    childTop =  getMeasuredHeight() - childHeight - cParams.bottomMargin
                            - getPaddingBottom() - getPaddingTop();
                    break;
                case 3:
                    childLeft = getMeasuredWidth() - childWidth - cParams.rightMargin;
                    childTop =  getMeasuredHeight() - childHeight - cParams.bottomMargin
                            -getPaddingTop() - getPaddingBottom();
                    break;
                default:
            }
    
            childRight = childLeft + childWidth;
            childBottom = childTop + childHeight;
    
            childView.layout(childLeft,childTop,childRight,childBottom);
        }
    }
    

    4. 简易LinearLayout实现

    分横向和竖向
    横向的时候当宽高为wrap_content的时候,宽度累加+左右padding值
    高度取子View的最大高度
    竖向的时候当宽高为wrap_content的时候,宽度取子View中的最大宽度,高度累加+上下padding值
    onLayout主要是计算子View的left,top,right,bottom的距离,细心一点就好

    1. onMeasure
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        //记录如果是wrap_content时设置的宽和高
        int width, height;
        int totalWidth = 0;
        int totalHeight = 0;
    
        //测量所有子孩子的宽高
        measureChildren(widthMeasureSpec,heightMeasureSpec);
    
        for (int i = 0; i < getChildCount(); i++) {
    
            View childView = getChildAt(i);
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();
            MarginLayoutParams mParams = (MarginLayoutParams) childView.getLayoutParams();
    
            if("horizontal".equals(mOrientation)){
                //横向高度取子孩子中高度最大值 带上margin和父View的padding值
                int cHeight = childHeight + mParams.topMargin + mParams.bottomMargin + this.getPaddingBottom() + this.getPaddingTop();
                if(cHeight > totalHeight){
                    totalHeight = cHeight;
                }
                //横向第一个子孩子和最后一个子孩子需要带上父View的padding值
                if(i == 0){
                    totalWidth+= getPaddingLeft();
                }else if(i == getChildCount() - 1){
                    totalWidth += getPaddingRight();
                }
                totalWidth += childWidth + mParams.leftMargin + mParams.rightMargin;
    
            }else {
    
                //竖向宽度取其中一个子孩子的最大高度
                int cWidth = childWidth + mParams.leftMargin + mParams.rightMargin + this.getPaddingLeft() + this.getPaddingRight();
                if (cWidth > totalWidth) {
                    totalWidth = cWidth;
                }
    
                //竖向的第一子孩子和最后一个子孩子需要带上父View的padding值
                if(i == 0){
                    totalHeight += getPaddingTop();
                }else if(i == getChildCount() - 1){
                    totalHeight += getPaddingBottom();
                }
                totalHeight += childHeight + mParams.topMargin + mParams.bottomMargin;
    
            }
        }
    
        width = totalWidth;
        height = totalHeight;
        int measureWidth  = widthMode == MeasureSpec.EXACTLY ? widthSize : width;
        int measureHeight  = heightMode == MeasureSpec.EXACTLY ? heightSize : height;
    
        setMeasuredDimension(measureWidth,measureHeight);
    }
    
    2. onLayout
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
        int childLeft ;
        int childTop ;
        int childRight ;
        int childBottom ;
        int lastTotalHeight = 0;
        int lastTotalWidth = 0;
        for (int i = 0; i < getChildCount(); i++) {
    
            View childView = getChildAt(i);
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();
            MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams();
    
            if("horizontal".equals(mOrientation)) {
                //横向第一个带上父View的左边padding值
                if(i == 0){
                    childLeft = cParams.leftMargin + getPaddingLeft();
                }else {
                    childLeft = lastTotalWidth + cParams.leftMargin;
                }
                //横向最后一个带上父View的右边padding值
                if(i == getChildCount() - 1) {
                    childRight = childLeft + childWidth+ getPaddingRight();
                }else{
                    childRight = childLeft + childWidth;
                }
                lastTotalWidth = childRight;
    
                //横向上下带上上下padding值
                childTop = cParams.topMargin + getPaddingTop();
                childBottom = childTop + childHeight + getPaddingBottom();
            }else{
                //竖向左右带上左右padding值
                childLeft = cParams.leftMargin + getPaddingLeft();
                childRight = childLeft + childWidth + getPaddingRight();
    
                //竖向第一个带上父View的上边padding值
                if(i == 0){
                    childTop =  cParams.topMargin + getPaddingTop();
                }else {
                    childTop = lastTotalHeight + cParams.topMargin;
                }
    
                竖向最后个带上父View的下边padding值
                if(i == getChildCount() - 1){
                    childBottom = childTop + childHeight+ getPaddingBottom();
                }else {
                    childBottom = childTop + childHeight;
                }
                lastTotalHeight = childBottom;
            }
    
            childView.layout(childLeft,childTop,childRight,childBottom);
        }
    }
    

    5. 简易RelativeLayout实现

    onMeasure
    当宽高为wrap_content的时候,宽度取子View的最大宽度
    高度取子View的最大高度,记得带上左右padding值
    onLayout
    根据自定义的子View的不同位置layout_position,计算子View的left,top,right,bottom的距离。

    1. onMeasure
    
         @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        //用于计算wrap_content时候的宽高
        int width = 0;
        int height = 0;
    
        for (int i = 0; i < getChildCount(); i++) {
    
            View child = getChildAt(i);
            //测量每一个子孩子
            measureChild(child,widthMeasureSpec,heightMeasureSpec);
    
            CustomLayoutParam lp = (CustomLayoutParam) child.getLayoutParams();
            int lrMargin = lp.leftMargin + lp.rightMargin;
            int tbMargin = lp.topMargin + lp.bottomMargin;
    
            int childWidth = child.getMeasuredWidth() + lrMargin;
            int childHeight = child.getMeasuredHeight() + tbMargin;
    
            //获取子孩子中最大的子孩子宽度
            if(width < childWidth){
                width = childWidth;
            }
    
            //获取子孩子总最大的子孩子高度
            if(height < childHeight){
                height = childHeight;
            }
        }
    
        //带上父View的上下左右的pading
        width += getPaddingLeft() + getPaddingRight();
        height += getPaddingTop() + getPaddingBottom();
    
        int measureWidth = widthMode == MeasureSpec.AT_MOST ? width : widthSize;
        int measureHeight = heightMode == MeasureSpec.AT_MOST ? height : heightSize;
    
        setMeasuredDimension(measureWidth,measureHeight);
    }     
    
    2. onLayout
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
        int pWidth = getMeasuredWidth();
        int pHeight = getMeasuredHeight();
    
        for (int i = 0; i < getChildCount(); i++) {
    
            View child = getChildAt(i);
            CustomLayoutParam lp = (CustomLayoutParam) child.getLayoutParams();
            int lrMargin = lp.leftMargin + lp.rightMargin;
            int tbMargin = lp.topMargin + lp.bottomMargin;
            int cWidthAndMargin = child.getMeasuredWidth() + lrMargin;
            int cHeightAndMargin = child.getMeasuredHeight() + tbMargin;
            int cWidth = child.getMeasuredWidth();
            int cHeight = child.getMeasuredHeight();
            int left = 0;
            int top = 0;
            int right = 0;
            int bottom = 0;
            switch (lp.getPosition()){
                case "leftTop":
                    left = lp.leftMargin + getPaddingLeft();
                    top = lp.topMargin + getPaddingTop();
                    break;
                case "rightTop":
                    left = pWidth - cWidthAndMargin- getPaddingRight();
                    top = lp.topMargin + getPaddingTop();
                    right += getPaddingRight();
                    break;
                case "leftBottom":
                    left = lp.leftMargin + getPaddingLeft();
                    top = pHeight - cHeightAndMargin;
                    bottom += getPaddingBottom();
                    break;
                case "rightBottom":
                    left = pWidth - cWidthAndMargin- getPaddingLeft();
                    top = pHeight - cHeightAndMargin;
                    bottom += getPaddingBottom();
                    break;
                case "horizontalCenter":
                    left = (pWidth - cWidth) / 2;
                    top = lp.topMargin + getPaddingTop();
                    break;
                case "verticalCenter":
                    left = lp.leftMargin + getPaddingLeft();
                    top = (pHeight - cHeight) / 2;
                    break;
                case "rightVerticalCenter":
                    left = pWidth - cWidthAndMargin- getPaddingLeft();
                    top = (pHeight - cHeight) / 2;
    
                    break;
                case "bottomHorizontalCenter":
                    left = (pWidth - cWidth) / 2;
                    top = pHeight - cHeightAndMargin;
                    bottom += getPaddingBottom();
                    break;
                case "center":
                    left = (pWidth - cWidth) / 2;
                    top = (pHeight - cHeight) / 2;
                    break;
            }
    
            right += left + child.getMeasuredWidth();
            bottom += top + child.getMeasuredHeight();
            child.layout(left,top,right,bottom);
        }
    }
    

    6. 复写generateLayoutParams方法

    @Override
     public LayoutParams generateLayoutParams(AttributeSet attrs) {
         return new CustomLayoutParam(getContext(),attrs);
     }
    
     public class CustomLayoutParam extends ViewGroup.MarginLayoutParams{
    
        /**
         * leftTop
         * rightTop
         * horizontalCenter
         * verticalCenter
         * rightVerticalCenter
         * bottomHorizontalCenter
         * center
         * leftBottom
         * rightBottom
         */
        private String mPosition;
    
        public CustomLayoutParam(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyRelativeLayout);
            mPosition = typedArray.getString(R.styleable.MyRelativeLayout_layout_position);
            if(TextUtils.isEmpty(mPosition)){
                mPosition = "leftTop";
            }
            typedArray.recycle();
        }
    
        public String getPosition() {
            return mPosition;
        }
    }

    7. 项目源代码下载

    后面统一提供下载地址
    

    8. 联系方式

    QQ:1509815887
    
    展开全文
  • 自定义View和自定义ViewGroup一步到位

    千次阅读 2018-09-29 18:15:30
    1.自定义View 首先我们要明白,为什么要自定义View?主要是Android系统内置的View无法实现我们的需求,我们需要针对我们的业务需求定制我们想要的View。自定义View我们大部分时候只需重写两个函数:onMeasure()、...

    1.自定义View

    首先我们要明白,为什么要自定义View?主要是Android系统内置的View无法实现我们的需求,我们需要针对我们的业务需求定制我们想要的View。自定义View我们大部分时候只需重写两个函数:onMeasure()、onDraw()。onMeasure负责对当前View的尺寸进行测量,onDraw负责把当前这个View绘制出来。当然了,你还得写至少写2个构造函数:

        public MyView(Context context) {
            super(context);
        }
    
        public MyView(Context context, AttributeSet attrs) {
            super(context, attrs); 
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.1.onMeasure

    我们自定义的View,首先得要测量宽高尺寸。为什么要测量宽高尺寸?我在刚学自定义View的时候非常无法理解!因为我当时觉得,我在xml文件中已经指定好了宽高尺寸了,我自定义View中有必要再次获取宽高并设置宽高吗?既然我自定义的View是继承自View类,google团队直接在View类中直接把xml设置的宽高获取,并且设置进去不就好了吗?那google为啥让我们做这样的“重复工作”呢?

    在学习Android的时候,我们就知道,在xml布局文件中,我们的layout_widthlayout_height参数可以不用写具体的尺寸,而是wrap_content或者是match_parent。其意思我们都知道,就是将尺寸设置为“包住内容”和“填充父布局给我们的所有空间”。这两个设置并没有指定真正的大小,可是我们绘制到屏幕上的View必须是要有具体的宽高的,正是因为这个原因,我们必须自己去处理和设置尺寸。当然了,View类给了默认的处理,但是如果View类的默认处理不满足我们的要求,我们就得重写onMeasure函数啦~。这里举个例子,比如我们希望我们的View是个正方形,如果在xml中指定宽高为wrap_content,如果使用View类提供的measure处理方式,显然无法满足我们的需求~。

    先看看onMeasure函数原型:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    • 1

    参数中的widthMeasureSpecheightMeasureSpec是个什么鬼?看起来很像width和height,没错,这两个参数就是包含宽和高的信息。什么?包含?难道还要其他信息?是的!它还包含测量模式,也就是说,一个int整数,里面放了测量模式和尺寸大小。那么一个数怎么放两个信息呢?我们知道,我们在设置宽高时有3个选择:wrap_contentmatch_parent以及指定固定尺寸,而测量模式也有3种:UNSPECIFIEDEXACTLYAT_MOST,当然,他们并不是一一对应关系哈,这三种模式后面我会详细介绍,但测量模式无非就是这3种情况,而如果使用二进制,我们只需要使用2个bit就可以做到,因为2个bit取值范围是[0,3]里面可以存放4个数足够我们用了。那么Google是怎么把一个int同时放测量模式和尺寸信息呢?我们知道int型数据占用32个bit,而google实现的是,将int数据的前面2个bit用于区分不同的布局模式,后面30个bit存放的是尺寸的数据。

    那我们怎么从int数据中提取测量模式和尺寸呢?放心,不用你每次都要写一次移位<<和取且&操作,Android内置类MeasureSpec帮我们写好了,我们只需按照下面方法就可以拿到:

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    • 1
    • 2

    既然我们能通过widthMeasureSpec拿到宽度尺寸大小,那我们还要测量模式干嘛?测量模式会不会是多余的?请注意:这里的的尺寸大小并不是最终我们的View的尺寸大小,而是父View提供的参考大小。我们看看测量模式,测量模式是干啥用的呢?

    测量模式 表示意思
    UNSPECIFIED 父容器没有对当前View有任何限制,当前View可以任意取尺寸
    EXACTLY 当前的尺寸就是当前View应该取的尺寸
    AT_MOST 当前尺寸是当前View能取的最大尺寸

    而上面的测量模式跟我们的布局时的wrap_contentmatch_parent以及写成固定的尺寸有什么对应关系呢?

    match_parent—>EXACTLY。怎么理解呢?match_parent就是要利用父View给我们提供的所有剩余空间,而父View剩余空间是确定的,也就是这个测量模式的整数里面存放的尺寸。

    wrap_content—>AT_MOST。怎么理解:就是我们想要将大小设置为包裹我们的view内容,那么尺寸大小就是父View给我们作为参考的尺寸,只要不超过这个尺寸就可以啦,具体尺寸就根据我们的需求去设定。

    固定尺寸(如100dp)—>EXACTLY。用户自己指定了尺寸大小,我们就不用再去干涉了,当然是以指定的大小为主啦。

    1.2.动手重写onMeasure函数

    上面讲了太多理论,我们实际操作一下吧,感受一下onMeasure的使用,假设我们要实现这样一个效果:将当前的View以正方形的形式显示,即要宽高相等,并且默认的宽高值为100像素。就可以这些编写:

     private int getMySize(int defaultSize, int measureSpec) {
            int mySize = defaultSize;
    
            int mode = MeasureSpec.getMode(measureSpec);
            int size = MeasureSpec.getSize(measureSpec);
    
            switch (mode) {
                case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
                    mySize = defaultSize;
                    break;
                }
                case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
                    //我们将大小取最大值,你也可以取其他值
                    mySize = size;
                    break;
                }
                case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
                    mySize = size;
                    break;
                }
            }
            return mySize;
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int width = getMySize(100, widthMeasureSpec);
            int height = getMySize(100, heightMeasureSpec);
    
            if (width < height) {
                height = width;
            } else {
                width = height;
            }
    
            setMeasuredDimension(width, height);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    我们设置一下布局

      <com.hc.studyview.MyView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="#ff0000" />
    
    • 1
    • 2
    • 3
    • 4
    • 5

    看看使用了我们自己定义的onMeasure函数后的效果:
    自定义View

    而如果我们不重写onMeasure,效果则是如下:

    默认size

    1.3.重写onDraw

    上面我们学会了自定义尺寸大小,那么尺寸我们会设定了,接下来就是把我们想要的效果画出来。绘制我们想要的效果很简单,直接在画板Canvas对象上绘制,我们以一个简单的例子去学习:假设我们需要实现的是,我们的View显示一个圆形,我们在上面已经实现了宽高尺寸相等的基础上,继续往下做:

      @Override
        protected void onDraw(Canvas canvas) {
            //调用父View的onDraw函数,因为View这个类帮我们实现了一些
            // 基本的而绘制功能,比如绘制背景颜色、背景图片等
            super.onDraw(canvas);
            int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
            //圆心的横坐标为当前的View的左边起始位置+半径
            int centerX = getLeft() + r;
            //圆心的纵坐标为当前的View的顶部起始位置+半径
            int centerY = getTop() + r;
    
            Paint paint = new Paint();
            paint.setColor(Color.GREEN);
            //开始绘制,画布绘制使用了参数列表的工具/参数
            canvas.drawCircle(centerX, centerY, r, paint);
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    显示效果

    1.4.自定义布局属性

    如果有些属性我们希望由用户指定,只有当用户不指定的时候才用我们硬编码的值,比如上面的默认尺寸,我们想要由用户自己在布局文件里面指定该怎么做呢?那当然是通我们自定属性,让用户用我们定义的属性。

    首先我们需要在res/values/styles.xml文件(如果没有请自己新建)里面声明一个我们自定义的属性:

    <resources>
    
        <!--name为声明的"属性集合"名,可以随便取,但是最好是设置为跟我们的View一样的名称-->
        <declare-styleable name="MyView">
            <!--声明我们的属性,名称为default_size,取值类型为尺寸类型(dp,px等)-->
            <attr name="default_size" format="dimension" />
        </declare-styleable>
    </resources>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    接下来就是在布局文件用上我们的自定义的属性啦~

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:hc="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.hc.studyview.MyView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            hc:default_size="100dp" />
    
    </LinearLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意:需要在根标签(LinearLayout)里面设定命名空间,命名空间名称可以随便取,比如hc,命名空间后面取得值是固定的:"http://schemas.android.com/apk/res-auto"

    最后就是在我们的自定义的View里面把我们自定义的属性的值取出来,在构造函数中,还记得有个AttributeSet属性吗?就是靠它帮我们把布局里面的属性取出来:

     private int defalutSize;
      public MyView(Context context, AttributeSet attrs) {
          super(context, attrs);
          //第二个参数就是我们在styles.xml文件中的<declare-styleable>标签
            //即属性集合的标签,在R文件中名称为R.styleable+name
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
    
            //第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称
            //第二个参数为,如果没有设置这个属性,则设置的默认的值
            defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);
    
            //最后记得将TypedArray对象回收
            a.recycle();
       }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    最后,把MyView的完整代码附上:

    
    public class MyView extends View {
    
        private int defalutSize;
    
        public MyView(Context context) {
            super(context);
        }
    
        public MyView(Context context, AttributeSet attrs) {
            super(context, attrs);
            //第二个参数就是我们在styles.xml文件中的<declare-styleable>标签
            //即属性集合的标签,在R文件中名称为R.styleable+name
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
            //第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称
            //第二个参数为,如果没有设置这个属性,则设置的默认的值
            defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);
            //最后记得将TypedArray对象回收
            a.recycle();
        }
    
    
        private int getMySize(int defaultSize, int measureSpec) {
            int mySize = defaultSize;
    
            int mode = MeasureSpec.getMode(measureSpec);
            int size = MeasureSpec.getSize(measureSpec);
    
            switch (mode) {
                case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
                    mySize = defaultSize;
                    break;
                }
                case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
                    //我们将大小取最大值,你也可以取其他值
                    mySize = size;
                    break;
                }
                case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
                    mySize = size;
                    break;
                }
            }
            return mySize;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int width = getMySize(defalutSize, widthMeasureSpec);
            int height = getMySize(defalutSize, heightMeasureSpec);
    
            if (width < height) {
                height = width;
            } else {
                width = height;
            }
    
            setMeasuredDimension(width, height);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            //调用父View的onDraw函数,因为View这个类帮我们实现了一些
            // 基本的而绘制功能,比如绘制背景颜色、背景图片等
            super.onDraw(canvas);
            int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
            //圆心的横坐标为当前的View的左边起始位置+半径
            int centerX = getLeft() + r;
            //圆心的纵坐标为当前的View的顶部起始位置+半径
            int centerY = getTop() + r;
    
            Paint paint = new Paint();
            paint.setColor(Color.GREEN);
            //开始绘制
            canvas.drawCircle(centerX, centerY, r, paint);
    
    
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    2 自定义ViewGroup

    自定义View的过程很简单,就那几步,可自定义ViewGroup就相对复杂一点,因为它不仅要管好自己的,还要兼顾它的子View。我们都知道ViewGroup是个View容器,它装纳child View并且负责把child View放入指定的位置。我们假象一下,如果是让你负责设计ViewGroup,你会怎么去设计呢?

    1.首先,我们得知道各个子View的大小吧,只有先知道子View的大小,我们才知道当前的ViewGroup该设置为多大去容纳它们。

    2.根据子View的大小,以及我们的ViewGroup要实现的功能,决定出ViewGroup的大小

    3.ViewGroup和子View的大小算出来了之后,接下来就是去摆放了吧,具体怎么去摆放呢?这得根据你定制的需求去摆放了,比如,你想让子View按照垂直顺序一个挨着一个放,或者是按照先后顺序一个叠一个去放,这是你自己决定的。

    4.已经知道怎么去摆放还不行啊,决定了怎么摆放就是相当于把已有的空间”分割”成大大小小的空间,每个空间对应一个子View,我们接下来就是把子View对号入座了,把它们放进它们该放的地方去。

    现在就完成了ViewGroup的设计了,我们来个具体的案例:将子View按从上到下垂直顺序一个挨着一个摆放,即模仿实现LinearLayout的垂直布局。

    首先重写onMeasure,实现测量子View大小以及设定ViewGroup的大小:

    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //将所有的子View进行测量,这会触发每个子View的onMeasure函数
            //注意要与measureChild区分,measureChild是对单个view进行测量
            measureChildren(widthMeasureSpec, heightMeasureSpec);
    
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            int childCount = getChildCount();
    
            if (childCount == 0) {//如果没有子View,当前ViewGroup没有存在的意义,不用占用空间
                setMeasuredDimension(0, 0);
            } else {
                //如果宽高都是包裹内容
                if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
                    //我们将高度设置为所有子View的高度相加,宽度设为子View中最大的宽度
                    int height = getTotleHeight();
                    int width = getMaxChildWidth();
                    setMeasuredDimension(width, height);
    
                } else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹内容
                    //宽度设置为ViewGroup自己的测量宽度,高度设置为所有子View的高度总和
                    setMeasuredDimension(widthSize, getTotleHeight());
                } else if (widthMode == MeasureSpec.AT_MOST) {//如果只有宽度是包裹内容
                    //宽度设置为子View中宽度最大的值,高度设置为ViewGroup自己的测量值
                    setMeasuredDimension(getMaxChildWidth(), heightSize);
    
                }
            }
        }
        /***
         * 获取子View中宽度最大的值
         */
        private int getMaxChildWidth() {
            int childCount = getChildCount();
            int maxWidth = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                if (childView.getMeasuredWidth() > maxWidth)
                    maxWidth = childView.getMeasuredWidth();
    
            }
    
            return maxWidth;
        }
    
        /***
         * 将所有子View的高度相加
         **/
        private int getTotleHeight() {
            int childCount = getChildCount();
            int height = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                height += childView.getMeasuredHeight();
    
            }
    
            return height;
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    代码中的注释我已经写得很详细,不再对每一行代码进行讲解。上面的onMeasure将子View测量好了,以及把自己的尺寸也设置好了,接下来我们去摆放子View吧~

     @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int count = getChildCount();
            //记录当前的高度位置
            int curHeight = t;
            //将子View逐个摆放
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                int height = child.getMeasuredHeight();
                int width = child.getMeasuredWidth();
                //摆放子View,参数分别是子View矩形区域的左、上、右、下边
                child.layout(l, curHeight, l + width, curHeight + height);
                curHeight += height;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们测试一下,将我们自定义的ViewGroup里面放3个Button ,将这3个Button的宽度设置不一样,把我们的ViewGroup的宽高都设置为包裹内容wrap_content,为了看的效果明显,我们给ViewGroup加个背景:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.hc.studyview.MyViewGroup
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ff9900">
    
            <Button
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:text="btn" />
    
            <Button
                android:layout_width="200dp"
                android:layout_height="wrap_content"
                android:text="btn" />
    
            <Button
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:text="btn" />
    
    
        </com.hc.studyview.MyViewGroup>
    
    </LinearLayout>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    看看最后的效果吧~

    自定义ViewGroup

    是不是很激动~我们自己也可以实现LinearLayout的效果啦~~~~

    最后附上MyViewGroup的完整源码:

    
    public class MyViewGroup extends ViewGroup {
        public MyViewGroup(Context context) {
            super(context);
        }
    
        public MyViewGroup(Context context, AttributeSet attrs) {
    
            super(context, attrs);
        }
    
        /***
         * 获取子View中宽度最大的值
         */
        private int getMaxChildWidth() {
            int childCount = getChildCount();
            int maxWidth = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                if (childView.getMeasuredWidth() > maxWidth)
                    maxWidth = childView.getMeasuredWidth();
    
            }
    
            return maxWidth;
        }
    
        /***
         * 将所有子View的高度相加
         **/
        private int getTotleHeight() {
            int childCount = getChildCount();
            int height = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                height += childView.getMeasuredHeight();
    
            }
    
            return height;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //将所有的子View进行测量,这会触发每个子View的onMeasure函数
            //注意要与measureChild区分,measureChild是对单个view进行测量
            measureChildren(widthMeasureSpec, heightMeasureSpec);
    
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            int childCount = getChildCount();
    
            if (childCount == 0) {//如果没有子View,当前ViewGroup没有存在的意义,不用占用空间
                setMeasuredDimension(0, 0);
            } else {
                //如果宽高都是包裹内容
                if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
                    //我们将高度设置为所有子View的高度相加,宽度设为子View中最大的宽度
                    int height = getTotleHeight();
                    int width = getMaxChildWidth();
                    setMeasuredDimension(width, height);
    
                } else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹内容
                    //宽度设置为ViewGroup自己的测量宽度,高度设置为所有子View的高度总和
                    setMeasuredDimension(widthSize, getTotleHeight());
                } else if (widthMode == MeasureSpec.AT_MOST) {//如果只有宽度是包裹内容
                    //宽度设置为子View中宽度最大的值,高度设置为ViewGroup自己的测量值
                    setMeasuredDimension(getMaxChildWidth(), heightSize);
    
                }
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int count = getChildCount();
            //记录当前的高度位置
            int curHeight = t;
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                int height = child.getMeasuredHeight();
                int width = child.getMeasuredWidth();
                child.layout(l, curHeight, l + width, curHeight + height);
                curHeight += height;
            }
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    展开全文
  • View和ViewGroup关系

    千次阅读 2018-07-24 10:38:55
    Android里的图形界面都是由View和ViewGroup以及他们的子类构成的: View:所有可视化控件的父类,提供组件描绘和时间处理方法 ViewGroup: View类的子类,可以拥有子控件,可以看作是容器 Android UI中的控件都是...
  • 文章目录1.效果和自定义view的完整代码2.对wrap_content属性的处理3.onLayout处理4.弹性滑动和快速滑动 1.效果和自定义view的完整代码 这里写一个水平滑动自定义view,效果如图 ...先给个完整的自定义...
  • Android自定义ViewGroup第十三式之移花接木

    千次阅读 多人点赞 2019-04-27 22:01:20
    前言 上个星期更新了网易云音乐之后,在发现 - 歌单页面中看到一个挺炫酷的效果,介系我没有见过的船新版本,看图: 对,一眼看上去就像是在ViewPager的基础上改造过,但仔细看,又不像ViewPager的行为,因为它...
  • 基础篇——View和ViewGroup的区别

    万次阅读 多人点赞 2018-09-07 17:35:53
    写代码的四点: 1.明确需求。要做什么? 2.分析思路。要怎么做?(1,2,3……) 3.确定步骤。每一个思路要用到哪些语句、方法和对象。 4.代码实现。用具体的语言代码将思路实现出来。... 1.... 2.... 3.......
  • View与ViewGroup的关系

    千次阅读 2019-04-17 00:42:04
    Android的UI界面都是由View和ViewGroup及其派生类组合而成的。  其中,View是所有UI组件的基类,而 ViewGroup是容纳这些组件的容器,其本身也是从View派生出来的.  View对象是Android平台中用户界面体现的基础单位...
  • Android中View和ViewGroup介绍

    万次阅读 多人点赞 2013-08-08 17:11:17
    1. 概念 Android中的View与我们以前理解的...ViewGroup是View的子类,所以它也具有View的特性,但它主要用来充当View的容器,将其中的View视作自己的孩子,对它的子View进行管理,当然它的孩子也可以是ViewGroup类型。
  • Android ViewGroup事件分发机制

    万次阅读 多人点赞 2014-09-09 09:38:03
    上一篇已经完整的解析了Android View的事件分发机制,今天给大家代码ViewGroup事件分发的源码解析~~凡是自定义ViewGroup实现各种滑动效果的,不可避免的会出现很多事件的冲突,对ViewGroup事件分发机制的了解,也...
  • ViewGroup

    千次阅读 2012-04-08 09:35:03
    ViewGroup extends View implements ViewManager ViewParent java.lang.Object  ↳ android.view.View    ↳ android.view.ViewGroup Class
  • 自定义View系列教程07--详解ViewGroup分发Touch事件

    万次阅读 多人点赞 2016-06-13 10:05:32
    在上一篇中已经分析完了View对于Touch事件的处理,在此基础上分析和理解ViewGroup对于Touch事件的分发就会相对容易些。 当一个Touch事件发生后,事件首先由系统传递给当前Activity并且由其dispatchTouchEvent()派发...
  • Android开发实践:自定义ViewGroup的onLayout()分析       前一篇文章主要讲了自定义View为什么要重载onMeasure()方法(见 http://www.linuxidc.com/Linux/2014-12/110164.htm),那么,...
  • 开发中,经常会用到动态在ScrollView...这个时候就开始期待,能不能有一种快速为任意ViewGroup添加子View的东西。 那么需求就来了: * 快速简单使用 * 支持任意ViewGroup * 无耦合 * 无侵入性 * Item支持多种类型
  • 转载请标明出处: ... 本文出自:【openXu的博客】 目录:简单实现水平排列效果 自定义LayoutParams 大致明确布局容器的需求初步定义布局属性 继承LayoutParams定义布局参数类 重写generateLayoutParams ...
  • ViewGroup源码解读

    千次阅读 2017-08-31 11:54:59
    这次我们来分析下viewgroup的。可能有人会想,怎么又是源码分析,肯定又是一大通。其实没你想的那么复杂。仔细分析一波就行了。解读ViewGroup我们都知道,一个事件完整的流程是从dispatchTouchevent–>onI
  • ================================================================ 预留填坑 ================================================================ ...大体思路,自定义一个ViewGroup,重写onMeasure,onLayout方法,
  • 自定义ViewGroup

    千次阅读 2016-06-28 16:49:37
    引子:标准的自定义ViewGroup应该包含什么github第一部分 支持wrap_content 即当ViewGroup的宽、高使用wrap-content时,ViewGroup的高宽根据子View的实际大小来确定 如果你不处理的话,“wrap-content”的和 ...
  • ViewGroup学习 demo:在ViewGroup中显示一个TextView demo:在ViewGroup中显示两个TextView 并且按照某种效果进行布局

空空如也

1 2 3 4 5 ... 20
收藏数 184,211
精华内容 73,684
关键字:

viewgroup