精华内容
参与话题
问答
  • Android 自定义View (一)

    万次阅读 多人点赞 2014-04-21 15:20:04
    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章。先总结下自定义View的步骤: 1、自定义View的属性 2、...

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901

    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章。先总结下自定义View的步骤:

    1、自定义View的属性

    2、在View的构造方法中获得我们自定义的属性

    [ 3、重写onMesure ]

    4、重写onDraw

    我把3用[]标出了,所以说3不一定是必须的,当然了大部分情况下还是需要重写的。

    1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <attr name="titleText" format="string" />
        <attr name="titleTextColor" format="color" />
        <attr name="titleTextSize" format="dimension" />
    
        <declare-styleable name="CustomTitleView">
            <attr name="titleText" />
            <attr name="titleTextColor" />
            <attr name="titleTextSize" />
        </declare-styleable>
    
    </resources>
    我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:

    一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;不清楚的可以google一把。

    然后在布局中声明我们的自定义View

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.example.customview01.view.CustomTitleView
            android:layout_width="200dp"
            android:layout_height="100dp"
            custom:titleText="3712"
            custom:titleTextColor="#ff0000"
            custom:titleTextSize="40sp" />
    
    </RelativeLayout>

    一定要引入 xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"我们的命名空间,后面的包路径指的是项目的package

    2、在View的构造方法中,获得我们的自定义的样式

    /**
    	 * 文本
    	 */
    	private String mTitleText;
    	/**
    	 * 文本的颜色
    	 */
    	private int mTitleTextColor;
    	/**
    	 * 文本的大小
    	 */
    	private int mTitleTextSize;
    
    	/**
    	 * 绘制时控制文本绘制的范围
    	 */
    	private Rect mBound;
    	private Paint mPaint;
    
    	public CustomTitleView(Context context, AttributeSet attrs)
    	{
    		this(context, attrs, 0);
    	}
    
    	public CustomTitleView(Context context)
    	{
    		this(context, null);
    	}
    
    	/**
    	 * 获得我自定义的样式属性
    	 * 
    	 * @param context
    	 * @param attrs
    	 * @param defStyle
    	 */
    	public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
    	{
    		super(context, attrs, defStyle);
    		/**
    		 * 获得我们所定义的自定义样式属性
    		 */
    		TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
    		int n = a.getIndexCount();
    		for (int i = 0; i < n; i++)
    		{
    			int attr = a.getIndex(i);
    			switch (attr)
    			{
    			case R.styleable.CustomTitleView_titleText:
    				mTitleText = a.getString(attr);
    				break;
    			case R.styleable.CustomTitleView_titleTextColor:
    				// 默认颜色设置为黑色
    				mTitleTextColor = a.getColor(attr, Color.BLACK);
    				break;
    			case R.styleable.CustomTitleView_titleTextSize:
    				// 默认设置为16sp,TypeValue也可以把sp转化为px
    				mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
    						TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
    				break;
    
    			}
    
    		}
    		a.recycle();
    
    		/**
    		 * 获得绘制文本的宽和高
    		 */
    		mPaint = new Paint();
    		mPaint.setTextSize(mTitleTextSize);
    		// mPaint.setColor(mTitleTextColor);
    		mBound = new Rect();
    		mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
    
    	}

    我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。

    3、我们重写onDraw,onMesure调用系统提供的:

    @Override
    	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    	{
    		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas)
    	{
    		mPaint.setColor(Color.YELLOW);
    		canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
    
    		mPaint.setColor(mTitleTextColor);
    		canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
    	}
    此时的效果是:

    是不是觉得还不错,基本已经实现了自定义View。但是此时如果我们把布局文件的宽和高写成wrap_content,会发现效果并不是我们的预期:


    系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。

    所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法”:

    重写之前先了解MeasureSpec的specMode,一共三种类型:

    EXACTLY:一般是设置了明确的值或者是MATCH_PARENT

    AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

    UNSPECIFIED:表示子布局想要多大就多大,很少使用

    下面是我们重写onMeasure代码:

    	@Override
    	protected void onMeasure(int widthMeasureSpec, int 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
    		{
    			mPaint.setTextSize(mTitleTextSize);
    			mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
    			float textWidth = mBounds.width();
    			int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
    			width = desired;
    		}
    
    		if (heightMode == MeasureSpec.EXACTLY)
    		{
    			height = heightSize;
    		} else
    		{
    			mPaint.setTextSize(mTitleTextSize);
    			mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
    			float textHeight = mBounds.height();
    			int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
    			height = desired;
    		}
    		
    		
    
    		setMeasuredDimension(width, height);
    	}
    

    现在我们修改下布局文件:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.example.customview01.view.CustomTitleView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            custom:titleText="3712"
            android:padding="10dp"
            custom:titleTextColor="#ff0000"
            android:layout_centerInParent="true"
            custom:titleTextSize="40sp" />
    
    </RelativeLayout>

    现在的效果是:


    完全复合我们的预期,现在我们可以对高度、宽度进行随便的设置了,基本可以满足我们的需求。

    当然了,这样下来我们这个自定义View与TextView相比岂不是没什么优势,所有我们觉得给自定义View添加一个事件:

    在构造中添加:

    this.setOnClickListener(new OnClickListener()
    		{
    
    			@Override
    			public void onClick(View v)
    			{
    				mTitleText = randomText();
    				postInvalidate();
    			}
    
    		});

    private String randomText()
    	{
    		Random random = new Random();
    		Set<Integer> set = new HashSet<Integer>();
    		while (set.size() < 4)
    		{
    			int randomInt = random.nextInt(10);
    			set.add(randomInt);
    		}
    		StringBuffer sb = new StringBuffer();
    		for (Integer i : set)
    		{
    			sb.append("" + i);
    		}
    
    		return sb.toString();
    	}

    下面再来运行:


    我们添加了一个点击事件,每次让它随机生成一个4位的随机数,有兴趣的可以在onDraw中添加一点噪点,然后改写为验证码,是不是感觉很不错。


    好了,各位学习的,打酱油的留个言,顶个呗~


    源码点击此处下载






    展开全文
  • Android 自定义View (二) 进阶

    万次阅读 多人点赞 2014-04-22 11:39:25
    继续自定义View之旅,前面已经介绍过一个自定义View的基础的例子,Android 自定义View (一),如果你还对自定义View不了解可以去看看。今天给大家带来一个稍微复杂点的例子。 自定义View显示一张图片,下面包含图片...

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125

    继续自定义View之旅,前面已经介绍过一个自定义View的基础的例子,Android 自定义View (一)如果你还对自定义View不了解可以去看看。今天给大家带来一个稍微复杂点的例子。

    自定义View显示一张图片,下面包含图片的文本介绍,类似相片介绍什么的,不过不重要,主要是学习自定义View的用法么。

    还记得上一篇讲的4个步骤么:

    1、自定义View的属性
    2、在View的构造方法中获得我们自定义的属性
    [ 3、重写onMesure ]
    4、重写onDraw

    直接切入正题:

    1、在res/values/attr.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <attr name="titleText" format="string" />
        <attr name="titleTextSize" format="dimension" />
        <attr name="titleTextColor" format="color" />
        <attr name="image" format="reference" />
        <attr name="imageScaleType">
            <enum name="fillXY" value="0" />
            <enum name="center" value="1" />
        </attr>
    
        <declare-styleable name="CustomImageView">
            <attr name="titleText" />
            <attr name="titleTextSize" />
            <attr name="titleTextColor" />
            <attr name="image" />
            <attr name="imageScaleType" />
        </declare-styleable>
    
    </resources>

    2、在构造中获得我们的自定义属性:

    /**
    	 * 初始化所特有自定义类型
    	 * 
    	 * @param context
    	 * @param attrs
    	 * @param defStyle
    	 */
    	public CustomImageView(Context context, AttributeSet attrs, int defStyle)
    	{
    		super(context, attrs, defStyle);
    
    		TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView, defStyle, 0);
    
    		int n = a.getIndexCount();
    
    		for (int i = 0; i < n; i++)
    		{
    			int attr = a.getIndex(i);
    
    			switch (attr)
    			{
    			case R.styleable.CustomImageView_image:
    				mImage = BitmapFactory.decodeResource(getResources(), a.getResourceId(attr, 0));
    				break;
    			case R.styleable.CustomImageView_imageScaleType:
    				mImageScale = a.getInt(attr, 0);
    				break;
    			case R.styleable.CustomImageView_titleText:
    				mTitle = a.getString(attr);
    				break;
    			case R.styleable.CustomImageView_titleTextColor:
    				mTextColor = a.getColor(attr, Color.BLACK);
    				break;
    			case R.styleable.CustomImageView_titleTextSize:
    				mTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
    						16, getResources().getDisplayMetrics()));
    				break;
    
    			}
    		}
    		a.recycle();
    		rect = new Rect();
    		mPaint = new Paint();
    		mTextBound = new Rect();
    		mPaint.setTextSize(mTextSize);
    		// 计算了描绘字体需要的范围
    		mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);
    
    	}

    3、重写onMeasure

    	@Override
    	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    	{
    		// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
    		/**
    		 * 设置宽度
    		 */
    		int specMode = MeasureSpec.getMode(widthMeasureSpec);
    		int specSize = MeasureSpec.getSize(widthMeasureSpec);
    
    		if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
    		{
    			Log.e("xxx", "EXACTLY");
    			mWidth = specSize;
    		} else
    		{
    			// 由图片决定的宽
    			int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();
    			// 由字体决定的宽
    			int desireByTitle = getPaddingLeft() + getPaddingRight() + mTextBound.width();
    
    			if (specMode == MeasureSpec.AT_MOST)// wrap_content
    			{
    				int desire = Math.max(desireByImg, desireByTitle);
    				mWidth = Math.min(desire, specSize);
    				Log.e("xxx", "AT_MOST");
    			}
    		}
    
    		/***
    		 * 设置高度
    		 */
    
    		specMode = MeasureSpec.getMode(heightMeasureSpec);
    		specSize = MeasureSpec.getSize(heightMeasureSpec);
    		if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
    		{
    			mHeight = specSize;
    		} else
    		{
    			int desire = getPaddingTop() + getPaddingBottom() + mImage.getHeight() + mTextBound.height();
    			if (specMode == MeasureSpec.AT_MOST)// wrap_content
    			{
    				mHeight = Math.min(desire, specSize);
    			}
    		}
    		setMeasuredDimension(mWidth, mHeight);
    
    	}

    4、重写onDraw

    @Override
    	protected void onDraw(Canvas canvas)
    	{
    		// super.onDraw(canvas);
    		/**
    		 * 边框
    		 */
    		mPaint.setStrokeWidth(4);
    		mPaint.setStyle(Paint.Style.STROKE);
    		mPaint.setColor(Color.CYAN);
    		canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
    
    		rect.left = getPaddingLeft();
    		rect.right = mWidth - getPaddingRight();
    		rect.top = getPaddingTop();
    		rect.bottom = mHeight - getPaddingBottom();
    
    		mPaint.setColor(mTextColor);
    		mPaint.setStyle(Style.FILL);
    		/**
    		 * 当前设置的宽度小于字体需要的宽度,将字体改为xxx...
    		 */
    		if (mTextBound.width() > mWidth)
    		{
    			TextPaint paint = new TextPaint(mPaint);
    			String msg = TextUtils.ellipsize(mTitle, paint, (float) mWidth - getPaddingLeft() - getPaddingRight(),
    					TextUtils.TruncateAt.END).toString();
    			canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint);
    
    		} else
    		{
    			//正常情况,将字体居中
    			canvas.drawText(mTitle, mWidth / 2 - mTextBound.width() * 1.0f / 2, mHeight - getPaddingBottom(), mPaint);
    		}
    
    		//取消使用掉的快
    		rect.bottom -= mTextBound.height();
    
    		if (mImageScale == IMAGE_SCALE_FITXY)
    		{
    			canvas.drawBitmap(mImage, null, rect, mPaint);
    		} else
    		{
    			//计算居中的矩形范围
    			rect.left = mWidth / 2 - mImage.getWidth() / 2;
    			rect.right = mWidth / 2 + mImage.getWidth() / 2;
    			rect.top = (mHeight - mTextBound.height()) / 2 - mImage.getHeight() / 2;
    			rect.bottom = (mHeight - mTextBound.height()) / 2 + mImage.getHeight() / 2;
    
    			canvas.drawBitmap(mImage, null, rect, mPaint);
    		}
    
    	}

    代码,结合注释和第一篇View的使用,应该可以看懂,不明白的留言。下面我们引入我们的自定义View:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.customview02"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <com.zhy.customview02.view.CustomImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:padding="10dp"
            zhy:image="@drawable/ic_launcher"
            zhy:imageScaleType="center"
            zhy:titleText="hello andorid ! "
            zhy:titleTextColor="#ff0000"
            zhy:titleTextSize="30sp" />
    
        <com.zhy.customview02.view.CustomImageView
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:padding="10dp"
            zhy:image="@drawable/ic_launcher"
            zhy:imageScaleType="center"
            zhy:titleText="helloworldwelcome"
            zhy:titleTextColor="#00ff00"
            zhy:titleTextSize="20sp" />
    
        <com.zhy.customview02.view.CustomImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:padding="10dp"
            zhy:image="@drawable/lmj"
            zhy:imageScaleType="center"
            zhy:titleText="妹子~"
            zhy:titleTextColor="#ff0000"
            zhy:titleTextSize="12sp" />
    
    </LinearLayout>

    我特意让显示出现3中情况:

    1、字体的宽度大于图片,且View宽度设置为wrap_content

    2、View宽度设置为精确值,字体的长度大于此宽度

    3、图片的宽度大于字体,且View宽度设置为wrap_content

    看看显示效果:


    怎么样,对于这三种情况所展示的效果都还不错吧。


    好了,就到这里,各位看官,没事留个言,顶一个呗~


    源码点击下载




    展开全文
  • 自定义View基础 - 最易懂的自定义View原理系列(1)

    万次阅读 多人点赞 2017-02-20 11:27:07
    自定义View原理是Android开发者必须了解的基础; 在了解自定义View之前,你需要有一定的知识储备; 本文将全面解析关于自定义View中的所有知识基础。 目录1. View的分类视图View主要分为两类: 类别 解释 特点 ...

    前言

    • 自定义View原理是Android开发者必须了解的基础;
    • 在了解自定义View之前,你需要有一定的知识储备;
    • 本文将全面解析关于自定义View中的所有知识基础。

    目录

    目录


    1. View的分类

    视图View主要分为两类:

    类别 解释 特点
    单一视图 即一个View,如TextView 不包含子View
    视图组 即多个View组成的ViewGroup,如LinearLayout 包含子View

    2. View类简介

    • View类是Android中各种组件的基类,如View是ViewGroup基类
    • View表现为显示在屏幕上的各种视图

    Android中的UI组件都由View、ViewGroup组成。

    • View的构造函数:共有4个,具体如下:

    自定义View必须重写至少一个构造函数:

    // 如果View是在Java代码里面new的,则调用第一个构造函数
     public CarsonView(Context context) {
            super(context);
        }
    
    // 如果View是在.xml里声明的,则调用第二个构造函数
    // 自定义属性是从AttributeSet参数传进来的
        public  CarsonView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
    // 不会自动调用
    // 一般是在第二个构造函数里主动调用
    // 如View有style属性时
        public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        //API21之后才使用
        // 不会自动调用
        // 一般是在第二个构造函数里主动调用
        // 如View有style属性时
        public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    

    更加具体的使用请看:深入理解View的构造函数
    理解View的构造函数


    3. View视图结构

    对于多View的视图,结构是树形结构:最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View,如下图:

    View树结构

    请记住:无论是measure过程、layout过程还是draw过程,**永远都是从View树的根节点开始测量或计算(即从树的顶端开始),一层一层、一个分支一个分支地进行(即树形递归),**最终计算整个View树中各个View,最终确定整个View树的相关属性。


    4. Android坐标系

    Android的坐标系定义为:

    • 屏幕的左上角为坐标原点
    • 向右为x轴增大方向
    • 向下为y轴增大方向

    具体如下图:
    屏幕坐标系

    注:区别于一般的数学坐标系

    两者坐标系的区别


    5. View位置(坐标)描述

    • View的位置由4个顶点决定的(如下A、B、C、D)

    View的顶点

    4个顶点的位置描述分别由4个值决定:
    (请记住:View的位置是相对于父控件而言的

    • Top:子View上边界到父view上边界的距离
    • Left:子View左边界到父view左边界的距离
    • Bottom:子View下边距到父View上边界的距离
    • Right:子View右边界到父view左边界的距离

    如下图:
    View的位置描述

    个人建议:按顶点位置来记忆:

    • Top:子View左上角距父View顶部的距离;
    • Left:子View左上角距父View左侧的距离;
    • Bottom:子View右下角距父View顶部的距离
    • Right:子View右下角距父View左侧的距离

    6. 位置获取方式

    • View的位置是通过view.getxxx()函数进行获取:(以Top为例)
    // 获取Top位置
    public final int getTop() {  
        return mTop;  
    }  
    
    // 其余如下:
      getLeft();      //获取子View左上角距父View左侧的距离
      getBottom();    //获取子View右下角距父View顶部的距离
      getRight();     //获取子View右下角距父View左侧的距离
    
    • 与MotionEvent中 get()getRaw()的区别
    //get() :触摸点相对于其所在组件坐标系的坐标
     event.getX();       
     event.getY();
    
    //getRaw() :触摸点相对于屏幕默认坐标系的坐标
     event.getRawX();    
     event.getRawY();
    
    

    具体如下图:

    get() 和 getRaw() 的区别

    #7. Android的角度(angle)与弧度(radian)

    • 自定义View实际上是将一些简单的形状通过计算,从而组合到一起形成的效果。

    这会涉及到画布的相关操作(旋转)、正余弦函数计算等,即会涉及到角度(angle)与弧度(radian)的相关知识。

    • 角度和弧度都是描述角的一种度量单位,区别如下图::

    角度和弧度区别

    在默认的屏幕坐标系中角度增大方向为顺时针。

    屏幕坐标系角度增大方向

    注:在常见的数学坐标系中角度增大方向为逆时针


    8. Android中颜色相关内容

    Android中的颜色相关内容包括颜色模式,创建颜色的方式,以及颜色的混合模式等。

    8.1 颜色模式

    Android支持的颜色模式:

    Android颜色模式

    以ARGB8888为例介绍颜色定义:

    ARGB88888

    8.2 定义颜色的方式

    8.2.1 在java中定义颜色

     //java中使用Color类定义颜色
     int color = Color.GRAY;     //灰色
    
      //Color类是使用ARGB值进行表示
      int color = Color.argb(127, 255, 0, 0);   //半透明红色
      int color = 0xaaff0000;                   //带有透明度的红色
    

    8.2.2 在xml文件中定义颜色

    在/res/values/color.xml 文件中如下定义:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        //定义了红色(没有alpha(透明)通道)
        <color name="red">#ff0000</color>
        //定义了蓝色(没有alpha(透明)通道)
        <color name="green">#00ff00</color>
    </resources>
    
    

    在xml文件中以”#“开头定义颜色,后面跟十六进制的值,有如下几种定义方式:

      #f00            //低精度 - 不带透明通道红色
      #af00           //低精度 - 带透明通道红色
    
      #ff0000         //高精度 - 不带透明通道红色
      #aaff0000       //高精度 - 带透明通道红色
    

    8.3 引用颜色的方式

    8.3.1 在java文件中引用xml中定义的颜色:

    //方法1
    int color = getResources().getColor(R.color.mycolor);
    
    //方法2(API 23及以上)
    int color = getColor(R.color.myColor);    
    

    8.3.2 在xml文件(layout或style)中引用或者创建颜色

     <!--在style文件中引用-->
        <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
            <item name="colorPrimary">@color/red</item>
        </style>
    
     <!--在layout文件中引用在/res/values/color.xml中定义的颜色-->
      android:background="@color/red"     
    
     <!--在layout文件中创建并使用颜色-->
      android:background="#ff0000"        
    

    8.4 取色工具

    • 颜色都是用RGB值定义的,而我们一般是无法直观的知道自己需要颜色的值,需要借用取色工具直接从图片或者其他地方获取颜色的RGB值。
    • 有时候一些简单的颜色选取就不用去麻烦UI了,开发者自己去选取效率更高
    • 这里,取色工具我强推Markman:一款设计师用于标注的工具,主要用于尺寸标注、字体大小标注、颜色标注,而且使用简单。本人强烈推荐!

    Markman


    请帮顶 / 评论点赞!因为你们的赞同/鼓励是我写作的最大动力!


    9. 总结

    展开全文
  • 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全、思路不清晰、无源码分析、简单问题复杂化 等 今天,我将全面总结自定义View原理中的measure过程,我能保证这是市面上的最全面、最清晰、最易懂的 ...

    前言

    • 自定义ViewAndroid开发者必须了解的基础
    • 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全、思路不清晰、无源码分析、简单问题复杂化 等
    • 今天,我将全面总结自定义View原理中的measure过程,我能保证这是市面上的最全面、最清晰、最易懂的

    文章较长,建议收藏等充足时间再进行阅读


    目录

    示意图


    1. 作用

    测量View的宽 / 高

    1. 在某些情况下,需要多次测量(measure)才能确定View最终的宽/高;
    2. 该情况下,measure过程后得到的宽 / 高可能不准确;
    3. 此处建议:在layout过程中onLayout()去获取最终的宽 / 高

    2. 储备知识

    了解measure过程前,需要先了解传递尺寸(宽 / 高测量值)的2个类:

    • ViewGroup.LayoutParams类()
    • MeasureSpecs 类(父视图对子视图的测量要求)

    2.1 ViewGroup.LayoutParams

    • 简介
      布局参数类
    1. ViewGroup 的子类(RelativeLayout、LinearLayout)有其对应的 ViewGroup.LayoutParams 子类
    2. 如:RelativeLayoutViewGroup.LayoutParams子类
      = RelativeLayoutParams
    • 作用
      指定视图View 的高度(height) 和 宽度(width)等布局参数。可

    • 具体使用
      通过以下参数指定

    参数 解释
    具体值 dp / px
    fill_parent 强制性使子视图的大小扩展至与父视图大小相等(不含 padding )
    match_parent 与fill_parent相同,用于Android 2.3 & 之后版本
    wrap_content 自适应大小,强制性地使视图扩展以便显示其全部内容(含 padding )
    android:layout_height="wrap_content"   //自适应大小  
    android:layout_height="match_parent"   //与父视图等高  
    android:layout_height="fill_parent"    //与父视图等高  
    android:layout_height="100dip"         //精确设置高度值为 100dip  
    
    • 构造函数
      构造函数 = View的入口,可用于初始化 & 获取自定义属性
    // View的构造函数有四种重载
        public DIY_View(Context context){
            super(context);
        }
    
        public DIY_View(Context context,AttributeSet attrs){
            super(context, attrs);
        }
    
        public DIY_View(Context context,AttributeSet attrs,int defStyleAttr ){
            super(context, attrs,defStyleAttr);
    
    // 第三个参数:默认Style
    // 默认Style:指在当前Application或Activity所用的Theme中的默认Style
    // 且只有在明确调用的时候才会生效,
        }
        
        public DIY_View(Context context,AttributeSet attrs,int defStyleAttr ,int defStyleRes){
            super(context, attrs,defStyleAttr,defStyleRes);
        }
    
    // 最常用的是1和2
    }
    

    2.2 MeasureSpec

    2.2.1 简介

    示意图

    2.2.2 组成

    测量规格(MeasureSpec) = 测量模式(mode) + 测量大小(size)

    示意图

    其中,测量模式(Mode)的类型有3种:UNSPECIFIED、EXACTLY 和
    AT_MOST。具体如下:

    示意图

    2.2.3 具体使用

    • MeasureSpec 被封装在View类中的一个内部类里:MeasureSpec
    • MeasureSpec类 用1个变量封装了2个数据(size,mode)通过使用二进制,将测量模式(mode) & 测量大小(size)打包成一个int值来,并提供了打包 & 解包的方法

    该措施的目的 = 减少对象内存分配

    • 实际使用
    /**
      * MeasureSpec类的具体使用
      **/
    
        // 1. 获取测量模式(Mode)
        int specMode = MeasureSpec.getMode(measureSpec)
    
        // 2. 获取测量大小(Size)
        int specSize = MeasureSpec.getSize(measureSpec)
    
        // 3. 通过Mode 和 Size 生成新的SpecMode
        int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
    
    • 源码分析
    /**
      * MeasureSpec类的源码分析
      **/
        public class MeasureSpec {
    
            // 进位大小 = 2的30次方
            // int的大小为32位,所以进位30位 = 使用int的32和31位做标志位
            private static final int MODE_SHIFT = 30;  
              
            // 运算遮罩:0x3为16进制,10进制为3,二进制为11
            // 3向左进位30 = 11 00000000000(11后跟30个0)  
            // 作用:用1标注需要的值,0标注不要的值。因1与任何数做与运算都得任何数、0与任何数做与运算都得0
            private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
      
            // UNSPECIFIED的模式设置:0向左进位30 = 00后跟30个0,即00 00000000000
            // 通过高2位
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
            
            // EXACTLY的模式设置:1向左进位30 = 01后跟30个0 ,即01 00000000000
            public static final int EXACTLY = 1 << MODE_SHIFT;  
    
            // AT_MOST的模式设置:2向左进位30 = 10后跟30个0,即10 00000000000
            public static final int AT_MOST = 2 << MODE_SHIFT;  
      
            /**
              * makeMeasureSpec()方法
              * 作用:根据提供的size和mode得到一个详细的测量结果吗,即measureSpec
              **/ 
                public static int makeMeasureSpec(int size, int mode) {  
                
                    return size + mode;  
                // measureSpec = size + mode;此为二进制的加法 而不是十进制
                // 设计目的:使用一个32位的二进制数,其中:32和31位代表测量模式(mode)、后30位代表测量大小(size)
                // 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100  
    
                }  
          
            /**
              * getMode()方法
              * 作用:通过measureSpec获得测量模式(mode)
              **/    
    
                public static int getMode(int measureSpec) {  
                 
                    return (measureSpec & MODE_MASK);  
                    // 即:测量模式(mode) = measureSpec & MODE_MASK;  
                    // MODE_MASK = 运算遮罩 = 11 00000000000(11后跟30个0)
                    //原理:保留measureSpec的高2位(即测量模式)、使用0替换后30位
                    // 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值
    
                }  
            /**
              * getSize方法
              * 作用:通过measureSpec获得测量大小size
              **/       
                public static int getSize(int measureSpec) {  
                 
                    return (measureSpec & ~MODE_MASK);  
                    // size = measureSpec & ~MODE_MASK;  
                   // 原理类似上面,即 将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size  
                } 
    
        }  
    
    

    2.2.6 MeasureSpec值的计算

    • 上面讲了那么久MeasureSpec,那么MeasureSpec值到底是如何计算得来?
    • 结论:子View的MeasureSpec值根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具体计算逻辑封装在getChildMeasureSpec()里。如下图:

    对于普通View

    即:子view的大小由父viewMeasureSpec值 和 子viewLayoutParams属性 共同决定

    • 下面,我们来看getChildMeasureSpec()的源码分析:
    /**
      * 源码分析:getChildMeasureSpec()
      * 作用:根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
      * 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams属性 共同决定
      **/
    
        public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    
             //参数说明
             * @param spec 父view的详细测量值(MeasureSpec) 
             * @param padding view当前尺寸的的内边距和外边距(padding,margin) 
             * @param childDimension 子视图的布局参数(宽/高)
    
                //父view的测量模式
                int specMode = MeasureSpec.getMode(spec);     
    
                //父view的大小
                int specSize = MeasureSpec.getSize(spec);     
              
                //通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)   
                int size = Math.max(0, specSize - padding);  
              
                //子view想要的实际大小和模式(需要计算)  
                int resultSize = 0;  
                int resultMode = 0;  
              
                //通过父view的MeasureSpec和子view的LayoutParams确定子view的大小  
    
    
                // 当父view的模式为EXACITY时,父view强加给子view确切的值
               //一般是父view设置为match_parent或者固定值的ViewGroup 
                switch (specMode) {  
                case MeasureSpec.EXACTLY:  
                    // 当子view的LayoutParams>0,即有确切的值  
                    if (childDimension >= 0) {  
                        //子view大小为子自身所赋的值,模式大小为EXACTLY  
                        resultSize = childDimension;  
                        resultMode = MeasureSpec.EXACTLY;  
    
                    // 当子view的LayoutParams为MATCH_PARENT时(-1)  
                    } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                        //子view大小为父view大小,模式为EXACTLY  
                        resultSize = size;  
                        resultMode = MeasureSpec.EXACTLY;  
    
                    // 当子view的LayoutParams为WRAP_CONTENT时(-2)      
                    } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                        //子view决定自己的大小,但最大不能超过父view,模式为AT_MOST  
                        resultSize = size;  
                        resultMode = MeasureSpec.AT_MOST;  
                    }  
                    break;  
              
                // 当父view的模式为AT_MOST时,父view强加给子view一个最大的值。(一般是父view设置为wrap_content)  
                case MeasureSpec.AT_MOST:  
                    // 道理同上  
                    if (childDimension >= 0) {  
                        resultSize = childDimension;  
                        resultMode = MeasureSpec.EXACTLY;  
                    } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                        resultSize = size;  
                        resultMode = MeasureSpec.AT_MOST;  
                    } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                        resultSize = size;  
                        resultMode = MeasureSpec.AT_MOST;  
                    }  
                    break;  
              
                // 当父view的模式为UNSPECIFIED时,父容器不对view有任何限制,要多大给多大
                // 多见于ListView、GridView  
                case MeasureSpec.UNSPECIFIED:  
                    if (childDimension >= 0) {  
                        // 子view大小为子自身所赋的值  
                        resultSize = childDimension;  
                        resultMode = MeasureSpec.EXACTLY;  
                    } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                        // 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0  
                        resultSize = 0;  
                        resultMode = MeasureSpec.UNSPECIFIED;  
                    } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                        // 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0  
                        resultSize = 0;  
                        resultMode = MeasureSpec.UNSPECIFIED;  
                    }  
                    break;  
                }  
                return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
            }  
    
    
    • 关于getChildMeasureSpec()里对子View的测量模式 & 大小的判断逻辑有点复杂;
    • 别担心,我已帮大家总结好。具体请看下表:

    示意图

    其中的规律总结:(以子View为标准,横向观察)
    示意图

    由于UNSPECIFIED模式适用于系统内部多次measure情况,很少用到,故此处不讨论


    • 区别于顶级View(即DecorView)的测量规格MeasureSpec计算逻辑:取决于 自身布局参数 & 窗口尺寸

    对于顶级View

    2.3 最基本的知识储备

    具体请看文章:自定义View基础 - 最易懂的自定义View原理系列


    3. measure过程详解

    • measure过程 根据View的类型分为2种情况:

    示意图

    • 接下来,我将详细分析这两种measure过程

    3.1 单一View的measure过程

    • 应用场景
      在无现成的控件View满足需求、需自己实现时,则使用自定义单一View
    1. 如:制作一个支持加载网络图片的ImageView控件
    2. 注:自定义View在多数情况下都有替代方案:图片 / 组合动画,但二者可能会导致内存耗费过大,从而引起内存溢出等问题。
    • 具体使用
      继承自ViewSurfaceView 或 其他View;不包含子View

    • 具体流程

    单一View的measure过程

    下面我将一个个方法进行详细分析:入口 = measure()

    
    /**
      * 源码分析:measure()
      * 定义:Measure过程的入口;属于View.java类 & final类型,即子类不能重写此方法
      * 作用:基本测量逻辑的判断
      **/ 
    
    	public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    
    		// 参数说明:View的宽 / 高测量规格
    
    	    ...
    
    	    int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
    	            mMeasureCache.indexOfKey(key);
    
    	    if (cacheIndex < 0 || sIgnoreMeasureCache) {
    	        
    	        onMeasure(widthMeasureSpec, heightMeasureSpec);
    	        // 计算视图大小 ->>分析1
    
    	    } else {
    	        ...
    	  
    	}
    
    /**
      * 分析1:onMeasure()
      * 作用:a. 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
      *      b. 存储测量后的View宽 / 高:setMeasuredDimension()
      **/ 
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    	// 参数说明:View的宽 / 高测量规格
    
    	setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
    	                     getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    	// setMeasuredDimension() :获得View宽/高的测量值 ->>分析2
    	// 传入的参数通过getDefaultSize()获得 ->>分析3
    }
    
    /**
      * 分析2:setMeasuredDimension()
      * 作用:存储测量后的View宽 / 高
      * 注:该方法即为我们重写onMeasure()所要实现的最终目的
      **/
    	protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
    
    	//参数说明:测量后子View的宽 / 高值
    
    		// 将测量后子View的宽 / 高值进行传递
    		    mMeasuredWidth = measuredWidth;  
    		    mMeasuredHeight = measuredHeight;  
    		  
    		    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;  
    		} 
    	// 由于setMeasuredDimension()的参数是从getDefaultSize()获得的
    	// 下面我们继续看getDefaultSize()的介绍
    
    /**
      * 分析3:getDefaultSize()
      * 作用:根据View宽/高的测量规格计算View的宽/高值
      **/
      public static int getDefaultSize(int size, int measureSpec) {  
    
    		// 参数说明:
    		// size:提供的默认大小
    		// measureSpec:宽/高的测量规格(含模式 & 测量大小)
    
    		    // 设置默认大小
    		    int result = size; 
    		    
    		    // 获取宽/高测量规格的模式 & 测量大小
    		    int specMode = MeasureSpec.getMode(measureSpec);  
    		    int specSize = MeasureSpec.getSize(measureSpec);  
    		  
    		    switch (specMode) {  
    		        // 模式为UNSPECIFIED时,使用提供的默认大小 = 参数Size
    		        case MeasureSpec.UNSPECIFIED:  
    		            result = size;  
    		            break;  
    
    		        // 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值 = measureSpec中的Size
    		        case MeasureSpec.AT_MOST:  
    		        case MeasureSpec.EXACTLY:  
    		            result = specSize;  
    		            break;  
    		    }  
    
    		 // 返回View的宽/高值
    		    return result;  
    		}    
    
    • 上面提到,当模式是UNSPECIFIED时,使用的是提供的默认大小(即第一个参数size);那么,提供的默认大小具体是多少呢?
    • 答:在onMeasure()方法中,getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)中传入的默认大小是getSuggestedMinimumWidth()

    接下来我们继续看getSuggestedMinimumWidth()的源码分析

    由于getSuggestedMinimumHeight()类似,所以此处仅分析getSuggestedMinimumWidth()

    • 源码分析如下:
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
    }
    
    //getSuggestedMinimumHeight()同理
    
    

    从代码可以看出:

    • View 无设置背景,那么View的宽度 = mMinWidth
    1. ·mMinWidth· = android:minWidth属性所指定的值;
    2. android:minWidth没指定,则默认为0
    • View设置了背景,View的宽度为mMinWidthmBackground.getMinimumWidth()中的最大值

    那么,mBackground.getMinimumWidth()的大小具体指多少?继续看getMinimumWidth()的源码分析:

    public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        //返回背景图Drawable的原始宽度
        return intrinsicWidth > 0 ? intrinsicWidth :0 ;
    }
    
    // 由源码可知:mBackground.getMinimumWidth()的大小 = 背景图Drawable的原始宽度
    // 若无原始宽度,则为0;
    // 注:BitmapDrawable有原始宽度,而ShapeDrawable没有
    

    总结:getDefaultSize()计算View的宽/高值的逻辑
    示意图

    至此,单一View的宽/高值已经测量完成,即对于单一View的measure过程已经完成。

    总结

    对于单一View的measure过程,如下:

    示意图

    实际作用的方法:getDefaultSize() = 计算View的宽/高值、setMeasuredDimension() = 存储测量后的View宽 / 高


    3.2 ViewGroup的measure过程

    • 应用场景
      利用现有的组件根据特定的布局方式来组成新的组件

    • 具体使用
      继承自ViewGroup 或 各种Layout;含有子 View

    如:底部导航条中的条目,一般都是上图标(ImageView)、下文字(TextView),那么这两个就可以用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片,使用起来会更加方便。
    Paste_Image.png

    • 原理
      1. 遍历 测量所有子View的尺寸
      2. 合并将所有子View的尺寸进行,最终得到ViewGroup父视图的测量值

      自上而下、一层层地传递下去,直到完成整个View树的measure()过程

    示意图

    • 流程

    示意图

    下面我将一个个方法进行详细分析:入口 = measure()

    若需进行自定义ViewGroup,则需重写onMeasure(),下文会提到

    /**
      * 源码分析:measure()
      * 作用:基本测量逻辑的判断;调用onMeasure()
      * 注:与单一View measure过程中讲的measure()一致
      **/ 
      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
    
            // 调用onMeasure()计算视图大小
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            ...
    }
    
    /**
      * 分析1:onMeasure()
      * 作用:遍历子View & 测量
      * 注:ViewGroup = 一个抽象类 = 无重写View的onMeasure(),需自身复写
      **/ 
    

    为什么ViewGroupmeasure过程不像单一Viewmeasure过程那样对onMeasure()做统一的实现?(如下代码)

    /**
      * 分析:子View的onMeasure()
      * 作用:a. 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
      *      b. 存储测量后的View宽 / 高:setMeasuredDimension()
      * 注:与单一View measure过程中讲的onMeasure()一致
      **/ 
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        // 参数说明:View的宽 / 高测量规格
    
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                             getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
        // setMeasuredDimension() :获得View宽/高的测量值 
        // 传入的参数通过getDefaultSize()获得
    }
    
    • 答:因为不同的ViewGroup子类(LinearLayoutRelativeLayout / 自定义ViewGroup子类等)具备不同的布局特性,这导致他们子View的测量方法各有不同

    onMeasure()的作用 = 测量View的宽/高值

    因此,ViewGroup无法对onMeasure()作统一实现。这个也是单一View的measure过程与ViewGroup过程最大的不同。

    1. 即 单一View measure过程的onMeasure()具有统一实现,而ViewGroup则没有
    2. 注:其实,在单一View measure过程中,getDefaultSize()只是简单的测量了宽高值,在实际使用时有时需更精细的测量。所以有时候也需重写onMeasure()

    在自定义ViewGroup中,关键在于:根据需求复写onMeasure()从而实现你的子View测量逻辑。复写onMeasure()的套路如下:

    /**
      * 根据自身的测量逻辑复写onMeasure(),分为3步
      * 1. 遍历所有子View & 测量:measureChildren()
      * 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值(自身实现)
      * 3. 存储测量后View宽/高的值:调用setMeasuredDimension()  
      **/ 
    
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    
            // 定义存放测量后的View宽/高的变量
            int widthMeasure ;
            int heightMeasure ;
    
            // 1. 遍历所有子View & 测量(measureChildren())
            // ->> 分析1
            measureChildren(widthMeasureSpec, heightMeasureSpec);
    
            // 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值
             void measureCarson{
                 ... // 自身实现
             }
    
            // 3. 存储测量后View宽/高的值:调用setMeasuredDimension()  
            // 类似单一View的过程,此处不作过多描述
            setMeasuredDimension(widthMeasure,  heightMeasure);  
      }
      // 从上可看出:
      // 复写onMeasure()有三步,其中2步直接调用系统方法
      // 需自身实现的功能实际仅为步骤2:合并所有子View的尺寸大小
    
    /**
      * 分析1:measureChildren()
      * 作用:遍历子View & 调用measureChild()进行下一步测量
      **/ 
    
    	protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    		// 参数说明:父视图的测量规格(MeasureSpec)
    
    		        final int size = mChildrenCount;
    		        final View[] children = mChildren;
    
    		        // 遍历所有子view
    		        for (int i = 0; i < size; ++i) {
    		            final View child = children[i];
    		             // 调用measureChild()进行下一步的测量 ->>分析1
    		            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
    		                measureChild(child, widthMeasureSpec, heightMeasureSpec);
    		            }
    		        }
    		    }
    
    /**
      * 分析2:measureChild()
      * 作用:a. 计算单个子View的MeasureSpec
      *      b. 测量每个子View最后的宽 / 高:调用子View的measure()
      **/ 
      protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
    
            // 1. 获取子视图的布局参数
            final LayoutParams lp = child.getLayoutParams();
    
            // 2. 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
            // getChildMeasureSpec() 请看上面第2节储备知识处
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec
                    mPaddingLeft + mPaddingRight, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSpec
                    mPaddingTop + mPaddingBottom, lp.height);
    
            // 3. 将计算好的子View的MeasureSpec值传入measure(),进行最后的测量
            // 下面的流程即类似单一View的过程,此处不作过多描述
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
        // 回到调用原处
    
    

    至此,ViewGroupmeasure过程分析完毕


    • 总结
      ViewGroupmeasure过程如下:

    示意图

    • 为了让大家更好地理解ViewGroupmeasure过程(特别是复写onMeasure()),下面,我将用ViewGroup的子类LinearLayout来分析下ViewGroupmeasure过程

    3.3 ViewGroup的measure过程实例解析(LinearLayout)

    此处直接进入LinearLayout复写的onMeasure()代码分析:

    详细分析请看代码注释

    
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
          // 根据不同的布局属性进行不同的计算
          // 此处只选垂直方向的测量过程,即measureVertical()->>分析1
          if (mOrientation == VERTICAL) {
              measureVertical(widthMeasureSpec, heightMeasureSpec);
          } else {
              measureHorizontal(widthMeasureSpec, heightMeasureSpec);
          }
    
          
    }
    
      /**
        * 分析1:measureVertical()
        * 作用:测量LinearLayout垂直方向的测量尺寸
        **/ 
      void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
          
          /**
           *  其余测量逻辑
           **/
              // 获取垂直方向上的子View个数
              final int count = getVirtualChildCount();
    
              // 遍历子View获取其高度,并记录下子View中最高的高度数值
              for (int i = 0; i < count; ++i) {
                  final View child = getVirtualChildAt(i);
    
                  // 子View不可见,直接跳过该View的measure过程,getChildrenSkipCount()返回值恒为0
                  // 注:若view的可见属性设置为VIEW.INVISIBLE,还是会计算该view大小
                  if (child.getVisibility() == View.GONE) {
                     i += getChildrenSkipCount(child, i);
                     continue;
                  }
    
                  // 记录子View是否有weight属性设置,用于后面判断是否需要二次measure
                  totalWeight += lp.weight;
    
                  if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                      // 如果LinearLayout的specMode为EXACTLY且子View设置了weight属性,在这里会跳过子View的measure过程
                      // 同时标记skippedMeasure属性为true,后面会根据该属性决定是否进行第二次measure
                    // 若LinearLayout的子View设置了weight,会进行两次measure计算,比较耗时
                      // 这就是为什么LinearLayout的子View需要使用weight属性时候,最好替换成RelativeLayout布局
                    
                      final int totalLength = mTotalLength;
                      mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                      skippedMeasure = true;
                  } else {
                      int oldHeight = Integer.MIN_VALUE;
         /**
           *  步骤1:遍历所有子View & 测量:measureChildren()
           *  注:该方法内部,最终会调用measureChildren(),从而 遍历所有子View & 测量
           **/
                measureChildBeforeLayout(
    
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);
                       ...
                }
    
          /**
           *  步骤2:合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值(自身实现)
           **/        
                  final int childHeight = child.getMeasuredHeight();
    
                  // 1. mTotalLength用于存储LinearLayout在竖直方向的高度
                  final int totalLength = mTotalLength;
    
                  // 2. 每测量一个子View的高度, mTotalLength就会增加
                  mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                         lp.bottomMargin + getNextLocationOffset(child));
          
                  // 3. 记录LinearLayout占用的总高度
                  // 即除了子View的高度,还有本身的padding属性值
                  mTotalLength += mPaddingTop + mPaddingBottom;
                  int heightSize = mTotalLength;
    
          /**
           *  步骤3:存储测量后View宽/高的值:调用setMeasuredDimension()  
           **/ 
           setMeasureDimension(resolveSizeAndState(maxWidth,width))
    
        ...
    
      }
    

    至此,自定义View的中最重要、最复杂的measure过程讲解完毕。


    4. 总结

    请帮顶 / 评论点赞!因为你们的赞同/鼓励是我写作的最大动力!

    展开全文
  • 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了...现在前半部分的承诺已经如约兑现了,那么今天我就要来兑现后面部分的承诺,讲一讲自定义View的实现方法,同时这也是带
  • 自定义view是一个综合的技术体系,本说明文档不去分析一个个具体的自定义view的实现,因为自定义view五花八门,是不可能全部分析一遍的。虽然我们不能把自定义view都分析一遍,但是我们能够提取出一种思想,而面对...
  • 自定义android进度条,带有进度指示,项目详情:http://blog.csdn.net/xiaanming/article/details/10298163
  • 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全、思路不清晰、无源码分析、简单问题复杂化 等 今天,我将全面总结自定义View原理中的Layout过程,我能保证这是市面上的最全面、最清晰、最易懂的 ...
  • 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全、思路不清晰、无源码分析、简单问题复杂化 等 今天,我将全面总结自定义View原理中的Draw过程,我能保证这是市面上的最全面、最清晰、最易懂的 ...
  • 我有现成的布局xml文件,现在想定义一个组合的自定义view,怎样把这个view的布局指定为一个xml文件
  • MainActivity如下: package cc.testcompounddrawables; import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.TextView;...
  • 自定义view入门

    千次阅读 2016-01-16 21:21:02
    如何自定义控件主要分为以下几个步骤: 1、自定义属性的声明与获取 (1)分析需要的自定义属性 ...(4)在view的构造方法中进行获取 2、测量onMewsure 3、布局onLayout(viewgroup) 4、绘制onDraw 5、onTouchEven
  • 自定义View入门

    千次阅读 2017-07-10 14:31:56
    自定义View类增加属性(两种方式) 绘制控件(导入布局) 响应用户事件 定义回调函数(根据自己需求来选择) 二、哪些方法需要被重写 onDraw() view中onDraw()是个空函数,也就是说具体的视图
  • Android 自定义View

    千次阅读 2014-08-20 23:36:00
    Android 自定义View
  • 觉得博文有用,请点赞,请评论,请关注,谢谢!~ 老规矩,先上效果图,看个效果,如果符合你的项目或者确定你要了解的内容,再往下看吧: MainActivity.java: ... import android.support.v7.app.AppCompatActivity;...
  • Android自定义View

    千次阅读 2012-10-09 16:21:33
    Android自定义View需要继承View,重写构造函数、onDraw,(onMeasure)等函数。 如果自定义的View需要有自定义的属性,需要在values下建立attrs.xml。在其中定义你的属性。 在使用到自定义View的xml布局文件中需要...
  • Android自定义View研究--View中的原点坐标相关问题 我们自定义了View,但是有没想过一个问题,就是View中的(0,0)坐标,也就是原点坐标在哪??我们是不是有时候很困惑,接下来我们就来研究View中的原点坐标相关的...
  • 自定义View

    千次阅读 多人点赞 2018-04-09 19:45:42
    自定义View基础篇安卓自定义View基础 - 坐标系安卓自定义View基础 - 角度弧度安卓自定义View基础 - 颜色进阶篇安卓自定义View进阶 - 分类和流程安卓自定义View进阶 - 绘制基本图形安卓自定义View进阶 - 画布操作安卓...
  • Android自定义View 自定义组合控件

    千次阅读 2014-11-13 23:09:28
    自定义组合控件: 以三国杀游戏武将为例,包括武将头像,血条,装备区 1.先定义该组合的XML文件布局 1 "1.0" encoding="utf-8"?> 2 "http://schemas.android.com/apk/res/android" 3 android:layout_width...
  • 自定义View 为什么要自定义View? 主要是Andorid系统内置的View 无法实现我们的 需求,我们需要针对我们的业务需求定制我们想要的 View.自定义View 我们大部分时候只需重写两个函数: onMeasure(),onDraw(). ...
  • 会默认给一个小边框,这让用户看起来有点丑,如果我们想把它做成一个比较好看的控件,这时我们可以做一个自定义控件MyCheckbox继承于View,然后可以在这个自定义View里面利用画图来绘制我们所需要的东西,...
  • 自定义View(一)自定义View的分类

    千次阅读 2016-12-21 16:27:42
    算起来,接触自定义View大半年了,可惜一直没能系统的总结一遍,好记性不如烂笔头,趁最近闲暇,我也得空坐下来好好归纳一下相关知识。  还是那句话,小女不才,入行时间短,火候尚浅,有说的不准确的地方,还请...
  • 本文部分内容参考自掘金网:点击打开链接坐标图解:概述Android已经为我们提供了大量的View供我们使用,但是可能有时候这些组件不能满足我们的需求,这时候就需要自定义控件了。自定义控件对于初学者总是感觉是一种...

空空如也

1 2 3 4 5 ... 20
收藏数 566,724
精华内容 226,689
关键字:

自定义view