自定义View-onDraw篇(1)
1、<a href='http://www.jianshu.com/p/f6623f00b4a0'>写给新人看的自定义View-onMeasure篇(1)</a>
2、<a href='http://www.jianshu.com/p/fb687e55b0d1'>写给新人看的自定义View-onMeasure篇(2)</a>
3、<a href='http://www.jianshu.com/p/a5b1e778744f'>写给新人看的自定义View-onLayout篇</a>
onDraw我觉得是最繁琐,最复杂的一个步骤,因为我每次写坐标,都很费事,可能我对坐标本身并不敏感,虽然很费事,但是绘制成功之后,还是成就感大大滴。
Paste_Image.png
最后待我们分析完后,一起来使用onDraw方法,画上图中的效果。
Path和属性动画一起使用,大家可以先思考一下.
最近有人问我,知道这些自定义View的流程,就是像onMeasure , onLayout ,onDraw 有什么用呢?项目中不太常用到,而且,就算用到了,也可以直接去github上找对应的库。而且平时自定义View都是直接继承LinearLayout,和Relativelayout,然后直接LayoutInfalte就可以了,根本不需要重写这些流程。
我觉得呢,这些步骤还是非常有用的,应该是非常非常有用,不知道大家有没有遇到,在繁琐的Xml布局中,有时候给控件wrap_content,会出现自己不想要的结果,或者嵌套自定义View,宽高也不是自己想要的,这种情况发生的时候,大多数情况下,都是去反复的调试,可能真的能调试出自己的想要的样子,但是却不知道为什么,下次出现了,又不会了。所以,知道这些流程,能帮助我们快速定位到布局中出现问题的地方,并且以后不会再触碰这些坑。。
OK,废了一些话,步入正题
国际惯例,还是先看一下源码
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
.......
}
代码实在忒多了,一点一点的来。
直接挑选重点看。
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
谷歌大大已经为我们标注好了大概的流程,我们来看一下。
第一步:画View的背景,也就是为这个View搞一个背景,我们在Xml文件中background属性。
第二步:这步我们暂时用不到(做了几个项目,也写了很多自定义View还没涉及到这个),源码也上也是说,如果有必要的话,在去分析。我们为了新人也能懂(其实,我也不知道),这边先不分析。先放一下。
第三步:画View的内容。
第四部:如果有子View,就画子View
第五步:如果必要。。。。 同第二步,现在对我们来说不是很必要。放一下。
第六步:画decorations(装饰品) ,啥是装饰品,也就类似于,我们View上面的滑动条啊,滚动条之类的,给我们View做个装饰。
第七步:没有第七步了。。。哈哈。没错,我是个智障。
if (!dirtyOpaque) {
drawBackground(canvas);
上面的代码已经可以看到第一步了,drawBackground。
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mHardwareRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
....
}
代码不多,mBackground是通过我们传递的resId生成的drawable,然后这个步骤,将Drawable画在View的背景上
第三步
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
哎呀,很快就看到今天的主角了。View绘制自己的内容,调用了onDraw方法。传了一个canvas进去。OK,这个onDraw是留给子类自己实现的,我们已经都知道了。继续看下面.
// Step 4, draw the children
dispatchDraw(canvas);
绘制子View,其实这个方法也是由子View(ViewGroup)实现的,我们可以看看ViewGourp是怎么实现的这个方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
是滴,很一针见血,直接调用了子View的onDraw方法。其实看一下LinearLayout的dispatchDraw方法就是知道,遍历子View,然后做了一些动画操作,之后调用了子View的onDraw方法。对对对,就是这样。这里不贴源码了,有兴趣的同学可以自行查看LinearLayout的dispatchDraw方法。
第五步:
和第二步一样,我们先不考虑,不是很有必要考虑他。
第六步:
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
画装饰
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
这里面主要是画一些滚动条,滑动指示器之类的。像我们的ListView,GridVIew。其实我们像我们的TextView,Button最基本的控件,也是有这些装饰品,只不过我们没有让他们显示出来。
OK,暂且先分析这几个基本的步骤。下面来看onDraw。
先来看几个东西
Canvas : 画什么; 比如我想画矩形,圆形,不规则图形
Bitmap : 载体;我画的东西,展示在这个上面
Paint : 画笔,怎么画;比如:我要画的东西要是黑色的,并且有边框,边框为5px,抗锯齿。
这三个东西是不可分开的,我们要将图形展示在界面上,这三个缺一不可。
好,现在看一个demo
public class MyView extends View {
Paint mPaint;
public MyView(Context context) {
this(context, null);
initView();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
initView();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0,0,100,100,mPaint);
}
private void initView() {
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//只测试一下宽度就好
switch (widthMode) {
//从ViewGroup的源码中可以看出,
case MeasureSpec.AT_MOST:
//我们这里写死了200
widthSize = 200;
break;
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.UNSPECIFIED:
break;
}
setMeasuredDimension(widthSize, heightSize);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0,0,100,100,mPaint);
}
OK ,我们画了一个矩形,是个蓝色的矩形。
有些人可能有疑问了,不是说好Canvas Paint Bitmap是牢牢不可分的吗?Canvas Paint 有了,Bitmap呢? 其实 这个Canvas传来的时候,已经绑定好了Bitmap,所以我们在这边才能看到。
未完待续......