Android进阶之路Android知识Android技术知识

android基础-view的测量,布局,绘制

2019-02-27  本文已影响12人  return_toLife

知识点

  1. view的测量
  2. view的布局
  3. view的绘制

android中的view显示方式主要就是测量出大小→决定在哪个位置→最后进行绘制


一、view的测量

view的测量是通过强大的MeasureSpec类帮助测量的,而关于该类起初我们只要了解它是一个32位的int值,其中高2位是用于标识当前view的测量模式,低30位就是用于记录view的大小。更多关于该类的知识可以查看官方文档MeasureSpec

view的测量模式有三种:

在view的测量中,系统默认的大小计算方法如下(API 27):

/**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    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 ,则采用系统默认大小,其余为measureSpec中所测量的大小,根据这个测量思路我们在自定义view的时候就可以采用自己的测量方式

举个例子:

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int resultWidth;
        int resultHeight;

        int specWMode = MeasureSpec.getMode(widthMeasureSpec);
        int specWSize = MeasureSpec.getSize(widthMeasureSpec);
         resultWidth=myMeasure(specWMode,specWSize, Dp2PxUtil.dip2px(mContext,200));
        
        int specHMode = MeasureSpec.getMode(heightMeasureSpec);
        int specHSize = MeasureSpec.getSize(heightMeasureSpec);

               resultHeight=myMeasure(specHMode,specHSize,Dp2PxUtil.dip2px(mContext,300));
        setMeasuredDimension(resultWidth,resultHeight);
    }

    /**
     * 
     * @param specMode 测量模式
     * @param specSize 测量大小
     * @param result  在非精确测量模式中用来约束的大小
     * @return
     */
    private  int myMeasure(int specMode,int specSize,int result){
        if(specMode==MeasureSpec.EXACTLY){
            result=specSize;
        }else if(specMode==MeasureSpec.AT_MOST){
            result=Math.min(specSize,result);
        }else {
            
        }
        return result;
    }

测试结果:

结果 备注
指定大小为100dp*100dp.png 设置自定义view的宽高为100dp*100dp
填充父布局.png 设置自定义view的宽高为match_parent
自适应.png 设置自定义view的宽高为wrap_content(实际根据我们的测量方法设置的是宽高为200dp*300dp)
不重写.png 如果没有重写自定义view中的onMeasure方法,并且设置宽高为wrap_content的时候,也是填充父布局,具体原因可以查看系统测量默认大小的源码

二、view的布局

与view布局相关主要就有两个方法:
layout(int l, int t, int r, int b) :
onLayout(boolean changed, int left, int top, int right, int bottom) :

2.1 onLayout
该方法在自定义view中一般不需用重写,其常用viewgroup通知子view进行布局Layout的时候使用

2.2 layout
view中源码如下(API 27)

   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;

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

我们不必要知道该方法的全部流程以及作用,主要看下面这行代码:
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
这行代码涉及了三个方法isLayoutModeOptical,setOpticalFrame,setFrame。isLayoutModeOptical看注释说是判断是否使用光学边界,这个我们也可以不管,主要看setOpticalFrame,setFrame这两个方法
我们先看setOpticalFrame:

 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);
    }

可以看到最终return的方法也是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;
            mDefaultFocusHighlightSizeChanged = true;
            if (mForegroundInfo != null) {
                mForegroundInfo.mBoundsChanged = true;
            }

            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
        return changed;
    }

这个方法我们也不用全部理解,主要看到它进行了当前位置参数和传进来的位置参数是否相等,如果不相等就进行重新绘制,看到这里,我们就知道了layout()方法如果传进去的位置和之前的位置参数不一样就会重新绘制该view,那么在viewgroup需要定位子view位置的时候,我们就可以调用每个子view的layout方法来重新给子view设置位置

三、view的绘制

当有了view的大小以及view的位置信息之后,我们就可以在屏幕上绘制该view了, view的绘制比较简单,我们可以直接重写onDraw方法

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

该方法会传递一个Canvas(画布)过来,我们只需要创建一个Paint(画笔)之类的在该画布上进行绘制就可以了

总结

view的测量,布局,绘制是基础中比较重要的,因为后续的一些复杂的特效,动画都可以由自定义view去实现,理解清楚其基本的绘制流程对后续开发会很有帮助

参考文章

《Android群英传》

上一篇 下一篇

猜你喜欢

热点阅读