首页 > 其他分享 >Android自定义View和Canvas绘图解析

Android自定义View和Canvas绘图解析

时间:2022-12-08 21:00:42浏览次数:45  
标签:Canvas 自定义 int MeasureSpec paint canvas Android null view

自定义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_prefDialogWidth, mTmpValue, true);
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 != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);


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

childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
这里传入的参数是屏幕尺寸以及DecorView自身的大小, 接着我们来看 getRootMeasureSpec方法:


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;
}


就是这个方法确定了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 parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

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

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}


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

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

int size = Math.max(0, specSize - 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 ? 0 : 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 ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}


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

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


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


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


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

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




int size = Math.max(0, specSize - 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(childWidthMeasureSpec, childHeightMeasureSpec);



进入到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 widthMeasureSpec, int 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(heightMeasureSpec, optical ? -oHeight : oHeight);
}

// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = 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 ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
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, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}


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

也就是说measure --> OnMeasure
OnMeasure就简单了 :


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



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

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




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;
}


如果我们忽略掉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 l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

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

boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
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 = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}

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



这里挑重点来看




boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);



private boolean setOpticalFrame(int left, int top, int right, int 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 left, int top, int right, int 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(mLeft, mTop, mRight, mBottom);

mPrivateFlags |= PFLAG_HAS_BOUNDS;


if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}

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(changed, l, t, r, b);


若发生变化则会调用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 changed, int left, int top, int right, int bottom) {
}


居然是空的!


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

@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);


居然是个抽象方法!

到这里我们发现view和viewGroup都没有真正实现onLayout方法。
既然ViewGroup中的方法是抽象方法,那么子类就一定会重写这个方法, 我们来看LinearLayout:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}


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

随便挑一个来看看

void layoutVertical(int left, int top, int right, int 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 = 0; i < count; i++) {
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(gravity, layoutDirection);
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(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

i += getChildrenSkipCount(child, i);
}
}
}


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




Draw:



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



canvas.save和canvas.restore



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



rotate(float degrees)



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




Canvas的图层概念:







for(int=0;<5;++){
canvas.drawCircle(50,50,50,);
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 context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
init(attrs);
}

public EditTextWithDelete(Context context, AttributeSet attrs) {
super(context, attrs);
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(attrs, R.styleable.EditTextWithDelete);
deleteSrc = typedArray.getResourceId(R.styleable.EditTextWithDelete_deleteSrc,R.drawable.img_close_normal);
moreSrc = typedArray.getResourceId(R.styleable.EditTextWithDelete_moreSrc, R.drawable.img_login_userxl);
typedArray.recycle();
imgEnable = layoutToDrawable(deleteSrc);
moreEnable = layoutToDrawable(moreSrc);
addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}

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

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

/** * Layout转Drawable * * @param imgId * @return */ public Drawable layoutToDrawable(int imgId) {
View mView = LayoutInflater.from(context).inflate(R.layout.layout_editdelete_img, null);
ImageView mImg = (ImageView) mView.findViewById(R.id.editdelete_img);
mImg.setImageResource(imgId);
mView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mView.layout(0, 0, mView.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(imgEnableleft, null, moreEnable, null);
} else {
setCompoundDrawablesWithIntrinsicBounds(imgEnableleft, null, null, null);
}
} else {
if (isFocusable()) {
setCompoundDrawablesWithIntrinsicBounds(imgEnableleft, null, imgEnable, null);
} else {
if (isShowMore) {
setCompoundDrawablesWithIntrinsicBounds(imgEnableleft, null, moreEnable, null);
} else {
setCompoundDrawablesWithIntrinsicBounds(imgEnableleft, null, null, null);
}
}
}
}

/** * event.getX() 获取相对应自身左上角的X坐标 event.getY() 获取相对应自身左上角的Y坐标 getWidth() * 获取控件的宽度 getTotalPaddingRight() 获取删除图标左边缘到控件右边缘的距离 getPaddingRight() * 获取删除图标右边缘到控件右边缘的距离 getWidth() - getTotalPaddingRight() 计算删除图标左边缘到控件左边缘的距离 * getWidth() - getPaddingRight() 计算删除图标右边缘到控件左边缘的距离 */ @Override
public boolean onTouchEvent(MotionEvent event) {
if (length() == 0 && 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(context, null);
// TODO Auto-generated constructor stub }

public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub }

public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub }

@Override
protected void onMeasure(int widthMeasureSpec, int 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 = 0; i < childCount; i++) {
View child = getChildAt(i);
//测量子View的宽和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//得到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(width, lineWidth);
//重置lineWidth
lineWidth = childWidth;
//记录行高
height += lineHeight;
lineHeight = childHeight;
} else {//不换行情况
//叠加行宽
lineWidth += childWidth;
//得到最大行高
lineHeight = Math.max(lineHeight, childHeight);
}
//处理最后一个子View的情况
if (i == childCount - 1) {
width = Math.max(width, lineWidth);
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 changed, int l, int t, int r, int 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 = 0; i < childCount; i++) {
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(lineHeight, childHeight + 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 = 0; i < lineCount; i++) {
//当前行的views和高度
lineViews = mAllChildViews.get(i);
lineHeight = mLineHeight.get(i);
for (int j = 0; j < 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(cLeft, cTop, cRight, cBottom);
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 method, final BaseRequest request, int 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(method, request.toMap());
} else {
stringRequest = NoHttpUtils.getRequest(method, new HashMap());
}
requestQueue.add(responseCode , stringRequest, mOnLoadListener);
}

/** * 请求回调 */ protected onl oadListener<String> mOnLoadListener = new onl oadListener<String>() {
@Override
public void onSuccess(int what, Response<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 one rror(int what, Response<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() {

}
}

标签:Canvas,自定义,int,MeasureSpec,paint,canvas,Android,null,view
From: https://blog.51cto.com/u_11407799/5923477

相关文章

  • android中使用heap分析工具导出到MAT
    android中可以使用heap分析工具导出到MAT,方法为1)在ECLIPSE工具栏中,选择如下图2要将dalvik的导出到java格式的MAT,可以这样 hprof-convdump.......
  • Mybatis自定义拦截器实现自动记录操作人信息
    1.前言Mybatis有四大核心对象,分别是Executor,StatementHandler,ParamterHandler,ResultSetHandler。在很多时候,对表中的数据都需要记录插入时间,修改时间,插入人和修改人,若每......
  • SAP ABAP 调用SAP报表(标准报表或者自定义报表)
    偷懒神器~TABLES:mara.DATA:lr_alv_data TYPE REF TO data.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE text-000.  SELECT-OPTIONS s_matnr f......
  • SpringBoot中搭配AOP实现自定义注解
    1springBoot的依赖确定项目中包含可以注解的依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactI......
  • 百度 Android 直播秒开体验优化
    作者|任雪龙导读网络直播功能作为一项互联网基本能力已经越来越重要,手机中的直播功能也越来越完善,电商直播、新闻直播、娱乐直播等多种直播类型为用户提供了丰富的直播内......
  • 您上传的 APK 或 Android App Bundle 内含活动、活动别名、服务或广播接收器,这些项目
    您上传的APK或AndroidAppBundle内含活动、活动别名、服务或广播接收器,这些项目有intent过滤器,但没有“android:exported”属性设置。此文件无法在Android12或更......
  • canvas导出图片方法总结
    1.直接调用canvas对象的toDataURL方法转化为指定类型varnewImg=newImage();newImg.src=canvas.toDataURL("image/png"));2.利用canvas对象的toBlob方法https://de......
  • android 服务
      1.创建服务 Exported:是否允许除了当前程序之外的其他程序访问这个服务Enable:是否启用这个服务 点击完成后自动生成importandroid.app.Service;importandroid.conte......
  • android build.gradle
    现在android开发SDK一般选择用最新的SDK版本,这是Google官方强烈建议的。app能运行的Android版本不是由SDK决定的,是由每一个项目的minSDK决定的。SDK都是向下兼容的。SDK......
  • android studio AndroidManifest
     一、目录结构 1.AndroidManifest.xml它是一个清单文件,提供应用的基本信息<?xmlversion="1.0"encoding="utf-8"?><!--package是android应用程序的包名,相当工程的id--......