Android系统里面有两种坐标系:Android坐标系、View坐标系。
Android的坐标系是以手机上可见的屏幕左上角顶点为坐标系原点,但是xy轴的方向和我们以前知道的有所不同,需要注意,从原点向右为x轴正方向,而从原点向下为y轴正方向。
android.view.MotionEvent
下面有两个方法getRawX()
和getRawY()
可以获得当前触摸位置的Android坐标系坐标。
可以说Android坐标系是屏幕绝对坐标,View坐标系是viewgroup的相对坐标,这两者可以结合使用,做到更加精确的控制。
方法 | 从属 | 含义 |
---|---|---|
getX() | MotionEvent | 触摸位置距离控件左边缘距离 |
getY() | MotionEvent | 触摸位置距离控件上边缘距离 |
getTop() | View | 当前view到其父布局上边缘距离 |
getBottom() | View | 当前view下边缘到其父布局上边缘距离 |
getLeft() | View | 当前view左边缘到其父布局左边缘距离 |
getRight() | View | 当前view右边缘到其父布局左边缘边缘距离 |
我们都知道要在Activity的onCreate()
方法里面用setContentView()方法加载布局,那布局在源码里是如何被加载出来的,来分析一下。
首先进入setContentView
,看到方法如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
*// @return Window The current window, or null if the activity is not visual.
*/
public Window getWindow() {
return mWindow;
}
这个方法里面首先先去调用getWindow()将布局塞入到获得的window对象里面,注释说明这个mWindow
是当前的window,如果activity不可见就返回null
。
再来看看window是如何被初始化出来的,我们在attach方法里面找到了赋值代码:
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow就是PhoneWindow对象的实例,所以getWindow().setContentView()是调用了PhoneWindow对象的setContentView方法:
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
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.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
...
}
该方法判断mContentParent
是否存在,不存在就会调用 installDecor()方法去创建DecorView,存在的话就把布局加载到mContentParent中,按照注释,这个mContentParent就是DecorView本身或者DecorView的content部分。
再来看installDecor方法,会先判断DecorView是否存在,不存在先调用generateDecor创建DecorView,然后判断mContentParent是否存在,不存在就把调用generateLayout(mDecor)创建mContentParent。
而generateLayout(mDecor)又会加载一个R.layout.screen_title的布局,这里面有TitleView布局和ContentView布局。
总结一下:Activity在首先会加载一个PhoneWindow对象,然后再去加载DecorView对象,DecorView会作为activity的根view存在,activity的setContentView最终会调用DecorView的setContentView方法,将布局加载到DecorView上,DecorView由title和content两部分组成。
上文介绍了view的层次和DecorView的创建过程,现在需要将创建好的DecorView添加到window中去。
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
每当我们启动一个Activity的时候,都会执行ActivityThread.main()
方法,main方法里面有一个Looper一直在在等待主线程的消息,可以看到sMainThreadHandler会接受一个handler,这是一个内部类H,继承自Handler。看一下它的handleMessage()方法,
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
....
case RELAUNCH_ACTIVITY:
handleRelaunchActivityLocally((IBinder) msg.obj);
break;
}
接收到启动activity的消息之后就会调用handleRelaunchActivityLocally
,然后该方法会调用handleLaunchActivity(r, pendingActions, customIntent),handleLaunchActivity
又会调用performLaunchActivity(r, customIntent),方法内部会调用mInstrumentation.callActivityOnCreate去回调生命周期方法onCreate
,这样就完成DecorView的创建。
onCreate之后会走到onStart,handleStartActivity
方法里面会走一些延迟加载的任务,保存状态等。
然后就是onResume,handleResumeActivity
会执行将DecorView加载到window的操作,看一下源码:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest,boolean isForward,String reason) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
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;
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);
}
}
...
}
}
这个方法内会将onCreate时创建的DecorView通过getWindowManager
返回的wm的实例方法wm.addView(decor, l)
添加到WindowManager中,WindowManager的实例是WindowManagerImpl,它的addView又会调用WindowManagerGlobal的addView方法,通过ViewRootImpl实例的setView()方法,将DecorView塞到window里面。
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);