View的绘制流程源码解析

从ActivityThread.handleResumeActivity()进行阐述

  当创建Activity时,调用AMS通过Binder机制调用ApplicationThread对象的方法,进而调用ActivityThread中的handleResumeActivity()方法

handleResumeActivity方法是在TransactionExecutor.java类中performLifecycleSequence()方法调用过来的
TransactionExecutor.java

 private void performLifecycleSequence(ActivityClientRecord r, IntArray path,
            ClientTransaction transaction) {
        // 通过mHelper调用getLifecyclePath返回的path 是 ON_START
        final int size = path.size();
        for (int i = 0, state; i < size; i++) {
            state = path.get(i); // 执行startActivity操作,path其实是保存了ONCREATE、ON_START、ONRESMUME三种状态
            if (DEBUG_RESOLVER) {
                Slog.d(TAG, tId(transaction) + "Transitioning activity: "
                        + getShortActivityName(r.token, mTransactionHandler)
                        + " to state: " + getStateName(state));
            }
            switch (state) {
                case ON_CREATE:
                    mTransactionHandler.handleLaunchActivity(r, mPendingActions,
                            null /* customIntent */);
                    break;
                case ON_START:
                    mTransactionHandler.handleStartActivity(r, mPendingActions,
                            null /* activityOptions */);
                    break;
                case ON_RESUME:
                    mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
                            r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
                    break;
                case ON_PAUSE:
                    mTransactionHandler.handlePauseActivity(r, false /* finished */,
                            false /* userLeaving */, 0 /* configChanges */, mPendingActions,
                            "LIFECYCLER_PAUSE_ACTIVITY");
                    break;
                case ON_STOP:
                    mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
                            mPendingActions, false /* finalStateRequest */,
                            "LIFECYCLER_STOP_ACTIVITY");
                    break;
                case ON_DESTROY:
                    mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
                            0 /* configChanges */, false /* getNonConfigInstance */,
                            "performLifecycleSequence. cycling to:" + path.get(size - 1));
                    break;
                case ON_RESTART:
                    mTransactionHandler.performRestartActivity(r, false /* start */);
                    break;
                default:
                    throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
            }
        }
    }

ActivityThread.java

@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
        ...
        // 代码1
        // 执行onResume()流程
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        ...
         if (r.window == null && !a.mFinished && willBeVisible) {
			// 拿到window,也就是PhoenWindow,PhoneWindow的创建是在Activity类的attach()方法中
			// mWindow = new PhoneWindow(this, window, activityConfigCallback);
            r.window = r.activity.getWindow();
			// 拿到DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
			// 获得WindowManager,WindowManager也是在Activity类的attach()方法中进行设置
			/* mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
                */
            ViewManager wm = a.getWindowManager();
			// 拿到window的LayoutParams参数
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    // 代码2
					// 执行WindowManger的addView(),WindowManager实现ViewManager接口, 
					// WindowManager是一个接口,具体实现是WindowManagerImpl
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
}

  从这两处代码我们就可以知道,如果onResume()流程还没执行完,获取控件的getMeasureWidth()和getMeasureHeight()值都是为0。

这里有两个地方需要注意下:
1:代码1 performResumeActivity(...)
这行代码就是执行onResume()流程

–> performResumeActivity
  --> r.activity.performResume
    --> mInstrumentation.callActivityOnResume

ActivityThread.java

@VisibleForTesting
public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        String reason) {
        ... 
        // 直接跳转到Activity的performResume()
        r.activity.performResume(r.startsNotResumed, reason);
		...
}

Activity.java

final void performResume(boolean followedByPause, String reason) {
	...
	// 执行Instrumentation的callActivityOnResume()方法
    mInstrumentation.callActivityOnResume(this);
	...
}

Instrumentation.java

public void callActivityOnResume(Activity activity) {
     activity.mResumed = true;
     // 启动应用的onResume()方法
     activity.onResume();
     
     if (mActivityMonitors != null) {
         synchronized (mSync) {
             final int N = mActivityMonitors.size();
             for (int i=0; i<N; i++) {
                 final ActivityMonitor am = mActivityMonitors.get(i);
                 am.match(activity, activity, activity.getIntent());
             }
         }
     }
 }

2:代码2 wm.addView(decor, l);
  其中,wm指WindowManager,decor指DecorView,WindowManager是一个接口,具体实现是WindowManagerImpl,从而跳转到WindowManagerImpl的addView()方法。

–> wm.addView(decor, l); (WindowManagerImpl.java)
  --> WindowManagerGlobal.addView
    --> root = new ViewRootImpl(view.getContext(), display);
    -->mViews.add(view); // DecorView
     mRoots.add(root); // ViewRootImpl
     mParams.add(wparams); //WindowManager.LayoutParams
    --> root.setView(view, wparams, panelParentView, userId);

WindowManagerImpl.java

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyTokens(params);
// WindowManagerImpl的实现就相当于一个中转,都是跳转到WindowManagerGlobal类中实现具体功能
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
            mContext.getUserId());
}

WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
    // 首先进行一些参数的检查
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    ...
	// 判定view是否已经添加,如果已经添加是不允许再次添加的,不然会报错
    if (index >= 0) {
         if (mDyingViews.contains(view)) {
             // Don't wait for MSG_DIE to make it's way through root's queue.
             mRoots.get(index).doDie();
         } else {
             throw new IllegalStateException("View " + view
                     + " has already been added to the window manager.");
         }
         // The previous removeView() had not completed executing. Now it has.
     }
     ...
     // 创建ViewRootImpl对象
     root = new ViewRootImpl(view.getContext(), display);

      view.setLayoutParams(wparams);

      // 保存view、root、wparams三个参数
      mViews.add(view);  // view指DecorView
      mRoots.add(root);  // root指ViewRootImpl
      mParams.add(wparams);  // wparams指WindowManager.LayoutParams参数
	  try {
		   // 将DecorView保存到ViewRootImpl中
           root.setView(view, wparams, panelParentView, userId);
       } catch (RuntimeException e) {
           // BadTokenException or InvalidDisplayException, clean up.
           if (index >= 0) {
               removeViewLocked(index, true);
           }
           throw e;
       }
       ...
}

  上面的流程涉及到三个类:WindowManagerImpl、WindowManagerGlobal、ViewRootImpl

  • WindowManagerImpl主要功能:确定View属于哪个屏幕、哪个父窗口(确定窗口

  • WindowManagerGlobal主要功能:管理整个进程 所有的窗口信息,即主要包含view(DecorView)、root(ViewRootImpl)、wparams(WindowManager.LayoutParams)(管理信息
      每个进程都对应一个WindowManagerGlobal,也就说每个app进程都对应有自己的WindowManagerGlobal

  • ViewRootImpl主要功能:WindowManagerGlobal实际操作者,操作自己的窗口(做大量的事情,真正的执行者
    ViewRootImpl存在多个,一个窗口对应一个ViewRootImpl
    实际功能:
    ① View树的树根并管理View树
    ② 触发View的测量、布局和绘制
    ③ 输入响应的中转站
    ④ 负责与WMS进行进程间通信(通过binder)


  最后执行到 root.setView(view, wparams, panelParentView, userId);,其中root是ViewRootImpl,view是,这条语句其实就是将DecorView保存到ViewRootImpl中,从而执行到ViewRootImpl类的setView()方法。

ViewRootImpl.setView
  --> requestLayout(); // 请求遍历
    --> scheduleTraversals
      --> mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        --> doTraversal
          --> performTraversals(); // 绘制View
  --> res = mWindowSession.addToDisplayAsUser // 将窗口添加到WMS上面 WindowManagerService
  --> 事件处理
  --> view.assignParent(this); // getParent ViewRootImpl

ViewRootImpl.java

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
		...
		// 1. 请求遍历
        requestLayout();
        ...
        // 2. 将窗口添加到WMS上面
        res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(), userId,
                mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                mTempControls);
        ...
        // 3. 创建接收事件,进行事件处理
        mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                Looper.myLooper());
        ...
        // 4. 将view添加到ViewRootImpl父容器中,循环通过getparent()向上遍历树就可以获取到ViewRootImpl
        // 将ViewRootImpl作为DecorView的parent
        view.assignParent(this);
        ...
 }           

  这个方法里面主要做了这四件事,但是最主要还是requestLayout()流程。

  这里打断分析下ViewRootImpl构造函数,其中有几个参数比较重要:
ViewRootImpl.java

public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
        mContext = context;
        mWindowSession = session;
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();
        mThread = Thread.currentThread(); // 拿到创建它的线程,默认是MainThread,这个变量UI刷新的时候会用到比较
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        mWidth = -1;
        mHeight = -1;
        mDirty = new Rect(); // 脏区域,收集哪些地方需要进行修改,比如textView需要更改它的文字,那这块区域就作为脏区域
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
        mWindow = new W(this);
        mLeashToken = new Binder();
        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        mViewVisibility = View.GONE;
        mTransparentRegion = new Region();
        mPreviousTransparentRegion = new Region();
        mFirst = true; // true for the first time the view is added
        mPerformContentCapture = true; // also true for the first time the view is added
        mAdded = false;
		// 保存当前窗口的一些信息,mWindowSession就是一个session对象
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);
       ...
}

回到requestLayout()流程继续分析:
ViewRootImpl.java

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
		//检查线程,哪个线程创建了ViewRootImpl,哪个线程进行更新UI
        checkThread(); 
        mLayoutRequested = true;
  		// 执行该语句进行后续流程
        scheduleTraversals();
    }
}

   checkThread():这个在进行UI更新的时候时常会进行检查,也就是常说的只能在主线程上更新UI,其实准确的说是谁创建了ViewRootImpl,就在那个线程中更新UI。
ViewRootImpl.java

void checkThread() {
   if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

  ViewRootImpl会调用scheduleTraversals准备重绘,但是,重绘一般不会立即执行,而是往Choreography.CALLBACK_TRAVERSAL队列中添加一个mTraversalRunnable,同时申请VSYNC,这个mTraversalRunnable要一直等到申请的VSYNC到来后才会被执行。
ViewRootImpl.java

 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
 void scheduleTraversals() {
      if (!mTraversalScheduled) {
          mTraversalScheduled = true;
          mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
          // 这里面其实也是通过handler发送消息,最终执行mTraversalRunnable的run()方法
	mChoreographer.postCallback(
                  Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
          notifyRendererOfFramePending();
          pokeDrawLockIfNeeded();
      }
  }

  mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null):执行这条语句其实也是通过handler发送消息,最终执行mTraversalRunnable的run()方法。
ViewRootImpl.java

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            // 执行该语句进行后续流程
            doTraversal();
        }
    }

ViewRootImpl.java

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        // 执行该语句,进行后续的View绘制流程
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

performTraversals@ViewRootImpl.java
  -->windowSizeMayChange |= measureHierarchy(); // 预测量
  -->relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 布局窗口
  -->performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 控件树测量
  --> performLayout(lp, mWidth, mHeight); // 布局
  --> performDraw(); // 绘制

绘制流程:
在这里插入图片描述

ViewRootImpl.java

private void performTraversals() {
	...
// Ask host how big it wants to be
            // 1.首先进行预测量,最多会执行3次测量
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
            ...
            // 2.布局窗口,具体实现在WMS中
            relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
            ...
            // 3.控件树测量
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            ...
            // 4.执行layout布局
            performLayout(lp, mWidth, mHeight);
			...
			// 5.执行绘制
            performDraw();
            ...
}

performTraversals()方法主要包含5个流程:

  1. 预测量
  2. 布局窗口–》这一流程具体实现在WMS中执行
  3. 执行performMeasure()进行控件树测量
  4. 执行performLayout()进行layout布局
  5. 执行performDraw()进行绘制

1. 预测量流程:执行measureHierarchy()
ViewRootImpl.java

// 预测量其实就是子view与父控件在协商大小应该多大,这个控件的协商测量
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
    int childWidthMeasureSpec;
    int childHeightMeasureSpec;
    boolean windowSizeMayChange = false;

    if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
            "Measuring " + host + " in display " + desiredWindowWidth
            + "x" + desiredWindowHeight + "...");

    boolean goodMeasure = false;
    //只是设置了WRAP_CONTENT才需要进行协商
    if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
        // On large screens, we don't want to allow dialogs to just
        // stretch to fill the entire width of the screen to display
        // one line of text.  First try doing the layout at a smaller
        // size to see if it will fit.
        final DisplayMetrics packageMetrics = res.getDisplayMetrics();
       // 父控件首先会给子view一个值config_prefDialogWidth(320dp),并设置给mTmpValue
        res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
        int baseSize = 0;
        if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
            // 将初始值赋值给baseSize
            baseSize = (int)mTmpValue.getDimension(packageMetrics);
        }
        if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                + ", desiredWindowWidth=" + desiredWindowWidth);
        if (baseSize != 0 && desiredWindowWidth > baseSize) {
            childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            // 首先进行第一次测量
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                    + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
                    + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
                    + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
            // 获取一个状态值
            if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                goodMeasure = true;
            } else {
                // Didn't fit in that size... try expanding a bit.
                // 如果不满意,更改baseSize的大小
                baseSize = (baseSize+desiredWindowWidth)/2;
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                        + baseSize);
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
	            // 再次进行测量(第二次测量)
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                    goodMeasure = true;
                }
            }
        }
    }

    if (!goodMeasure) {
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
        // 如果还不满意,就直接将窗口的宽高(自己的最大值)全部给子view,进行第三次测量
        // 这里面的最大值指父控件的大小,父控件的大小还是要看它的父控件的设置是否能提供这么大,
        // 所以后续还需要进行一次测量
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        // 如果宽跟父控件给的宽不一样或者高跟父控件给的高不一样
        if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
            windowSizeMayChange = true; //windowSizeMayChange = true表示测量无效,还需要进行一次测量
        }
    }

    if (DBG) {
        System.out.println("======================================");
        System.out.println("performTraversals -- after measure");
        host.debug();
    }

    return windowSizeMayChange;
}

上面代码可以简单理解为:(预测量流程中最多执行三次测量)

  1. 首先设置一个值,进行第一次测量,MEASURED_STATE_TOO_SMALL
  2. 获取一个状态值,并且与View.MEASURED_STATE_TOO_SMALL进行相与,看是否满足要求
  3. 如果不满足,改变大小 baseSize = (baseSize+desiredWindowWidth)/2;
  4. 进行第二次测量
  5. 如果还不满意,直接给自己的最大值,然后第三次测量 – 不确定是否合适,这里的最大值指父控件的大小,父控件的大小还是要看它的父控件的设置是否能提供这么大,所以在performTraversals()方法的预测量后续流程中还需要进行一次performMeasure()测量
    如果 windowSizeMayChange = true; --》 表示还需要测量

2. 执行performMeasure()进行控件树测量

performMeasure
   --> mView.measure
   --> onMeasure --> 如果重写onMeasure()方法一定要调用setMeasuredDimension();

ViewRootImpl.java

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
			// 直接调用View的measure()方法
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

View.java

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
	...
	// 执行到onMeasure()方法
    onMeasure(widthMeasureSpec, heightMeasureSpec);
	...
	// flag not set, setMeasuredDimension() was not invoked, we raise
    // an exception to warn the developer
     // 重写了onMeasure()方法之后,一定要执行setMeasuredDimension(),不然会报错
     if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
         throw new IllegalStateException("View with id " + getId() + ": "
                 + getClass().getName() + "#onMeasure() did not set the"
                 + " measured dimension by calling"
                 + " setMeasuredDimension()");
     }
     ...
}

View.java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	// 执行setMeasuredDimension()继续后续流程
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

3.执行performLayout()进行layout布局

performLayout
  --> host.layout
    --> onLayout(changed, l, t, r, b);
      --> child.layout

ViewRootImpl.java

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
	...
	// host就是view
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
	...
}

View.java

 public void layout(int l, int t, int r, int b) {
 	 ...
 	 // 执行onLaout()
     onLayout(changed, l, t, r, b);
 	 ...
	 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) {
			 // 对onLayoutChange 进行监听,对布局改变的监听
             listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
         }
     }
     ...
     
 }

View.java

// view是一个控件,不是一个容器,所以去一个具体实现容器查看具体的代码,比如RelativeLayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

RelativeLayout.java

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    //  The layout has actually already been performed and the positions
    //  cached.  Apply the cached values to the children.
    final int count = getChildCount();

    // 遍历子view
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            RelativeLayout.LayoutParams st =
                    (RelativeLayout.LayoutParams) child.getLayoutParams();
			// 对子view进行layout布局,这里面的左、上、右、下是针对父容器(控件)
            child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
        }
    }
}

4. 执行performDraw()进行绘制

performDraw@ViewRootImpl.java
  --> draw@ViewRootImpl.java
    -->scrollToRectOrFocus 计算滚动,防止核心的控件被遮住,比如输入的时候弹出输入框
    --> 硬件加速绘制 mAttachInfo.mThreadedRenderer.draw() 效果更好
    --> 软件绘制 drawSoftware()
      --> mView.draw(canvas);
      -> onDraw(canvas);
      --> dispatchDraw(canvas);

ViewRootImpl.java

private void performDraw() {
    ...
	// 调用draw()方法
    boolean canUseAsync = draw(fullRedrawNeeded);
    ...
    
}

ViewRootImpl.java

private boolean draw(boolean fullRedrawNeeded) {
	...
	// 计算滚动,防止核心的控件被遮住,比如输入的时候弹出输入框,整个view就会向上移动,就是这里进行了处理
    scrollToRectOrFocus(null, false);
	...
	// The app owns the surface, we won't draw.
    // 在绘制之前进行脏数据的置空,置空的目的是为了有改动的情况下还可以进行添加
    // 脏区域的理解:如果该控件有改变会被标记为脏区域,重绘的时候就会把这块进行重绘,不用重绘整个布局
    dirty.setEmpty();
    ...
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) {
		 // 这里绘制分两种请情况,一种是硬件加速绘制(效果更好些),一种是软件绘制
         // mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()
		 if (isHardwareEnabled()) {
		 	...
		 	// 进行硬件加速绘制
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
         } else {
			// 进行软件绘制drawSoftware()
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                     scalingRequired, dirty, surfaceInsets)) {
                 return false;
             }
	     }
	
}

下面以软件绘制drawSoftware()进行流程分析
ViewRootImpl.java

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
	...
	// 首先拿到canvas
    canvas = mSurface.lockCanvas(dirty);
    ...
    // 进行平移
    canvas.translate(-xoff, -yoff);
    ...
    // 最终调用view 的draw()
    mView.draw(canvas);
    ...
    
}

最终执行到View的draw()方法
View.java

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    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)
     *      7. If necessary, draw the default focus highlight
     */

    // Step 1, draw the background, if needed
    int saveCount;

    // 绘制background
    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
        // 绘制自己
        onDraw(canvas);

        // Step 4, draw the children
        // 绘制子view
        dispatchDraw(canvas);

        drawAutofilledHighlight(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);

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

        if (isShowingLayoutBounds()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }

    /*
     * Here we do the full fledged routine...
     * (this is an uncommon case where speed matters less,
     * this is why we repeat some of the tests that have been
     * done above)
     */

    boolean drawTop = false;
    boolean drawBottom = false;
    boolean drawLeft = false;
    boolean drawRight = false;

    float topFadeStrength = 0.0f;
    float bottomFadeStrength = 0.0f;
    float leftFadeStrength = 0.0f;
    float rightFadeStrength = 0.0f;

    // Step 2, save the canvas' layers
    int paddingLeft = mPaddingLeft;

    final boolean offsetRequired = isPaddingOffsetRequired();
    if (offsetRequired) {
        paddingLeft += getLeftPaddingOffset();
    }

    int left = mScrollX + paddingLeft;
    int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    int top = mScrollY + getFadeTop(offsetRequired);
    int bottom = top + getFadeHeight(offsetRequired);

    if (offsetRequired) {
        right += getRightPaddingOffset();
        bottom += getBottomPaddingOffset();
    }

    final ScrollabilityCache scrollabilityCache = mScrollCache;
    final float fadeHeight = scrollabilityCache.fadingEdgeLength;
    int length = (int) fadeHeight;

    // clip the fade length if top and bottom fades overlap
    // overlapping fades produce odd-looking artifacts
    if (verticalEdges && (top + length > bottom - length)) {
        length = (bottom - top) / 2;
    }

    // also clip horizontal fades if necessary
    if (horizontalEdges && (left + length > right - length)) {
        length = (right - left) / 2;
    }

    if (verticalEdges) {
        topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
        drawTop = topFadeStrength * fadeHeight > 1.0f;
        bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
        drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
    }

    if (horizontalEdges) {
        leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
        drawLeft = leftFadeStrength * fadeHeight > 1.0f;
        rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
        drawRight = rightFadeStrength * fadeHeight > 1.0f;
    }

    saveCount = canvas.getSaveCount();
    int topSaveCount = -1;
    int bottomSaveCount = -1;
    int leftSaveCount = -1;
    int rightSaveCount = -1;

    int solidColor = getSolidColor();
    if (solidColor == 0) {
        if (drawTop) {
            topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
        }

        if (drawBottom) {
            bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
        }

        if (drawLeft) {
            leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
        }

        if (drawRight) {
            rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
        }
    } else {
        scrollabilityCache.setFadeColor(solidColor);
    }

    // Step 3, draw the content
    onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers
    final Paint p = scrollabilityCache.paint;
    final Matrix matrix = scrollabilityCache.matrix;
    final Shader fade = scrollabilityCache.shader;

    // must be restored in the reverse order that they were saved
    if (drawRight) {
        matrix.setScale(1, fadeHeight * rightFadeStrength);
        matrix.postRotate(90);
        matrix.postTranslate(right, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(rightSaveCount, p);

        } else {
            canvas.drawRect(right - length, top, right, bottom, p);
        }
    }

    if (drawLeft) {
        matrix.setScale(1, fadeHeight * leftFadeStrength);
        matrix.postRotate(-90);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(leftSaveCount, p);
        } else {
            canvas.drawRect(left, top, left + length, bottom, p);
        }
    }

    if (drawBottom) {
        matrix.setScale(1, fadeHeight * bottomFadeStrength);
        matrix.postRotate(180);
        matrix.postTranslate(left, bottom);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(bottomSaveCount, p);
        } else {
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }
    }

    if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(topSaveCount, p);
        } else {
            canvas.drawRect(left, top, right, top + length, p);
        }
    }

    canvas.restoreToCount(saveCount);

    drawAutofilledHighlight(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);

    // Step 7, draw the default focus highlight
    drawDefaultFocusHighlight(canvas);

    if (isShowingLayoutBounds()) {
        debugDrawFocus(canvas);
    }
}

  自定义View的最基本的三个方法分别是:onMeasure()、onLayout()、onDraw();View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。

  • 测量:onMeasure()决定View的大小
  • 布局:onLayout()决定View在ViewGroup中的位置
  • 绘制:onDraw()决定绘制这个View

自定义控件分类

  • 自定义View:只需要重写onMeasure()和onDraw()
  • 自定义ViewGroup:则只需要重写onMeasure()和onLayout()

自定义view的注意事项

  1. 让View支持wrap_content属性
    直接继承View或ViewGroup的控件,如果不在onMeasure中做处理,当控件设置wrap_content属性时无法达到预期效果。wrap_content属性会失效。
  2. 让View支持padding属性
    直接继承View的控件,如果不处理padding属性,则padding会失效。如果继承ViewGroup的控件,还需要处理子元素的margin属性。
  3. 为了让控件使用更方便,尽量添加自定义属性。
  4. 如果View需要响应用户touch事件,需要处理好滑动冲突。
  5. 尽量不要在View中使用Handler,可以用post方法代替。
  6. 如果View中有子线程或者动画,要在onDetachedFromWindow中及时停止。
  7. 在onDraw方法中尽量不要创建临时对象,不要做任何耗时的操作,不要执行大数据量

Android view的刷新有三个方式:

  • (1) invalidate();
    只会触发执行onDraw方法,只会改变绘制里面的内容,条目的绘制

  • (2) postInvalidate();
    只会触发执行onDraw方法,但是可以在子线程中刷新

  • (3) requestLayout();
    view的布局参数改变之后刷新,比如view的宽度和高度都修改了,只能通过requestLayout()方法刷新

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值