博客相关资料 :
涉及到的 Android 源码查看方法 :
/**
* This manages the execution of the main thread in an
* application process, scheduling and executing activities,
* broadcasts, and other operations on it as the activity
* manager requests.
*
* {@hide}
*/
public final class ActivityThread {
/** @hide */
public static final String TAG = "ActivityThread";
本博客源码查看说明 :
frameworks\base\core\java\android\
路径下, 该文件已经扒出放在博客附件中;Sdk\platforms\android-27
目录下, 该 jar 包相关资源库没有打包好, 仅用于在 Android Studio 中方便查看源码, 不能编译运行应用, 因此最好将原始的 android.jar 备份下;简要概述 : 下面先对生命周期回调机制进行简要概述, 在进行详细解析;
ActivityThread
中的 main
函数中, 将主线程设置为 Loop
线程, 在H
类中的 handleMessage
中处理 Activity 中的各种生命周期事件.ActivityThread
与 ApplicationThread
进行关联, Activity 中的状态变化会通过 ApplicationThread
通知给具体的 Activity 进行处理;ApplicationThread
的 scheduleXXXActivity
, 如 scheduleLaunchActivity
, 在 scheduleXXXActivity
中会调用 H 发送先关 Message 到主线程中进行处理;handleXXXActivity
方法, 如 handleResumeActivity
方法; 在该方法中主要调用 performXXXActivity
方法处理主要逻辑;Android 应用启动主方法入口 :
ActivityThread.java
;main
函数作为程序入口, 是所有程序的最开始的方法;ActivityThread.java
中的 main()
方法, 启动一个新的应用线程;ActivityThread
代表了 Android 应用的主线程 , 该 main
方法就是主线程的启动方法;Looper
, Handler
机制, ② 创建 ActivityThread
对象; loop
线程, 执行 Looper.prepareMainLooper()
方法后, 该主线程开始进入无线循环状态, 等待接受 Message 信息; 信息最终由 H
对象处理; 生命周期实际实处的方法是通过该 Loop Handler 机制进行处理;ActivityThread
对象, 并进行初始化操作;public final class ActivityThread {
...
public static void main(String[] args) {
...
Looper.prepareMainLooper();
//创建 ActivityThread 线程, 并运行
ActivityThread thread = new ActivityThread();
//attach 方法 进行 thread 的最初初始化操作
thread.attach(false);
...
Looper.loop();
...
}//main
...
}//ActivityThread
attach() 方法 进行上述关联操作 :
ActivityThread.java
;main()
方法中创建了 ActivityThread
对象, 然后调用 ActivityThread
的 attach()
方法, 对 ActivityThread
进行设置;ActivityThread
通过 获取到 ApplicationThread
后, 通过 mgr.attachApplication(this.mAppThread)
步骤是将 ActivityThread
与 ApplicationThread
进行关联, 此时在 ApplicationThread
中存储这个 Activity
的相关信息, 为之后的 该 Activity
各种状态下的操作进行一些准备工作; ...
final ActivityThread.ApplicationThread mAppThread = new ActivityThread.ApplicationThread(null);
...
private void attach(boolean system) {
...
if (!system) {
...
//获取 Activity 管理对象
final IActivityManager mgr = ActivityManager.getService();
try {
//将本 ActivityThread 与 ApplicationThread 进行关联
//其中 ActivityThread 代表当前页面, ApplicationThread 代表整个应用进程
mgr.attachApplication(this.mAppThread);
}
...
}
...
public class ActivityManager {
...
//ActivityThread attach 方法中跨进程调用 getService 获取 IActivityManager 对象
public static IActivityManager getService() {
return (IActivityManager)IActivityManagerSingleton.get();
}
...
//使用了 Singleton<T> 单例类结构
private static final Singleton<IActivityManager> IActivityManagerSingleton = new Singleton<IActivityManager>() {
protected IActivityManager create() {
//通过 ServiceManager 获取 Activity 的 Service
IBinder b = ServiceManager.getService("activity");
//这里通过 Activity 的 Service 提供的 Binder 进行跨进程调用
IActivityManager am = Stub.asInterface(b);
return am;
}
};
...
}//ActivityManager
ApplicationThread 内部类 : 该类与上面的代码没有直接调用关系, ActivityThread
与 ApplicationThread
关联后, 系统就可以通过调用 一系列的 schedule 方法控制 Activity
的各种状态了;
scheduleXXXActivity
方法 : 这一系列的 schedule 方法都是Activity
运行过程中触发某种状态调用的方法. 当进入新页面 系统会自动调用 scheduleLaunchActivity
方法, 当页面退出时由系统调用 schedulePauseActivity
和 scheduleStopActivity
方法; 这些 scheduleXXXActivity
方法都会通过 H
对象发送消息并进行处理;H
对象, 通过调用 sendMessage(100, r)
方法; Activity
, 在 ActivityClientRecord
中定义有 Activity
成员变量, 这个 Activity
就是要显示的界面;ActivityThread
中的 sendMessage
方法, 将 Activity
相关信息发送给 H
内部类, 该类是 Handler 的子类;public final class ActivityThread {
...
//消息处理的类
final ActivityThread.H mH = new ActivityThread.H(null);
...
private class ApplicationThread extends android.app.IApplicationThread.Stub {
...
public final void scheduleLaunchActivity(...) {
...
//创建 Activity, ActivityClientRecord 中有 Activity 成员变量
ActivityThread.ActivityClientRecord r = new ActivityThread.ActivityClientRecord();
...
//将 Activity 发送出去
ActivityThread.this.sendMessage(100, r);
}
public final void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, boolean dontReport) {
...
}
public final void scheduleStopActivity(IBinder token, boolean showWindow, int configChanges) {
...
}
...
public final void scheduleResumeActivity(IBinder token, int processState, boolean isForward, Bundle resumeArgs) {
...
}
...
}//ApplicationThread
...
static final class ActivityClientRecord {
...
//在 ActivityClientRecord 中定义有 Activity 成员变量
Activity activity;
...
}
...
//发送消息调用的方法
private void sendMessage(int what, Object obj) {
this.sendMessage(what, obj, 0, 0, false);
}
...
}//ActivityThread
H 类 :
H
类的 handleMessage
方法中可以处理各种传递来的 Message 信息; Handler
类, H
继承了 Handler
类, 在 main
函数中 处理各种 Message
事件的就是这个 Handler
;public final class ActivityThread {
...
//消息处理的类
final ActivityThread.H mH = new ActivityThread.H(null);
...
//处理 Message 的 Handler 类
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
...
public void handleMessage(Message msg) {
ActivityThread.ActivityClientRecord r;
SomeArgs args;
switch(msg.what) {
case LAUNCH_ACTIVITY: {
...
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
...
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
...
} break;
...
}//switch(msg.what)
}//handleMessage
}//H
...
...
}//ActivityThread
handleXXXActivity 方法 :
ApplicationThread
中系统会回调 scheduleXXXActivity
方法, 在该方法中向 H
发送 Message
信息, 在 handleMessage 中处理 对应状态的方法就是调用该 handleXXXActivity
方法;H
内部类中, 处理 Activity 生命周期相关的操作时, 都调用 ActivityThread
的对应的 handleXXXActivity
方法; 可见 在 Activity
运行过程中, 其状态改变操作都是由 Handler
机制控制的;Looper
一直在进行循环, 收到 ApplicationThread
发送的 Message
信息后, 在 H
的 handleMessage
中执行对应的方法;performXXXActivity
方法进行后续操作;Activity
启动的操作;public final class ActivityThread {
...
private void handleLaunchActivity(ActivityThread.ActivityClientRecord r, Intent customIntent, String reason) {
...
Activity a = this.performLaunchActivity(r, customIntent);
...
}
...
}//ActivityThread
performXXXActivity 方法 :
ApplicationThread
的 scheduleXXXActivity
方法, 在 scheduleXXXActivity
中会向 H
发送对应状态的 Message
信息, 然后将public final class ActivityThread {
...
private Activity performLaunchActivity(ActivityThread.ActivityClientRecord r, Intent customIntent) {
...
//通过反射构建 Activity
Activity activity = null;
try {
ClassLoader cl = appContext.getClassLoader();
activity = this.mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
} ...
try {
...
//
activity.attach(appContext, this, this.getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback);
...
//在此处回调 Activity 的 OnCreate 方法
if (r.isPersistable()) {
this.mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
this.mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
} ...
return activity;
}
...
}//ActivityThread
简要概述 :
setContentView 方法调用分析 : 从 Activity
中调用 setContentView
处, 向上查询代码, 发现最终调用的是 Window
类的 setContentView
方法;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Activity
中调用 this.getWindow().setContentView(layoutResID)
方法, 下面分析Activity
的 getWindow
和 Window
抽象类的 setContentView
抽象方法;public class Activity extends ContextThemeWrapper implements ... {
...
public void setContentView(int layoutResID) {
this.getWindow().setContentView(layoutResID);
this.initWindowDecorActionBar();
}
...
}
Window 和 PhoneWindow 解析 :
this.getWindow()
获取的是一个 Window
对象, Window
是一个抽象类, 其 setContentView
是一个抽象方法, 在其子类中实现 , 下面分析 Window
对象;public class Activity extends ContextThemeWrapper implements ... {
...
private Window mWindow;
...
public Window getWindow() {
return this.mWindow;
}
...
}
Window
抽象类只有一个子类, 那就是 PhoneWindow
类, setContentView
抽象方法在 PhoneWindow
中实现的; 下面分析 PhoneWindow
类;public abstract class Window {
...
public abstract void setContentView(int var1);
...
}
PhoneWindow 实现的 setContentView 方法解析 :
PhoneWindow
中的 setContentView
的方法, 执行了两个重要步骤, ① installDecor()
, ② inflate()
; 下面分别介绍这两个重要方法;public class PhoneWindow extends Window implements Callback {
...
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
//上述翻译 : FEATURE_CONTENT_TRANSITIONS 可能在 窗口安装过程 中被设置,
// 此时各种主题设置等属性被具体化执行. 在安装窗口之前, 不检查 feature 特性;
//就是设置 feature 属性需要在 setContentView 之前进行设置, 在 installDecor 方法中会对这些 feature 属性进行初始化生效操作
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
//加载自己的布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
...
}
DecorView mDecor
成员变量, ②生成 ViewGroup mContentParent
窗口容器; DecorView mDecor
是窗口的最顶层的 view, 其包含了 window decor;mDecor
本身, 要么就是存放 mDecor
子类内容的容器; 该容器是 顶层 View 容器;public class PhoneWindow extends Window implements Callback {
...
//这是 窗口 最顶层的 View, 包含了 window decor
private DecorView mDecor;
...
//窗口的内容被放置在这个 View 中, 要么是 mDecor 本身, 要么就是 mDecor 子类内容存放容器
ViewGroup mContentParent;
...
private void installDecor() {
this.mForceDecorInstall = false;
if (this.mDecor == null) {
//创建 DecorView 成员变量对象
this.mDecor = this.generateDecor(-1);
...
} else {
this.mDecor.setWindow(this);
}
if (this.mContentParent == null) {
//创建内容存放容器对象
this.mContentParent = this.generateLayout(this.mDecor);
...
}
...
}
new DecorView
创建了DecorView 对象;public class PhoneWindow extends Window implements Callback {
...
protected DecorView generateDecor(int featureId) {
...
return new DecorView((Context)context, featureId, this, this.getAttributes());
}
...
}
generateLayout
方法创建存放布局组件的 容器; 该方法中处理 requestFeature
中的设置; Activity
中可以调用 getWindow().setRequestFeature()
方法, 但是该方法必须在 setContentView
方法之前调用;setContentView
调用 installDecor
方法, 在 installDecor
中调用的 generateLayout
方法对这些设置进行处理, 如果在 setContentView
之后设置, 就无法进行处理;R.layout.screen_simple
作为基础布局, 下面分析这个布局;public class PhoneWindow extends Window implements Callback {
...
protected ViewGroup generateLayout(DecorView decor) {
...
//这一组代码处理 requestFeature 设置
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
...
//这一组代码负责处理布局文件相关
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
...
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
...
} ...
else {
// Embedded, so no decoration is needed.
//默认状态下不做任何设置的布局
layoutResource = R.layout.screen_simple;
}
}
...
}
<?xml version="1.0" encoding="utf-8"?>
<!-- -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
DecorView mDecor
, 然后通过 mDecor
创建 ViewGroup mContentParent
容器, 这就是布局容器;generateLayout
方法, 处理 requestFeature 设置, 根据处理结果加载不同的基础布局;PhoneWindow
中的 setContentView
中, mLayoutInflater.inflate(layoutResID, mContentParent);
语句执行的是加载开发者自己的布局文件; 其中 mLayoutInflater
是在 PhoneWindow
创建时就进行初始化的布局加载器;public class PhoneWindow extends Window implements Callback {
...
private LayoutInflater mLayoutInflater;
...
public PhoneWindow(Context context) {
super(context);
//PhoneWindow 创建时初始化 mLayoutInflater 成员变量
mLayoutInflater = LayoutInflater.from(context);
}
...
@Override
public void setContentView(int layoutResID) {
...
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
//加载自己的布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
...
}
public abstract class LayoutInflater {
...
//PhoneWindow 中的 setContentView 调用的该方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
...
//从特定的 xml 资源文件中初始化一个界面布局层级
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
...
//进行 xml 解析, 解析出具体的组件及层级
final XmlResourceParser parser = res.getLayout(resource);
...
}
...
}
UI 绘制起点分析 :
ActivityThread
中 的 H
发送 RESUME_ACTIVITY
信息, 处理流程如下, 由代码可以看出主要是调用了 handleResumeActivity
方法;public final class ActivityThread {
...
//消息处理的类
final ActivityThread.H mH = new ActivityThread.H(null);
...
//处理 Message 的 Handler 类
private class H extends Handler {
...
public static final int RESUME_ACTIVITY = 107;
...
public void handleMessage(Message msg) {
ActivityThread.ActivityClientRecord r;
SomeArgs args;
switch(msg.what) {
...
case RESUME_ACTIVITY:
...
SomeArgs args = (SomeArgs) msg.obj;
//核心步骤是调用 该方法
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
...
break;
...
}//switch(msg.what)
}//handleMessage
}//H
...
...
}//ActivityThread
wm.addView(decor, l);
将开发者开发的 布局 加载到 decor 基础布局中; 其中 vm 是 WindowManager ( WindowManager 是 ViewManager 的子接口 );public final class ActivityThread {
...
final void handleResumeActivity(...) {
...
if (r != null) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
...
ViewManager wm = a.getWindowManager();
//加载开发者的资源
WindowManager.LayoutParams l = r.window.getAttributes();
//这是布局只有基础的布局及容器
a.mDecor = decor;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
...
//通过 ViewManager 将开发者开发的资源 加载到 decor 布局中
wm.addView(decor, l);
} else {
...
}
}
...
}
...
}
...
}//handleResumeActivity
...
}//ActivityThread
分析 WIndowManager :
WindowManager
是一个接口, 其 继承了 ViewManager 接口
, WindowManager
实际的 实现类是 WindowManagerImpl
类, 相关的核心逻辑都定义在 WindowManagerGlobal
中;public interface ViewManager{}
...
public interface WindowManager extends ViewManager {}
...
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
//在 实现的 addView 方法中没有实际的逻辑, 最终的方法逻辑定义在 WindowManagerGlobal 中
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}
root.setView(view, wparams, panelParentView);
是核心语句;public final class WindowManagerGlobal {
...
//在该队列中保存 View 对象, DecorView 对象
private final ArrayList<View> mViews = new ArrayList<View>();
//保存与 顶层的 DecorView 关联的 ViewRootImpl 对象
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//保存创建顶层 DecorView 的 布局参数 LayoutParams 对象
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
...
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//将上述三种数据全部交给 root
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
throw e;
}
}
}
...
}
ViewRootImpl 分析 :
WindowManagerGlobal
中调用了 ViewRootImpl
的 setView
方法, 从此处开始逐层分析下一步的操作; setView
方法中调用了 requestLayout
方法,requestLayout
方法中调用了 scheduleTraversals
方法,scheduleTraversals
方法中通过回调调用了 TraversalRunnable
中的 run
方法,run
方法中调用了 doTraversal
方法,doTraversal
方法中调用了 performTraversals
方法, 这是 UI 绘制的核心方法;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
//1. 在该方法中调用了 requestLayout 方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
...
}
...
//2. requestLayout 方法中调用了 scheduleTraversals 方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
...
scheduleTraversals();
}
}
...
//3. 在该方法中使用了回调, 调用了 mTraversalRunnable 中的run 方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
...
//4. mTraversalRunnable 中的 run 方法主要执行了 doTraversal 方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...
//5. 在 doTraversal 方法中调用了 UI 绘制的核心方法 performTraversals 方法
void doTraversal() {
if (mTraversalScheduled) {
...
performTraversals();
...
}
}
...
}
UI 绘制核心流程 :
performTraversals
中创建一个 Rect
, 以确定控件的位置;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void performTraversals() {
...
//所有的布局加载的信息都再此处存放, 这是资源信息
WindowManager.LayoutParams lp = mWindowAttributes;
...
//绘制一个矩形, 确定 布局整体的位置
Rect frame = mWinFrame;
...
}
...
}
performMeasure
中调用了 View
中的 measure
方法;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
//布局测量
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
...
}
}
...
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
...
}
...
}
performLayout
方法中进行; 该方法通过调用 View 的 layout
方法来摆放 组件, host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
//布局摆放
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
final View host = mView;
...
try {
//调用 View 的 layout 方法摆放组件
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
//得到资源中的所有 View
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
...
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
...
//轮询所有的资源中的 View, 如果存在, 那么在此调用 View 的 requestLayout 方法, 相当于递归调用该逻辑
view.requestLayout();
}
...
}
...
}
}
...
}
...
}
performDraw
方法中执行, 该方法主要调用 draw
方法执行核心逻辑;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void performDraw() {
...
try {
//最终绘制的方法
draw(fullRedrawNeeded);
}
...
}
...
//组件绘制的最终方法
private void draw(boolean fullRedrawNeeded) {
//绘制的画布
Surface surface = mSurface;
...
...
}
}
测量流程 : ActivityThread 中
ViewRootImpl
中 performTraversals
方法中 调用的 performMeasure
方法是测量的起始位置;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void performTraversals() {
...
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
...
if (!mStopped || mReportNextDraw) {
...
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
//获取布局测量的参数
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//进行布局测量
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
...
}
...
}
...
}
...
}
measure
方法; 先分析方法的两个参数 ① int childWidthMeasureSpec
, ② int childHeightMeasureSpec
;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
...
}
}
...
}
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
//基于窗口的根组件的布局参数计算出对应的 MeasureSpec 参数, windowSize 窗口的宽或高, rootDimension 窗口的某个方向的布局参数
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
//窗口不可以重新设置大小, 强制使用根 view 当做窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
//窗口不可以重新设置大小, 设置根 viee 的最大大小;
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
//窗口想要被设定成一个固定的大小, 强制将根 view 设置成指定的大小;
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
...
}
MeasureSpec
规格; match_parent
设置 即 父容器已经测量出大小, 该组件与父容器宽高完全一致, 充满父容器;wrap_content
设置就是与子容器相关, 其大小就是相关的子容器测量后的占用的大小, 最大不能超过父容器;MeasureSpec 打包流程 :
(size & ~MODE_MASK) | (mode & MODE_MASK)
位运算 或者 直接相加的方法, 将规格 与 size值 进行或运算, 将 int 值的最高两位设置成 模式, 后 30 位设置成 size 数值; //根据传入的 大小值 和 模式 创建一个 MeasureSpec 值
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
// MODE_MASK 取反后, 最高的2 位为0, 后面 30 位 为 1
// 取反 结果 与上 size 那么就会得到 size 后 30 位的大小;
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
measureSpec & MODE_MASK
计算, 只要前两位, 后面 30 位 设置成0; //提取布局测量模式
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
measureSpec & ~MODE_MASK
只要后 30 位, 前两位 设置成 0 ; //提取最终的值
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
//UNSPECIFIED 模式: 父容器对子组件不做任何限制, 子组件想要多大就可以设置多大
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//EXACTLY 模式 : 父容器已经计算出子组件的大小,该组件的大小就是后30位代表的值大小
//该 EXACTLY 模式 对应布局中的 match_parent 和 px或dip值 设置两种模式;
public static final int EXACTLY = 1 << MODE_SHIFT;
//AT_MOST 模式 : 子组件的大小根据测量的本身的大小决定, 这个大小不能大于父容器的大小
public static final int AT_MOST = 2 << MODE_SHIFT;
//根据传入的 大小值 和 模式 创建一个 MeasureSpec 值
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
// MODE_MASK 取反后, 最高的2 位为0, 后面 30 位 为 1
// 取反 结果 与上 size 那么就会得到 size 后 30 位的大小;
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
...
//提取布局测量模式, 保留前两位的值, 后 30 位设置成 0
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//提取最终的值, 将前两位设置成0, 保留后 30 位的数值
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
static int adjust(int measureSpec, int delta) {
...
return makeMeasureSpec(size, mode);
}
...
}
...
}
onMeasure 方法调用 : 继续之前的测量流程分析, 之前分析到 ViewRootImpl
的 performTraversals 中进行布局 测量 摆放 绘制 的流程, 其中 测量 主要是调用了 performMeasure
方法;
DecorView
的 measure
方法;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
//布局测量
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
...
}
}
...
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (forceLayout || needsLayout) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
...
}
...
}
...
}
onMeasure 方法作用 :
布局摆放流程 :
ViewRootImpl
的 performTraversals
处理核心逻辑, 先后通过直接或间接调用下面方法 ① 调用 performMeasure 方法进行测量, ② 调用 performLayout 进行布局摆放, ③ 调用 performDraw 进行绘制 ; 在本章节详细介绍如何进行布局摆放;DecorView
调用 layout
方法, 在其中调用 onLayout
方法, 在 onLayout
方法中递归调用子组件的 onLayout
的方法, 直至调用到最底层的组件; 这个逻辑 与 布局测量基本一致, 布局测量也是 顶层 View 循环调用子组件的 onMeasure
方法, 直至调用到 最底层的 组件;View
的 layout
方法, 在 layout
中调用了 onLayout
方法; 这里注意, performMeasure
最终也是调用了 View 的 onMeasure
方法;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
final View host = mView;
...
try {
//调用了 View 的 onLayout 方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
...
}
...
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
....
public void layout(int l, int t, int r, int b) {
...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//setFrame 方法设置布局摆放的范围
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);
...
}
...
}
....
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
/**
* 为组件指定一个大小 和 位置
*
* 在 onLayout 中调用该方法
*
* @param 相对于父容器的左侧位置
* @param 相对于父容器的顶部位置
* @param 相对于父容器的右侧位置
* @param 相对于父容器的底部位置
* @return true if the new size and position are different than the
* previous ones
* {@hide}
*/
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;
}
...
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
....
//该方法中没有定义内容, 需要组件自己定义
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
....
}
layoutChildren
方法, 这个方法是调用的核心逻辑;public class FrameLayout extends ViewGroup {
...
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
//FrameLayout 中布局摆放的实际方法逻辑
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
...
}
组件绘制调用解析 :
performDraw
方法开始看起, 这是 组件绘制的起点方法, 经过 performMeasure
测量, performLayout
布局摆放后, 开始调用 performDraw
进行布局绘制;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void performDraw() {
...
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
...
try {
//调用 draw 方法
draw(fullRedrawNeeded);
}
...
}
...
}
Surface
画布, 并根据 fullRedrawNeeded
参数 获取组件要绘制的 Rect dirty
范围, 将绘制组件相关参数传入 drawSoftware
方法 后, 开始进行绘制; draw(fullRedrawNeeded);
代码中的 fullRedrawNeeded
参数, 其作用是标识是否需要绘制所有的视图, 如果是第一次调用该方法绘制组件, 那么显然绘制所有的组件, 即整个屏幕, 如果之后再次调用可能不需要重新值绘制所有的组件, 只需要绘制部分组件;final Rect dirty = mDirty;
中的 mDirty 区域就是需要绘制的范围; 如果 fullRedrawNeeded
参数为 true, 那么说明这是第一次绘制, 需要绘制整个屏幕, 即 将 mDirty 的范围设置成整个屏幕大小;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void draw(boolean fullRedrawNeeded) {
//绘制组件的画布
Surface surface = mSurface;
...
//确定组件绘制的范围, 这个 mDirty 就是需要重新绘制的范围
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return;
}
//fullRedrawNeeded 为 true, 则说明这是第一次绘制, 需要绘制整个屏幕, 此时将 dirty 大小设置成屏幕大小
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
}else{
...
//绘制组件的核心方法, 传入画布, 绘制范围, 及相关信息
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
...
}
mSurface.lockCanvas(dirty)
获取, Surface 画布 和 dirty 区域决定 Canvas 对象;mView.draw(canvas);
其中的 mView 就是根节点, 即 DecorView;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// 软件渲染绘制
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 进行处理, 画布区域由 dirty 决定
canvas = mSurface.lockCanvas(dirty);
...
}
...
try {
...
//保证绘制之前画布的清洁, 保证绘制组件前画布是空白的
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
...
try {
...
//绘制的主要方法
mView.draw(canvas);
...
}
...
}
...
}
...
}
View 中的 draw 方法解析 : 简单介绍一下 draw 的绘制步骤, 本博客限于篇幅不在展开详细解析, 这是下一篇博客的重点;
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
public void draw(Canvas canvas) {
...
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. 绘制背景
* 2. 图层保存 : 如果有必要, 保存当前画布层, 以便进行渐变绘制
* 3. 绘制组件内容
* 4. 绘制子组件
* 5. 图层恢复 : 如果有必要, 恢复画布层, 绘制渐变内容If necessary, draw the fading edges and restore layers
* 6. 绘制如滚动条等装饰内容
*/
// 步骤 1 : 如果有必要的话, 先绘制背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
//步骤 2 : 图层保存
// 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) {
// 步骤 3 : 绘制组件内容, onDraw 方法需要组件自己实现, 在 View 中该方法是空的
if (!dirtyOpaque) onDraw(canvas);
// 步骤 4 : 绘制子组件, dispatchDraw 方法是空的, 需要组件自己实现
dispatchDraw(canvas);
// 步骤 5 : 图层恢复
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 步骤 6 : 绘制前景 滚动条 等
onDrawForeground(canvas);
// 步骤 7 : 绘制默认的选中高亮状态
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
...
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
/**
* Draws the background onto the specified canvas.
*
* @param canvas Canvas on which to draw the background
*/
private void drawBackground(Canvas canvas) {
//绘制背景的图像
final Drawable background = mBackground;
...
//设置背景位置
setBackgroundBounds();
...
//将背景绘制到画布上
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
...
}
if (!dirtyOpaque) onDraw(canvas);
, 其中的 onDraw 是一个空方法, 需要组件自己实现这个方法, 该方法与 onMeasure, onLayout 是一个性质的, 需要各个组件进行不同的实现;此处只是简单的介绍, 下一篇博客详细讲解绘制的流程;
瀑布流示例 :
onMeasure 两次调用优化问题 : 这是一个注意点 ;
onMeasure
方法会重复调用两次, 如果每次调用都重复相同的逻辑, 而且是递归调用到最底层组件, 这样比较浪费性能, 因此这里进行优化;ViewRootImpl
中 performTraversals
方法中 会调用 performMeasure
方法, performMeasure
方法会调用 onMeasure
方法, 同时 performTraversals
方法中会再次调用 ViewRootImpl
的 scheduleTraversals
方法, 从而又重新调用了一次 performTraversals
方法, 因此 onMeasure
方法被调用了两次;ActivityThread
中的 ApplicationThread
回调对应的 scheduleResumeActivity
方法, 在该方法中向 H ( Handler 子类 ) 发送 Message 信息;handleMessage
方法中, 通过识别 Message 信息的 what 值 来调用 handleResumeActivity
方法;handleResumeActivity
方法中调用了 WindowManager
的 addView
方法;WindowManager
的 addView
方法实际上调用的是 WindowManagerGlobal
的 addView
方法;WindowManagerGlobal
中的 addView
方法, 调用的是 ViewRootImpl
的 addView
方法;ViewRootImpl
的 addView
调用了 requestLayout
方法;requestLayout
方法中调用了 scheduleTraversals
方法;scheduleTraversals
方法中调用 doTraversal
方法;doTraversal
方法中调用了 performTraversals
方法;performTraversals
方法中, 如果组件在显示的情况下, 又调用了一次 scheduleTraversals
, 而在 performTraversals
中进行 测量 摆放 绘制 流程, 因此 测量流程 onMeasure
被调用了 两次;public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void performTraversals() {
...
//这是方法结尾处的代码块
if (!cancelDraw && !newSurface) {
...
performDraw();
} else {
if (isViewVisible) {
// 又重新调用了 scheduleTraversals 方法
scheduleTraversals();
}
...
}
...
}
...
}
onMeasure 测量流程 : 在测量过程中, 需要精确的测量每个子组件的 宽 和 高, 确保 瀑布流布局的实现;
TextView
组件放在 瀑布流容器中, 如果一行的宽度将要超过布局的宽度, 那么需要另起一行进行放置, 每行的宽度以该行中组件的最大宽度为准; /**
* 宽和高 都不规则的组件 进行排列
*
* 基本思想 : 确定布局组件的宽度 和 高度, 根据 WaterfallFlowLayout 布局的 width 和 height 属性进行计算
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i(TAG, "onMeasure");
//onMeasure 可能会进行多次调用, 这里需要兼容 7.0 之后的情况
childViewsLists.clear();
heightLists.clear();
//布局测量 : 1. 定义存储测量最终结果的变量
int width = 0;
int height = 0;
//布局测量 : 2. 获取 宽 和 高 的 模式
int widthMod = MeasureSpec.getMode(widthMeasureSpec);
int heightMod = MeasureSpec.getMode(heightMeasureSpec);
//布局测量 : 3. 获取 宽 和 高 的 值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//布局测量 : 4. 根据 宽 和 高 的 模式 和 大小 计算出组件的 宽 和 高
if(widthMod == MeasureSpec.EXACTLY && heightMod == MeasureSpec.EXACTLY){
//实际测量 : EXACTLY 模式 : EXACTLY 模式对应布局中的 match_parent 或者 实际px 或 dip 等实际值设置
width = widthSize;
height = heightSize;
}else{
//实际测量 : AT_MOST 模式 : 如果是其他模式, 那么就需要遍历所有的子组件, 并计算所需要的大小
//AT_MOST 测量 : 1. 定义变量存储, 累加实时的宽高
int currentWidth = 0;
int currentHeight = 0;
//AT_MOST 测量 : 2. 定义变量存储 子组件的 宽高
int childWidth = 0;
int childHeight = 0;
//AT_MOST 测量 : 3. 存放一行的子组件, 换行时将该队列放入 childViewLists 集合中, 并创建新的集合
ArrayList<View> childViewList = new ArrayList<>();
//AT_MOST 测量 : 4. 遍历所有的子组件,
for(int i = 0; i < getChildCount(); i ++){
View child = getChildAt(i);
//子组件测量 : 1. 测量子组件
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//子组件测量 : 2. 获取子组件的布局参数
//获取子组件 四个方向的 margin 值, 将布局参数强转为 MarginLayoutParams 类型, 需要重写 generateLayoutParams 方法, 让其返回 MarginLayoutParams 类型
//注意 : 这里只计算 margin 值, 即 以组件大小为基准, 向外扩展的大小; padding 值是以组件宽高为基准, 向内部的压缩子组件的宽高, 不在测量的考虑范围内
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
//子组件测量 : 3. 获取子组件占用的实际宽度, 组件大小 + 左右 margin 大小
childWidth = child.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
childHeight = child.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
//子组件测量 : 4. 计算该组件是否需要换行, 当组件实际占用宽度 + 累加宽度 大于组件宽度时, 进行换行操作
if(childWidth + currentWidth > widthSize){
//累加超出了计算的大小, 换行
//子组件测量 换行逻辑 : 1.保存当前行, 每次换行的时候都
//取值策略 : 两个值相加大于总宽度, 此时该子组件的宽度取 currentWidth 累加值 或 childWidth 子组件中的最大值
width = Math.max(width, currentWidth);
//如果换行, 那么高度累加
height += currentHeight;
//更新记录信息
heightLists.add(currentHeight);
childViewsLists.add(childViewList);
//子组件测量 换行逻辑 : 2. 记录新的行信息, 更新当前记录的 宽 和 高
currentWidth = childWidth;
currentHeight = childHeight;
//创建新的行组件记录集合
childViewList = new ArrayList<>();
childViewList.add(child);
}else{//累加后可以在本行显示, 不换行
//子组件测量 不换行逻辑 :
// 不换行的话, 宽度累加
currentWidth += childWidth;
//高度设置策略 : 取 childHeight 值 : 如果是第一次累加, currentHeight 为 0, 这时取 currentHeight = childHeight 为最大值
// 取 currentHeight 值 : 第一次之后的累加时都是 currentHeight = currentHeight;
currentHeight = Math.max(currentHeight, childHeight);
//向代表每行组件的 childViewList 集合中添加该子组件
childViewList.add(child);
}
if(i == getChildCount() - 1){
//处理换行逻辑, 虽然没有换行, 但是处理到了最后一个, 需要处理整行信息
width = Math.max(width, currentWidth);
height += currentHeight;
heightLists.add(currentHeight);
childViewsLists.add(childViewList);
}
}
}
//布局测量 : 5. 设置最终测量出来的宽和高
setMeasuredDimension(width, height);
Log.i(TAG, "onMeasure width : " + width + " , height : " + height + " , heightLists : " + heightLists.size() + " , childViewsLists : " + childViewsLists.size());
}
onLayout 布局摆放流程 : 根据每个组件的 左右位置 和 其 margin 属性, 以及 每行的 高度, 之前行的累加高度, 计算出每个组件的位置, 使用 layout 方法, 放置每个组件 ;
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i(TAG, "onLayout");
//布局摆放 : 1. 定义用于记录每个组件的 左上右下 坐标的变量
int left, top, right, bottom;
//布局摆放 : 2. 定义 用于记录累加的 左 和 上 的坐标
int currentLeft = 0, currentTop = 0;
//布局摆放 : 3. 进行布局摆放, 遍历所有的子组件, 并放置子组件, 该层循环是遍历一行组件的 集合, 单个元素是一个组件集合
for (int i = 0; i < childViewsLists.size(); i ++){
ArrayList<View> childViewsList = childViewsLists.get(i);
//该层循环的遍历的是 每行 具体的 子组件 集合, 单个元素是一个子组件
for(int j = 0; j < childViewsList.size(); j ++){
View child = childViewsList.get(j);
//将布局参数转为可获取 左上右下 margin 的 MarginLayoutParams 类型布局参数
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
//组件的左 上 右 下 的四个位置
left = currentLeft + marginLayoutParams.leftMargin;
top = currentTop + marginLayoutParams.topMargin;
right = left + child.getMeasuredWidth();
bottom = top + child.getMeasuredHeight();
//放置子组件
child.layout(left, top, right, bottom);
//处理累加数据值 : 累加水平方向的左侧值
currentLeft = right + marginLayoutParams.rightMargin;
}
//处理累加数据 : 重置水平方向的左侧值 为 0, 累加垂直方向的高度值
currentLeft = 0;
currentTop += heightLists.get(i);
childViewsList.clear();
}
//布局摆放 : 4. 清空集合, 最大限度及时节省内存
childViewsLists.clear();
heightLists.clear();
}
瀑布流布局的完整代码 :
package hanshuliang.com.ui_demos_4_csdn_blog;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class A_2_WaterfallFlowLayout extends ViewGroup {
public static final String TAG = "A_2_WaterfallFlowLayout";
/**
* ArrayList<View> 存放每一行的组件集合
* ArrayList<ArrayList<View>> 存放多行的 组件集合 的集合
*/
private ArrayList<ArrayList<View>> childViewsLists = new ArrayList<>();
/**
* 每一行的高度集合
*/
private ArrayList<Integer> heightLists = new ArrayList<>();
public A_2_WaterfallFlowLayout(Context context) {
super(context);
}
public A_2_WaterfallFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public A_2_WaterfallFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 宽和高 都不规则的组件 进行排列
*
* 基本思想 : 确定布局组件的宽度 和 高度, 根据 WaterfallFlowLayout 布局的 width 和 height 属性进行计算
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i(TAG, "onMeasure");
//onMeasure 可能会进行多次调用, 这里需要兼容 7.0 之后的情况
childViewsLists.clear();
heightLists.clear();
//布局测量 : 1. 定义存储测量最终结果的变量
int width = 0;
int height = 0;
//布局测量 : 2. 获取 宽 和 高 的 模式
int widthMod = MeasureSpec.getMode(widthMeasureSpec);
int heightMod = MeasureSpec.getMode(heightMeasureSpec);
//布局测量 : 3. 获取 宽 和 高 的 值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//布局测量 : 4. 根据 宽 和 高 的 模式 和 大小 计算出组件的 宽 和 高
if(widthMod == MeasureSpec.EXACTLY && heightMod == MeasureSpec.EXACTLY){
//实际测量 : EXACTLY 模式 : EXACTLY 模式对应布局中的 match_parent 或者 实际px 或 dip 等实际值设置
width = widthSize;
height = heightSize;
}else{
//实际测量 : AT_MOST 模式 : 如果是其他模式, 那么就需要遍历所有的子组件, 并计算所需要的大小
//AT_MOST 测量 : 1. 定义变量存储, 累加实时的宽高
int currentWidth = 0;
int currentHeight = 0;
//AT_MOST 测量 : 2. 定义变量存储 子组件的 宽高
int childWidth = 0;
int childHeight = 0;
//AT_MOST 测量 : 3. 存放一行的子组件, 换行时将该队列放入 childViewLists 集合中, 并创建新的集合
ArrayList<View> childViewList = new ArrayList<>();
//AT_MOST 测量 : 4. 遍历所有的子组件,
for(int i = 0; i < getChildCount(); i ++){
View child = getChildAt(i);
//子组件测量 : 1. 测量子组件
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//子组件测量 : 2. 获取子组件的布局参数
//获取子组件 四个方向的 margin 值, 将布局参数强转为 MarginLayoutParams 类型, 需要重写 generateLayoutParams 方法, 让其返回 MarginLayoutParams 类型
//注意 : 这里只计算 margin 值, 即 以组件大小为基准, 向外扩展的大小; padding 值是以组件宽高为基准, 向内部的压缩子组件的宽高, 不在测量的考虑范围内
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
//子组件测量 : 3. 获取子组件占用的实际宽度, 组件大小 + 左右 margin 大小
childWidth = child.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
childHeight = child.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
//子组件测量 : 4. 计算该组件是否需要换行, 当组件实际占用宽度 + 累加宽度 大于组件宽度时, 进行换行操作
if(childWidth + currentWidth > widthSize){
//累加超出了计算的大小, 换行
//子组件测量 换行逻辑 : 1.保存当前行, 每次换行的时候都
//取值策略 : 两个值相加大于总宽度, 此时该子组件的宽度取 currentWidth 累加值 或 childWidth 子组件中的最大值
width = Math.max(width, currentWidth);
//如果换行, 那么高度累加
height += currentHeight;
//更新记录信息
heightLists.add(currentHeight);
childViewsLists.add(childViewList);
//子组件测量 换行逻辑 : 2. 记录新的行信息, 更新当前记录的 宽 和 高
currentWidth = childWidth;
currentHeight = childHeight;
//创建新的行组件记录集合
childViewList = new ArrayList<>();
childViewList.add(child);
}else{//累加后可以在本行显示, 不换行
//子组件测量 不换行逻辑 :
// 不换行的话, 宽度累加
currentWidth += childWidth;
//高度设置策略 : 取 childHeight 值 : 如果是第一次累加, currentHeight 为 0, 这时取 currentHeight = childHeight 为最大值
// 取 currentHeight 值 : 第一次之后的累加时都是 currentHeight = currentHeight;
currentHeight = Math.max(currentHeight, childHeight);
//向代表每行组件的 childViewList 集合中添加该子组件
childViewList.add(child);
}
if(i == getChildCount() - 1){
//处理换行逻辑, 虽然没有换行, 但是处理到了最后一个, 需要处理整行信息
width = Math.max(width, currentWidth);
height += currentHeight;
heightLists.add(currentHeight);
childViewsLists.add(childViewList);
}
}
}
//布局测量 : 5. 设置最终测量出来的宽和高
setMeasuredDimension(width, height);
Log.i(TAG, "onMeasure width : " + width + " , height : " + height + " , heightLists : " + heightLists.size() + " , childViewsLists : " + childViewsLists.size());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i(TAG, "onLayout");
//布局摆放 : 1. 定义用于记录每个组件的 左上右下 坐标的变量
int left, top, right, bottom;
//布局摆放 : 2. 定义 用于记录累加的 左 和 上 的坐标
int currentLeft = 0, currentTop = 0;
//布局摆放 : 3. 进行布局摆放, 遍历所有的子组件, 并放置子组件, 该层循环是遍历一行组件的 集合, 单个元素是一个组件集合
for (int i = 0; i < childViewsLists.size(); i ++){
ArrayList<View> childViewsList = childViewsLists.get(i);
//该层循环的遍历的是 每行 具体的 子组件 集合, 单个元素是一个子组件
for(int j = 0; j < childViewsList.size(); j ++){
View child = childViewsList.get(j);
//将布局参数转为可获取 左上右下 margin 的 MarginLayoutParams 类型布局参数
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
//组件的左 上 右 下 的四个位置
left = currentLeft + marginLayoutParams.leftMargin;
top = currentTop + marginLayoutParams.topMargin;
right = left + child.getMeasuredWidth();
bottom = top + child.getMeasuredHeight();
//放置子组件
child.layout(left, top, right, bottom);
//处理累加数据值 : 累加水平方向的左侧值
currentLeft = right + marginLayoutParams.rightMargin;
}
//处理累加数据 : 重置水平方向的左侧值 为 0, 累加垂直方向的高度值
currentLeft = 0;
currentTop += heightLists.get(i);
childViewsList.clear();
}
//布局摆放 : 4. 清空集合, 最大限度及时节省内存
childViewsLists.clear();
heightLists.clear();
}
/**
* 子组件获取的 LayoutParams 转成 MarginLayoutParams, 必须在此处将返回值修改为 MarginLayoutParams 对象
* 否则获取子组件的 布局参数 转为 MarginLayoutParams 类型会出错
* @param attrs
* @return
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}