Android View系统分析01

1 View的绘制流程简单介绍
View是Android系统中很重要的一个部分,在Android的官方文档中是这样描述的:表示了用户界面的基本构建模块。一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理。

而 Activity 相当于视图层中的控制层,是用来控制和管理 View 的,真正用来显示和处理事件的实际上是 View,当我们在 Activity 中调用 setContentView();并传入一个 View 或者一个 LayoutId,界面上就会显示设置的 View 出来,setContentView()的过程稍后分析。

一个 view 要显示在界面上,需要经历一个 view 树的遍历过程,这个过程又可以分为三个过程,分别是:

测量 确定一个View的大小
布局 确定view在父节点上的位置
绘制 绘制view的内容
这个过程的启动由一个叫ViewRoot(之后修改为ViewRootImpl)类中performTraversals()函数发起的,子view也可以通过一些方法来请求重绘view树,但是在重绘view树时并不是所有的view都需要重新绘制,所在在view树的遍历过程中,系统会问view是否需要重新绘制,如果需要才会真的去绘制view。

这个实现在 view 的 mPrivateFlag 中,

View中有一个私有int变量mPrivateFlags,用于保存View的状态,int型32位,通过0/1可以保存32个状态的true或者false,采用这种方式可以有效的减少内存占用,提高运算效率,应该可以把这种方式叫做二进制映射。关于这个mPrivateFlags会在view的绘制流程中进行学习。
当某一个View发起了测量请求时,将会把mPrivateFlags中的某一位从0变为1,同时请求父View,父View也会把自身的该值从0变为1,同时也将会把其他子View的值从0变为1。这样一层一层传递,最终传到到DecorView,DecorView的parent是ViewRoot,所以最终都将由ViewRoot来进行处理。
ViewRoot收到请求后,将会从上至下开始遍历,检查标记,只要有相对应的标记就执行测量/布局/绘制
流程图如下所示:
在这里插入图片描述
2 View树的构建流程,从setContentView说起
当我们在Activity中调用setContentView();并传入一个View或者一个ViewId,界面上就会显示我们设置的View,那么这个setContentView()到底做了什么事呢?从源码中找答案。

进入Activity源码可以看到如下代码:

private Window mWindow;

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

调用的是getWindow().setContentView(layoutResID);而getrWindow返回的是mWindow,在代码中可以看到mWindow是这样被初始化的:

mWindow = new PhoneWindow(this);

PhoneWindow是 Window 的子类,从 Window 的注释我们也可以得到 PhoneWindow 是 Window 的实现,而 Window 是对安卓窗口概念的抽象:

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.policy.PhoneWindow, which you should instantiate when needing a
 * Window.  Eventually that class will be refactored and a factory method
 * added for creating Window instances without knowing about a particular
 * implementation.
 */
public abstract class Window {......}

一个抽象的基础的顶级窗口类,定义的基本的行为和外观政策,这类的实例应该用作顶层视图添加到窗口管理器,它提供了标准UI政策背景等标题区域,默认键处理等。

继续跟踪PhoneWindow,下面是PhoneWindow的源码部分,可以看到其内部定义了平时开发中用到的各种元素。

/**
 * Android-specific Window.
 * <p>
 * todo: need to pull the generic functionality out into a base class
 * in android.widget.
 */
public class PhoneWindow extends Window implements MenuBuilder.Callback {
      ......
      private boolean mIsFloating;

      private LayoutInflater mLayoutInflater;

      private TextView mTitleView;
......

}

查看setContentView的源码实现:如果是view则会为其设置一个ViewGroup的LayoutParams,宽高都为匹配父元素,添加到mContentParent中,如果是布局id,先用布局填充器来解析布局id指定的xml文件,然后添加到mContentParent中,那mContentParent怎么初始化的呢?很明显是在installDecor()中被初始化的。

  @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();//安装decorView
        } else {
            mContentParent.removeAllViews();//如果已经安装过,就移除之前的conent,重新设置
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else {
        mContentParent.removeAllViews();
    }
    mContentParent.addView(view, params);
    final Callback cb = getCallback();
    if (cb != null) {
        cb.onContentChanged();
    }
}

接下来先看布局填充器如何解析xml布局

2.1 布局填充器LayoutInflate解析xml布局分析
首先mLayoutInflate是这样被初始化的

mLayoutInflater = LayoutInflater.from(context);

查看 LayoutInfater 源码:

/**
 * 这个类用来实例化xml中定义的view节点,
 * 使用Activity#getLayoutInflater()或者 Context#getSystemService}获取一个标准的LayoutInflater实例
*/
  public abstract class LayoutInflater {

    //...... 省略很多代码

    /**
     * 比如通过from方法获取一个LayoutInflater实例
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

    ......

}

可以看到可以看到 LayoutInflater 是个抽象类,LayoutInflater使用pull解析的方式来解析xml,而它的继承者是PhoneLayoutInflater,可以在Policy类中找到相关构建过程。

具体的解析xml 布局流程
其具体的解析xml布局实现为下面代码,下面贴出了所有相关的方法

    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        if (DEBUG) System.out.println("INFLATING from resource: " + resource);
            //xml构建一个xml解析器
            XmlResourceParser parser = getContext().getResources().getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

    /**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * 从指定的节点inflate一个新的view层
     */
    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {//解析需要同步,(同步可能导致低效,代码构建view层优于xml解析)
            final AttributeSet attrs = Xml.asAttributeSet(parser);//解析属性
            /**
             private final Object[] mConstructorArgs = new Object[2];
            */
            Context lastContext = (Context)mConstructorArgs[0];
            mConstructorArgs[0] = mContext;
            View result = root;//定义根节点view,首先指向root
            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

               //这里处理了merge节点
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {//可以看到merge的使用条件
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //解析merge节点
                    rInflate(parser, root, attrs);
                } else {
                    // Temp is the root view that was found in the xml
                    View temp = createViewFromTag(name, attrs);//从tag创建view

                    //构建LayoutParams
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);//从我们指定的root构建LayoutParams
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    // Inflate all children under temp,解析子节点
                    rInflate(parser, temp, attrs);
                    //关于attach参数的处理
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    //关于返回值的处理
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (IOException e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                        + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }
            return result;
        }
    }

    /**
     * 根据指定的名称,前缀,属性创建一个view
     * @return View The newly instantied view, or null.
     */
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor constructor = sConstructorMap.get(name);
        Class clazz = null;
        try {
            if (constructor == null) {//从构造器的缓存中获取构造器
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name);
                
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                sConstructorMap.put(name, constructor);//添加到容器中
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name);
                        
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object[] args = mConstructorArgs;
            args[1] = attrs;
            return (View) constructor.newInstance(args);

        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;

        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()));
            ie.initCause(e);
            throw ie;
        }
    }

  

    /**
     * This routine is responsible for creating the correct subclass of View
     * given the xml element name. Override it to handle custom view objects. If
     * you override this in your subclass be sure to call through to
     * super.onCreateView(name) for names you do not recognize.
     * 
     可以看到这里调用了createView,并传入"android.view."前缀,其实就是Android系统View的包名了,所以我们可能
     在xml中只写系统view的类名
     */
    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

    /*
     * 默认构建view的方法
     */
    View createViewFromTag(String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        try {
            View view = (mFactory == null) ? null : mFactory.onCreateView(name,
                    mContext, attrs);

            if (view == null) {//判断是否是系统级别view
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(name, attrs);
                } else {//自定义view需要写全路径名
                    view = createView(name, null, attrs);
                }
            }

            return view;

        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;

        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
        }
    }

    /**
     * 这里是用递归的方法构建view的所有层级。当view从xml中初始化它的所有子view之后,会调用onFinishInflate()方法
     */
    private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
            throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();
            
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(name, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs);
                viewGroup.addView(view, params);
            }
        }

        parent.onFinishInflate();
    }

    private void parseRequestFocus(XmlPullParser parser, View parent)
            throws XmlPullParserException, IOException {
        int type;
        parent.requestFocus();
        final int currentDepth = parser.getDepth();
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
            // Empty
        }
    }
    
    //处理include节点
    private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
            throws XmlPullParserException, IOException {

        int type;

        if (parent instanceof ViewGroup) {
            final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
            if (layout == 0) {
                final String value = attrs.getAttributeValue(null, "layout");
                if (value == null) {
                    throw new InflateException("You must specifiy a layout in the"
                            + " include tag: <include layout=\"@layout/layoutID\" />");
                } else {
                    throw new InflateException("You must specifiy a valid layout "
                            + "reference. The layout ID " + value + " is not valid.");
                }
            } else {
                final XmlResourceParser childParser =
                        getContext().getResources().getLayout(layout);

                try {
                    final AttributeSet childAttrs = Xml.asAttributeSet(childParser);

                    while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                            type != XmlPullParser.END_DOCUMENT) {
                        // Empty.
                    }

                    if (type != XmlPullParser.START_TAG) {
                        throw new InflateException(childParser.getPositionDescription() +
                                ": No start tag found!");
                    }

                    final String childName = childParser.getName();

                    if (TAG_MERGE.equals(childName)) {
                        // Inflate all children.
                        rInflate(childParser, parent, childAttrs);
                    } else {
                                                   ViewGroup.LayoutParams params = null;
                        try {
                            params = group.generateLayoutParams(attrs);
                        } catch (RuntimeException e) {
                            params = group.generateLayoutParams(childAttrs);
                        } finally {
                            if (params != null) {
                                view.setLayoutParams(params);
                            }
                        }

                        // Inflate all children.
                        rInflate(childParser, view, childAttrs);
                        TypedArray a = mContext.obtainStyledAttributes(attrs,
                            com.android.internal.R.styleable.View, 0, 0);
                        int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
                        // While we're at it, let's try to override android:visibility.
                        int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
                        a.recycle();

                        if (id != View.NO_ID) {
                            view.setId(id);
                        }

                        switch (visibility) {
                            case 0:
                                view.setVisibility(View.VISIBLE);
                                break;
                            case 1:
                                view.setVisibility(View.INVISIBLE);
                                break;
                            case 2:
                                view.setVisibility(View.GONE);
                                break;
                        }

                        group.addView(view);
                    }
                } finally {
                    childParser.close();
                }
            }
        } else {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }

        final int currentDepth = parser.getDepth();
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
            // Empty
        }
    }  

首先其解析xml的方式是pull解析,inflate方法的三个参数都非常重要,稍后分析。

方法的开发是对xml规范的一些判断,不符合直接抛异常,如对merge的处理是父view必须非null而且必须添加到父view中去:

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                }

接下来看一个view的初始化,是通过 View temp = createViewFromTag(name, attrs); 方法继续初始化的,关联方法是createView,从其内部逻辑看出,xml中的View都是通过反射进行实例化的

下面是关于inflate方法三个参数的逻辑:

                    View temp = createViewFromTag(name, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }

第一步:如果 root 非 null,attachToRoot 为 false
ViewGroup.LayoutParams会被初始化,调用的是root.generateLayoutParams(attrs)。将创建的 LayoutParams 设置给被创建的 view。

第二步:如果 root 非 null 并且 attachToRoot 为 true
LayoutParams设置给被创建的view,并且被创建的view添加到root总作为子view

第三步:如果 root 为 null 并且 attachToRoot 为 false
则只是单纯的创建一个View了

有一点需要注意的是LayoutInflater的返回值问题,如果 root 非 null 并且 attachToRoot 为 true 返回的 View 是 root,否则为 inflate 的根 View。

到这里应该可以明白为何有时候,在平时的 inflate 方法调用时,如果 root 传空的话,根布局的 layout_width 等属性都是无效的!!!

当一个节点调用完毕,又会调用rInflate(parser, temp, attrs);进行递归解析

在rInflate函数中还可以看到 parent.onFinishInflate();函数的调用时机。

以上基本就是xml布局解析的过程,更多具体细节可以参考源码。

2.2 installDecor() 的源码实现分析
源码如下:

private void installDecor() {
        //初始化decorView
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        //初始化ContentParent
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            //查找title
            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                //FEATURE_NO_TITLE的处理
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            }
        }
    }

可以看到如果mDecor为null的话会调用generateDecor初始化一个DecorView,代码如下:

  protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
}

代码很简单就是直接创建了一个DecorView,那么来看看DecorView的实现:

 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{
      ......
}

DecorView是PhoneWindow的内部类,并且是final的,集成自FrameLayout,其实DecorView是每一个View树的跟布局。

接下来还有一个重点:mContentView的初始化

mContentParent = generateLayout(mDecor);

根据一系列的风格判断,最终确定 ContentView 的布局id,比如 mIsFloating、Window_windowNoTitle 等等…

确定好id之后会进行解析,然后添加到 decorView 中,最后初始化mContentView

 View in = mLayoutInflater.inflate(layoutResource, null);
 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

ID_ANDROID_CONTENT的值为:com.android.internal.R.id.content;

上面说的根据一系列窗口特点和系统风格确定好布局id,style是在xml的theme中指定,那么怎么改变窗口的特征呢?其实就是requestFeature这个方法了,但是其内部有一段代码是这样的:

if (mContentParent != null) {
    throw new AndroidRuntimeException("requestFeature() must be called before adding content");
}

也就是说,requestFeature必须在setContentView之前调用。

分析到这里也可以明白,有写时候不希望界面显示title,我们会调用:

  getWindow().requestFeature(Window.FEATURE_NO_TITLE);

其实原理就是在构建view树的时候,根据设置的窗口特性,去解析不同的布局xml

比如一般解析的是:screen_title







如果没有title则会解析screen_simple


到此View树的创建分析完毕。

2.3 总结
Activity被创建后,会调用Activity的onCreate方法。我们通过设置setContentView就会调用到Window中的setContextView,从而初始化DecorView。

我们需要隐藏标题栏什么的,都需要在DecorView初始化之前进行设置。

3 View的mPrivaeFlag简要说明
刚刚说到,view的重新绘制过程中并不是需要绘制view树种的所有view,这由view的一些flag决定,

例如view的draw方法中:

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        //判断是否需要绘制,View的是否绘制由mPrivateFlags中一位标识,
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        //重置
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // 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;
        }
        ......}

一般情况下viewGroup的ondraw方法是不会被调用,因为没有可以绘制的内容,这时它就是透明的状态,对于不透明的计算条件有一个方法computeOpaqueFlags:

protected void computeOpaqueFlags() {
        // Opaque if:不透明条件
        //   - Has a background 
        //   - Background is opaque 
        //   - Doesn't have scrollbars or scrollbars overlay

        if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
            mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
        } else {
            mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
        }

        final int flags = mViewFlags;
        if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
            mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
        } else {
            mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
        }
    }

setWillNotDraw方法,其实也是对mPrivateFlat进行这种运算。

    /**
     * If this view doesn't do any drawing on its own, set this flag to
     * allow further optimizations. By default, this flag is not set on
     * View, but could be set on some View subclasses such as ViewGroup.
     *
     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
     * you should clear this flag.
     *
     * @param willNotDraw whether or not this View draw on its own
     */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

猜你喜欢

转载自blog.csdn.net/liaochaoyun/article/details/87875687
今日推荐