Android 沉浸状态栏
2016-02-29 09:14
169 查看
前言
这里说的沉浸状态栏是指透明状态栏,至于为什么国内喜欢将透明状态栏说成沉浸式状态栏,可参考 为什么在国内会有很多用户把「透明栏」(Translucent Bars)称作 「沉浸式顶栏」?。有很多其他地方都介绍了沉浸状态栏,可参考:
Android App 沉浸式状态栏解决方案
Android 沉浸式状态栏攻略 让你的状态栏变色吧
开源项目:SystemBarTint
本文主要介绍沉浸状态栏在4.4和5.x上的实现方式,以及搭配DrawerLayout和滑动返回的使用方法。
FLAG_TRANSLUCENT_STATUS
为什么4.4以上能设置沉浸状态栏,正是因为下面的flag引入了,通过这两个flag可以设置Status Bar或Nav Bar透明。WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
Added in API level 19,如果这个flag被设置,
View.SYSTEM_UI_FLAG_LAYOUT_STABLE和
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN这两个flag会被自动添加到system UI visibility中。
如果要设置Nav Bar透明,需要添加下面的flag:
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
Added in API level 19,如果这个flag被设置,
View.SYSTEM_UI_FLAG_LAYOUT_STABLE和
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION这两个flag会被自动添加到system UI visibility中。
我们注意到上面的flag名为TRANSLUCENT,而不是TRANSPARENT, 知乎 指出了,TranslucentStatus 在 4.4 和 5.x 上表现不同,4.4 是一层渐变的遮罩层,5.x 以上是一条半透明的遮罩层,比如Genymotion模拟器上就是这样,但在一些其他机器比如小米上,就是全透明的,这应该是和系统有关的。
Genymotion模拟器4.4上透明状态栏的效果:
小米4.4上透明状态栏的效果:
Genymotion模拟器5.x上透明状态栏的效果:
4.4上的解决方案
在5.x以上可以通过现有API直接设置StatusBar的颜色,可以满足大部分的需求,那如何兼容4.4呢?目前在4.4上StatusBar变色的基本原理就是将StatusBar本身设置为透明,然后在StatusBar的位置添加一个相同大小的View并着色。可以参考 SystemBarTint。具体实现如下:
1、设置状态栏透明
final int sdk = Build.VERSION.SDK_INT; Window window = mActivity.getWindow(); WindowManager.LayoutParams params = window.getAttributes(); if (sdk >= Build.VERSION_CODES.KITKAT) { int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; // 设置透明状态栏 if ((params.flags & bits) == 0) { params.flags |= bits; window.setAttributes(params); } }
2、设置fitsSystemWindows
当设置Status Bar透明后,由于visibility flag的自动添加,屏幕会变成全屏的。所以这时候ToolBar就直接跑到StatusBar下面了,如图:
这时需要设置设置Activity的layout的属性
android:fitsSystemWindows="true",添加后的效果:
3、状态栏着色
状态栏的位置已经空出来了,接下来添加一个View,这个View是添加在layout hierarchy里的。
final int sdk = Build.VERSION.SDK_INT; Window window = mActivity.getWindow(); WindowManager.LayoutParams params = window.getAttributes(); if (sdk == Build.VERSION_CODES.KITKAT) { int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; // 设置透明状态栏 if ((params.flags & bits) == 0) { params.flags |= bits; window.setAttributes(params); } // 设置状态栏颜色 ViewGroup contentLayout = (ViewGroup) mActivity.findViewById(android.R.id.content); setupStatusBarView(contentLayout, color); // 设置Activity layout的fitsSystemWindows View contentChild = contentLayout.getChildAt(0); contentChild.setFitsSystemWindows(true); }
private void setupStatusBarView(ViewGroup contentLayout, int color) { if (mStatusBarView == null) { View statusBarView = new View(mActivity); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(mActivity)); contentLayout.addView(statusBarView, lp); mStatusBarView = statusBarView; } mStatusBarView.setBackgroundColor(color); } /** * 获得状态栏高度 */ private static int getStatusBarHeight(Context context) { int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); return context.getResources().getDimensionPixelSize(resourceId); }
效果如下:
)
这时候通过hierarchyviewer查看布局:
可以看到android.R.id.content下除了Activity的layout外还多了一个View,所以我们也可以将这个View添加到Activity的layout下,甚至可以给ToolBar设置paddingTop,值为状态栏高度。但对大量页面来说,显然上面的方法更好。
5.x以上的解决方案
在5.x上,完全可以用上面4.4的方法设置状态栏颜色,在Genymotion模拟器5.x上的效果:这里就出现了上面说的flag的问题,在部分机型上Status Bar不能设置为全透明。
如果用5.x自带的方法
window.setStatusBarColor(getResources().getColor(R.color.colorPrimary))设置,效果如下:
这里没有设置Status Bar透明,所以页面不是全屏的,所以也不用设置fitSystemWindows。一般来说,5.x以上用
window.setStatusBarColor()就能满足需求了。
特殊情况下的解决方案
在一些情况下,页面内容需要全屏显示,如下图:在4.4上,设置状态栏透明后,页面已经是全屏的,这时候只要设置状态栏的View的背景为透明即可。
在5.x上,需要先设置页面全屏,用4.4上使用的flag可以实现,但可能在不同机型上有不同的效果,这里用全屏+自带API的方法来实现。可参考:Android 5.0 如何实现将布局的内容延伸到状态栏实?
1、实现全屏:
window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2、设置Status Bar颜色:
window.setStatusBarColor(color);
3、要注意的是,由于页面是全屏的,所以Activity的layout不能设置
android:fitSystemWindows="true",但又不能让ToolBar显示在Status Bar下面,这里有很多种方法,可以设置ToolBar的paddingTop,也可以使用下面的方法,给ToolBar套一层layout,然后设置它的
android:fitsSystemWindows="true"。
<FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/colorPrimary" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> </FrameLayout>
输入法的问题
在WindowManager.LayoutParams.FLAG_FULLSCREEN中提到过,
A fullscreen window will ignore a value of SOFT_INPUT_ADJUST_RESIZE for the window's softInputMode field; the window will stay fullscreen and will not resize.,全屏模式下,
SOFT_INPUT_ADJUST_RESIZE会失效。
解决方法:
1、stackoverflow 上指出,对Activity的根布局设置
android:fitsSystemWindows="true"就能解决问题。
2、但在一些页面内容需要全屏显示的情况下,就不能这么设置了。 AndroidBug5497Workaround 提到了另一种方法,经测试,如果Activity layout中的所有控件都不设置android:fitsSystemWindows=”true”,是有效的,但如果对其中的部分控件设置了android:fitsSystemWindows=”true”,该方法就失效了。而且该方法中计算的高度数据并不是很准确,所以暂不推荐使用该方法。如果要使用该方案,在 Android 5.0 如何实现将布局的内容延伸到状态栏实? - 张启的回答 中提到了,建议只设置 windowTranslucentStatus,不要同时设置 Navigation,因为一旦同时启用了这两个属性,那么该解决方案就无效了。
3、既然对Activity的根布局设置
android:fitsSystemWindows="true"就能解决问题,那就从这里入手,CustomInsetsFrameLayout提到了一种方法,仍然是给Activity的根布局设置
android:fitsSystemWindows="true",但在将insets转换成padding的时候,将padding值设置为0,以解决全屏和fitSystemWindows共存的问题。这个类中也指出了,不能将insets.bottom设置为0,否则该方法会失效。
DrawerLayout解决方案
根据 Navigation drawer 的效果,DrawerLayout展开的时候侧边栏看起来像是在Status Bar下面。在5.x以上,DrawerLayout本身就能实现该效果。5.x效果实现
5.x上的效果实现可以参考 Material Design 之 侧边栏与 Status Bar 不得不说的故事 。
如果想要侧边栏能显示在Status Bar下面,要满足两个条件:1、页面要全屏;2、Status Bar的颜色要透明。
与此同时,也需要解决下面两个问题:
1、页面全屏后如何处理ToolBar等内容的位置。
2、Status Bar透明了,如何让Status Bar看起来像正常设置了颜色一样。
在5.x上,DrawerLayout本身已经满足了上面两个条件,并且已经处理了上面两个问题。(仅仅在5.x上实现了)
分析源码,DrawerLayout构造方法中有这样一段代码:
public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { // ... if (ViewCompat.getFitsSystemWindows(this)) { IMPL.configureApplyInsets(this); mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context); } // ... }
这里的
IMPL只对5.x以上做了具体实现,
IMPL.configureApplyInsets(this)在5.x上的实现如下:
public void configureApplyInsets(View drawerLayout) { DrawerLayoutCompatApi21.configureApplyInsets(drawerLayout); } class DrawerLayoutCompatApi21 { // 省略... public static void configureApplyInsets(View drawerLayout) { if (drawerLayout instanceof DrawerLayoutImpl) { drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener()); drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } } // 省略... }
在这里添加了
View.SYSTEM_UI_FLAG_LAYOUT_STABLE和
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,这时页面已经是全屏的了,满足了上面的条件一:
1、页面要全屏。条件二需要在Activity中设置
getWindow().setStatusBarColor(Color.TRANSPARENT)或是在theme中设置。这里还设置了
setOnApplyWindowInsetsListener()方法,用来处理
fitSystemWindows()相关方法的逻辑。
InsetsListener的实现如下:
static class InsetsListener implements View.OnApplyWindowInsetsListener { @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { final DrawerLayoutImpl drawerLayout = (DrawerLayoutImpl) v; drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0); return insets.consumeSystemWindowInsets(); } }
直接调用到了DrawerLayout中的方法:
public void setChildInsets(Object insets, boolean draw) { mLastInsets = insets; mDrawStatusBarBackground = draw; setWillNotDraw(!draw && getBackground() == null); requestLayout(); }
页面重新layout,看到
onMeasure()方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // ... final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this); final int layoutDirection = ViewCompat.getLayoutDirection(this); // Gravity value for each drawer we've seen. Only one of each permitted. int foundDrawers = 0; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (applyInsets) { final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection); if (ViewCompat.getFitsSystemWindows(child)) { IMPL.dispatchChildInsets(child, mLastInsets, cgrav); } else { IMPL.applyMarginInsets(lp, mLastInsets, cgrav); } } // ... } }
根据上面的分析,这里
applyInsets仅当
DrawerLayout设置了
android:fitsSystemWindows="true"并且是5.x以上的系统才会为true。
进入for循环,对
DrawerLayout的child进行遍历,这里需要分析一下里面的if语句。
IMPL.dispatchChildInsets(child, mLastInsets, cgrav)代表的是如果child设置了
android:fitsSystemWindows="true",那么对这个child进行fitSystemWindows操作,即
insets的值会添加到child的padding里面。注意:上面的
InsetsListener中,返回的结果是消费了fitsSystemWindows事件的,按正常逻辑是如果child也设置了
android:fitsSystemWindows="true"是没有效果的,而这里DrawerLayout主动对child进行了该处理,所以在DrawerLayout中,同时给它本身和它的child设置
android:fitsSystemWindows="true",都是有效果的。另外,
InsetsListener并没有将
insets值添加到DrawerLayout本身的padding里面。
如果child没有设置
android:fitsSystemWindows="true",则会调用
IMPL.applyMarginInsets(lp, mLastInsets, cgrav),该方法在5.x上的实现是将
insets的值设置为
child.LayoutParams的margin值。
然后看
onLayout()方法:
protected void onLayout(boolean changed, int l, int t, int r, int b) { mInLayout = true; final int width = r - l; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (isContentView(child)) { child.layout(lp.leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(), lp.topMargin + child.getMeasuredHeight()); } else { // Drawer, if it wasn't onMeasure would have thrown an exception. } // ... } }
看到
if (isContentView(child)),这里对
ContentView进行
child.layout()操作,只考虑了child的margin值,而没有考虑DrawerLayout本身的padding值,所以即使给DrawerLayout设置padding,也不会对child的位置有影响,在4.4上同样如此。而根据上面
onMeasure()的分析,
insets的值会被添加到child的padding或者margin里面,所以这里layout的时候,child的内容是不会跑到Status Bar下面去的。
到这里已经解决了上面的问题一:
1、页面全屏后如何处理ToolBar等内容的位置。
这时整个DrawerLayout都已经按正常位置layout完成了,只剩下Status Bar下的空白区域了,我们要做的就是对这块区域着色。接下来看
onDraw()方法:
public void onDraw(Canvas c) { super.onDraw(c); if (mDrawStatusBarBackground && mStatusBarBackground != null) { final int inset = IMPL.getTopInset(mLastInsets); if (inset > 0) { mStatusBarBackground.setBounds(0, 0, getWidth(), inset); mStatusBarBackground.draw(c); } } }
这里的作用是在Status Bar的位置画一块颜色区域,因为
inset只在5.x上才会有值,所以这里也只针对5.x进行了处理。这里就解决了上面的问题二:
2、Status Bar透明了,如何让Status Bar看起来像正常设置了颜色一样。
最后就是如何让Drawer弹出来的时候看起来像是在Status Bar下面,这里用到了这个类 ScrimInsetsFrameLayout (新版),作用就是在Status Bar的位置画一层半透明的颜色,从而实现效果。具体的可参考 How do I use DrawerLayout to display over the ActionBar/Toolbar and under the status bar?
最终效果如下:
4.4效果实现
根据上面的分析,DrawerLayout在4.4上没有对状态栏做任何处理,所以得我们自己来实现。按下面步骤来分析:
1、和5.x中一样,需要设置Status Bar透明,否则侧边栏会被遮住。
2、因为要让侧边栏全屏显示,所以不能对DrawerLayout设置
android:fitsSystemWindows="true"。
3、5.x上DrawerLayout本身已有关于Status Bar的实现,所以这里不能影响5.x。
实现这三点的代码如下:
StatusBarHelper helper = new StatusBarHelper(this, StatusBarHelper.LEVEL_19_TRANSLUCENT, StatusBarHelper.LEVEL_NONE); helper.setActivityRootLayoutFitSystemWindows(false); helper.setColor(Color.TRANSPARENT);
4、如何设置Status Bar位置的颜色呢?可以给ContentView设置一个padding或者margin(5.x中就是这么做的),这样Status Bar的位置就空出来了,那如何着色呢(DrawerLayout在4.4不支持着色,需要自己实现)?这里有多种方法,如果有ToolBar,可以给ToolBar设置一个paddingTop,然后Status Bar的位置就和ToolBar一样的颜色了。或者也可以在ToolBar上面加一个View,这样就可以自定义颜色,但要注意只能在4.4上显示出来。这里提供一种更加优雅的方法,对 ScrimInsetsFrameLayout (新版)进行修改:
public class ScrimInsetsFrameLayout extends FrameLayout { private Drawable mInsetForeground; private boolean mConsumeInsets; private Rect mInsets; private Rect mTempRect = new Rect(); private OnInsetsCallback mOnInsetsCallback; public ScrimInsetsFrameLayout(Context context) { super(context); init(context, null, 0); } public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0); } public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs, defStyle); } private void init(Context context, AttributeSet attrs, int defStyle) { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ScrimInsetsView, defStyle, 0); if (a == null) { return; } mInsetForeground = a.getDrawable(R.styleable.ScrimInsetsView_appInsetForeground); mConsumeInsets = a.getBoolean(R.styleable.ScrimInsetsView_appConsumeInsets, true); a.recycle(); setWillNotDraw(true); } @Override protected boolean fitSystemWindows(Rect insets) { mInsets = new Rect(insets); setWillNotDraw(mInsetForeground == null); ViewCompat.postInvalidateOnAnimation(this); if (mOnInsetsCallback != null) { mOnInsetsCallback.onInsetsChanged(insets); } return mConsumeInsets || super.fitSystemWindows(insets); } @Override public void draw(Canvas canvas) { super.draw(canvas); int width = getWidth(); int height = getHeight(); if (mInsets != null && mInsetForeground != null) { int sc = canvas.save(); canvas.translate(getScrollX(), getScrollY()); // Top mTempRect.set(0, 0, width, mInsets.top); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Bottom mTempRect.set(0, height - mInsets.bottom, width, height); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Left mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Right mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); canvas.restoreToCount(sc); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mInsetForeground != null) { mInsetForeground.setCallback(this); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mInsetForeground != null) { mInsetForeground.setCallback(null); } } /** * Allows the calling container to specify a callback for custom processing when insets change (i.e. when * {@link #fitSystemWindows(Rect)} is called. This is useful for setting padding on UI elements based on * UI chrome insets (e.g. a Google Map or a ListView). When using with ListView or GridView, remember to set * clipToPadding to false. */ public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) { mOnInsetsCallback = onInsetsCallback; } public interface OnInsetsCallback { void onInsetsChanged(Rect insets); } }
然后我们的layout文件就可以这样写:
<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout android:id="@+id/drawer" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity"> <!--content--> <me.naturs.statusbarhelper.ScrimInsetsFrameLayout android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent" app:appConsumeInsets="false" app:appInsetForeground="@color/colorPrimaryDark"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/colorPrimary" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> </LinearLayout> </me.naturs.statusbarhelper.ScrimInsetsFrameLayout> <!--drawer--> <me.naturs.statusbarhelper.ScrimInsetsFrameLayout android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:appInsetForeground="#4000"> </me.naturs.statusbarhelper.ScrimInsetsFrameLayout> </android.support.v4.widget.DrawerLayout>
这里在原来的
ScrimInsetsFrameLayout基础上添加一个
appConsumeInsets属性,用来控制
protected boolean fitSystemWindows(Rect insets)方法的返回值。上面content的layout设置为
app:appConsumeInsets="false",但没有设置
android:fitsSystemWindows="true",这样Status Bar区域着色了,但没有消耗insets事件,也就不会影响drawer的着色了。
最后需要在代码中监听insets的变化,以便给content设置padding:
mScrimInsetsFrameLayout = (ScrimInsetsFrameLayout) findViewById(R.id.layout); mScrimInsetsFrameLayout.setOnInsetsCallback(new ScrimInsetsFrameLayout.OnInsetsCallback() { @Override public void onInsetsChanged(Rect insets) { mScrimInsetsFrameLayout.setPadding(0, insets.top, 0, 0); } });
同样的代码运行在5.x上,不会有任何影响,不需要做额外的处理。因为5.x上会在DrawerLayout中设置ContentView的padding或margin,这里fitSystemWindows为false,所以会设置margin,也就是
ScrimInsetsFrameLayout.fitSystemWindows()方法不会被调用。
搭配滑动返回使用
滑动返回的开源库:SwipeBackLayout与滑动返回搭配使用的时候,需要考虑的问题主要是滑动返回的时候,状态栏该怎样变化,而该沉浸状态栏的实现方式可以直接兼容滑动返回。看到SwipeBackLayout中的关键代码:
public void attachToActivity(Activity activity) { mActivity = activity; TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{ android.R.attr.windowBackground }); int background = a.getResourceId(0, 0); a.recycle(); ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView(); ViewGroup decorChild = (ViewGroup) decor.getChildAt(0); decorChild.setBackgroundResource(background); decor.removeView(decorChild); addView(decorChild); setContentView(decorChild); decor.addView(this); }
滑动返回的layout是直接添加到decor view下的,而我们的沉浸状态栏是在android.R.id.content下处理的。但要注意的是,在5.x上使用的时候,应该使用
StatusBarHelper.LEVEL_21_VIEW才能达到更好的效果。
开源
项目地址:https://github.com/naturs/StatusBarHelper相关文章推荐
- Android LogUtil(log工具类)
- Android客户端性能优化(魅族资深工程师毫无保留奉献)
- Android RecyclerView
- 《Android第一行代码》笔记
- Android中Toast如何在子线程中调用
- Android Studio 使用小技巧和快捷键
- 广师Android群分享之各版本特性
- android性能优化
- Android Studio sdk tools文件夹下文件缺失问题以及解决方法
- Android:我为何要封装DialogFragment?
- 带侧边栏字母索引的列表--重写Button类
- 【Android】15.3 Notification基础知识
- 【Android】15.2 广播
- 【Android】15.1 后台任务和前台任务
- Android内存泄露 (五)
- Android内存泄露 (三)
- ANDROID 探究OOM内幕
- Android内存泄露 (二)
- dp与px之间的转换(android屏幕适配)
- Android:将布局的内容延伸到状态栏