Android作为一种前端语言,view肯定是占据着很重要的位置,熟用view也是我们必须具备的能力,今天就来看看view相关的那些问题:
Activity包含了一个PhoneWindow
,而PhoneWindow
就是继承于Window的,Activity通过setContentView
将View设置到了PhoneWindow
上,而View通过WindowManager的addView()、removeView()、updateViewLayout()
对View进行管理。Window的添加过程以及Activity的启动流程都是一次IPC的过程。Activity的启动需要通过AMS完成;Window的添加过程需要通过WindowSession
完成。
如果在onCreate、onStart、onResume
中直接调用View的getWidth/getHeight
方法,是无法得到View宽高的正确信息,因为view的measure过程与Activity的生命周期是不同步的,所以无法保证在这些生命周期里view 的measure已经完成。所以很有可能获取的宽高为0。
所以主要有以下三个方法来获取view的宽高:
在该方法里的runnable
对象,能保证view已经绘制完成,也就是执行完measure、layout和draw
方法了。
view.post(new Runnable() {
@Override
public void run() {
int width = view.getWidth();
int hight = view.getHeight();
}
});
Activity中可以重写onWindowFocusChanged
方法,该方法表示Activity的窗口得到焦点或者失去焦点的时候,所以Activitiy获取焦点时,view肯定绘制完成了,这时候获取宽高也是没问题的:
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
int width = view.getWidth();
int hight = view.getHeight();
}
}
ViewTreeObserver
是一个观察者,主要是用来观察视图树的各种变化。OnGlobalLayoutListener
的作用是当View树的状态发生改变或者View树中某view的可见性发生改变时,OnGlobalLayoutListener
的onGlobalLayout方法将会被回调。因此,此时获取view的宽高也是可以的。
ViewTreeObserver observer = title_name.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int width = view.getWidth();
int hight = view.getHeight();
}
});
能获取宽高的原因肯定就是因为在此之前view 绘制已经完成,所以View.post()
添加的任务能够保证在所有 View 绘制流程结束之后才被执行。
看看post的源码:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
//RunQueue .class
void post(Runnable action) {
postDelayed(action, 0);
}
void postDelayed(Runnable action, long delayMillis) {
HandlerAction handlerAction = new HandlerAction();
handlerAction.action = action;
handlerAction.delay = delayMillis;
synchronized (mActions) {
mActions.add(handlerAction);
}
}
void executeActions(Handler handler) {
synchronized (mActions) {
final ArrayList<HandlerAction> actions = mActions;
final int count = actions.size();
for (int i = 0; i < count; i++) {
final HandlerAction handlerAction = actions.get(i);
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
actions.clear();
}
}
所以在执行View.post()
的方法时,那些Runnable并没有马上被执行,而是保存到RunQueue里面,然后通过executeActions
方法执行,也就是通过handler,post了一个延时任务Runnable。而executeActions
方法什么时候会执行呢?
private void performTraversals() {
getRunQueue().executeActions(attachInfo.mHandler);
...
performMeasure();
...
performLayout();
...
performDraw();
}
可以看到在performTraversals
方法中执行了,但是在view绘制之前,这是因为在绘制之前就把需要执行的runnable
封装成Message发送到MessageQueue
里排队了,但是Looper不会马上去取这个消息,因为Looper
会按顺序取消息,主线程还有什么消息没执行完呢?其实就是当前的这个performTraversals
所在的任务,所以要等下面的·performMeasure,performLayout,performDraw·都执行完,也就是view绘制完毕了,才会去执行之前我们post的那个runnable,也就是我们能在view.post
方法里的runnable
能获取宽高的主要原因了。
这里我说的比较简单,其实里面涉及到的知识点还是很多的,大家可以看看网上的文章,这里推荐两篇:https://www.jianshu.com/p/ea464eb15436 https://www.cnblogs.com/dasusu/p/8047172.html
下次有机会我也会详细说下,今天就到这里了。拜拜。
Android开发者们,快来关注公| 众 |号【码上积木】,每天三问面试题,并详细剖析,助你成为offer收割机。 相信积累的力量。