自定义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的自定义了
































  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值