基础
对于应用来说,Activity仅仅是一个载体,它本身并不负责任何界面的绘制,只是允许在其上创建界面,并提供一些API用于响应用户的操作,同时维护应用程序的生命周期等。所有的绘制都是交由Activity内部的Window(只有一个实现对象PhoneWindow)对象来实现的,而PhoneWindow内部在添加View之前,会首先创建一个DecorView,后继所有的View都是添加到DecorView中的。因此,DecorView是所有View的最深层节点。
可以通过getWindow().getDecorView()获取到当前Activity对应的DecorView对象。利用该对象可以实现截屏等功能。如下:
View view = getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
Bitmap bitmap = view.getDrawingCache();
try {
bitmap.compress(Bitmap.CompressFormat.JPEG,50,new FileOutputStream(new File(Environment.getExternalStorageDirectory(),System.currentTimeMillis()+".jpg")));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
层级分析
在5.0以上和5.0以下是不同的。如下:
//----------------------------------4.4--------------------------------------------
|-name = DecorView, [0,0][720,1280]
|-name = LinearLayout, [0,0][720,1280]
|-name = ViewStub, [0,0][0,0]
|-name = FrameLayout, [0,50][720,1280]
|-name = ActionBarOverlayLayout, [0,0][720,1230]
|-name = ContentFrameLayout, [0,112][720,1230]
|-name = LinearLayout, [0,0][720,1118]
|-name = AppCompatEditText, [0,0][720,38]
|-name = AppCompatButton, [0,38][720,134]
|-name = AppCompatTextView, [0,134][720,172]
|-name = ActionBarContainer, [0,0][720,112]
|-name = Toolbar, [0,0][720,112]
|-name = TextView, [32,29][160,83]
|-name = ActionMenuView, [0,0][0,0]
|-name = ActionBarContextView, [0,0][0,0]
//----------------------------------模拟器6.0--------------------------------------------
|-name = DecorView, [0,0][480,854] //最顶层的DecorView
|-name = LinearLayout, [0,0][480,854] //整体空间,包含状态栏在内,但无法修改状态栏
|-name = ViewStub, [0,0][0,0]
|-name = FrameLayout, [0,24][480,854] //除状态栏之后的剩余空间
|-name = ActionBarOverlayLayout, [0,0][480,830] //占满上面的FrameLayout
|-name = ContentFrameLayout, [0,56][480,830] //除标题栏之外的剩余空间
|-name = LinearLayout, [0,0][480,774] //自己View的根结点
|-name = AppCompatEditText, [0,0][480,28]
|-name = AppCompatButton, [0,28][480,76]
|-name = AppCompatTextView, [0,76][480,95]
|-name = ActionBarContainer, [0,0][480,56] //标题栏
|-name = Toolbar, [0,0][480,56]
|-name = TextView, [16,14][79,42]
|-name = ActionMenuView, [0,0][0,0]
|-name = ActionBarContextView, [0,0][0,0]
|-name = View, [0,0][480,24] //状态栏
对比上面可以发现:主要区别在于5.0以上DecorView有两个子View,而5.0以下的只有一个。多出的第二个子View就是状态栏。因此,可以通过下面代码直接修改状态栏的背景色,当然此种方法只能在5.0以上的系统运行,5.0以下的就会直接崩溃:
decorView.getChildAt(1).setBackgroundColor(Color.YELLOW);//将状态栏背景改为黄色
ContentFrameLayout,它是布局所要填充到的View,其Id为android.R.id.content。其内部只有一个View,就是自己布局的根View。因此可以通过下面代码获取布局的根View。如下:
ViewGroup content = (ViewGroup) decorView.findViewById(android.R.id.content);
content.getChildAt(0);
ActionBarContainer:与ContentFrameLayout平级,代表着标题栏。因此,可以通过下面代码修改标题栏。如下:
//先找到内容区域,再获取内容区域的父View。因为标题栏区域与内容区域平级
ViewGroup vp = (ViewGroup) decorView.findViewById(android.R.id.content).getParent();
//所以使用上步获取到的父View.getChildAt(1)就可以拿到标题栏区域的根结点
ViewGroup actionbar = (ViewGroup) vp.getChildAt(1);
// actionbar.setBackgroundColor(Color.YELLOW);//修改标题栏的背景色
//重定义标题栏的布局
actionbar.removeAllViews();
TextView tv = new TextView(this);
tv.setText("custom");
tv.setGravity(Gravity.RIGHT);
actionbar.addView(tv);
附件
获取上面DecorView的层级结构的代码如下,直接使用showPos(decorView,0)即可。
private void showPos(View view,int level){
StringBuilder sb = new StringBuilder();
int x;
for(x = 0;x<level;x++){
sb.append(" ");
}
sb.append("|-");
String name = view.getClass().getSimpleName();
sb.append("name = ");
sb.append(name);
sb.append(", ");
sb.append(String.format(Locale.CHINA,"[%d,%d][%d,%d]",view.getLeft(),view.getTop(),view.getRight(),view.getBottom()));
Log.e(TAG,sb.toString());
if(view instanceof ViewGroup){
ViewGroup v = (ViewGroup) view;
for(x = 0;x<v.getChildCount();x++){
showPos(v.getChildAt(x),level+1);
}
}
}
构造函数
Activity#setContentView()->PhoneWindow#setContentView()->installDecor()->generateDecor()->DecorView#构造函数。
protected DecorView generateDecor() {//PhoneWindow#generateDecor()
return new DecorView(getContext(), -1);
}
由Window与PhoneWindow可知,getContext()返回的就是Window所关联的Activity实例。因此,DecorView中的mContext指的也是Window所关联的Activity实例。