在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);}

效果:


项目地址

更多相关文章

  1. Android实现自适应正方形GridView Read more: http://blog.cheng
  2. Android获取三轴加速度和view的重绘
  3. 自定义获取WI-FI列表及相关设置
  4. Android中TextView实现分段显示不同颜色的字符串
  5. Android里的Xmpp的理解(消息推送)
  6. 基于android的远程视频监控系统——实现,
  7. 获取Android设备唯一标识码以及其他信息
  8. Android基础知识复习之打开照相机拍照并获取照片
  9. Android高效率编码-第三方SDK详解系列(一)——百度地图,绘制,覆盖

随机推荐

  1. Android(安卓)init.rc文件解析过程详解(
  2. Android内存使用情况的应用实例
  3. 殷志威
  4. android 中查看当前是否联网
  5. android所有版本源码下载网站
  6. android studio 3.0 build错误
  7. 第十二节(android常用控件三)
  8. android添加reboot选项
  9. androidの获取android手机信息
  10. Android 修改默认USB模式