Android自定义View和Canvas绘图解析
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_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 https://www.360docs.net/doc/9317054719.html,youtParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case https://www.360docs.net/doc/9317054719.html,youtParams.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;
// 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
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
(ArrayList
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 https://www.360docs.net/doc/9317054719.html,youtParams lp =
(https://www.360docs.net/doc/9317054719.html,youtParams) 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的特性。
平时用的最多的就是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方法,其余的方法都会清除。
下面请看几个小示例:
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); //画当前进度值
android 自定义圆角头像以及使用declare-styleable进行配置属性解析
android 自定义圆角头像以及使用declare-styleable进行配置属性解析由于最新项目中正在检查UI是否与效果图匹配,结果关于联系人模块给的默认图片是四角稍带弧度的圆角,而我们截取的图片是正方形的,现在要给应用统一替换。应用中既用到大圆角头像(即整个头像是圆的)又用到四角稍带弧度的圆角头像,封装一下以便重用。以下直接见代码 [java] view plain copy 在CODE上查看代码片派生到我的代码片 package com.test.demo; import com.test.demo.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Shader.TileMode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.widget.ImageView; /** * 圆角imageview */ public class RoundImageView extends ImageView { private static final String TAG = "RoundImageView"; /** * 图片的类型,圆形or圆角 */ private int type; public static final int TYPE_CIRCLE = 0; public static final int TYPE_ROUND = 1; /** * 圆角大小的默认值
Android开发规范参考文档
Android开发参考文档 一、Android编码规范 1. java代码中不出现中文,最多注释中可以出现中文.xml代码中注释 2. 成员变量,局部变量、静态成员变量命名、常量(宏)命名 1). 成员变量: activity中的成员变量以m开头,后面的单词首字母大写(如Button mBackButton; String mName);实体类和自定义View的成员变量可以不以m开头(如ImageView imageView,String name), 2). 局部变量命名:只能包含字母,组合变量单词首字母出第一个外,都为大写,其他字母都为小写 3). 常量(宏)命名: 只能包含字母和_,字母全部大写,单词之间用_隔开UMENG_APP_KEY 3. Application命名 项目名称+App,如SlimApp,里面可以存放全局变量,但是杜绝存放过大的实体对象4. activity和其中的view变量命名 activity命名模式为:逻辑名称+Activity view命名模式为:逻辑名称+View 建议:如果layout文件很复杂,建议将layout分成多个模块,每个模块定义一个moduleViewHolder,其成员变量包含所属view 5. layout及其id命名规则 layout命名模式:activity_逻辑名称,或者把对应的activity的名字用“_”把单词分开。
命名模式为:view缩写_模块名称_view的逻辑名称, 用单词首字母进行缩写 view的缩写详情如下 LayoutView:lv RelativeView:rv TextView:tv ImageView:iv ImageButton:ib Button:btn 6. strings.xml中的 1). id命名模式: activity名称_功能模块名称_逻辑名称/activity名称_逻辑名称/common_逻辑名称,strings.xml中,使用activity名称注释,将文件内容区分开来 2). strings.xml中使用%1$s实现字符串的通配,合起来写 7. drawable中的图片命名 命名模式:activity名称_逻辑名称/common_逻辑名称/ic_逻辑名称 (逻辑名称: 这是一个什么样的图片,展示功能是什么) 8. styles.xml 将layout中不断重现的style提炼出通用的style通用组件,放到styles.xml中; 9. 使用layer-list和selector,主要是View onCclick onTouch等事件界面反映
Android平台我的日记设计文档
Android平台我的日记 设计文档 项目名称:mydiray 项目结构示意: 阶段任务名称(一)布局的设计 开始时间: 结束时间: 设计者: 梁凌旭 一、本次任务完成的功能 1、各控件的显示 二、最终功能及效果 三、涉及知识点介绍 四、代码设计 activity_main.xml: android:layout_centerHorizontal="true" android:layout_marginTop="88dp" android:text="@string/wo" android:textSize="35sp"/> 实用第一智慧密集 2011. 05 实现基于Android 的日历系统 摘要: Android 作为目前较为流行的智能手机操作系统已成为大多数人的首选。在美国乃至世界 的很多地方的出货量已经超越Iphone,成为世界上最大智能手机操作系统。因此,世界各地的程 序员都跃跃欲试地想学习Android 的开发,并希望从中捞得属于自己的第一桶金。在此给出一个 基于Android 的日历系统的完整实现过程。 关键词: Android;日历;绘画;农历;记录;提醒 1 引言 要实现的日历除了常规的日历功能外,还可以显示与当前 日期相关的信息,如当前日期的农历日期、天干地支、节日等 信息。下面先看看日历的绚丽界面,如图1、图2 所示。 主要功能 2 绘画基础 由于实现的日历系统要涉及到大量的Android 绘图技术, 因此,要简单介绍Android 的绘图技术。 绘制图形通常在Android.view.View 或其子类的onDraw 方 法中进行。该方法的定义如下: protected void onDraw(Canvas canvas); 其中Canvas 对象提供了大量用于绘图的方法,这些方法 主要包括绘制像素点、直线、圆形、弧、文本,这些都是组成 复杂图形的基本元素。如果要画更复杂的图形,可以采用组合 这些图形基本元素的方式来完成。例如,可以采用画3 条直线 的方式来画三角形。下面来看一下绘制图形基本元素的方法。 2.1 绘制像素点 public native void drawPoint(float x, float y, Paint paint); // 画一个像素点 public native void drawPoints(float[] pts, int offset, int count, Paint paint); // 画多个像素点 public void drawPoints(float[] pts, Paint paint); // 画多个像素点 参数的含义如下: (1) x:像素点的横坐标。 (2) y:像素点的纵坐标。 (3) paint:描述像素点属性的Paint 对象。可设置像素点 的大小、颜色等属性。绘制其他图形元素的Paint 对象与绘制 像素点的Paint 对象的含义相同。在绘制具体的图形元素时可 根据实际的情况设置Paint 对象。 (4) pts: drawPoints 方法可一次性画多个像素点。pts 参数 表示多个像素点的坐标。该数组元素必须是偶数个,两个一组 为一个像素点的坐标。 (5) offset: drawPoints 方法可以取pts 数组中的一部分连 续元素作为像素点的坐标,因此,需要通过offset 参数来指定 取得数组中连续元素的第一个元素的位置,也就是元素偏移 量,从0 开始。例如,要从第3 个元素开始取数组元素,那么 offset 参数值就是2。 (6) count:要获得的数组元素个数, count 必须为偶数 (两个数组元素为一个像素点的坐标)。 要注意的是, offset 可以从任意一个元素开始取值,例如, offset 可以为1,然后count 为4。 ListView :在Android应用开发过程中属于最常用的系统组件之一,当然可能同学们问为什么会突然游戏开发中讲这个,呵呵,其实在游戏开发中,也会常常使用到系统组件,比如游戏排行榜,简单的游戏关卡选择等等,都可以来使用ListView来实现; 当然关于ListView我想大家都会使用了,那么这篇文章也不是跟大家讲解ListView是如果使用的,而是如何自定义通用适配器类; 在ListView三种适配器当中,最受大家青睐的肯定就是SimpleAdapter 适配器,用过的童鞋们都很清楚,它的扩展性很强,可以将ListView中每一项都使用自定义布局,插入N多组件;但是SimpleAdapter也有弱点,那就是当ListView中每一项有Button、CheckBox等这些有事件的组件,我们想监听它们就必须自定义适配器!那么今天的重点也就是来讲解一下如何写一个自定义通用适配器类! SimpleAdapter 构造的时候,我们知道需要五个参数来进行映射数据到ListView中,那么我们今天的自定义通用适配器其实也就是实现系统SimpleAdapter的一个自定义版; OK,可能我说这么多,大家还是不太懂,其实今天要讲述的自定义通用适配器优点有两点: 1.使用通用适配器就不需要每次使用自定义适配器的时候,都要去重新去写一个,太累。。。。 2.构造方法与SimpleAdapter构造方法相同,五个参数也一摸一样! 3.只需要在自定义的适配器类中,将我们需要监听的组件进行设置监听即可!别的代码不需要去改动! 例如我们需要完成下图这种ListView: (图1) 首先我们来完成ListView中每项的布局:main.xml: android 自定义控件的过程 invalidate()会导致computeScroll()以及onDraw()方法的执行computeScroll()方法是在屏幕流动的时候不停的去调用,scrollTo(int x,int y)则是滚动到相应的位置; scrollBy(int x, int y)则是移动一些距离,X为正是向左移动,为负时向右移动,Y与X的意义一个,只是是上下移动而已View对象显示在屏幕上,有几个重要步骤: 1.构造方法创建对象 2.测量View的大小onMeasure(int,int); 3.确定View的位置,View自身有一些权,决定权在父View手中. onLayout();基本上不常用,在继承View的时候基本上用不着,但在继承ViewGroup的时候的就要用到了,因为要对View进行布局,确定View的位置,确定的时候使用 指定子View的位置,左,上,右,下,是指在ViewGroup坐标系中的位置https://www.360docs.net/doc/9317054719.html,yout(int xtop,int ytop, int xbottom, int ybottom); 4.绘制View的内容onDraw(Canvas) 实现过程: 1、构造方法: /** * 在布局文件中声名的view,创建的时候由系统调用 * * @param context * 上下文对象 * @param attrs * 属性集 */ public MyToggleButton(Context context, AttributeSet attrs) { super(context, attrs); initView(); } 2、测量View的大小: /** * 测量尺寸时的回调方法 */ https://www.360docs.net/doc/9317054719.html,/cmdn/bbs/viewthread.php?tid=18736&page=1 #pid89255 Android UI开发专题(一) 之界面设计 近期很多网友对Android用户界面的设计表示很感兴趣,对于Android UI开发自绘控件和游戏制作而言掌握好绘图基础是必不可少的。本次专题分10节来讲述,有关OpenGL ES相关的可能将放到以后再透露。本次主要涉及以下四个包的相关内容:android.content.res 资源类 android.graphics 底层图形类 android.view 显示类 android.widget 控件类 一、android.content.res.Resources 对于Android平台的资源类android.content.res.Resources可能很多网友比较陌生,一起来看看SDK上是怎么介绍的吧,Contains classes for accessing application resources, such as raw asset files, colors, drawables, media or other other files in the package, plus important device configuration details (orientation, input types, etc.) that affect how the application may behave.平时用到的二进制源文件raw、颜色colors、图形drawables和多媒体文件media的相关资源均通过该类来管理。 int getColor(int id) 对应res/values/colors.xml Drawable getDrawable(int id) 对应res/drawable/ XmlResourceParser getLayout(int id) 对应res/layout/ String getString(int id) 和CharSequence getText(int id) 对应res/values/strings.xml InputStream openRawResource(int id) 对应res/raw/ void parseBundleExtra (String tagName, AttributeSet attrs, Bundle outBundle) 对应res/xml/ String[] getStringArray(int id) res/values/arrays.xml float getDimension(int id) res/values/dimens.xml 二、android.graphics.Bitmap 作为位图操作类,Bitmap提供了很多实用的方法,常用的我们总结如下: boolean compress(https://www.360docs.net/doc/9317054719.html,pressFormat format, int quality, OutputStream stream) 压缩一个Bitmap对象根据相关的编码、画质保存到一个OutputStream中。其中第一个压缩格式目前有JPG和PNG void copyPixelsFromBuffer(Buffer src) 从一个Buffer缓冲区复制位图像素 void copyPixelsToBuffer(Buffer dst) 将当前位图像素内容复制到一个Buffer缓冲区 我们看到创建位图对象createBitmap包含了6种方法在目前的Android 2.1 SDK中,当然他们使用的是API Level均为1,所以说从Android 1.0 SDK开始就支持了,所以大家可以放心使用。 Android 自定义View——动态进度条 这个是看了梁肖的demo,根据他的思路自己写了一个,但是我写的这个貌似计算还是有些问题,从上面的图就可以看出来,左侧、顶部、右侧的线会有被截掉的部分,有懂得希望能给说一下,改进一下,这个过程还是有点曲折的,不过还是觉得收获挺多的。比如通动画来进行动态的展示(之前做的都是通过Handler进行更新的所以现在换一种思路觉得特别好),还有圆弧的起止角度,矩形区域的计算等!关于绘制我们可以循序渐进,比如最开始先画圆,然后再画周围的线,最后设置动画部分就可以了。不多说了,上代码了。 代码 自定义View public class ColorProgressBar extends View{ //下面这两行在本demo中没什么用,只是前几天看别人的代码时学到的按一定尺寸,设置其他尺寸的方式,自动忽略或者学习一下也不错 // private int defaultStepIndicatorNum= (int) TypedValue.applyDimension(https://www.360docs.net/doc/9317054719.html,PLEX_UNIT_DIP,40,getResources().getDisplay Metrics()); // int mCircleRadius=0.28f*defaultStepIndicatorNum; //布局的宽高 private int mWidth; private int mHeight; //直径 private int mDiameter=500; //底层圆画笔 private Paint mPaintbg; //顶层圆的画笔 private Paint mPaintft; //周围线的画笔 private Paint mPaintLine; //外层线条的长度 private int mLongItem=dip2px(20); //线条与圆的间距 private int mDistanceItem=dip2px(10); //进度条的最大宽度(取底层进度条与顶层进度条宽度最大的) private int mProgressWidth; //底层圆的颜色 private int mBackColor; //顶层圆的颜色 private int mFrontColor; //底层圆、顶层圆的宽度 private float mBackWidth; private float mFrontWidth; //设置进度 private float currentvalue; //通过动画演示进度 private ValueAnimator animator; private int curvalue; public ColorProgressBar(Context context) { this(context,null,0); } public ColorProgressBar(Context context, AttributeSet attrs) { this(context, attrs,0); } public ColorProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.ColorProgressBar); Android进阶——自定义View之自己绘 制彩虹圆环调色板 引言 前面几篇文章都是关于通过继承系统View和组合现有View来实现自定义View的,刚好由于项目需要实现一个滑动切换LED彩灯颜色的功能,所以需要一个类似调色板的功能,随着手在调色板有效区域滑动,LED彩灯随即显示相应的颜色,也可以通过左右的按钮,按顺序切换显示一组颜色,同时都随着亮度的改变LED彩灯的亮度随即变化,这篇基本上把继承View重绘实现自定义控件的大部分知识总结了下(当然还有蛮多没有涉及到,比如说自适应布局等),源码在Github上 一、继承View绘制自定义控件的通用步骤 自定义属性和继承View重写onDraw方法 实现构造方法,其中public RainbowPalette(Context context, AttributeSet attrs) 必须实现,否则无法通过xml引用,public RainbowPalette(Context context) ,public RainbowPalette(Context context, AttributeSet attrs, int defStyleAttr)可选,通常在构造方法中完成属性和其他成员变量的初始化 重写onMeasure方法,否则在xml中有些设置布局参数无效 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(width, height);//重新设置View的位置,若不重写的话,则不会布局,即使设置centerInParent为true也无效 //setMeasuredDimension(width,height); } 手动调用invalidate或者postInvalidateon方法完成界面刷新 重写onTouchEvent方法实现基本的交互 定义回调接口供外部调用 二、彩虹圆环调色板设计思想 Android 适应任何样式提示弹出框Dialog 封装 在Android开发中,难免有各种各样的提示框,如加载数据等待框,删除确认框、输入密码提示框等等,这些是完全可以自定义的,这里给出一个框架以及一个示例,帮助你开发任何样式布局的提示框,废话不多说,直接贴代码: HintDialog.java import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.view.KeyEvent; import android.view.Window; public class HintDialog { Dialog mDialog = null; private Context mContext = null; private IHintDialog mIDialogInstance = null; /** * 构造函数 * @param context */ public HintDialog(Context context) { mContext = context; mDialog = new AlertDialog(mContext) { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && mIDialogInstance != null) { mIDialogInstance.onKeyDown(keyCode, event); return true; } return super.onKeyDown(keyCode, event); } 自定义Dialog; dialog = new Dialog(this); dialog.setContentView(https://www.360docs.net/doc/9317054719.html,yout.by_baseinfo); dialog.setTitle("dialog的title"); /* * 获取Dialog的窗口对象及参数对象以修改对话框的布局设置, 可以直接调用this.getWindow(),表示获得这个Activity的Window * 对象,这样这可以以同样的方式改变这个Activity的属性. * Activity不可见时getWindow()返回值为null; */ Window dialogWindow = dialog.getWindow(); // 对话框的布局设置参数; https://www.360docs.net/doc/9317054719.html,youtParams layoutParams = dialogWindow.getAttributes(); // 设置Window中的内容为左上对齐; dialogWindow.setGravity(Gravity.LEFT | Gravity.TOP); /* * lp.x与lp.y表示相对于原始位置的偏移. * 当参数值包含Gravity.LEFT时,对话框出现在左边,所以lp.x就表示相对左边的偏移,负值忽略. * 当参数值包含Gravity.RIGHT时,对话框出现在右边,所以lp.x就表示相对右边的偏移,负值忽略. * 当参数值包含Gravity.TOP时,对话框出现在上边,所以lp.y就表示相对上边的偏移,负值忽略. * 当参数值包含Gravity.BOTTOM时,对话框出现在下边,所以lp.y就表示相对下边的偏移,负值忽略. * 当参数值包含Gravity.CENTER_HORIZONTAL时 * ,对话框水平居中,所以lp.x就表示在水平居中的位置移动lp.x像素,正值向右移动,负值向左移动. * 当参数值包含Gravity.CENTER_VERTICAL时 * ,对话框垂直居中,所以lp.y就表示在垂直居中的位置移动lp.y像素,正值向右移动,负值向左移动. * gravity的默认值为Gravity.CENTER,即Gravity.CENTER_HORIZONTAL | * Gravity.CENTER_VERTICAL. * * 本来setGravity的参数值为Gravity.LEFT | Gravity.TOP时对话框应出现在程序的左上角,但在 * 我手机上测试时发现距左边与上边都有一小段距离,而且垂直坐标把程序标题栏也计算在内了, Gravity.LEFT, Gravity.TOP, * Gravity.BOTTOM与Gravity.RIGHT都是如此,据边界有一小段距离 */ // 相对于屏幕原位置(加上标题栏) 的偏移量; lp.x = 100; // 新位置X坐标 Android Canvas绘图详解(图文) 摘要Android中使用图形处理引擎,2D部分是android SDK内部自己提供,3D部分是用Open GL ES 1.0。今天我们主要要了解的是2D相关的,如果你想看3D的话那么可以跳过这篇文章。大部分2D 使用的api都在android.graphics和android.graphics.drawable包中。他们提供了图 Android中使用图形处理引擎,2D部分是android SDK内部自己提供,3D部分是用Open GL ES 1.0。今天我们主要要了解的是2D相关的,如果你想看3D的话那么可以跳过这篇文章。 大部分2D使用的api都在android.graphics和android.graphics.drawable包中。他们提供了图形处理相关的:Canvas、ColorFilter、Point(点)和RetcF(矩形)等,还有一些动画相关的:AnimationDrawable、BitmapDrawable和TransitionDrawable等。以图形处理来说,我们最常用到的就是在一个View上画一些图片、形状或者自定义的文本内容,这里我们都是使用Canvas来实现的。你可以获取View中的Canvas对象,绘制一些自定义形状,然后调用View. invalidate方法让View重新刷新,然后绘制一个新的形状,这样达到2D动画效果。下面我们就主要来了解下Canvas的使用方法。 Canvas对象的获取方式有两种:一种我们通过重写View.onDraw方法,View中的Canvas 对象会被当做参数传递过来,我们操作这个Canvas,效果会直接反应在View中。另一种就是当你想创建一个Canvas对象时使用的方法: 1 2 Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); Canvas c =new Canvas(b); 上面代码创建了一个尺寸是100*100的Bitmap,使用它作为Canvas操作的对象,这时候的Canvas就是使用创建的方式。当你使用创建的Canvas在bitmap上执行绘制方法后,你还可以将绘制的结果提交给另外一个Canvas,这样就可以达到两个Canvas协作完成的效果,简化逻辑。但是android SDK建议使用View.onDraw参数里提供的Canvas就好,没必要自己创建一个新的Canvas对象。接下来我们看看Canvas提供我们哪些绘制图形的方法。我们创建一个自定义View对象,使用onDraw方法提供的Canvas进行绘制图形。 CanvasDemoActivity.java: 1 2 3 4 5 6 package com.android777.demo.uicontroller.graphics; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; android开发者资料大全 第一篇:安装SDK 这里主要介绍如何安装Android的SDK开发包和配置开发环境。如果你还没有下载SDK,点击下面的链接开始。 Download the Android SDK 系统和软件配置要求 要通过Android SDK中提供的代码和工具进行Android应用程序的开发,需要一个合适的用于开发的电脑和合适的开发环境,具体要求如下: 支持的开发环境 Eclipse Eclipse 3.2,3.3(Europa) Android开发工具插件(可选) 其他的开发环境或者IDE JDK5.0或者JDK6.0(仅有JRE是不够的) 安装SDK 下载好SDK包后,将zip文件解压缩至合适的地方。在下文中,我们默认你的SDK安装目录为$SDK_ROOT 你可以选择将$SDK_ROOT/tools加入到你的路径中 1.Linux下,打开文件~/.bash_profile或者~/.bashrc,找到设定PATH环境变量的一行,将$SDK_ROOT/tools的完整路径加入其中。如果没有找到设定PATH变量的行,你可以自己添加一行: export PATH=${PATH}:<你的$SDK_ROOT/tools的完全路径> 2.Mac下,在你的home目录中找到文件.bash_profile,和Linux的一样处理。如果还没有在机器上设定这个文件,你可以创建一个.bash_profile文件。 3.Windows下,右键点击【我的电脑】,选择【属性】,在【高级】页中,点击【环境变量】按键,在弹出的对话框中双击“系统变量”中的变量“Path”,将$SDK/tools的完全路径加入其中。 今天和大家分享下组合控件的使用。很多时候android自定义控件并不能满足需求,如何做呢?很多方法,可以自己绘制一个,可以通过继承基础控件来重写某些环节,当然也可以将控件组合成一个新控件,这也是最方便的一个方法。今天就来介绍下如何使用组合控件,将通过两个实例来介绍。 第一个实现一个带图片和文字的按钮,如图所示: 整个过程可以分四步走。第一步,定义一个layout,实现按钮内部的布局。代码如下: 1. 2. Android UI开发专题(一) 之界面设计 发帖日期:2010-02-09 10:49:28 标签:ophone 近期很多网友对Android用户界面的设计表示很感兴趣,对于Android UI开发自绘控件和游戏制作而言掌握好绘图基础是必不可少的。本次专题分10节来讲述,有关OpenGL ES相关的可能将放到以后再透露。本次主要涉及以下四个包的相关内容: android.content.res 资源类 android.graphics 底层图形类 android.view 显示类 android.widget 控件类 一、android.content.res.Resources 对于Android平台的资源类android.content.res.Resources可能很多网友比较陌生,一起来看看SDK上是怎么介绍的吧,Contains classes for accessing application resources, such as raw asset files, colors, drawables, media or other other files in the package, plus important device configuration details (orientation, input types, etc.) that affect how the application may behave.平时用到的二进制源文件raw、颜色colors、图形drawables和多媒体文件media的相关资源均通过该类来管理。 int getColor(int id) 对应res/values/colors.xml Drawable getDrawable(int id) 对应res/drawable/ XmlResourceParser getLayout(int id) 对应res/layout/ String getString(int id) 和CharSequence getText(int id) 对应 res/values/strings.xml InputStream openRawResource(int id) 对应res/raw/ void parseBundleExtra (String tagName, AttributeSet attrs, Bundle outBundle) 对应res/xml/ String[] getStringArray(int id) res/values/arrays.xml float getDimension(int id) res/values/dimens.xml 二、android.graphics.Bitmap 作为位图操作类,Bitmap提供了很多实用的方法,常用的我们总结如下: boolean compress(https://www.360docs.net/doc/9317054719.html,pressFormat format, int quality, OutputStream stream) 压缩一个Bitmap对象根据相关的编码、画质保存到一个OutputStream中。其中第一个压缩格式目前有JPG和PNG void copyPixelsFromBuffer(Buffer src) 从一个Buffer缓冲区复制位图像素 第一章Android GDI之基本原理及其总体 框架 Android GDI基本框架 在Android中所涉及的概念和代码最多,最繁杂的就是GDI相关的代码了。但是本质从抽象上来讲,这么多的代码和框架就干了一件事情:对显示缓冲区的操作和管理。 GDI主要管理图形图像的输出,从整体方向上来看,GDI可以被认为是一个物理屏幕使用的管理器。因为在实际的产品中,我们需要在物理屏幕上输出不同的窗口,而每个窗口认为自己独占屏幕的使用,对所有窗口输出,应用程序不会关心物理屏幕是否被别的窗口占用,而只是关心自己在本窗口的输出,至于输出是否能在屏幕上看见,则需要GDI来管理。 从最上层到最底层的数据流的分析可以看到实际上GDI在上层为GUI提供一个抽象的概念,就好像操作系统中的文件系统所提供文件,目录等抽象概念一样,GDI输出抽象成了文本,画笔,位图操作等设备无关的操作,让应用程序员只需要面对逻辑的设备上下文进行输出操作,而不要涉及到具体输出设备,以及输出边界的管理。GDI负责将文本、线条、位图等概念对象映射到具体的物理设备,所以GDI的在大体方向上可以分为以下几大要素:画布 字体 文本输出 绘画对象 位图输出 Android的GDI系统 Android的GDI系统所涉及到概念太多,加之使用了OpenGL使得Android的层次和代 码很繁杂。但是我们对于Android的GDI系统需要了解的方面不是他的静态的代码关系,而是动态的对象关系,在逻辑运行的架构上理解GDI。我们首先还是需要从代码结构开始我们的理解。 Frameworks/Libs/Surfaceflinger Frameworks/base/core/jni/android_view_Surface.cpp Frameworks/base/core/java/android/view/surface.java Frameworks/base/Graphics:绘图接口 Frameworks/Libs/Ui External/Skia 其中External/Skia是一个C++的2D图形引擎库,Android的2D绘制系统都是建立在该基础之上.Skia完成了:文本输出,位图,点,线,图像解码等功能。在这里给出Android GDI 的基本框架示意图。 对于上面的GDI架构图我们只是一个大概的了解,我们有太多的问题需要解决,有太多的疑问需要得到答案,我就一直在想,为什么设计者有提出如此众多的概念,这个概念的背景是什么?他要管理什么,他要抽象什么?从前面知道,Android的整个设计理念就是无边界化,他是如何穿透Linux进程这个鸿沟来达到无边界的?Surface,Canvas,Layer,LayerBase, NativeBuffer,SurfaceFlinger,SurfaceFlingerClient这些到底是一个什么东西?如Android日历完整实现
Android自定义适配器的编写
android 自定义控件的过程
Android UI开发专题
Android 自定义View——动态进度条
Android进阶——自定义View之自己绘制彩虹圆环调色板
Android 适应任何自定义样式 提示弹出框 Dialog 封装
android自定义布局或View
Android Canvas绘图详解
2016尚学堂Android开发入门教程
Android自定义控件
android UI界面设计
Android 图形系统分析-surfaceFlinger流程