在Android的Material Design出现后,一个更简洁,更舒服,更清爽的界面是开发者们所追求动,也是一个成功app的前提。那么怎么让app看起来更加舒服呢?这里有一个方法。
让app看起来整体统一,整体统一也就是一体化的意思,怎么做到一体化呢,有两种方式,网上对于两种不同的方式经常混在一起讲,不过两者都是一体化的的概念
1. Translucent bar(半透明的状态栏):状态栏和导航栏半透明,颜色可以随着app的样式进行相应的调整,和app的内容风格进行融合。这个是Google在SDK19中,提出的一个概念,也通过增加了api进行了一定的支持。本博客提供一个工具类能够更方便的操作Translucent bar——DyeingBarHelper
2. 沉浸式状态栏:没有状态栏和导航栏,让一个app完整的展示在屏幕上。
从字面上的意思大概可以区分这两种模式的区别,下面也通过例子进行一个更加直观的说明,同时也进行一个实现。在继续看之前,要先了解下Status Bar(最顶部的有电池Wi-Fi信息的一栏)和NavigationBar(底部的返回键,home键,menu键,这个不是所有机子都有的,主要看是不是做在屏幕内了,还是独立于屏幕外)
这种模式的主要效果是让app的整体风格保持一致,看起来更加清爽,舒服,界面统一。
在android的19之后,就有提供设置设置状态栏为半透明的方法:
// 设置Navigtion bar半透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
// 设置Status bar半透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
还可以通过xml设置,然后再activity中设置theme
<style name="AppTheme" parent="AppBaseTheme">
-- Status Bar -->
<item name="android:windowTranslucentStatus">trueitem>
-- Navigation Bar -->
<item name="android:windowTranslucentNavigation">trueitem>
style>
设置了半透明之后需要注意,整个的app会拉伸,也就是可使用的布局空间变成一整个屏幕,即空间会被status bar遮挡,再xml中可以设置如下代码,使得特定的组件不会被移动到顶部。我的建议是,如果你想和原来一样的话,直接在布局的最外层在加一层layout,在这个layout上使用一下代码,这样其他的布局都和原来一致,也不会有遮挡的问题。
android:fitsSystemWindows="true"
在android的21之后,就有提供API可以对状态栏进行颜色的更改:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(Color.GREEN);
getWindow().setNavigationBarColor(Color.GREEN);
mText.setText("看,轻而易举的实现了translucent bar的效果,但是目前可进行定制的能力不强");
} else {
mText.setText("系统api要求21以上");
}
尽管Google已经提供给开发者更好的api去开发app,但是这远远达不到我们想随意操作的地步,所以,为了能够更加便捷的随心所欲的开发更具有一体化感觉的app,更改System UI也就是Status Bar和Navigation Bar是一个尤为重要的步骤。这里通过介绍一个工具类DyeingBarHelper来进行如何自定义System UI的分享。
System UI指的是Decor View,也就是一个app的窗口Window的最基本的组件view,也就是RootView,而DecorView是一个FrameLayout,其子view仅包括了一层LinearLayout,而LinearLaoyout->FrameLaoyout->LinearLayout之后就是开发者可以添加app界面的一个布局位置
而由于SystemUI的实现是在LinearLayout层,而现在提供的API能够将SystemUI设置为半透明,那么我们要做的就是在FrameLayout中,把预设好的view塞到SystemUI的位置,这样我们就可以随心所欲的对view进行订制。
// 设置状态栏和导航栏为透明,否则无法达到效果
DyeingBarHelper.setBarTranslucent(this);
// DyeingBarHelper的初始化
DyeingBarHelper helper = new DyeingBarHelper(this);
// 提供自定义navigation bar和status bar
// DyeingBarHelper helper = new DyeingBarHelper(this, statusView, navigationView);
// 设置颜色
helper.setStatusBarColor(Color.BLUE);
helper.setNavigationBarColor(Color.BLUE);
// 设置透明度
helper.setStatusBarViewAlpha(0);
helper.setNavigationBarViewAlpha(0);
// 设置可见
helper.setStatusBarViewVisibility(View.VISIBLE);
helper.setNavigationBarViewVisibility(View.VISIBLE);
// 设置背景
helper.setStatusBarViewBackground(drawable);
helper.setNavigationBarViewBackground(drawable);
工程主要包括三个类,
1. DyeingBarHelper.java 作用:设置SystemUI。
2. SystemBarConfig.java 作用:获取System UI的一些设置,比如StatusBar的高度等
3. TintingUtil.java 作用:帮助染色,从一个view中获取它的背景颜色,比如最多的颜色,平均颜色。方便之处,可以从title bar中获取颜色,使得整个app颜色达到一体化,可以看demo中的slideview的测试。
下面主要介绍一下DyeingBarHelper的整体思路:
1.判断是否设置了System UI透明。这个可以通过flag进行和Window的flag进行与运算。看其标志位是否被设置了。这个方法只适合于api19以后,前文有提及。若要设置透明度,前文也有提及。
// 以下方法在需要在api 19中使用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 判断是否在theme里面定义了bar得透明
int[] attrs = new int[]{android.R.attr.windowTranslucentStatus,
android.R.attr.windowTranslucentStatus};
TypedArray typedArray = mContext.obtainStyledAttributes(attrs);
try {
isStatusBarTranslucent = typedArray.getBoolean(0, false);
isStatusBarTranslucent = typedArray.getBoolean(1, false);
} finally {
typedArray.recycle();
}
// 判断是否在代码中设置了bar透明
Window window = ((Activity) mContext).getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if ((bits & layoutParams.flags) != 0) {
isStatusBarTranslucent = true;
}
bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
if ((bits & layoutParams.flags) != 0) {
isNavigationBarTranslucent = true;
}
}
2.不是所有的机子都有Navigation Bar的,所以 要判断一下是否有NavigationBar,这个方法实在android中的源码学习而来,学会看android源码,才能进阶哇。
/**
* 在Android l以上判断是否有NavigationBar的方法,通过查看phoneWindowManager源码可以知道这个方法
*
* @return
*/
public boolean hasNavigationBar() {
boolean hasNavigationBar = false;
Resources rs = mContext.getResources();
int id = rs.getIdentifier("config_showNavigationBar", "bool", "android");
if (id > 0) {
hasNavigationBar = rs.getBoolean(id);
}
try {
Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
Method m = systemPropertiesClass.getMethod("get", String.class);
String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
hasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
hasNavigationBar = true;
}
} catch (Exception e) {
Log.w(TAG, e);
}
return hasNavigationBar;
}
3.如果有status bar和navigation bar那么就可以把自定义view塞到它们的位置上去了,由于获得的decor view是frame layout 所以,layoutparams使用frame layout的,同时要注意横屏和竖屏,通过判断手机当前的方向。另外需要注意的就是,如何获得status bar的高度和navigation bar的高度,主要是通过resource.getIdentifier()方法获取,当然首先是要知道对应的字段。
// 判断当前手机的方向
this.isPortrait = (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
/**
* 获取Status Bar的高度
*/
public int getStatusBarHeight() {
String key = "status_bar_height";
return getInternalDimensionSize(key);
}
/**
* 获取Navigation Bar的高度
*
* @return
*/
public int getNavigationBarHeight() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
if (hasNavigationBar()) {
String key;
if (isPortrait) {
key = "navigation_bar_height";
} else {
key = "navigation_bar_height_landscape";
}
return getInternalDimensionSize(key);
}
}
return 0;
}
/**
* 获取Navigation Bar的宽度
*
* @return
*/
public int getNavigationBarWidth() {
String key = "navigation_bar_width";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
if (hasNavigationBar()) {
return getInternalDimensionSize(key);
}
}
return 0;
}
/**
* 获取指定资源的值
*
* @param key
* @return
*/
private int getInternalDimensionSize(String key) {
int result = 0;
int resourceId = res.getIdentifier(key, "dimen", "android");
if (resourceId > 0) {
result = res.getDimensionPixelSize(resourceId);
}
return result;
}
/**
* 设置顶部Status Bar的view
*
* @param decorViewGroup
*/
private void setStatusBarView(ViewGroup decorViewGroup) {
if (mStatusBarView == null) {
mStatusBarView = new View(mContext);
}
FrameLayout.LayoutParams params;
params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
mConfig.getStatusBarHeight());
params.gravity = Gravity.TOP;
// 如果是横屏,则应该减去右边navigation bar的宽度,防止挡住navigation bar
if (!mConfig.isNavigationBarAtBottom()) {
params.rightMargin = mConfig.getNavigationBarWidth();
}
mStatusBarView.setLayoutParams(params);
mStatusBarView.setBackgroundColor(DEFAULT_BAR_COLOR);
decorViewGroup.addView(mStatusBarView);
}
/**
* 设置底部Navigation Bar的view
*
* @param decorViewGroup
*/
private void setNavigationBarView(ViewGroup decorViewGroup) {
if (mNavigationBarView == null) {
mNavigationBarView = new View(mContext);
}
FrameLayout.LayoutParams params;
if (mConfig.isNavigationBarAtBottom()) {
params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, mConfig.getNavigationBarHeight());
params.gravity = Gravity.BOTTOM;
} else {
params = new FrameLayout.LayoutParams(mConfig.getNavigationBarWidth(), FrameLayout.LayoutParams.MATCH_PARENT);
params.gravity = Gravity.RIGHT;
}
mNavigationBarView.setLayoutParams(params);
mNavigationBarView.setBackgroundColor(DEFAULT_BAR_COLOR);
decorViewGroup.addView(mNavigationBarView);
}
这样就大功告成。
接着介绍一下TintingUtil,这个的主要作用是提取view的颜色,想法很简单,就是把view的bitmap取出,就可以得到每一个像素点的颜色,这样就可以进行类似颜色最多的求取,平均颜色的求取。
1.首先从view中获取bitmap,多做的一个步骤就是在create bitmap的时候,防止内存不足而多次create。
/**
* 从view中去获取一个bitmap
* @param view
* @return
*/
protected Bitmap createBitmapFromView(View view) {
// ImageView直接获取它的drawable
if (view instanceof ImageView) {
Drawable drawable = ((ImageView) view).getDrawable();
if (drawable != null && drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
}
// 有可能为0, 比如在activity正在onCreate时,不建议进行此操作
int width = view.getWidth();
int height = view.getHeight();
Bitmap bitmap = createBitmapSafely(width, height, Bitmap.Config.ARGB_8888, 1);
if (bitmap != null) {
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
canvas.setBitmap(null);
canvas = null;
}
return bitmap;
}
/**
*
* @param width
* @param height
* @param config
* @param times create bitmap的次数,决定于当前的内存
* @return
*/
protected Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int times) {
try {
return Bitmap.createBitmap(width, height, config);
} catch (OutOfMemoryError e) {
if (times > 0) {
System.gc();
return createBitmapSafely(width, height, config, --times);
}
} catch (Exception e) {
Log.e(TAG, "view width or height shuold not < 0");
}
return null;
}
2.获取bitmap像素点颜色,做其他操作。
int color = bitmap.getPixel(i, j);
android粒子爆炸效果
android systembarTint
这种模式主要是让整一个app能够霸占整个屏幕,能够让用户完全的沉浸于这个app中,有很多的app都有这样的功能,比如阅读类app,下面的展示图就是小米自带的一个阅读app。
主要的原理是隐藏status bar和navigation bar
在android的官网上有这样的System UI的教程,如果想了解的更细致,建议浏览官网。
操作System UI的做法,由于System UI属于activity基础界面 decor view的其中的一个布局,可以通过设置可见来实现隐藏和展示:
// 获取窗口的decor view
mDecorView = getWindow().getDecorView();
/**
* 隐藏status bar和navigation bar
*/
private void hideSystemUI() {
mDecorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_IMMERSIVE);
}
/**
* 显示status bar和navigation bar
*/
private void showSystemUI() {
mDecorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
在隐藏了System UI之后,如何动态的控制它在出现之后又自动隐藏呢,比如阅读app上的效果,点击出现System UI,一阵子后就消失。这个可以通过延时任务进行操作,同时要设置decorView的监听器,监听SystemUi的变化:
mDecorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
invokeHide();
}
}
});
public void invokeHide() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
hideSystemUI();
}
}, 1000);
}
效果: