Android之Translucent Bar半透明栏和immersive bar沉浸式的区别
在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键,这个不是所有机子都有的,主要看是不是做在屏幕内了,还是独立于屏幕外)
Tanslucent Bar的实现——DyeingBarHelper(仓库地址)
这种模式的主要效果是让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的使用
// 设置状态栏和导航栏为透明,否则无法达到效果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);
示例
DyeingBarHelper的实现
工程主要包括三个类,
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);
DyeingBarHelper项目仓库地址
参考网址
android粒子爆炸效果
android systembarTint
沉浸式状态栏(仓库地址)
这种模式主要是让整一个app能够霸占整个屏幕,能够让用户完全的沉浸于这个app中,有很多的app都有这样的功能,比如阅读类app,下面的展示图就是小米自带的一个阅读app。
原理
主要的原理是隐藏status bar和navigation bar
实现(android官网参考)
在android的官网上有这样的System UI的教程,如果想了解的更细致,建议浏览官网。
操作System UI的做法,由于System UI属于activity基础界面 decor view的其中的一个布局,可以通过设置可见来实现隐藏和展示:
// 获取窗口的decor viewmDecorView = 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);}
效果:
项目地址
更多相关文章
- Android实现自适应正方形GridView Read more: http://blog.cheng
- Android获取三轴加速度和view的重绘
- 自定义获取WI-FI列表及相关设置
- Android中TextView实现分段显示不同颜色的字符串
- Android里的Xmpp的理解(消息推送)
- 基于android的远程视频监控系统——实现,
- 获取Android设备唯一标识码以及其他信息
- Android基础知识复习之打开照相机拍照并获取照片
- Android高效率编码-第三方SDK详解系列(一)——百度地图,绘制,覆盖