Android自定义View和Canvas绘图解析

Posted shineflowers

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义View和Canvas绘图解析相关的知识,希望对你有一定的参考价值。

自定义view的流程分为measure、 layout、draw三个主要步骤,今天我们通过源码来分下下measure的过程

我们从顶级view开始,顶级view即DecorView, view的事件都是先经过这个DecorView, 接下来我们来看看这个DecorView的MeasureSpec的创建过程: 


ViewRoot 对应 ViewRootImpl类,  是连接WindowManager 和 DecorView的纽带,   进入ViewRootImpl中,查看measureHierarchy方法,有如下代码:

final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidthmTmpValuetrue);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
    baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag"Window " mView ": baseSize=" + baseSize
        + ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != && desiredWindowWidth > baseSize) {
    childWidthMeasureSpec = getRootMeasureSpec(baseSizelp.width);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeightlp.height);
    performMeasure(childWidthMeasureSpecchildHeightMeasureSpec);

这里只是截选一部分的源码, 我们看到这个baseSize, 其实就是屏幕的尺寸大小, 获取宽的MeasureSpc的方法:    

childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);

这里传入的参数是屏幕尺寸以及DecorView自身的大小, 接着我们来看 getRootMeasureSpec方法:


private static int getRootMeasureSpec(int windowSizeint rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can‘t resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSizeMeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSizeMeasureSpec.AT_MOST);
        break;
    default:
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimensionMeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

就是这个方法确定了DecorView的MeasureSpec, 这里分了三种情况,

1.如果传入的view大小为math_parent,那么这个view的mode为EXACTLY, 大小为屏幕的尺寸.

2.如果传入的view大小为wrap_content,那么这个view的mode为AT_MOST,大小为屏幕的尺寸.

3.如果传入的view大小为一个具体的值,那么这个view的mode为EXACTLY,大小为view本身大小。


以上就是DecorView的MeaureSpec的整个创建的过程了。 


看了顶级view之后我们来看普通的view, 普通的view的measure过程是由viewgroup传递过来的,接着我们来看看viewgroup的measureChildWithMargins方法:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpecint widthUsed,
        int parentHeightMeasureSpecint heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsedlp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsedlp.height);

    child.measure(childWidthMeasureSpecchildHeightMeasureSpec);
}

这个方法获得了子view的MeasureSpec,并且将其传入子view的measure方法中, 这里重点来看下viewgroup是如何创建子view的MeasuerSpec的。来看getChildMeasureSpec方法内部的实现:

public static int getChildMeasureSpec(int specint paddingint childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can‘t be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            // Child wants a specific size... so be it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can‘t be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent asked to see how big we want to be
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSizeresultMode);
}

这个方法很长,但是我们只需要注意到AT_MOST跟EXACTLY这两种情况就行,稍微分析下这个过程:

首先要理解这个方法的三个参数, 第一个是父view的MeasureSpec, 第二个是父view已占用的大小,第三个是view的LayoutParams的大小,如果不理解可以看看ViewGroup的MeasureChildWithMargins方法中的调用:


final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                + widthUsedlp.width);

第二个参数很长 , mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + withUsed  ,这些所有的值都有一个共同特点,就是这些位置是不能摆放任何view的,即父view已经占用的地盘,现在是不是对参数更加理解了呢。 


接着我们回到getChildMeasureSpec方法中继续看看viewGroup到底是怎么创建view的MeasureSpec的。


第一步: 根据参数一,即传入的父view的MeasureSpec获得父view的Mode和Size。这里的第三行代码:

int size = Math.max(0specSize - padding);

这个size表示取0与父容器中可占用的位置的最大值,可以直接理解为父view的大小。 

第二步:根据父view的Mode分情况处理, 到这一步我们应该就清楚为什么说view的大小是由父view的MeasureSpec与本身LayoutParmas大小共同决定的吧。


这里我们依然只看AT_MOST跟EXACTLY两种情况, 

switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
    if (childDimension >= 0) {
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
    } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // Child wants to be our size. So be it.
        resultSize = size;
        resultMode = MeasureSpec.EXACTLY;
    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // Child wants to determine its own size. It can‘t be
        // bigger than us.
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
    }
    break;

// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
    if (childDimension >= 0) {
        // Child wants a specific size... so be it
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
    } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // Child wants to be our size, but our size is not fixed.
        // Constrain child to not be bigger than us.
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // Child wants to determine its own size. It can‘t be
        // bigger than us.
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
    }
    break;

这里有个比较难以理解的值就是childDimension > 0 , 这个其实就表示view的大小是一个具体的值比如100dp , 因为view的match_parent和 wrap_content在系统内部定义的都是负数,一个是-1. 一个是-2  ,所以判断childDimension > 0即,view的大小为一个具体的值。 

接着就比较好理解了, 我们来稍微总结下:

无论父view是match_parent还是 wrap_content ,只要view是一个具体的值,view的Mode永远都是EXACTLY, 大小均是view本身定义的大小。

父view模式如果是EXACTLY,  --->  子view如果是mathch_parent ,那么子view的大小是父view的大小,模式也跟父view一样为EXACTLY.  子view如果是wrap_content, 大小还是父view的大小, 模式为AT_MOST


父view模式如果是AT_MOST ,  --- >  子view如果是math_parent, 那么子view大小为父view大小, 模式与父view一样都是AT_MOST,  子view如果是wrap_content, 子view大小为父view大小, 模式为AT_MOST


上面说的有点绕,但其实我们只需要记住一点, 无论上面那种情况,子view在wrap_content下,大小都是父view的大小,  到这里我们是不是就能理解为什么在自定义view的过程中如果不重写onMeasure, wrap_content是和match_parent是一个效果了吧。


以上过程是viewGroup中创建子view的MeasureSpec的过程, 有了这个MeasureSpec,测量子view大小就很简单了,我们可以看到在ViewGroup获取到子view的MeasureSpec之后,传入到子view的measure方法中:


child.measure(childWidthMeasureSpecchildHeightMeasureSpec);

进入到view的measure方法,

不知不觉我们已经从viewgroup进入到了view的测量过程,


这里是不是突然意识到,ViewGroup根本没有测绘自己本身啊, 只是获取到子view的MeasureSpec然后传入子view的measure方法里去,这是因为ViewGroup是个抽象类,本身并没有定义测量的过程, ViewGroup的onMeasure需要各个子类去实现,比如LinearLayout 、 RelativeLayout等等,并且每个子类的测量过程都不一样,这个我们后面会讲, 现在我们还是接着看view的Measure过程。


上面说到viewgroup将创建的子view的MeasureSpec传入到了view的Measure方法中, 那么我们就来看看View的Measure方法:

public final void measure(int widthMeasureSpecint heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpecoptical ? -oHeight : oHeight);
    }

    // Suppress sign extension for the low bytes
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == nullmMeasureCache new LongSparseLongArray(2);

    final boolean forceLayout = (mPrivateFlags PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

    // Optimize layout by avoiding an extra EXACTLY pass when the view is
    // already measured as the correct size. In API 23 and below, this
    // extra pass is required to make LinearLayout re-distribute weight.
    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
            || heightMeasureSpec != mOldHeightMeasureSpec;
    final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
            && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
            && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

    if (forceLayout || needsLayout) {
        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();

        int cacheIndex = forceLayout ? -mMeasureCache.indexOfKey(key);
        if (cacheIndex < || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpecheightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32)(int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        // flag not set, setMeasuredDimension() was not invoked, we raise
        // an exception to warn the developer
        if ((mPrivateFlags PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("View with id " + getId() + ": "
                    + getClass().getName() + "#onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }

        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key((longmMeasuredWidth) << 32 |
            (longmMeasuredHeight 0xffffffffL)// suppress sign extension
}

这个方法真是又臭又长。。。讲道理的话其实我也看不懂, 但是我们只需要注意到一点, 就是这个方法调用了OnMeasure方法! 

也就是说measure --> OnMeasure 

OnMeasure就简单了 :


protected void onMeasure(int widthMeasureSpecint heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth()widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight()heightMeasureSpec));
}

很简洁对不,但是简洁并不代表简单, 这里套了好几层。。。 不要被迷惑 , 我们看最外层其实就是setMeasureDimension().

设置宽和高, 这个宽和高是在 getDefaultSize方法里返回的, 所以我们来看看getDefaultSize的具体代码:

public static int getDefaultSize(int sizeint 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;
}

如果我们忽略掉UNSPECIFIED情况的话, 我们会发现第一个参数size根本用不到。。。

也就是说view的大小其实就是父view给他创建的MeasureSpec中的size大小。 


这也进一步说明,view在wrap_content情况下 ,大小还是会跟父view大小一样, 所以我们需要在自定义view的时候重写OnMeasure。

//为了支持wrap_content, 一般的实现方法如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec , heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
    int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);

    if(widthSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST){
        setMeasuredDimension(mWidth, mHeight);
    }else if(widthSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(mWidth, heightSpceSize);
    }else if(heightSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(widthSpceSize, mHeight);
    }

}


Layout:

直接看view的layout源码:

public void layout(int lint tint rint b) {
    if ((mPrivateFlags3 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpecmOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(ltrb) : setFrame(ltrb);

    if (changed || (mPrivateFlags PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changedltrb);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0i < numListeners++i) {
                listenersCopy.get(i).onLayoutChange(thisltrboldLoldToldRoldB);
            }
        }
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

这里挑重点来看 

boolean changed = isLayoutModeOptical(mParent) ?
        setOpticalFrame(ltrb) : setFrame(ltrb);

private boolean setOpticalFrame(int leftint topint rightint bottom) {
    Insets parentInsets = mParent instanceof View ?
            ((View) mParent).getOpticalInsets() : Insets.NONE;
    Insets childInsets = getOpticalInsets();
    return setFrame(
            left   + parentInsets.left - childInsets.left,
            top    + parentInsets.top  - childInsets.top,
            right  + parentInsets.left + childInsets.right,
            bottom + parentInsets.top  + childInsets.bottom);
}

点进setOpticalFrame我们发现最终也是调用的setFrame方法, 所以我们直接来看这个方法:

protected boolean setFrame(int leftint topint rightint bottom) {
    boolean changed = false;

    if (DBG) {
        Log.d("View"this " View.setFrame(" + left + "," + top + ","
                + right + "," + bottom + ")");
    }

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        int drawn = mPrivateFlags PFLAG_DRAWN;

        int oldWidth = mRight mLeft;
        int oldHeight = mBottom mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position
        invalidate(sizeChanged);

        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeftmTopmRightmBottom);

        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        if (sizeChanged) {
            sizeChange(newWidthnewHeightoldWidtholdHeight);
        }

        if ((mViewFlags VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
            // If we are visible, force the DRAWN bit to on so that
            // this invalidate will go through (at least to our parent).
            // This is because someone may have invalidated this view
            // before this call to setFrame came in, thereby clearing
            // the DRAWN bit.
            mPrivateFlags |= PFLAG_DRAWN;
            invalidate(sizeChanged);
            // parent display list may need to be recreated based on a change in the bounds
            // of any child
            invalidateParentCaches();
        }

        // Reset drawn bit to original value (invalidate turns it off)
        mPrivateFlags |= drawn;

        mBackgroundSizeChanged true;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged true;
        }

        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}

看到这句:

if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
    changed = true;

在该方法中把l,t, r, b分别与之前的mLeft,mTop,mRight,mBottom一一作比较,假若其中任意一个值发生了变化,那么就判定该View的位置发生了变化 
if (changed || (mPrivateFlags PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    onLayout(changedltrb);

若发生变化则会调用onLayout方法。

接着我们来看View的onLayout方法:

/**
 * Called from layout when this view should
 * assign a size and position to each of its children.
 *
 * Derived classes with children should override
 * this method and call layout on each of
 * their children.
 * @param changed This is a new size or position for this view
 * @param left Left position, relative to parent
 * @param top Top position, relative to parent
 * @param right Right position, relative to parent
 * @param bottom Bottom position, relative to parent
 */
protected void onLayout(boolean changedint leftint topint rightint bottom) {
}

居然是空的!


查看注释我们发现View的onLayout是确定子view的位置的,所以我们直接来看viewGroup的onLayout方法:

@Override
protected abstract void onLayout(boolean changed,
        int lint tint rint b);

居然是个抽象方法!

到这里我们发现view和viewGroup都没有真正实现onLayout方法。

既然ViewGroup中的方法是抽象方法,那么子类就一定会重写这个方法, 我们来看LinearLayout:

@Override
protected void onLayout(boolean changedint lint tint rint b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(ltrb);
    } else {
        layoutHorizontal(ltrb);
    }
}

果然重写了,并且分为水平跟垂直的两种情况

随便挑一个来看看

void layoutVertical(int leftint topint rightint bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;
   
    // Where right end of child should go
    final int width = right - left;
    int childRight = width - mPaddingRight;
   
    // Space available for child
    int childSpace = width - paddingLeft - mPaddingRight;
   
    final int count = getVirtualChildCount();

    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

    switch (majorGravity) {
       case Gravity.BOTTOM:
           // mTotalLength contains the padding already
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;

           // mTotalLength contains the padding already
       case Gravity.CENTER_VERTICAL:
           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
           break;

       case Gravity.TOP:
       default:
           childTop = mPaddingTop;
           break;
    }

    for (int i = 0i < counti++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
           
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();
           
            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravitylayoutDirection);
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;

                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;

                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }

            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            setChildFrame(childchildLeftchildTop + getLocationOffset(child),
                    childWidthchildHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(childi);
        }
    }
}

这里简单的分析下layoutVertical的逻辑, 首先遍历所有子元素并调用setChildFrame方法为子元素指定对应的位置,其中childTop在不断的增大,这就意味着越后面的子元素位置就越靠下,刚好符合垂直linearLayout的特性。

Draw:

平时用的最多的就是canvas里的各种绘图api, 以及一些关于画布的操作

canvas.save和canvas.restore

网上有一种说法叫: save跟restore一般都是成对出现,但是restore不能比save多,否则会抛异常。 但是我在测试的时候发现restore即使比save多也没有出现异常。

rotate(float degrees) 

画布旋转, 值为正顺时针, 负逆时针。 

Canvas的图层概念:

for(int i=0; i < 5; i++) {
    canvas.drawCircle(50, 50, 50, mPaint);
    canvas.translate(100, 100);}

如图画布的坐标原点每次分别在x轴、y轴上移动100 , 那么假如我们要重新回到(0 ,0)点处绘制新的图形呢, 不会要translate( -100 ,-100) 慢慢的平移过去吧,

技术分享

我们在平移之前可以将当前的canvas状态进行保存, canvas为我们提供了图层的支持, 而这些图层是按栈结构来进行管理的, 当我们调用save()方法,会保存当前Canvas的状态后最为一个Layer(图层) , 添加到Canvas栈中, 另外这个Layer不是一个具体的类, 就是一个概念性的东西而已, 而当我们调用restore()方法的时候, 会恢复之前canvas的状态,而此时Canvas的图层栈会弹出栈顶那个layer


PorterDuffXfermode 

public Bitmap getRoundCornerBitmap(Bitmap bitmap, float pixels) {
 

//生成Canvas , 给canvas设置的Bitmap的大小是和原图的大小一致
int width=bitmap.getWidth();
int height=bitmap.getHeight();
Bitmap roundCornerBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(roundCornerBitmap);
 
//绘制圆角矩形
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
Rect rect = new Rect(0, 0, width, height);
RectF rectF = new RectF(rect);
canvas.drawRoundRect(rectF, pixels, pixels, paint);

//为paint设置PorterDuffXfermode 
PorterDuffXfermode xfermode=new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
paint.setXfermode(xfermode);

//绘制原图
canvas.drawBitmap(bitmap, rect, rect, paint);

return roundCornerBitmap;
}

Bitmap和Matrix 

除了刚才提到的给图片设置圆角之外,在开发中还常有其他涉及到图片的操作,比如图片的旋转,缩放,平移等等,这些操作可以结合Matrix来实现。 
在此举个例子,看看利用matrix实现图片的平移和缩放。

private void drawBitmapWithMatrix(Canvas canvas){

//画出原图
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.mm); i
nt width=bitmap.getWidth();
int height=bitmap.getHeight();
Matrix matrix = new Matrix();
canvas.drawBitmap(bitmap, matrix, paint);

//平移原图
matrix.setTranslate(width/2, height);
canvas.drawBitmap(bitmap, matrix, paint);

//缩放原图
matrix.postScale(0.5f, 0.5f);  
canvas.drawBitmap(bitmap, matrix, paint); }

利用Matrix对图形操作是跟坐标系无关的 ,操作的是每个像素点,比如平移缩放每个像素点

matrix.postScale(0.5f, 0.5f);
如上代码,表示对每个像素点缩放到原来的一半大小

在使用Matrix时经常用到一系列的set,pre,post方法


  • pre表示在队头插入一个方法
  • post表示在队尾插入一个方法
  • set表示清空队列 
    队列中只保留该set方法,其余的方法都会清除。

下面请看几个小示例:

1.
Matrix m = new Matrix();
m.setRotate(45);
m.setTranslate(80, 80);

只有m.setTranslate(80, 80)有效,因为m.setRotate(45)被清除.

2.
Matrix m = new Matrix();
m.setTranslate(80, 80);
m.postRotate(45);

先执行m.setTranslate(80, 80)后执行m.postRotate(45)

3.
Matrix m = new Matrix();
m.setTranslate(80, 80);
m.preRotate(45);

先执行m.preRotate(45)后执行m.setTranslate(80, 80)

4.

Matrix m = new Matrix();
m.preScale(2f,2f);
m.preTranslate(50f, 20f);
m.postScale(0.2f, 0.5f);
m.postTranslate(20f, 20f);
执行顺序: 
m.preTranslate(50f, 20f)–>m.preScale(2f,2f)–>m.postScale(0.2f, 0.5f)–>m.postTranslate(20f, 20f)

5.

Matrix m = new Matrix();
m.postTranslate(20, 20);
m.preScale(0.2f, 0.5f);
m.setScale(0.8f, 0.8f);
m.postScale(3f, 3f);
m.preTranslate(0.5f, 0.5f);

执行顺序: 
m.preTranslate(0.5f, 0.5f)–>m.setScale(0.8f, 0.8f)–>m.postScale(3f, 3f)

Demo1:

/**
* Created by Administrator on 2016/12/17.
*
* 仿支付宝芝麻信用圆形仪表盘
*/

public class ERoundIndicatorView extends View {

    private Paint paint;
    private Paint paint_2;
    private Paint paint_3;
    private Paint paint_4;
    private Context context;
    private int maxNum;
    private int startAngle;
    private int sweepAngle;
    private int radius;
    private int mWidth;
    private int mHeight;
    private int sweepInWidth;//内圆的宽度
    private int sweepOutWidth;//外圆的宽度
    private int currentNum=0;//需设置setter、getter 供属性动画使用
    private String[] text ={"较差","中等","良好","优秀","极好"};
    private int[] indicatorColor = {0xffffffff,0x00ffffff,0x99ffffff,0xffffffff};

    public int getCurrentNum() {
        return currentNum;
    }

    public void setCurrentNum(int currentNum) {
        this.currentNum = currentNum;
        invalidate();
    }

    public ERoundIndicatorView(Context context) {
        this(context,null);
    }

    public ERoundIndicatorView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ERoundIndicatorView(final Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        setBackgroundColor(0xFFFF6347);
        initAttr(attrs);
        initPaint();
    }

    public void setCurrentNumAnim(int num) {
        float duration = (float)Math.abs(num-currentNum)/maxNum *1500+500; //根据进度差计算动画时间
        ObjectAnimator anim = ObjectAnimator.ofInt(this,"currentNum",num);
        anim.setDuration((long) Math.min(duration,2000));
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                int color = calculateColor(value);
                setBackgroundColor(color);
            }
        });
        anim.start();
    }
    private int calculateColor(int value){
        ArgbEvaluator evealuator = new ArgbEvaluator();
        float fraction = 0;
        int color = 0;
        if(value <= maxNum/2){
            fraction = (float)value/(maxNum/2);
            color = (int) evealuator.evaluate(fraction,0xFFFF6347,0xFFFF8C00); //由红到橙
        }else {
            fraction = ( (float)value-maxNum/2 ) / (maxNum/2);
            color = (int) evealuator.evaluate(fraction,0xFFFF8C00,0xFF00CED1); //由橙到蓝
        }
        return color;
    }

    private void initPaint() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setDither(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(0xffffffff);
        paint_2 = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint_3 = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint_4 = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    private void initAttr(AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.RoundIndicatorView);
        maxNum = array.getInt(R.styleable.RoundIndicatorView_maxNum,500);
        startAngle = array.getInt(R.styleable.RoundIndicatorView_startAngle,160);
        sweepAngle = array.getInt(R.styleable.RoundIndicatorView_sweepAngle,220);
        //内外圆的宽度
        sweepInWidth = dp2px(8);
        sweepOutWidth = dp2px(3);
        array.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);

        if (wMode == MeasureSpec.EXACTLY ){
            mWidth = wSize;
        }else {
            mWidth =dp2px(300);
        }
        if (hMode == MeasureSpec.EXACTLY ){
            mHeight= hSize;
        }else {
            mHeight =dp2px(400);
        }
        setMeasuredDimension(mWidth,mHeight);
    }

   
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        radius = getMeasuredWidth()/4; //不要在构造方法里初始化,那时还没测量宽高
        canvas.save();
        canvas.translate(mWidth/2,(mWidth)/2);
        drawRound(canvas);  //画内外圆
        drawScale(canvas);//画刻度
        drawIndicator(canvas); //画当前进度值
        drawCenterText(canvas);//画中间的文字
        canvas.restore();
    }

    private void drawCenterText(Canvas canvas) {
        canvas.save();
        paint_4.setStyle(Paint.Style.FILL);
        paint_4.setTextSize(radius/2);
        paint_4.setColor(0xffffffff);
        canvas.drawText(currentNum+"",-paint_4.measureText(currentNum+"")/2,0,paint_4);
        paint_4.setTextSize(radius/4);
        String content = "信用";
        if(currentNum < maxNum*1/5){
            content += text[0];
        }else if(currentNum >= maxNum*1/5 && currentNum < maxNum*2/5){
            content += text[1];
        }else if(currentNum >= maxNum*2/5 && currentNum < maxNum*3/5){
            content += text[2];
        }else if(currentNum >= maxNum*3/5 && currentNum < maxNum*4/5){
            content += text[3];
        }else if(currentNum >= maxNum*4/5){
            content += text[4];
        }
        Rect r = new Rect();
        paint_4.getTextBounds(content,0,content.length(),r);
        canvas.drawText(content,-r.width()/2,r.height()+20,paint_4);
        canvas.restore();
    }

    private void drawIndicator(Canvas canvas) {
        canvas.save();
        paint_2.setStyle(Paint.Style.STROKE);
        int sweep = 0;
        if(currentNum<=maxNum){
            sweep = (int)((float)currentNum/(float)maxNum*sweepAngle);
        }else {
            sweep = sweepAngle;
        }
        paint_2.setStrokeWidth(sweepOutWidth);
        Shader shader =new SweepGradient(0,0,indicatorColor,null);
        paint_2.setShader(shader);
        int w = dp2px(10);
        RectF rectf = new RectF(-radius-w , -radius-w , radius+w , radius+w);
        canvas.drawArc(rectf,startAngle,sweep,false,paint_2);
        float x = (float) ((radius+dp2px(10))*Math.cos(Math.toRadians(startAngle+sweep)));
        float y = (float) ((radius+dp2px(10))*Math.sin(Math.toRadians(startAngle+sweep)));

技术分享


        Log.d("x ----> ", x + "");
        Log.d("y ----> ", y + "");
        paint_3.setStyle(Paint.Style.FILL);
        paint_3.setColor(0xffffffff);
        paint_3.setMaskFilter(new BlurMaskFilter(dp2px(3), BlurMaskFilter.Blur.SOLID)); //需关闭硬件加速
        canvas.drawCircle(x,y,dp2px(3),paint_3);
        canvas.restore();
    }

    private void drawScale(Canvas canvas) {
        canvas.save();
        float angle = (float)sweepAngle/30;//刻度间隔
        canvas.rotate(-270+startAngle); //将起始刻度点旋转到正上方(270)   画布旋转负角度表示逆时针





技术分享







        for (int i = 0; i <= 30; i++) {
            if(i%6 == 0){  //画粗刻度和刻度值
                paint.setStrokeWidth(dp2px(2));
                paint.setAlpha(0x70);
                canvas.drawLine(0, -radius-sweepInWidth/2,0, -radius+sweepInWidth/2+dp2px(1), paint);
                drawText(canvas,i*maxNum/30+"",paint);
            }else {        //画细刻度
                paint.setStrokeWidth(dp2px(1));
                paint.setAlpha(0x50);
                canvas.drawLine(0,-radius-sweepInWidth/2,0, -radius+sweepInWidth/2, paint);
            }
            if(i==3 || i==9 || i==15 || i==21 || i==27){  //画刻度区间文字
                paint.setStrokeWidth(dp2px(2));
                paint.setAlpha(0x50);
                drawText(canvas,text[(i-3)/6], paint);
            }
            canvas.rotate(angle); //逆时针
        }
        canvas.restore();
    }

    private void drawText(Canvas canvas ,String text ,Paint paint) {
        paint.setStyle(Paint.Style.FILL);
        paint.setTextSize(sp2px(8));
        float width = paint.measureText(text); //相比getTextBounds来说,这个方法获得的类型是float,更精确些
//        Rect rect = new Rect();
//        paint.getTextBounds(text,0,text.length(),rect);
        canvas.drawText(text,-width/2 , -radius + dp2px(15),paint);
        paint.setStyle(Paint.Style.STROKE);
    }

    private void drawRound(Canvas canvas) {
        canvas.save();
        //内圆
        paint.setAlpha(0x40);
        paint.setStrokeWidth(sweepInWidth);
        RectF rectf = new RectF(-radius,-radius,radius,radius);
        canvas.drawArc(rectf,startAngle,sweepAngle,false,paint);
        //外圆
        paint.setStrokeWidth(sweepOutWidth);
        int w = dp2px(10);
        RectF rectf2 = new RectF(-radius-w , -radius-w , radius+w , radius+w);
        canvas.drawArc(rectf2,startAngle,sweepAngle,false,paint);
        canvas.restore();
    }

    //一些工具方法
    protected int dp2px(int dp){
        return (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                dp,
                getResources().getDisplayMetrics());
    }
    protected int sp2px(int sp){
        return (int)TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP,
                sp,
                getResources().getDisplayMetrics());
    }
    public static DisplayMetrics getScreenMetrics(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        return dm;
    }
}



/**
 * Created by gao_feng on 2016/12/20 下午9:17.
 */

public class WaveView extends View {
    private Path mPath;
    private Paint mPaint;
    private int vWidth , vHeight;//控件宽高
    private float ctrx , ctry;//控制点的xy坐标
    private float waveY;//整个Wave顶部两端的Y坐标

    private boolean isInc; //判断控制点是右移还是左移


    public WaveView(Context context) {
        this(context , null);
    }

    public WaveView(Context context, AttributeSet attrs) {
        this(context, attrs , 0);
    }

    public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setColor(0xFFA2D6AE);

        //实例化路径对象
        mPath = new Path();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.d("a", "onMeasure");

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //获取控件宽、高
        vWidth = w;
        vHeight = h;
        waveY = 1 / 8F * vHeight;
        //计算端点Y坐标
        ctry = -1 / 16F * vHeight;



    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.moveTo(-1 / 4F * vWidth , waveY);//起始点
        mPath.quadTo(ctrx , ctry , vWidth + 1/4F * vWidth , waveY);
        mPath.lineTo(vWidth + 1/4F * vWidth, vHeight);
        mPath.lineTo(-1 / 4f * vWidth, vHeight);
        mPath.close();
        canvas.drawPath(mPath, mPaint);

        if (ctrx >= vWidth + 1/4F * vWidth) {
            isInc = false;
        } else if (ctrx <= -1 / 4F * vWidth) {
            isInc = true;
        }
        ctrx = isInc ? ctrx + 20 : ctrx - 20;
        if (ctry <= vHeight) {
            ctry += 2;
            waveY += 2;
        }
        mPath.reset();
        invalidate();
    }
}


继承系统自带的控件:

/**
 * @author admin 
 * @date 2014-11-27 下午4:23:09
 * @description 带删除的EditText
 */
public class EditTextWithDelete extends EditText {
    private Drawable imgEnable;
    private Drawable imgEnableleft;
    private Context context;
    private boolean canDelete true;
    private Drawable moreEnable;
    private IEditDeleteListener mListener;
    private boolean isShowMore false;
    private int deleteSrc;
    private int moreSrc;

    public EditTextWithDelete(Context context) {
        super(context);
        this.context = context;
        init(null);
    }

    public EditTextWithDelete(Context contextAttributeSet attrsint defStyle) {
        super(contextattrsdefStyle);
        this.context = context;
        init(attrs);
    }

    public EditTextWithDelete(Context contextAttributeSet attrs) {
        super(contextattrs);
        this.context = context;
        init(attrs);
    }

    public void setListener(IEditDeleteListener listener) {
        this.mListener = listener;
    }

    public void setShowMore(boolean isMore) {
        isShowMore = isMore;
    }

    private void init(AttributeSet attrs) {
        // 获取图片资源
        TypedArray typedArray = context.obtainStyledAttributes(attrsR.styleable.EditTextWithDelete);
        deleteSrc = typedArray.getResourceId(R.styleable.EditTextWithDelete_deleteSrc,R.drawable.img_close_normal);
        moreSrc = typedArray.getResourceId(R.styleable.EditTextWithDelete_moreSrcR.drawable.img_login_userxl);
        typedArray.recycle();
        imgEnable = layoutToDrawable(deleteSrc);
        moreEnable = layoutToDrawable(moreSrc);
        addTextChangedListener(new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence sint startint beforeint count) {
            }

            @Override
            public void beforeTextChanged(CharSequence sint startint countint after) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                // 如果获取焦点了
                if (isFocusable()) {
                    setDrawable();
                } else {
                    setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullnullnull);
                }
            }
        });

        // 这个是判断是否有左图标
        Drawable[] compoundDrawables = getCompoundDrawables();
        if (compoundDrawables.length 0) {
            imgEnableleft = compoundDrawables[0];
        }
        // setDrawable();
        setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View vboolean hasFocus) {
                if (hasFocus) {
                    String value = getText().toString().trim();
                    if (TextUtils.isEmpty(value)) {
                        if (isShowMore) {
                            setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullmoreEnablenull);
                        } else {
                            setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullnullnull);
                        }
                    } else {
                        setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullimgEnablenull);
                    }
                } else {
                    if (isShowMore) {
                        setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullmoreEnablenull);
                    } else {
                        setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullnullnull);
                    }
                    if (length() > 0) {
                        setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullimgEnablenull);
                    }
                }
            }
        });
    }

    /**
     * LayoutDrawable
     *
     * @param imgId
     @return
     */
    public Drawable layoutToDrawable(int imgId) {
        View mView = LayoutInflater.from(context).inflate(R.layout.layout_editdelete_imgnull);
        ImageView mImg = (ImageView) mView.findViewById(R.id.editdelete_img);
        mImg.setImageResource(imgId);
        mView.measure(MeasureSpec.makeMeasureSpec(0MeasureSpec.UNSPECIFIED),
                MeasureSpec.makeMeasureSpec(0MeasureSpec.UNSPECIFIED));
        mView.layout(00mView.getMeasuredWidth()mView.getMeasuredHeight());
        mView.buildDrawingCache();
        Bitmap bitmap = mView.getDrawingCache();
        Drawable drawable = (Drawable) new BitmapDrawable(bitmap);
        return drawable;
    }

    /**
     * 设置删除图片
     */
    private void setDrawable() {
        if (length() == 0) {
            if (isShowMore) {
                setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullmoreEnablenull);
            } else {
                setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullnullnull);
            }
        } else {
            if (isFocusable()) {
                setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullimgEnablenull);
            } else {
                if (isShowMore) {
                    setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullmoreEnablenull);
                } else {
                    setCompoundDrawablesWithIntrinsicBounds(imgEnableleftnullnullnull);
                }
            }
        }
    }

    /**
     * event.getX() 获取相对应自身左上角的X坐标 event.getY() 获取相对应自身左上角的Y坐标 getWidth()
     * 获取控件的宽度 getTotalPaddingRight() 获取删除图标左边缘到控件右边缘的距离 getPaddingRight()
     * 获取删除图标右边缘到控件右边缘的距离 getWidth() - getTotalPaddingRight() 计算删除图标左边缘到控件左边缘的距离
     * getWidth() - getPaddingRight() 计算删除图标右边缘到控件左边缘的距离
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (length() == && event.getAction() == MotionEvent.ACTION_UP) {
            if (null != mListener) {
                int x = (int) event.getX();
                // 判断触摸点是否在水平范围内
                boolean isInnerWidth = (x > (getWidth() - getTotalPaddingRight())) && (x < (getWidth() - getPaddingRight()));
                // 获取删除图标的边界,返回一个Rect对象
                Rect rect = moreEnable.getBounds();
                // 获取删除图标的高度
                int height = rect.height();
                int y = (int) event.getY();
                // 计算图标底部到控件底部的距离
                int distance = (getHeight() - height) / 2;
                // 判断触摸点是否在竖直范围内(可能会有点误差)
                // 触摸点的纵坐标在distance到(distance+图标自身的高度)之内,则视为点中删除图标
                boolean isInnerHeight = (y > distance) && (y < (distance + height));
                if (isInnerHeight && isInnerWidth) {
                    mListener.shwoMore();
                }
            }
        }
        if (imgEnable != null && event.getAction() == MotionEvent.ACTION_UP) {
            int x = (int) event.getX();
            // 判断触摸点是否在水平范围内
            boolean isInnerWidth = (x > (getWidth() - getTotalPaddingRight())) && (x < (getWidth() - getPaddingRight()));
            // 获取删除图标的边界,返回一个Rect对象
            Rect rect = imgEnable.getBounds();
            // 获取删除图标的高度
            int height = rect.height();
            int y = (int) event.getY();
            // 计算图标底部到控件底部的距离
            int distance = (getHeight() - height) / 2;
            // 判断触摸点是否在竖直范围内(可能会有点误差)
            // 触摸点的纵坐标在distance到(distance+图标自身的高度)之内,则视为点中删除图标
            boolean isInnerHeight = (y > distance) && (y < (distance + height));
            if (isInnerWidth && isInnerHeight && canDelete) {
                setText("");
            }
        }
        return super.onTouchEvent(event);
    }

    public void setCanDelete(boolean isCanDelete) {
        this.canDelete = isCanDelete;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
    }

    public interface IEditDeleteListener {
        public void shwoMore();
    }

}

Demo2:

/**
 * @author 御轩
 @created on 2016-2-23
 */
public class FlowLayout extends ViewGroup {

    //存储所有子View
    private List<List<View>> mAllChildViews new ArrayList<List<View>>();
    //每一行的高度
    private List<Integer> mLineHeight new ArrayList<Integer>();

    public FlowLayout(Context context) {
        this(contextnull);
        // TODO Auto-generated constructor stub
    }

    public FlowLayout(Context contextAttributeSet attrs) {
        this(contextattrs0);
        // TODO Auto-generated constructor stub
    }

    public FlowLayout(Context contextAttributeSet attrsint defStyle) {
        super(contextattrsdefStyle);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onMeasure(int widthMeasureSpecint heightMeasureSpec) {
        // TODO Auto-generated method stub

        //父控件传进来的宽度和高度以及对应的测量模式
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        //如果当前ViewGroup的宽高为wrap_content的情况
        int width = 0;//自己测量的 宽度
        int height = 0;//自己测量的高度
        //记录每一行的宽度和高度
        int lineWidth = 0;
        int lineHeight = 0;

        //获取子view的个数
        int childCount = getChildCount();
        for (int i = 0i < childCounti++) {
            View child = getChildAt(i);
            //测量子View的宽和高
            measureChild(childwidthMeasureSpecheightMeasureSpec);
            //得到LayoutParams
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            //View占据的宽度
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            //View占据的高度
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            //换行时候
            if (lineWidth + childWidth > sizeWidth) {
                //对比得到最大的宽度
                width = Math.max(widthlineWidth);
                //重置lineWidth
                lineWidth = childWidth;
                //记录行高
                height += lineHeight;
                lineHeight = childHeight;
            } else {//不换行情况
                //叠加行宽
                lineWidth += childWidth;
                //得到最大行高
                lineHeight = Math.max(lineHeightchildHeight);
            }
            //处理最后一个子View的情况
            if (i == childCount - 1) {
                width = Math.max(widthlineWidth);
                height += lineHeight;
            }
        }
        //wrap_content
        setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width,
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height);
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onLayout(boolean changedint lint tint rint b) {
        // TODO Auto-generated method stub
        mAllChildViews.clear();
        mLineHeight.clear();
        //获取当前ViewGroup的宽度
        int width = getWidth();

        int lineWidth = 0;
        int lineHeight = 0;
        //记录当前行的view
        List<View> lineViews = new ArrayList<View>();
        int childCount = getChildCount();
        for (int i = 0i < childCounti++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            //如果需要换行
            if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width) {
                //记录LineHeight
                mLineHeight.add(lineHeight);
                //记录当前行的Views
                mAllChildViews.add(lineViews);
                //重置行的宽高
                lineWidth = 0;
                lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
                //重置view的集合
                lineViews = new ArrayList();
            }
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
            lineHeight = Math.max(lineHeightchildHeight + lp.topMargin + lp.bottomMargin);
            lineViews.add(child);
        }
        //处理最后一行
        mLineHeight.add(lineHeight);
        mAllChildViews.add(lineViews);

        //设置子View的位置
        int left = 0;
        int top = 0;
        //获取行数
        int lineCount = mAllChildViews.size();
        for (int i = 0i < lineCounti++) {
            //当前行的views和高度
            lineViews = mAllChildViews.get(i);
            lineHeight = mLineHeight.get(i);
            for (int j = 0j < lineViews.size()j++) {
                View child = lineViews.get(j);
                //判断是否显示
                if (child.getVisibility() == View.GONE) {
                    continue;
                }
                MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                int cLeft = left + lp.leftMargin;
                int cTop = top + lp.topMargin;
                int cRight = cLeft + child.getMeasuredWidth();
                int cBottom = cTop + child.getMeasuredHeight();
                //进行子View进行布局
                child.layout(cLeftcTopcRightcBottom);
                left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            }
            left = 0;
            top += lineHeight;
        }

    }

 

网络:

/**
 * Created by gaofeng on 2016/11/30.
 * 对网络请求的简单封装。
 */

public class MyBaseFragment extends BaseFragment implements IResponseListener {
    protected int responseCode 0;// 标记第N个网络请求
    protected LoadingView mLoadingView;
    protected boolean isFirstRequest true;

    /**
     * 请求失败点击刷新按钮
     */
    private void OnReload() {
        if (mLoadingView != null) {
            mLoadingView.setOnRefreshListener(new LoadingView.OnReloadListener() {
                @Override
                public void onReload(View v) {
                    onReLoad();
                }
            });
        }
    }

    /**
     * 发送网络请求的方法
     @param method 请求url
     * @param request 请求参数
     @param code 请求码,不能重复。
     @param isFirstRequest 是否是当前页面第一次请求
     */
    protected void sendRequest(String methodfinal BaseRequest requestint code boolean isFirstRequest) {
        this.isFirstRequest = isFirstRequest;
        //页面第一次进来需要展示loadingView,此外都是展示progress.
        if (!isFirstRequest) {
            showProgressDialog();
        } else {
            if (mLoadingView != null) {
                if (!NetUtils.isHttpConnected(mActivity)) { //没有网络请求
                    mLoadingView.setStatus(LoadingView.No_Network);
                } else {
                    mLoadingView.setStatus(LoadingView.Loading);
                }
            }
        }
        this.responseCode = code;
        Request<String> stringRequest ;
        if (request != null) {
            stringRequest = NoHttpUtils.getRequest(methodrequest.toMap());
        } else {
            stringRequest = NoHttpUtils.getRequest(methodnew HashMap());
        }
        requestQueue.add(responseCode stringRequestmOnLoadListener);
    }

    /**
     * 请求回调
     */
    protected OnLoadListener<String> mOnLoadListener new OnLoadListener<String>() {
        @Override
        public void onSuccess(int whatResponse<String> response) {
            dismissProgress();
            try {
                JSONObject object = new JSONObject(response.get());
                String code = object.getString("code");
                if (code.equals("0")) {
                    if (mLoadingView != null)
                        mLoadingView.setStatus(LoadingView.Success);
                    if (object.has("data")) {
                        onResponse(responseCode ,response.get());
                    }
                } else if (code.equals("4")) {//token失效返回登陆页面
                    Common.getInstance().logOut(mActivity);
                } else if (isFirstRequest && mLoadingView != null) {
                    mLoadingView.setStatus(LoadingView.Error);
                    mLoadingView.setErrorText(object.getString("msg"));
                } else {
                    toast(object.getString("msg"));
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onError(int whatResponse<String> response) {
            dismissProgress();
            onResponseFail(responseCode ,response.get());
            if (isFirstRequest && mLoadingView != null) {
                mLoadingView.setStatus(LoadingView.Error);
            }
        }
    };

    /**
     * 成功回调
     @param what
     @param response
     */
    @Override
    public void onResponse(int what ,String response) {

    }

    /**
     * 失败回调
     @param what
     @param errorInfo
     */

    @Override
    public void onResponseFail(int what ,String errorInfo) {

    }

    /**
     * 点击重新加载的回调
     */
    @Override
    public void onReLoad() {

    }
}






以上是关于Android自定义View和Canvas绘图解析的主要内容,如果未能解决你的问题,请参考以下文章

Android 绘图基础:Canvas画布——自定义View基础(绘制表盘矩形圆形弧渐变)

Android Canvas绘图

Android 2D绘图解析之 Canvas,Paint

[转]Android自定义控件三部曲系列完全解析(动画, 绘图, 自定义View)

一篇Canvas,画出你想要的一切

Android自己定义View画家(画布)Canvas与画笔Paint的应用——绘图涂鸦板app的实现