自定义Textview-Read The Fucking Source Code
2018-02-01 20:02
302 查看
关于自定义view的实现,有很多大神前辈都给出了很好的日志文章。因项目需求,需要做一个显示循环小数的textview控件,本篇文章就记录一下实现过程中使用到和学习到的一些知识。
调用时机:
两个参数
而这两个参数值的生成却是由一个类来定义的,这个类是View的一个静态内部类 –MeasureSpec
所以清楚android的是怎么测量控件的宽高的,就先要了解MeasureSpec这个类
总的来说就是如何测量,为此
在MeasureSpec类定义了测量的三种模式
具体说明
方法很简单,结果就是int返回的int值高两位代表测量的模式,后面的表示具体的值,而MeasureSpec也提供了两个静态方法获取测量模式(getMode)和值(getSize)
所以,onMeasure(int widthMeasureSpec, int heightMeasureSpec)通过传入的参数widthMeasureSpec就可以获取到父控件或者是xml里定义的测量值以及测量模式,这样就可以在这个方法里自定义自己的测量模式
回到项目需求,绘制循环数的思路分两部分,一部分是文字的绘制,第二部分是点的绘制,(暂时不考虑换行),所以测量代码就比较简单了宽度就是文字的宽度,高度就是文字的高度+点的高度+点和文字的间距。这里的难点在于文字的宽高不好测量,比如数字1和3和中文所占的宽度就不一样。实际上不用担心android都给我们提供了api就是Paint类里的getTextBounds方法,传入一个String,开始和结束的角标,bounds对象为存储测量的边界,稍微转换一下就是宽高了
所以文字的宽高就测量出来了,文字的高+点的高度和点和文字间距就是控件的高度,因为这里不考虑换行,所以文字的宽度就是控件的宽度,具体的测量代码如下
resolveSize方法的就是兼容各个测量模式下这个值是否可行,源码如下
就是依据父控件给的measureSpec和我们计算出来的size做兼容,如果父控件给的specSize比我们的计算值还要小,那么当前控件也只能是父控件的宽高了~,到此,测量就基本结束了,(但是比如RelativeLayout相对布局等比较复杂的布局可能会测量多次,因为一次可能测不准子控件的宽高–!)
android.text.Layout类里的方法getDesiredWidth(可以精确的得到一个view展示一行文字需要的宽度)
所以测量方法需要改宽度的测量,代码如下
测量完成,view要怎么放,放在那里,就需要第二步——布局来完成
所以,如果我们在使用LinearLayout如果不指定Orientation,默认的是横向布局哒,下面看layoutVertical方法实现,代码比较长,分段分析
这部分代码主要是计算padding,因为android里的坐标系是左上角为原点,这里解释下开始的childTop在Gravity.BOTTOM的时候的计算,如果LinearLayout里设置Gravity是从底部开始布局,那么childTop的计算就不是单纯的paddingTop了,计算方式如下
很奇怪,一下子看不懂呀~
回归正常思维,如果从底部开始布局的话,那么第一个child的top应该是LinearLayout的高(bottom - top)减去所有child的高,以及child之间的间距,再减去底部的padding(mPaddingBottom)
也就是说是
再看看mTotalLength是个什么鬼,这个值需要从onMeasure里看了,这里不详细贴源码分析了(下面是关键的代码),mTotalLength在竖直布局的时候,
所以源码里的这个计算没问题,只是有点绕,下面是mTotalLength在竖直布局下的关键计算部分(同样的就可以理解居中放置child的计算了,然后发现padding的设置会影响居中,也就是说如果paddingtop很大,paddingbottom很小,那么会导致Gravity.CENTER_VERTICAL的效果就是偏底部的啦)。
ok,第一个child的top位置计算好了,下面就是从这个位置一个个的放置child就好了,下面继续看layoutVertical布局child的部分
这部分就是一个for循环,循环计算每个child的位置(left,top,right,bottom),然后调用child的onlayout方法啦,这样布局就完成,其他的布局比如RelativeLayout,FrameLayout等有不同的特性布局,都是依据这两步骤来设置哒,
就是可以给每个child设置一个分隔图片,对应的api如下
第二步layout也就完成了
绘制内容的话主要就是使用Canvas的api了,画文字图片,线段等等,需要怎么绘制就找对应的api就好了
这里需要注意的是canvas.drawText方法是从文字的底部开始绘制的,传入的第三个参数即y 需要计算文字的高度,toppadding等值的!
绘制显示循环小数的完整代码如下:
简单翻译一下 ,
所以即使自定义view的时候,background,ondraw内容,child,Foreground等是层级绘制上去的,后面的会覆盖前面(如果有重叠)。
实际上里面还有
文字的测量需要使用TextPaint,Layout,Paint类的getTextBounds获取的是文字的最小区域,
常用的布局(LinearLayout等)都是在onLayout里处理其特性
画文字是从文字底部开始绘制
绘制顺序:background->onDraw->绘制child->绘制阴影-> Foreground
三部曲
有过开发经验的都知道,自定义view展示需要三个步骤;步骤 | 回调方法 | 功能说明 | 注释 |
---|---|---|---|
测量 | onMeasure(int widthMeasureSpec, int heightMeasureSpec) | 主要测量当前控件以及子控件的方法 | |
布局 | onLayout(boolean changed, int left, int top, int right, int bottom) | 放置子控件的位置,回调方法为 ,如果只是单纯的不包含子控件的控件,即自定义textview,imageview这类view的话,是不需要重写这个方法的。比如LinearLayout和Framlayout,RelativeLayout这些继承自ViewGroup的,可以含子控件的view就需要重写 | |
绘制 | onDraw(Canvas canvas) | 使用canvas绘制当前控件 |
1.三部曲一测量-onMeasure
测量的主要回调方法就是 onMeasure(int widthMeasureSpec, int heightMeasureSpec),调用时机:
这个方法在布局文件xml文件解析完了之后,父控件测量子控件的时候调用,
两个参数
宽高(widthMeasureSpec 和 heightMeasureSpec)就是android解析xml文件里定义的宽高比如(wrap_content,match_parent,或者具体值)得到的,含测量模式和宽高值 (当然也不一定,因为有些控件会测量多次,那么传入的值和模式可能就会变化)
而这两个参数值的生成却是由一个类来定义的,这个类是View的一个静态内部类 –MeasureSpec
所以清楚android的是怎么测量控件的宽高的,就先要了解MeasureSpec这个类
1.1MeasureSpec测量模式
测量的时候,控件怎么测量子控件,子控件怎么告诉父控件需要的宽高是多少,如果父控件空间不够了,怎么处理?总的来说就是如何测量,为此
在MeasureSpec类定义了测量的三种模式
/** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT;
具体说明
模式 | 值 | xml值举例 | 说明 |
---|---|---|---|
UNSPECIFIED | 0x0<<30 | wrap_content | 不指定,不限制子控件的大小,子控件需要多大就可以是多大 |
AT_MOST | 0x1<<30 | match_parent(fill_parent) | 子控件最大化 ,通常就是父控件的大小 |
EXACTLY | 0x2<<30 | 100dp等 | 父控件会指定一个大小区域给子控件,可不管子控件想要多大就给多大的空间了 |
1.2 MeasureSpec构造
我们知道onMeasure方法传入的宽高值是两个int值,这两个int值是怎么包含模式值和控件的真实宽高的,查看MeasureSpec源码却发现没有构造函数,是通过makeSafeMeasureSpec(int size, int mode)方法传入一个size,和mode来构建MeasureSpec值的,private static final int MODE_MASK = 0x3 << 30; public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } return makeMeasureSpec(size, mode); }
方法很简单,结果就是int返回的int值高两位代表测量的模式,后面的表示具体的值,而MeasureSpec也提供了两个静态方法获取测量模式(getMode)和值(getSize)
所以,onMeasure(int widthMeasureSpec, int heightMeasureSpec)通过传入的参数widthMeasureSpec就可以获取到父控件或者是xml里定义的测量值以及测量模式,这样就可以在这个方法里自定义自己的测量模式
回到项目需求,绘制循环数的思路分两部分,一部分是文字的绘制,第二部分是点的绘制,(暂时不考虑换行),所以测量代码就比较简单了宽度就是文字的宽度,高度就是文字的高度+点的高度+点和文字的间距。这里的难点在于文字的宽高不好测量,比如数字1和3和中文所占的宽度就不一样。实际上不用担心android都给我们提供了api就是Paint类里的getTextBounds方法,传入一个String,开始和结束的角标,bounds对象为存储测量的边界,稍微转换一下就是宽高了
/** * Return in bounds (allocated by the caller) the smallest rectangle that * encloses all of the characters, with an implied origin at (0,0). * * @param text string to measure and return its bounds * @param start index of the first char in the string to measure * @param end 1 past the last char in the string to measure * @param bounds returns the unioned bounds of all the text. Must be allocated by the caller */ public void getTextBounds(String text, int start, int end, Rect bounds) { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } if (bounds == null) { throw new NullPointerException("need bounds Rect"); } nGetStringBounds(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, bounds); }
所以文字的宽高就测量出来了,文字的高+点的高度和点和文字间距就是控件的高度,因为这里不考虑换行,所以文字的宽度就是控件的宽度,具体的测量代码如下
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mPaint.getTextBounds(mText, 0, mText.length(), mBound); setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } private int measureWidth(int widthMeasureSpec) { return resolveSize(mBound.width(),widthMeasureSpec); } private int measureHeight(int heightMeasureSpec) { return resolveSize(mBound.height() + mDotLineHeight, heightMeasureSpec); }
resolveSize方法的就是兼容各个测量模式下这个值是否可行,源码如下
public static int resolveSize(int size, int measureSpec) { return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK; } public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
就是依据父控件给的measureSpec和我们计算出来的size做兼容,如果父控件给的specSize比我们的计算值还要小,那么当前控件也只能是父控件的宽高了~,到此,测量就基本结束了,(但是比如RelativeLayout相对布局等比较复杂的布局可能会测量多次,因为一次可能测不准子控件的宽高–!)
1.3 测量补充
Paint类的getTextBounds方法只是给出了String的最小边距,并没有考虑文字与文字之间的间距,默认的文字和文字之间的间距是由字体文件决定的,所以这个方法并不能测量很准确的文字的宽度,如果文字多的话,可能会导致测量宽度小,一部分文字显示不出来,所以下面给出一个TextView里测量文字的宽方法(Read The Fucking Source Code)android.text.Layout类里的方法getDesiredWidth(可以精确的得到一个view展示一行文字需要的宽度)
/** * Return how wide a layout must be in order to display the specified text with one line per * paragraph. * * <p>As of O, Uses * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR} as the default text direction heuristics. In * the earlier versions uses {@link TextDirectionHeuristics#LTR} as the default.</p> */ public static float getDesiredWidth(CharSequence source, TextPaint paint) { return getDesiredWidth(source, 0, source.length(), paint); }
所以测量方法需要改宽度的测量,代码如下
private int measureWidth(int widthMeasureSpec) { return resolveSize((int) Math.ceil(Layout.getDesiredWidth(mText, mPaint)), widthMeasureSpec); }
测量完成,view要怎么放,放在那里,就需要第二步——布局来完成
2.三部曲-布局-onLayout
如果是单纯的View的话,不是ViewGroup类的话(没有child的自定义view),是不需要复写布局这个方法的。这里就分析LinearLayout的垂直布局是如何完成的就好了public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; @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); } }
所以,如果我们在使用LinearLayout如果不指定Orientation,默认的是横向布局哒,下面看layoutVertical方法实现,代码比较长,分段分析
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; }
这部分代码主要是计算padding,因为android里的坐标系是左上角为原点,这里解释下开始的childTop在Gravity.BOTTOM的时候的计算,如果LinearLayout里设置Gravity是从底部开始布局,那么childTop的计算就不是单纯的paddingTop了,计算方式如下
childTop = mPaddingTop + bottom - top - mTotalLength;
很奇怪,一下子看不懂呀~
回归正常思维,如果从底部开始布局的话,那么第一个child的top应该是LinearLayout的高(bottom - top)减去所有child的高,以及child之间的间距,再减去底部的padding(mPaddingBottom)
也就是说是
childTop=bottom - top-(child的高+child之间的间距)-mPaddingBottom
再看看mTotalLength是个什么鬼,这个值需要从onMeasure里看了,这里不详细贴源码分析了(下面是关键的代码),mTotalLength在竖直布局的时候,
mTotalLength = child的高+child之间的间距 + mPaddingBottom + mPaddingTop
所以源码里的这个计算没问题,只是有点绕,下面是mTotalLength在竖直布局下的关键计算部分(同样的就可以理解居中放置child的计算了,然后发现padding的设置会影响居中,也就是说如果paddingtop很大,paddingbottom很小,那么会导致Gravity.CENTER_VERTICAL的效果就是偏底部的啦)。
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; for (int i = 0; i < count; ++i) { ... ... // 计算divider的高度 if (hasDividerBeforeChildAt(i)) { mTotalLength += mDividerHeight; } ... ... if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) { ...... // 计算MeasureSpec.EXACTLY 模式下child的高度 mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } else { ...... // 计算child的高度 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); } } ...... // Add in our padding 计算LinearLayout的padding mTotalLength += mPaddingTop + mPaddingBottom; ...... }
ok,第一个child的top位置计算好了,下面就是从这个位置一个个的放置child就好了,下面继续看layoutVertical布局child的部分
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); } }
这部分就是一个for循环,循环计算每个child的位置(left,top,right,bottom),然后调用child的onlayout方法啦,这样布局就完成,其他的布局比如RelativeLayout,FrameLayout等有不同的特性布局,都是依据这两步骤来设置哒,
2.2 LinearLayout的Divider
之前没发现LinearLayout还有divider的功能呀,通过看源码发现了LinearLayout的Divider用法!!!就是可以给每个child设置一个分隔图片,对应的api如下
方法 | 说明 |
---|---|
setDividerDrawable(Drawable divider) | 设置间隔图片 |
setDividerPadding(int padding) | 设置分隔的间距 |
setShowDividers(@DividerMode int showDividers) | 设置Divider显示的模式,开始显示,结束显示,还是child之间显示,不显示; |
3 三部曲-绘制-onDraw
onDraw方法里就是对内容进行绘制了,先提一些问题:1.onDraw是绘制所有的view的内容么? 2.setBackground或者在xml里设置的Background会不会因为复写的onDraw方法没执行super.onDraw()导致不显示? 3.绘制顺序是怎么样的?
绘制内容的话主要就是使用Canvas的api了,画文字图片,线段等等,需要怎么绘制就找对应的api就好了
这里需要注意的是canvas.drawText方法是从文字的底部开始绘制的,传入的第三个参数即y 需要计算文字的高度,toppadding等值的!
绘制显示循环小数的完整代码如下:
public class DotNumView extends View {
public DotNumView(Context context) {
super(context);
init();
}
public DotNumView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
init();
}
public DotNumView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
init();
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DotNumView);
if (ta != null) {
mTextColor = ta.getColor(R.styleable.DotNumView_android_textColor, DEFAULT_TEXT_COLOR);
mTextSize = ta.getDimensionPixelSize(R.styleable.DotNumView_android_textSize,
(int) DEFAULT_TEXT_SIZE);
mText = ta.getString(R.styleable.DotNumView_android_text);
mDotPadding = ta.getDimension(R.styleable.DotNumView_dotPadding, DEFAULT_DOT_PADDING);
mDotRadius = ta.getDimension(R.styleable.DotNumView_dotRadius, DEFAULT_DOT_RADIUS);
ta.recycle();
}
}
/**
* 默认文字大小
*/
private static final float DEFAULT_TEXT_SIZE = 30;
/**
* 默认的点半径
*/
private static final float DEFAULT_DOT_RADIUS = 2;
/**
* 默认的点和文字的距离
*/
private static final float DEFAULT_DOT_PADDING = 5;
private static final int DEFAULT_TEXT_COLOR = Color.BLACK;
/**
* 默认的右侧padding
*/
private static final float DEFAULT_PADDING_RIGHT = 3;
/**
* 文字大小
*/
float mTextSize = DEFAULT_TEXT_SIZE;
/**
* 点半径
*/
float mDotRadius = DEFAULT_DOT_RADIUS;
/**
* 点和文字的距离
*/
float mDotPadding = DEFAULT_DOT_PADDING;
/**
* 需要绘制的文字
*/
String mText = "";
int mTextColor = DEFAULT_TEXT_COLOR;
/**
* 文字边框
*/
Rect mBound = new Rect();
private TextPaint mPaint;
/**
* 需要画点的文字角标
*/
private int[] mDotIndexs;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mPaint.getTextBounds(mText, 0, mText.length(), mBound);
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int widthMeasureSpec) { return resolveSize((int) Math.ceil(Layout.getDesiredWidth(mText, mPaint)), widthMeasureSpec); }
private int measureHeight(int heightMeasureSpec) {
return resolveSize((int) (mBound.height() + mDotRadius * 4 + mDotPadding * 4),
heightMeasureSpec);
}
private void init() {
mPaint = new TextPaint();
mPaint.setColor(mTextColor);
mPaint.setTextSize(mTextSize);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
if (mText == null) {
mText = "";
}
}
public void setTypeFace(Typeface textFont) {
mPaint.setTypeface(textFont);
}
/**
* 设置文字颜色
*
* @param color
*/
public void setTextColor(int color) {
mTextColor = color;
invalidate();
}
/**
* 设置点和文字的间距
*/
public void setDotPadding(int padding) {
mDotPadding = padding;
refresh();
}
/**
* 设置文字
*
* @param text
*/
public void setText(String text) {
if (text == null) {
return;
}
this.mText = text;
refresh();
}
/**
* 设置文字 index为需要画点的文字角标
*
* @param text
*/
public void setTextWithDotIndex(String text, int... index) {
if (text == null) {
return;
}
this.mText = text;
mDotIndexs = index;
refresh();
}
private void refresh() {
requestLayout();
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
mPaint.setColor(mTextColor);
mPaint.getTextBounds(mText, 0, mText.length(), mBound);
canvas.drawText(mText, 0, mBound.height() + mDotRadius * 2 + mDotPadding * 2, mPaint);
if (mDotIndexs == null) {
return;
}
for (int index : mDotIndexs) {
drawDot(canvas, index);
}
}
/**
* 绘制点
*
* @param canvas
* @param i
*/
private void drawDot(Canvas canvas, int i) {
if (i >= mText.length()) {
return;
}
Rect dotBound = new Rect();
mPaint.getTextBounds(mText, 0, i, dotBound);
int left = dotBound.right;
mPaint.getTextBounds(mText, 0, i + 1, dotBound);
float x = (dotBound.right + left) / 2.0f + mDotRadius;
canvas.drawCircle(x, mDotRadius + mDotPadding, mDotRadius, mPaint);
}
}
3.1 onDraw并不是绘制全部
这里我们看View里的源码就好了,View里draw方法,关键代码和注释如下@CallSuper public void draw(Canvas canvas) { ...... // Step 1, draw the background, if needed if (!dirtyOpaque) { drawBackground(canvas); } ..... // Step 2, save the canvas' layers ..... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ...... // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); ..... }
简单翻译一下 ,
1.如果需要,绘制背景 2.保存canvas状态 3.绘制内容,也就是调用ondraw方法 4.绘制child 5.绘制阴影等效果,恢复canvas的状态 6.绘制前景色,绘制scrollbars等
所以即使自定义view的时候,background,ondraw内容,child,Foreground等是层级绘制上去的,后面的会覆盖前面(如果有重叠)。
实际上里面还有
4.总结
onMeasure测量有3种模式,关键类MeasureSpec文字的测量需要使用TextPaint,Layout,Paint类的getTextBounds获取的是文字的最小区域,
常用的布局(LinearLayout等)都是在onLayout里处理其特性
画文字是从文字底部开始绘制
绘制顺序:background->onDraw->绘制child->绘制阴影-> Foreground
相关文章推荐
- read the fucking source code
- Read the fucking source code
- Bozh 的技术博客 梦想成为Gnu/Linux | Unix后台架构师 | Read the fucking source code
- jscese 知其白 守其黑 為天下式 __Read The Fucking Source Code的博客汇总
- RTFSC:Read The Fucking Source Code
- Read The Fucking Source Code 学习记录(1)-----目录
- RTFSC (Read the fucking source code )
- View the format of IEEE 754 floating point numbers source code
- Units Problem: How to read text size as custom attr from xml and set it to TextView in java code
- View the android source code in Eclipse
- read the f*cking source code
- Units Problem: How to read text size as custom attr from xml and set it to TextView in java code
- Units Problem: How to read text size as custom attr from xml and set it to TextView in java code
- 自定义TextView实现跑马灯效果
- AutoCompleteTextView 实现自定义匹配规则提示
- 在DTCoreText 中添加自定义的文章头,自定义View
- (原创)自定义view(view的绘制过程)、无限轮播并触碰停止轮播的viewpage、水平和垂直滚动的TextView、仿QQ滑动删除、下拉刷新上拉加载view、毛玻璃效果、低版本水波纹、圆环头像图
- 你还在使用Handler做计时器么?来一个自定义TextView解决这些困扰吧!
- Android 自定义TextView 实现文本间距
- android 自定义Textview多层边框,实现了文字内容闪烁的功能!