自定义View系列(三)View的绘制分析

来源:互联网 发布:stc15w201s数据手册 编辑:程序博客网 时间:2024/06/05 04:16

都知道View的绘制流程是onMeasure -> onLayout -> onDraw了。

这里主要是从源代码分析onMeasure的过程,解决自定义View wrap_content不起作用的问题。


一、onMeasure
      自定义View如果不重写onMeasure的话,layoutWidth/height 设置wrap_content就不会生效。
要说明这个问题的原因,首先有必要先说明一下MeasureSpec的3种模式UNSPECIFIED 、EXACTLY、AT_MOST。
  <span style="white-space:pre"></span>/**         * Measure specification mode: The parent has not imposed any constraint         * on the child. It can be whatever size it wants.         */        public static final int UNSPECIFIED = 0 << MODE_SHIFT;        /**         * Measure specification mode: The parent has determined an exact size         * for the child. The child is going to be given those bounds regardless         * of how big it wants to be.         */        public static final int EXACTLY     = 1 << MODE_SHIFT;        /**         * Measure specification mode: The child can be as large as it wants up         * to the specified size.         */        public static final int AT_MOST     = 2 << MODE_SHIFT;

具体值可以无视,反正就是用MeasureSpec的某几位来表示模式,用&运算来区分。
只要只要它们的对应情况就可以了,看注释:
UNSPECIFIED :   任意模式,可以是任意值。(貌似不会用到)
        
    EXACTLY:精确模式, 比如 layout_width = "10dp" 。
还有种情况  layout_width = "match_parent"
AT_MOST:  最大值模式,即 wrap_content

从下面源码中可以得到验证:默认情况是EXACTLY。WRAP_CONTENT对应AT_MOST,MATCH_PARENT对应EXACTLY。
private static int getRootMeasureSpec(int windowSize, int rootDimension) {      int measureSpec;      switch (rootDimension) {        case ViewGroup.LayoutParams.MATCH_PARENT:          // Window can't resize. Force root view to be windowSize.          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);          break;      case ViewGroup.LayoutParams.WRAP_CONTENT:          // Window can resize. Set max size for root view.          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);          break;      default:          // Window wants to be an exact size. Force root view to be that size.          measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);          break;      }      return measureSpec;  }  

之所以自定义View不实现onMeasure会导致wrap_content不生效,
是因为在View的默认onMeasure实现中,AT_MOST会被当成EXACTLY来处理。
        还是看源代码:
     
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }

public static int getDefaultSize(int size, int measureSpec) {        int result = size;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        switch (specMode) {        case MeasureSpec.UNSPECIFIED:            result = size;            break;        case MeasureSpec.AT_MOST:        case MeasureSpec.EXACTLY:            result = specSize;            break;        }        return result;    }
默认的测量方法中并没有对AT_MOST进行特殊处理,其处理和EXACTLY是一样的。
当xml中设置layout_width / layou_height为wrap_content时,是不起作用的。

对症下药,手动实现的办法就是对AT_MOST也做一套测量方案。
        可以套用下面的模板:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getMyDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getMyDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }    private int getMyDefaultSize(int size, int measureSpec) {        int result = 200;//可以自己设置        int specMode = View.MeasureSpec.getMode(measureSpec);        int specSize = View.MeasureSpec.getSize(measureSpec);        switch (specMode) {            case View.MeasureSpec.UNSPECIFIED:                result = size;                break;            case View.MeasureSpec.AT_MOST:                break;            case View.MeasureSpec.EXACTLY:                result = Math.min(result, specSize);                break;        }        return result;    }

  其中result = 200 可以根据你想给自己的View设置的长/宽来确定。设置一个大概的值就可以了。
当然也可以不用wrap_content直接设置dp值。

二、onLayout
这个基本可以不管的

三、onDraw
这个就真的很神奇了, 一般不用去重写。但是见过一些通过重写onDraw来实现很炫的效果的View的。
想玩转onDraw,有必要熟练掌握Canvas类。
一些动画的自定义View。通过反复调用onDraw来达到自己的动画效果。
比如这个项目的阴影和动画就是在onDraw中实现的,有兴趣的朋友可以看一下:
https://github.com/dolphinwang/ImageCoverFlow

先做为一块空缺吧。


最后提一下手动绘制View的方式。

第一种, invalidate,只能在UI 线程中调用。

**     * Invalidate the whole view. If the view is visible,     * {@link #onDraw(android.graphics.Canvas)} will be called at some point in     * the future.     * <p>     * This must be called from a UI thread. To call from a non-UI thread, call     * {@link #postInvalidate()}.     */    public void invalidate() {        invalidate(true);    }

第二种,postInvalidate,可以在非UI线程中调用。
其内部是通过Handler来切换到UI线程中进行View绘制的。
  /**     * <p>Cause an invalidate to happen on a subsequent cycle through the event loop.     * Use this to invalidate the View from a non-UI thread.</p>     *     * <p>This method can be invoked from outside of the UI thread     * only when this View is attached to a window.</p>     *     * @see #invalidate()     * @see #postInvalidateDelayed(long)     */    public void postInvalidate() {        postInvalidateDelayed(0);    }


0 0
原创粉丝点击