1,我们前三篇博客了解了一下自定义View的基本方法和流程

从源码的角度一步步打造自己的TextView

深入了解自定义属性

onMeasure()源码分析

  之前,我们只是学习过自定义View,其实自定义ViewGroup和自定义View的步骤差不了多少,他们的的区别主要来自各自的作用不同,ViewGroup是容器,用来包含其他控件,而View是真正意义上看得见摸得着的,它需要将自己画出来。ViewGroup需要重写onMeasure方法测量子控件的宽高和自己的宽高,然后实现onLayout方法摆放子控件。而 View则是需要重写onMeasure根据测量模式和父控件给出的建议的宽高值计算自己的宽高,然后再父控件为其指定的区域绘制自己的图形。

  但是仅仅是了解自定义view还是不够的,我们还要学习一下我们的ViewGroup,例如SlideMenu、CardLayout、 CustomLayout等。先看一下我们的官方文档来怎么描述我们的

ViewGroup是一种可以包含其他视图的特殊视图,他是各种布局和所有容器的基类,这些类也定义了ViewGroup.LayoutParams类作为类的布局参数。

  所以我们现在可以自定义ViewGroup分为下面这几步:

  1,继承自ViewGroup,重写构造方法2,重写OnMeasure()方法,丈量子控件和自身宽高3,重写OnLayout()方法,摆放子控件位置

  

2,实现简单的水平排列结果

  先创建自定义ViewGroup,实现从左到右,排满换行的的功能

package com.qianmo.activitydetail.view;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;/*** Created by wangjitao on 2017/3/23 0023.* E-Mail:543441727@qq.com*/public class MyLayout extends ViewGroup {private static String TAG = "MyLayout";public MyLayout(Context context) {this(context, null);}public MyLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 所有子view自己测量大小,然后根据自孩子的大小完成自己的尺寸测量** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//首先计算所有子view的宽高measureChildren(widthMeasureSpec, heightMeasureSpec);//保留测量的宽高(这里使用wrap_content和match_parent都是填充屏幕)setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}/*** 为所有的子控件摆放位置** @param changed* @param left* @param top* @param right* @param bottom*/@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {//获取子控件数量final int count = getChildCount();int childMeasureWidth = 0;int childMeasureHeight = 0;//容器已经占据的宽高度int layoutWidth = 0;int layoutHeight = 0;//每一行的高度是这一行中最高控件的高度int maxChildHeight = 0;for (int i = 0; i < count; i++) {View child = getChildAt(i);//注意此处不能使用getWidth和getHeight,这两个方法必须在onLayout执行完,才能正确获取宽高childMeasureHeight = child.getMeasuredHeight();childMeasureWidth = child.getMeasuredWidth();Log.i(TAG, "getWidth():" + getWidth());Log.i(TAG, "childMeasureHeight:" + childMeasureHeight);Log.i(TAG, "childMeasureWidth:" + childMeasureWidth);getWidth();if (layoutWidth < getWidth()) {//如果一行没有排满,继续往右排列left = layoutWidth;right = left + childMeasureWidth;top = layoutHeight;bottom = top + childMeasureHeight;} else {//排满后就换行layoutWidth = 0;layoutHeight += maxChildHeight;left = layoutWidth;right = left + childMeasureWidth;top = layoutHeight;bottom = top + childMeasureHeight;}//宽度累加layoutWidth += childMeasureWidth;//记录本次最高宽度if (childMeasureHeight > maxChildHeight) {maxChildHeight = childMeasureHeight;}//确定子控件的位置,四个参数分别代表上下左右的坐标值child.layout(left, top, right, bottom);}}
}

  布局文件

<?xml version="1.0" encoding="utf-8"?>
<com.qianmo.activitydetail.view.MyLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:myview="http://schemas.android.com/apk/res-auto"android:layout_width="wrap_content"android:layout_height="wrap_content"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#FF8247"android:padding="20dip"android:text="按钮1"android:textColor="#ffffff"android:textSize="20dip"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#8B0A50"android:padding="10dip"android:text="按钮2222222222222"android:textColor="#ffffff"android:textSize="20dip"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#7CFC00"android:padding="15dip"android:text="按钮333333"android:textColor="#ffffff"android:textSize="20dip"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#1E90FF"android:padding="10dip"android:text="按钮4"android:textColor="#ffffff"android:textSize="10dip"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#191970"android:padding="15dip"android:text="按钮5"android:textColor="#ffffff"android:textSize="20dip"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#7A67EE"android:padding="20dip"android:text="按钮6"android:textColor="#ffffff"android:textSize="20dip"/></com.qianmo.activitydetail.view.MyLayout>

  看一下运行效果

3,自定义LayoutParams,实现RelativeLayout的layout_alignLeft、layout_alignRight、layout_alignTop、layout_alignBottom功能

  回想一下我们平时使用RelativeLayout的时候,在布局文件中使用android:layout_alignParentRight="true"、android:layout_centerInParent="true"等各种属性,就能控制子控件显示在父控件的上下左右、居中等效果。 在上一篇讲onMeasure的博客中,我们有了解过ViewGroup.LayoutParams类,ViewGroup中有两个内部类ViewGroup.LayoutParams和ViewGroup.MarginLayoutParams,MarginLayoutParams继承自LayoutParams,这两个内部类就是ViewGroup的布局参数类,比如我们在LinearLayout等布局中使用的layout_width\layout_hight等以“layout_ ”开头的属性都是布局属性。
  在View中有一个mLayoutParams的变量用来保存这个View的所有布局属性。ViewGroup.LayoutParams有两个属性layout_width和layout_height,因为所有的容器都需要设置子控件的宽高,所以这个LayoutParams是所有布局参数的基类,如果需要扩展其他属性,都应该继承自它。比如RelativeLayout中就提供了它自己的布局参数类RelativeLayout.LayoutParams,并扩展了很多布局参数。

  • 大致明确布局容器的需求,初步定义布局属性

  在定义属性之前要弄清楚,我们自定义的布局容器需要满足那些需求,需要哪些属性,比如,我们现在要实现像相对布局一样,为子控件设置一个位置属性layout_position=”“,来控制子控件在布局中显示的位置。暂定位置有五种:左上、左下、右上、右下、居中。有了需求,我们就在attr.xml定义自己的布局属性

 <declare-styleable name="MyLayout2"><attr name="layout_position"><enum name="left" value="1"/><enum name="top" value="2"/><enum name="right" value="3"/><enum name="bottom" value="4"/><enum name="center" value="5"/></attr></declare-styleable>

  • 继承LayoutParams,定义布局参数类

  我们可以选择继承ViewGroup.LayoutParams,覆盖构造方法,然后在有AttributeSet参数的构造方法中初始化参数值,这个构造方法才是布局文件被映射为对象的时候被调用的。

package com.qianmo.activitydetail.java;import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.ViewGroup;import com.qianmo.activitydetail.R;/*** Created by wangjitao on 2017/3/23 0023.* E-Mail:543441727@qq.com*/public class MyLayoutParams extends ViewGroup.LayoutParams {public static final int POSITION_LEFT = 1;public static final int POSITION_TOP = 2;public static final int POSITION_RIGHT = 3;public static final int POSITION_BOTTOM = 4;public static final int POSITION_CENTER = 5;public int position = POSITION_LEFT;public MyLayoutParams(Context c, AttributeSet attrs) {super(c, attrs);TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.MyLayout2);position = a.getInt(R.styleable.MyLayout2_layout_position, POSITION_LEFT);}public MyLayoutParams(int width, int height) {super(width, height);}public MyLayoutParams(ViewGroup.LayoutParams source) {super(source);}
}

  • 重写generateLayoutParams()

  在ViewGroup中有下面几个关于LayoutParams的方法,generateLayoutParams (AttributeSet attrs)是在布局文件被填充为对象的时候调用的,这个方法是下面几个方法中最重要的,如果不重写它,我们布局文件中设置的布局参数都不能拿到。

    @Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new MyLayoutParams(getContext(), attrs);}@Overrideprotected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {return new MyLayoutParams(p);}@Overrideprotected LayoutParams generateDefaultLayoutParams() {return new MyLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);}@Overrideprotected boolean checkLayoutParams(ViewGroup.LayoutParams p) {return p instanceof MyLayoutParams;}

  • 在布局文件中使用布局属性
<?xml version="1.0" encoding= "utf-8"?>
<com.qianmo.activitydetail.view.MyLayout2xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:myview="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#FF8247"android:padding="20dip"android:text="按钮1"android:textColor="#ffffff"android:textSize="20dip"myview:layout_position="left"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#8B0A50"android:padding="10dip"android:text="按钮2222222222222"android:textColor="#ffffff"android:textSize="18dip"myview:layout_position="right"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#7CFC00"android:padding="15dip"android:text="按钮333333"android:textColor="#ffffff"android:textSize="20dip"myview:layout_position="bottom"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#1E90FF"android:padding="10dip"android:text="按钮4"android:textColor="#ffffff"android:textSize="15dip"myview:layout_position="top"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#191970"android:padding="15dip"android:text="按钮5"android:textColor="#ffffff"android:textSize="20dip"myview:layout_position="center"/>
</com.qianmo.activitydetail.view.MyLayout2>

  • 在onMeasure和onLayout中使用布局参数

  经过上面几步之后,我们运行程序,就能获取子控件的布局参数了,在onMeasure方法和onLayout方法中,我们按照自定义布局容器的特殊需求,对宽度和位置坐特殊处理。这里我们需要注意一下,如果布局容器被设置为包裹类容,我们只需要保证能将最大的子控件包裹住就ok,代码注释比较详细,就不多说了。

package com.qianmo.activitydetail.view;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;import com.qianmo.activitydetail.java.MyLayoutParams;/*** Created by wangjitao on 2017/3/23 0023.* E-Mail:543441727@qq.com* 通过自定义LayoutParams设置特殊的属性*/public class MyLayout2 extends ViewGroup {private static String TAG = "MyLayout";public MyLayout2(Context context) {this(context, null);}public MyLayout2(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MyLayout2(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 在ViewGroup中有下面几个关于LayoutParams的方法,generateLayoutParams (AttributeSet attrs)是在布局文件被填充为对象的时候调用的* 如果不重写它,我么布局文件中设置的布局参数都不能拿到。** @param attrs* @return*/@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new MyLayoutParams(getContext(), attrs);}@Overrideprotected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {return new MyLayoutParams(p);}@Overrideprotected LayoutParams generateDefaultLayoutParams() {return new MyLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);}@Overrideprotected boolean checkLayoutParams(ViewGroup.LayoutParams p) {return p instanceof MyLayoutParams;}/*** 所有子view自己测量大小,然后根据自孩子的大小完成自己的尺寸测量** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//获取本ViewGroup上机容器为其推荐的款和高,以及计算模式int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);//计算出所有子控件的宽和高measureChildren(widthMeasureSpec, heightMeasureSpec);int childWidth = 0;int childHeight = 0;int chileCount = getChildCount();//测量的父控件的宽高int layoutHeight = 0;int layoutWidth = 0;//进行宽度模式的判断if (widthMode == MeasureSpec.EXACTLY) {//这时不具容器的宽度模式是确定的(具体的size或者match_patent,直接使用父窗体建议的宽度)layoutWidth = widthSize;} else {//如果是未指定的活wrap_content,我们一般按照包裹内容来处理,宽度就拿所有控件的宽度和为宽度for (int i = 0; i < chileCount; i++) {View child = getChildAt(i);childWidth = child.getMeasuredWidth();//获取子控件最大宽度layoutWidth = childWidth > layoutWidth ? childWidth : layoutWidth;}}//高度模式一样if (heightMode == MeasureSpec.EXACTLY) {//这时不具容器的宽度模式是确定的(具体的size或者match_patent,直接使用父窗体建议的宽度)layoutHeight = heightSize;} else {//如果是未指定的活wrap_content,我们一般按照包裹内容来处理,宽度就拿所有控件的宽度和为宽度for (int i = 0; i < chileCount; i++) {View child = getChildAt(i);childHeight = child.getMeasuredHeight();//获取子控件最大高度layoutHeight = childHeight > layoutHeight ? childHeight : layoutHeight;}}//保存测量宽高数据setMeasuredDimension(layoutWidth, layoutHeight);}/*** 为所有的子控件摆放位置** @param changed* @param left* @param top* @param right* @param bottom*/@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {int childWidth = 0;int childHeight = 0;int chileCount = getChildCount();MyLayoutParams params = null;for (int i = 0; i < chileCount; i++) {childWidth = getChildAt(i).getMeasuredWidth();childHeight = getChildAt(i).getMeasuredHeight();params = (MyLayoutParams) getChildAt(i).getLayoutParams();switch (params.position) {case MyLayoutParams.POSITION_LEFT://左上方left = 0;top = 0;break;case MyLayoutParams.POSITION_TOP://右上方left = getWidth() - childWidth;top = 0;break;case MyLayoutParams.POSITION_RIGHT://右下方left = 0;top = getHeight() - childHeight;break;case MyLayoutParams.POSITION_BOTTOM:left = getWidth() - childWidth;top = getHeight() - childHeight;break;case MyLayoutParams.POSITION_CENTER:left = (getWidth() - childWidth) / 2;top = (getHeight() - childHeight) / 2;break;default:break;}// 确定子控件的位置,四个参数分别代表(左上右下)点的坐标值getChildAt(i).layout(left, top, left + childWidth, top + childHeight);}}
}

  看一下运行效果

4,支持layout_margin属性

  如果我们自定义的布局参数类继承自MarginLayoutParams,就自动支持了layout_margin属性了,我们需要做的就是直接在布局文件中使用layout_margin属性,然后再onMeasure和onLayout中使用margin属性值测量和摆放子控件。需要注意的是我们测量子控件的时候应该调用measureChildWithMargin()方法。

<?xml version="1.0" encoding= "utf-8"?>
<com.qianmo.activitydetail.view.MyLayout3xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:myview="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:layout_width= "wrap_content"android:layout_height= "wrap_content"myview:layout_position= "left"android:layout_marginLeft = "20dip"android:background= "#FF8247"android:textColor= "#ffffff"android:textSize="20dip"android:padding= "20dip"android:text="按钮1" /><Buttonandroid:layout_width= "wrap_content"android:layout_height= "wrap_content"android:layout_marginTop = "30dip"myview:layout_position= "top"android:background= "#8B0A50"android:textColor= "#ffffff"android:textSize="18dip"android:padding= "10dip"android:text="按钮2222222222222" /><Buttonandroid:layout_width= "wrap_content"android:layout_height= "wrap_content"android:layout_marginLeft = "30dip"android:layout_marginBottom = "10dip"myview:layout_position= "bottom"android:background= "#7CFC00"android:textColor= "#ffffff"android:textSize="20dip"android:padding= "15dip"android:text="按钮333333" /><Buttonandroid:layout_width= "wrap_content"android:layout_height= "wrap_content"myview:layout_position= "right"android:layout_marginBottom = "30dip"android:background= "#1E90FF"android:textColor= "#ffffff"android:textSize="15dip"android:padding= "10dip"android:text="按钮4" /><Buttonandroid:layout_width= "wrap_content"android:layout_height= "wrap_content"myview:layout_position= "center"android:layout_marginBottom = "30dip"android:layout_marginRight = "30dip"android:background= "#191970"android:textColor= "#ffffff"android:textSize="20dip"android:padding= "15dip"android:text="按钮5" /></com.qianmo.activitydetail.view.MyLayout3>

  我们创建类继承自MarginParams类

package com.qianmo.activitydetail.java;import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.ViewGroup;import com.qianmo.activitydetail.R;/*** Created by wangjitao on 2017/3/23 0023.* E-Mail:543441727@qq.com* 添加外边框参数*/public class MyLayoutParamsWithMargin extends ViewGroup.MarginLayoutParams {public static final int POSITION_LEFT = 1;public static final int POSITION_TOP = 2;public static final int POSITION_RIGHT = 3;public static final int POSITION_BOTTOM = 4;public static final int POSITION_CENTER = 5;public int position = POSITION_LEFT;public MyLayoutParamsWithMargin(Context c, AttributeSet attrs) {super(c, attrs);TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.MyLayout2);position = a.getInt(R.styleable.MyLayout2_layout_position, POSITION_LEFT);}public MyLayoutParamsWithMargin(int width, int height) {super(width, height);}public MyLayoutParamsWithMargin(ViewGroup.LayoutParams source) {super(source);}
}

  在generateLayoutParams()方法中替换类

 @Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new MyLayoutParamsWithMargin(getContext(), attrs);}@Overrideprotected LayoutParams generateLayoutParams(LayoutParams p) {return new MyLayoutParamsWithMargin(p);}@Overrideprotected LayoutParams generateDefaultLayoutParams() {return new MyLayoutParamsWithMargin(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);}@Overrideprotected boolean checkLayoutParams(LayoutParams p) {return p instanceof MyLayoutParamsWithMargin;}

  onMeasure和onLayout:

 /*** 所有子view自己测量大小,然后根据自孩子的大小完成自己的尺寸测量** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//获取本ViewGroup上机容器为其推荐的款和高,以及计算模式int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);//计算出所有子控件的宽和高
//        measureChildren(widthMeasureSpec, heightMeasureSpec);int childWidth = 0;int childHeight = 0;int chileCount = getChildCount();//测量的父控件的宽高int layoutHeight = 0;int layoutWidth = 0;// 计算出所有的childView的宽和高for (int i = 0; i < chileCount; i++) {View child = getChildAt(i);measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);}MyLayoutParamsWithMargin params = null;//进行宽度模式的判断if (widthMode == MeasureSpec.EXACTLY) {//这时不具容器的宽度模式是确定的(具体的size或者match_patent,直接使用父窗体建议的宽度)layoutWidth = widthSize;} else {//如果是未指定的活wrap_content,我们一般按照包裹内容来处理,宽度就拿所有控件的宽度和为宽度for (int i = 0; i < chileCount; i++) {View child = getChildAt(i);childWidth = child.getMeasuredWidth();params = (MyLayoutParamsWithMargin) child.getLayoutParams();//获取子控件最大宽度(要算上左右间距)layoutWidth = childWidth > layoutWidth + params.leftMargin + params.rightMargin ? childWidth : layoutWidth;}}//高度模式一样if (heightMode == MeasureSpec.EXACTLY) {//这时不具容器的宽度模式是确定的(具体的size或者match_patent,直接使用父窗体建议的宽度)layoutHeight = heightSize;} else {//如果是未指定的活wrap_content,我们一般按照包裹内容来处理,宽度就拿所有控件的宽度和为宽度for (int i = 0; i < chileCount; i++) {View child = getChildAt(i);childHeight = child.getMeasuredHeight();params = (MyLayoutParamsWithMargin) child.getLayoutParams();//获取子控件最大高度layoutHeight = childHeight > layoutHeight + params.topMargin + params.bottomMargin ? childHeight : layoutHeight;}}//保存测量宽高数据setMeasuredDimension(layoutWidth, layoutHeight);}/*** 为所有的子控件摆放位置** @param changed* @param left* @param top* @param right* @param bottom*/@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {int childWidth = 0;int childHeight = 0;int chileCount = getChildCount();MyLayoutParamsWithMargin params = null;for (int i = 0; i < chileCount; i++) {childWidth = getChildAt(i).getMeasuredWidth();childHeight = getChildAt(i).getMeasuredHeight();params = (MyLayoutParamsWithMargin) getChildAt(i).getLayoutParams();switch (params.position) {case MyLayoutParams.POSITION_LEFT://左上方left = 0 + params.leftMargin;top = 0 + params.topMargin;break;case MyLayoutParams.POSITION_TOP://右上方left = getWidth() - childWidth - params.rightMargin;top = 0 + params.topMargin;break;case MyLayoutParams.POSITION_RIGHT://左下方left = 0 + params.leftMargin;top = getHeight() - childHeight - params.bottomMargin;break;case MyLayoutParams.POSITION_BOTTOM://右下角left = getWidth() - childWidth - params.rightMargin;top = getHeight() - childHeight - params.bottomMargin;break;case MyLayoutParams.POSITION_CENTER:left = (getWidth() - childWidth) / 2;top = (getHeight() - childHeight) / 2;break;default:break;}// 确定子控件的位置,四个参数分别代表(左上右下)点的坐标值getChildAt(i).layout(left, top, left + childWidth, top + childHeight);}}

  运行效果

  总结一下我们的学习内容

  自定义ViewGroup的步骤:

①. 继承ViewGroup,覆盖构造方法 
②. 重写onMeasure方法测量子控件和自身宽高 
③. 实现onLayout方法摆放子控件

  为布局容器自定义布局属性:

①. 大致明确布局容器的需求,初步定义布局属性 
②. 继承LayoutParams,定义布局参数类 
③. 重写获取布局参数的方法 
④. 在布局文件中使用布局属性 
⑤. 在onMeasure和onLayout中使用布局参数

Android -- ViewGroup源码分析+自定义相关推荐

  1. 【Android SDM660源码分析】- 01 - 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序

    [Android SDM660源码分析]- 01 - 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序 一.创建DXE_DRIVER ...

  2. Android框架源码分析——从设计模式角度看 Retrofit 核心源码

    Android框架源码分析--从设计模式角度看 Retrofit 核心源码 Retrofit中用到了许多常见的设计模式:代理模式.外观模式.构建者模式等.我们将从这三种设计模式入手,分析 Retrof ...

  3. Android HandlerThread 源码分析

    HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我们又有一 个耗时任务需要执行,我们不得不重新创建 ...

  4. Android ADB 源码分析(三)

    前言 之前分析的两篇文章 Android Adb 源码分析(一) 嵌入式Linux:Android root破解原理(二) 写完之后,都没有写到相关的实现代码,这篇文章写下ADB的通信流程的一些细节 ...

  5. 【Android SDM660源码分析】- 02 - UEFI XBL QcomChargerApp充电流程代码分析

    [Android SDM660源码分析]- 02 - UEFI XBL QcomChargerApp充电流程代码分析 一.加载 UEFI 默认应用程序 1.1 LaunchDefaultBDSApps ...

  6. 【Android SDM660源码分析】- 03 - UEFI XBL GraphicsOutput BMP图片显示流程

    [Android SDM660源码分析]- 03 - UEFI XBL GraphicsOutput BMP图片显示流程 1. GraphicsOutput.h 2. 显示驱动初化 DisplayDx ...

  7. 【Android SDM660源码分析】- 04 - UEFI ABL LinuxLoader 代码分析

    [Android SDM660源码分析]- 04 - UEFI ABL LinuxLoader 代码分析 1. LinuxLoader.c 系列文章: <[Android SDM660开机流程] ...

  8. Android 音频源码分析——AndroidRecord录音(一)

    Android 音频源码分析--AndroidRecord录音(一) Android 音频源码分析--AndroidRecord录音(二) Android 音频源码分析--AndroidRecord音 ...

  9. 人人网官方Android客户端源码分析(1)

    ContentProvider是不同应用程序之间进行数据交换的标准API,ContentProvider以某种Uri的形式对外提供数据,允许其他应用访问或修改数据;其他应用程序使用ContentRes ...

最新文章

  1. mysql防止从节点可写数据_mysql 主从数据不一致 Slave_SQL_Running: No 解决方法
  2. (C#)WinForm窗体间传值
  3. golang 代码安全审计
  4. [leetcode] 101. 对称二叉树
  5. Swift之深入解析如何结合Core Data和SwiftUI
  6. python----面向对象:1类的定义
  7. JavaSE——IO(下)(Properties类、序列化与反序列化)
  8. ssh放行端口_安全组中已经添加规则放行SSH端口的访问之后如何使用f1 RTL
  9. 举例 微积分 拉格朗日方程_变量数学时代——微积分的发明
  10. mysql为何500w拆表_【mysql】MySQL 单表500W+数据,查询超时,如何优化呢?
  11. @程序员,Python 这次彻底上位了!
  12. Q98:三角形网格细分Bezier曲面时,注意三角形顶点的顺序(确保其对应的法向量向外)
  13. django 笔记3
  14. 微信小程序 页面递归生成
  15. Linux上获取软件程序包
  16. 微信聊天机器人-存储好友分享消息
  17. 小岛上的蓝眼睛的人要几天才能全部离开
  18. 5G毫米波存在的挑战和测试方案
  19. VEGAS中的项目工程文件打不开该如何解决?
  20. 车市下滑 领克汽车为什么逆势上扬?

热门文章

  1. PHP 中和 HTTP 相关的函数及使用
  2. 不能从const char *转换为LPCWSTR --VS经常碰到
  3. 开源软件 Cachet 被曝RCE漏洞
  4. Jenkins 内部服务器遭访问且被部署密币挖机
  5. 成都睿铂 | 云南省地矿测绘院1:500地形免像控项目分享
  6. SpringCloud的EurekaClient : 客户端应用访问注册的微服务(无断路器场景)
  7. Highcharts基础教程(七):图例(Legend)
  8. 基于JMS规范的ActiveMQ
  9. smartctl command's RETURN VALUES
  10. How do you simple use git repository