自定义View(一)
一直忙着工作,有时候去查别人写的自定义View ,这几天有点时间 在安卓巴士上找到了一本《Android自定义开发详解》,感觉对初期的学习挺有帮助的,所以做点笔记,
首先,要清楚的是activity的组件的组成 (图片来源于网络)
Window 负责管理窗口,具体来说就是PhoneWindow ,窗口的绘制有DecorView完成,开发的时候自己定义的layout将会是DecorView的子视图ContentParent的子视图 ,PhoneWindow 关联了一个mWindowManager 的Windowmanager 对象,WindowManager会创建一个ViewRootImpl 对象和WindowmanagerService 进行交互,WindowManagerService 可以进行获取触摸事件、键盘事件、轨迹球事件并且通过ViewRootImpl将这些事件发送给Activity,ViewRootImpl 负责整个Activity的绘制
ViewRootImpl 绘制View树的具体流程,其实是通过它的一个方法开始绘制的performTraversals() 但是这个方法有几百行代码,查看的时候就三个位置
private void performTraversals() { ........ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
performLayout(lp, mWidth, mHeight);....
performDraw()
..... }
代码确实挺多的,但是View的绘制流程只需要知道这三个方法就OK了
1、performMeasure() 负责测量尺寸,查看performMeasure()的源码
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }mView 是View的对象 ,调用了measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 。。。 onMeasure(widthMeasureSpec, heightMeasureSpec); 。。。。 }只观察其中的这句onMeasure()方法 ,是为测量组件尺寸预留的功能接口,
⚠️ 如果测量的是容器的尺寸,而容器的尺寸依赖于自组件的尺寸,所以要先测量容器中自组件的尺寸,不然测出来的宽高都将会是0
2、performLayout() 定位自组件的位置
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { .... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ..... // Check the valid requests again, this time without checking/clearing the // layout flags, since requests happening during the second pass get noop'd validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true); if (validLayoutRequesters != null) { final ArrayList<View> finalRequesters = validLayoutRequesters; // Post second-pass requests to the next frame getRunQueue().post(new Runnable() { @Override public void run() { int numValidRequests = finalRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = finalRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame"); view.requestLayout(); } } }); } } } } ... }host是View的根试图DecorView ,也就是最外层的容器,容器的位置在左上角(0,0)的位置,看一下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); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } 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; }
如果在定位之前要进行测量组件的大小,则调用onMerasure() 方法,然后调用setOpticalFrame() 或者setFrame() 方法进行定位和大小,此时,只是确定了她的值,但是和绘制没有一毛钱关系,随后再调用onLayout()方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }然而onLayout 方法只是一个空方法,why?? 其实,onLayout方法的作用是当组件是容器的时候,进行子组件的位置的定位,当然,这就相当于是一个循环或者递归的过程了,当他的子组件也是一个容器的时候它将会进行相同的工作,直到所有的组件定位完成,她的工作才会完成,
3、performDraw() 绘制子 View
private void performDraw() { ..... draw(fullRedrawNeeded); ..... }同样的只看其中的draw()方法
private void draw(boolean fullRedrawNeeded) {。。。。。。 mAttachInfo.mTreeObserver.dispatchOnDraw(); if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } if (animating) { mFullRedrawNeeded = true; scheduleTraversals(); } }它的源码也很多 ,只看其中的drawSoftware()方法 如果你问我为什么那么多代码你就知道要看drawSoftware方法,这个是在看源码的时候,要结合方法的命名,google的工程师他们有一个非常良好的命名规则,然后继续看drawSoftware方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; canvas = mSurface.lockCanvas(dirty); // The dirty rectangle can be modified by Surface.lockCanvas() //noinspection ConstantConditions if (left != dirty.left || top != dirty.top || right != dirty.right || bottom != dirty.bottom) { attachInfo.mIgnoreDirtyState = true; } // TODO: Do this in native canvas.setDensity(mDensity); } catch (Surface.OutOfResourcesException e) { handleOutOfResourcesException(e); return false; } catch (IllegalArgumentException e) { Log.e(mTag, "Could not lock surface", e); // Don't assume this is due to out of memory, it could be // something else, and if it is something else then we could // kill stuff (or ourself) for no reason. mLayoutRequested = true; // ask wm for a new surface next time. return false; } try { if (DEBUG_ORIENTATION || DEBUG_DRAW) { Log.v(mTag, "Surface " + surface + " drawing to bitmap w=" + canvas.getWidth() + ", h=" + canvas.getHeight()); //canvas.drawARGB(255, 255, 0, 0); } // If this bitmap's format includes an alpha channel, we // need to clear it before drawing so that the child will // properly re-composite its drawing on a transparent // background. This automatically respects the clip/dirty region // or // If we are applying an offset, we need to clear the area // where the offset doesn't appear to avoid having garbage // left in the blank areas. if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } dirty.setEmpty(); mIsAnimating = false; mView.mPrivateFlags |= View.PFLAG_DRAWN; if (DEBUG_DRAW) { Context cxt = mView.getContext(); Log.i(mTag, "Drawing: package:" + cxt.getPackageName() + ", metrics=" + cxt.getResources().getDisplayMetrics() + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); } try { canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { if (!attachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call attachInfo.mIgnoreDirtyState = false; } } } finally { try { surface.unlockCanvasAndPost(canvas); } catch (IllegalArgumentException e) { Log.e(mTag, "Could not unlock surface", e); mLayoutRequested = true; // ask wm for a new surface next time. //noinspection ReturnInsideFinallyBlock return false; } if (LOCAL_LOGV) { Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost"); } } return true; }到这里,代码就愈来愈熟悉了,因为它是由Canvas 来绘制完成的,当然它还调用了Draw()方法
在这里主要做了两件事,一件事是调用父类去绘制自己,而是将位图绘制到canvas上,具体查看View的draw 方法,这个方法有点多,
draw方法具体做的事有哪些呢?总结一下就有
1、background.draw(canvas) 绘制背景
2、onDraw(canvas) 绘制自己
3、dispatchDraw(canvas) 绘制子视图
4、onDrawScrollbars(canvas) 绘制滚动条
这就是view的绘制过程,最后,绘制View的一个过程主要有三个方法分别是:
1、onMasure() 测量大小的时候回调的方法
2、onLayout() 组件定位的时候回调
3、onDraw() 绘制组件的时候回调
整个的View 绘制的过程就在这里,下面将要学习的就是View的自定义了