精华内容
下载资源
问答
  • 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中添加一点噪点,然后改写为验证码,是不是感觉很不错。


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


    源码点击此处下载






    展开全文
  • 自定义View

    2016-06-21 17:52:27
    * 自定义View的分类 * 自定义View的注意事项 * 自定义View的实现 * 自定义View使其支持wrap_content和padding * 自定义属性的实现过程
  • 自定义view

    2016-11-30 18:28:01
    一些自定义的view,用来配合自己的博客学习自定义view,博客:一步一步学习自定义View(一)和一步一步学习自定义View(二)
  • 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是Android开发中非常常用的知识 可是,在使用过程中,有些开发者会发现:为什么自定义View 中设置的wrap_content属性不起作用(与match_parent相同作用)? 今天,我将全面分析上述问题并给出解决方案。 ...

    前言

    • 自定义View是Android开发中非常常用的知识
    • 可是,在使用过程中,有些开发者会发现:为什么自定义View 中设置的wrap_content属性不起作用(与match_parent相同作用)?
    • 今天,我将全面分析上述问题并给出解决方案。

    Carson带你学Android自定义View文章系列:
    Carson带你学Android:自定义View基础
    Carson带你学Android:一文梳理自定义View工作流程
    Carson带你学Android:自定义View Measure过程
    Carson带你学Android:自定义View Layout过程
    Carson带你学Android:自定义View Draw过程
    Carson带你学Android:手把手教你写一个完整的自定义View
    Carson带你学Android:Canvas类全面解析
    Carson带你学Android:Path类全面解析


    目录

    目录


    1. 问题描述

    在使用自定义View时,View宽 / 高的wrap_content属性不起自身应有的作用,而且是起到与match_parent相同作用。

    wrap_contentmatch_parent区别:

    1. wrap_content:视图的宽/高被设定成刚好适应视图内容的最小尺寸
    2. match_parent:视图的宽/高被设置为充满整个父布局
      (在Android API 8之前叫作fill_parent)

    其实这里有两个问题:

    • 问题1:wrap_content属性不起自身应有的作用
    • 问题2:wrap_content起到与match_parent相同的作用

    2. 知识储备

    请分析 & 解决问题之前,请先看自定义View原理中(2)自定义View Measure过程 - 最易懂的自定义View原理系列


    3. 问题分析

    问题出现在View的宽 / 高设置,那我们直接来看自定义View绘制中第一步对View宽 / 高设置的过程:measure过程中的onMeasure()方法

    onMeasure()

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    //参数说明:View的宽 / 高测量规格
    
    //setMeasuredDimension()  用于获得View宽/高的测量值
    //这两个参数是通过getDefaultSize()获得的
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    }
    

    继续往下看getDefaultSize()

    getDefaultSize()

    • 作用:根据View宽/高的测量规格计算View的宽/高值
    • 源码分析如下:
    public static int getDefaultSize(int size, int measureSpec) {  
    
    //参数说明:
    // 第一个参数size:提供的默认大小
    // 第二个参数:宽/高的测量规格(含模式 & 测量大小)
    
        //设置默认大小
        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中的specSize
            case MeasureSpec.AT_MOST:  
            case MeasureSpec.EXACTLY:  
                result = specSize;  
                break;  
        }  
    
     //返回View的宽/高值
        return result;  
    }
    

    从上面发现:

    • getDefaultSize()的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。
    • 因为AT_MOST对应wrap_content;EXACTLY对应match_parent,所以,默认情况下,wrap_contentmatch_parent是具有相同的效果的。

    解决了问题2:wrap_content起到与match_parent相同的作用

    那么有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?

    • 由于在getDefaultSize()的默认实现中,当View被设置成wrap_contentmatch_parent时,View的大小都会被设置成子View MeasureSpec的specSize。
    • 所以,这个问题的关键在于子View MeasureSpec的specSize的值是多少

    我们知道,子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来,具体计算逻辑封装在getChildMeasureSpec()里。

    接下来,我们看生成子View MeasureSpec的方法: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的测量模式和大小请看下表:

    Paste_Image.png

    从上面可以看出,当子View的布局参数使用wrap_contentwrap_content时:

    • 子View的specMode模式:AT_MOST
    • 子View的specSize(宽 / 高):parenSize = 父容器当前剩余空间大小 = match_content

    4. 问题总结

    • onMeasure()中的getDefaultSize()的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。

    • 因为AT_MOST对应wrap_content;EXACTLY对应match_parent,所以,默认情况下,wrap_contentmatch_parent是具有相同的效果的。

    • 因为在计算子View MeasureSpec的getChildMeasureSpec()中,子View MeasureSpec在属性被设置为wrap_contentmatch_parent情况下,子View MeasureSpec的specSize被设置成parenSize = 父容器当前剩余空间大小

    所以:wrap_content起到了和match_parent相同的作用:等于父容器当前剩余空间大小


    5. 解决方案:

    当自定义View的布局参数设置成wrap_content时时,指定一个默认大小(宽 / 高)。

    具体是在复写onMeasure()里进行设置

    
     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
    
            // 获取宽-测量规则的模式和大小
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
            // 获取高-测量规则的模式和大小
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            // 设置wrap_content的默认宽 / 高值
            // 默认宽/高的设定并无固定依据,根据需要灵活设置
            // 类似TextView,ImageView等针对wrap_content均在onMeasure()对设置默认宽 / 高值有特殊处理,具体读者可以自行查看
            int mWidth = 400;
            int mHeight = 400;
    
          // 当布局参数设置为wrap_content时,设置默认值
            if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                setMeasuredDimension(mWidth, mHeight);
            // 宽 / 高任意一个布局参数为= wrap_content时,都设置默认值
            } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                setMeasuredDimension(mWidth, heightSize);
            } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                setMeasuredDimension(widthSize, mHeight);
            }
    

    这样,当你的自定义View的宽 / 高设置成wrap_content属性时就会生效了。

    特别注意

    网上流传着这么一个解决方案:

     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
    
            // 获取宽-测量规则的模式和大小
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
            // 获取高-测量规则的模式和大小
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            // 设置wrap_content的默认宽 / 高值
            // 默认宽/高的设定并无固定依据,根据需要灵活设置
            // 类似TextView,ImageView等针对wrap_content均在onMeasure()对设置默认宽 / 高值有特殊处理,具体读者可以自行查看
            int mWidth = 400;
            int mHeight = 400;
    
          // 当模式是AT_MOST(即wrap_content)时设置默认值
            if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(mWidth, mHeight);
            // 宽 / 高任意一个模式为AT_MOST(即wrap_content)时,都设置默认值
            } else if (widthMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(mWidth, heightSize);
            } else if (heightMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(widthSize, mHeight);
            }
    
    • 上述的解决方案是:通过判断测量模式是否ATMOST从而来判断View的参数是否是wrap_content
    • 可是,通过下表发现:View的AT_MOST模式对应的不只是wrap_content,也有可能是match_parent

    即当父View是AT_MOST、View的属性设置为match_parent

    Paste_Image.png

    • 如果还是按照上述的做法,当父View为AT_MOST、View为match_parent时,该View的match_parent的效果不就等于wrap_content 吗?

    **答:**是,当父View为AT_MOST、View为match_parent时,该View的match_parent的效果就等于wrap_content 。上述方法存在逻辑错误,但由于这种情况非常特殊的,所以导致最终的结果没有错误。具体分析请看下面例子:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:cust="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    <-- 父View设为wrap_content,即AT_MOST模式 -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            <scut.com.learncustomview.TestMeasureView
              <-- 子View设为match_parent -->
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </LinearLayout>
    </RelativeLayout>
    

    效果图

    从上面的效果可以看出,View大小 = 默认值

    我再将子View的属性改为wrap_content

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:cust="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    <-- 父View设为wrap_content,即AT_MOST模式 -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            <scut.com.learncustomview.TestMeasureView
              <-- 子View设为wrap_content -->
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </RelativeLayout>
    
    
    

    效果图

    从上面的效果可以看出,View大小还是等于默认值。

    同上述分析

    • 对于第一种情况:当父View为AT_MOST、View为match_parent时,该View的match_parent的效果就等于wrap_content,上面说了这种情况很特殊:父View的大小能刚好包裹子View,子View的大小充满父View的大小

    也就是说:父View的大小是看子View的,子View的大小又是看父View的。

    • 那么到底是谁看谁的大小呢?
      答:
    • 如果没设置默认值,就继续往上层VIew充满大小,即从父View的大小等于顶层View的大小(),那么子View的大小 = 父View的大小
    • 如果设置了默认值,就用默认值。

    相信看到这里你已经看懂了:

    • 其实上面说的解决方案(通过判断测量模式是否AT_MOST从而来判断View的参数是否是wrap_content)只是在逻辑上表示有些错误,但从最终结果上来说是没有错的
    • 因为当父View为AT_MOST、View为match_parent时,该View的match_parent的效果就等于wrap_content
    1. 如果没设置默认值,就继续往上层VIew充满大小,即从父View的大小等于顶层View的大小(),那么子View的大小 = 父View的大小
    1. 如果设置了默认值,就用默认值。

    为了更好的表示判断逻辑,我建议你们用本文提供的解决方案,即根据布局参数判断默认值的设置


    6. 总结


    欢迎关注Carson_Ho的CSDN博客 与 公众号!

    博客链接:https://carsonho.blog.csdn.net/


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

    展开全文
  • 自定义View系列教程05--自定义View示例分析

    万次阅读 多人点赞 2016-05-31 22:06:40
    之前结合源码分析完了自定义View的三个阶段:measure,layout,draw。 那么,自定义有哪几种常见的方式呢? 直接继承自View 在使用该方式实现自定义View时通常的核心操作都在onDraw( )当中进行。但是,请注意,...

    版权声明

    • 本文原创作者:谷哥的小弟
    • 作者博客地址:http://blog.csdn.net/lfdfhl

    自定义View概述

    之前结合源码分析完了自定义View的三个阶段:measure,layout,draw。
    那么,自定义有哪几种常见的方式呢?

    1. 直接继承自View
      在使用该方式实现自定义View时通常的核心操作都在onDraw( )当中进行。但是,请注意,在分析measure部分源码的时候,我们提到如果直接继承自View在onMeasure( )中要处理view大小为wrap_content的情况,否则这种情况下的大小和match_parent一样。除此以为,还需要注意对于padding的处理。

    2. 继承自系统已有的View
      比如常见的TextView,Button等等。如果采用该方式,我们只需要在系统控件的基础上做出一些调整和扩展即可,而且也不需要去自己支持wrap_content和padding。

    3. 直接继承自ViewGroup
      如果使用该方式实现自定义View,请注意两个问题
      第一点:
      在onMeasure( )实现wrap_content的支持。这点和直接继承自View是一样的。
      第二点:
      在onMeasure( )和onLayout中需要处理自身的padding以及子View的margin

    4. 继承自系统已有的ViewGroup
      比如LinearLayout,RelativeLayout等等。如果采用该方式,那么在3中提到的两个问题就不用再过多考虑了,简

    展开全文
  • 今天,我将手把手教你写一个自定义View,并理清自定义View所有应该的注意点 阅读本文前,请先阅读我写的一系列自定义View文章 自定义View基础 - 最易懂的自定义View原理系列(1) 自定义View Measure过程 - 最...
  • 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了...现在前半部分的承诺已经如约兑现了,那么今天我就要来兑现后面部分的承诺,讲一讲自定义View的实现方法,同时这也是带
  • 自定义View---三种事件的触发、自定义View属性
  • Android自定义View详解

    2021-01-05 13:40:57
    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章。先总结下自定义View的步骤: 1、自定义View的属性 2、...
  • 自定义View圆形菜单

    2018-09-11 13:01:30
    自定义View圆形菜单自定义View圆形菜单自定义View圆形菜单自定义View圆形菜单
  • 自定义View 为什么要自定义View? 主要是Andorid系统内置的View 无法实现我们的 需求,我们需要针对我们的业务需求定制我们想要的 View.自定义View 我们大部分时候只需重写两个函数: onMeasure(),onDraw(). ...
  • 自定义View时钟android

    2019-04-14 17:03:44
    自定义View设计的小时种,适合入门自定义View设计的新手,注释详细 自定义View设计的小时种,适合入门自定义View设计的新手,注释详细 自定义View设计的小时种,适合入门自定义View设计的新手,注释详细
  • Android中自定义View

    2016-03-20 13:27:31
    Android中自定义View操作Android中自定义View操作Android中自定义View操作
  • android 自定义view及自定义属性
  • 自定义View实例

    2017-12-20 10:34:29
    自定义View代码实例 其中包含几个实例,比如自定义TextView实现验证码功能,自定义进度条,自定义音量控制器
  • 自定义View使用自定义属性,是仿照极客学院上的案例做的
  • 自定义view是一个综合的技术体系,本说明文档不去分析一个个具体的自定义view的实现,因为自定义view五花八门,是不可能全部分析一遍的。虽然我们不能把自定义view都分析一遍,但是我们能够提取出一种思想,而面对...
  • 编写自己的自定义view 2. 加入逻辑线程 3. 提取和封装自定义view 4. 利用xml中定义样式来影响显示效果 一、编写自定义的view 1.在xml中使用自己的view <!-- 可以使用view的公共属性,例如背景 --&...
  • 自定义View+属性

    2016-03-02 15:31:47
    自定义view基础原理 + 怎样自定义view的属性
  • SeekBar 自定义View 进度条
  • 自定义View进阶一

    2016-05-28 15:18:40
    自定义view进阶一,最后先看自定义view基础篇。
  • Android开发之自定义View的功能实现详解。教你一步一步学会自定义View
  • android demo,自定义控件view,点击该自定义view,onclick随机生成数字
  • 我有现成的布局xml文件,现在想定义一个组合的自定义view,怎样把这个view的布局指定为一个xml文件
  • 睡眠监测自定义view

    2019-07-01 20:41:12
    睡眠监测自定义view,可以根据传过来的数据来显示睡眠数据.
  • 自定义View拆线图

    2017-02-06 17:37:52
    自定义View拆线图
  • 很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章。先总结下自定义View的步骤: 1、自定义View的属性 2、...
  • Carson带你学Android:自定义View的基础都在这里了!

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 590,947
精华内容 236,378
关键字:

自定义view