详解Android中的沉浸式状态栏效果实例

无意间了解到沉浸式状态栏,感觉贼拉的高大上,于是就是试着去了解一下,就有了这篇文章。下面就来了解一下啥叫沉浸式状态栏。传统的手机状态栏是呈现出黑色条状的,有的和手机主界面有很明显的区别。这一样就在一定程度上牺牲了视觉宽度,界面面积变小。

Google从android kitkat(Android 4.4)开始,给我们开发者提供了一套能透明的系统ui样式给状态栏和导航栏,这样的话就不用向以前那样每天面对着黑乎乎的上下两条黑栏了,还可以调成跟Activity一样的样式,形成一个完整的主题,和IOS7.0以上系统一样了,沉浸式状态栏和主界面颜色和谐一体,视觉效果更加炫酷。

不过虽然听上去好像是很高大上的沉浸式效果,实际看上去貌似就是将内容全屏化了而已嘛。其实这算是一个争议点了。不少人纠结于沉浸式状态栏到底是将屏幕显示内容扩大还是仅仅是改变状态栏、标题栏的颜色。其实我更倾向于后者。在4.4之前状态栏一直是黑色的,在4.4中带来了 windowTranslucentStatus 这一特性,因此可以实现给状态栏设置颜色,视觉上的效果,感觉容器部分和状态栏、标题栏融为一体,更加直接的说就是改变状态栏、标题栏的颜色,当时可以根据界面颜色改变状态栏、标题栏的颜色实现跟加完整的界面显示,这应该是沉浸式状态栏受追捧的原因吧。

谷歌并没有给出沉浸式状态栏这个概念,谷歌只说了沉浸式模式(Immersive Mode)。不过沉浸式状态栏这个名字其实挺不错,只能随大众,但是Android的环境并没有IOS环境一样特别统一,比如华为rom的跟小米rom的虚拟按键完全不一样,并且安卓版本众多涉及到版本兼容问题,所有Android开发者不容易。这点在沉浸式状态栏的开发中显得尤为重要。如果你在4.4之前的机子上显示沉浸式状态栏的话,经常出现一些意想不到的结果。

沉浸式是APP界面图片延伸到状态栏, 应用本身沉浸于状态栏,所以如果第三方的软件没有为状态栏分配图片,那么自然就是黑色。顶端的状态栏和下面的虚拟按键都隐藏,需要的时候从边缘划出。沉浸模式。当启用该模式,应用程序的界面将占据整个屏幕,系统自动将隐藏系统的状态栏和导航栏,让应用程序内容可以在最大显示范围呈现,增加大屏体验,而当需要查看通知的时候只需要从顶部向下滑动就能呼出通知栏。

沉浸模式实际上有两种: 一种叫“沉浸模式”,状态栏和虚拟按钮会自动隐藏、应用自动全屏,这种模式下,应用占据屏幕的全部空间, 只有当用户从屏幕的上方边沿处向下划动时, 才会退出沉浸模式, 用户触摸屏幕其它部分时, 不会退出该模式, 这种模式比较适用于阅读器、 杂志类应用。另外一种叫“黏性沉浸模式”,让状态栏和虚拟按钮半透明,应用使用屏幕的全部空间, 当用户从屏幕的上方边沿处向下滑动时,也不会退出该模式, 但是系统界面 (状态栏、 导航栏) 将会以半透明的效果浮现在应用视图之上 , 只有当用户点击系统界面上的控件时, 才会退出黏性沉浸模式。

下面来说一说具体的实现。一个Android应用程序的界面上其实是有很多系统元素的,有状态栏、ActionBar、导航栏等。而打造沉浸式模式的用户体验,就是要将这些系统元素进行整合,当主界面改变时,状态栏、ActionBar、导航栏同时也发生改变。这里先调用getWindow().getDecorView()方法获取到了当前界面的DecorView,然后调用它的setSystemUiVisibility()方法来设置系统UI元素的可见性。其中,SYSTEM_UI_FLAG_FULLSCREEN表示全屏的意思,也就是会将状态栏隐藏。另外,根据Android的设计建议,ActionBar是不应该独立于状态栏而单独显示的,因此状态栏如果隐藏了,我们同时也需要调用ActionBar的hide()方法将ActionBar也进行隐藏这种效果不叫沉浸式状态栏,也完全没有沉浸式状态栏这种说法,我们估且可以把它叫做透明状态栏效果吧。

隐藏状态栏:

setContentView(R.layout.activity_main); //再该方法后执行
if (Build.VERSION.SDK_INT >= 21) {
  View decorView = getWindow().getDecorView();
  int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
      | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
  decorView.setSystemUiVisibility(option);
  getWindow().setStatusBarColor(Color.TRANSPARENT);
}
ActionBar actionBar = getSupportActionBar();
actionBar.hide();

具体的沉浸效果该如何实现呢,系统提供实现沉浸式状态栏的方法,通过WindowManager来实现,可分为两步:

1. 在需要实现沉浸式状态栏的Activity的布局中添加以下参数

android:fitsSystemWindows="true"
android:clipToPadding="true"

2. 在Activity的setContentView()方法后面调用初始化的方法即可。

private void initState() {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      //透明状态栏
       getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
      //透明导航栏
       getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
      }
    }

当上述的实现效果,其实并不好, 没有在布局中设置clipToPadding为true的时候,会对应用的顶部Toolbar进行拉伸,在布局中两个参数都进行设置后,顶部状态栏变成了白色。这样,我在github上找到一个很好的沉浸状态栏效果,来看一下。

首先添加依赖,导入下面的包。有时候可能会出现版本不统一的问题,依次保证联网的情况下点击一下同步android studio会自动下载包。

compile 'com.jaeger.statusbaruitl:library:1.2.5'

在自定义控件中实现的进本逻辑,代码较长。

package com.xiaoyuan;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.widget.ScrollView;

import java.util.ArrayList;

/**
 * @author Emil Sj�lander - sjolander.emil@gmail.com
 */
public class StickyScrollView extends ScrollView {

  /**
   * Tag for views that should stick and have constant drawing. e.g.
   * TextViews, ImageViews etc
   */
  public static final String STICKY_TAG = "sticky";

  /**
   * Flag for views that should stick and have non-constant drawing. e.g.
   * Buttons, ProgressBars etc
   */
  public static final String FLAG_NONCONSTANT = "-nonconstant";

  /**
   * Flag for views that have aren't fully opaque
   */
  public static final String FLAG_HASTRANSPARANCY = "-hastransparancy";

  /**
   * Default height of the shadow peeking out below the stuck view.
   */
  private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp;
  /**
   * XKJ add for add 50dp offset of top
   */
  private static int MIN_STICK_TOP = 100;// px
  //  private static final int MIN_STICK_TOP = 0;
  private ArrayList<View> stickyViews;
  private View currentlyStickingView;
  private float stickyViewTopOffset;
  private int stickyViewLeftOffset;
  private boolean redirectTouchesToStickyView;
  private boolean clippingToPadding;
  private boolean clipToPaddingHasBeenSet;

  private int mShadowHeight;
  private Drawable mShadowDrawable;
  private OnScrollChangedListener mOnScrollHandler = null;
  private IOnScrollToEnd mOnScrollToEnd = null;

  private final Runnable invalidateRunnable = new Runnable() {

    @Override
    public void run() {
      if (currentlyStickingView != null) {
        int l = getLeftForViewRelativeOnlyChild(currentlyStickingView);
        int t = getBottomForViewRelativeOnlyChild(currentlyStickingView);
        int r = getRightForViewRelativeOnlyChild(currentlyStickingView);
        int b = (int) (getScrollY() + (currentlyStickingView.getHeight() + stickyViewTopOffset));
        invalidate(l, t, r, b);
      }
      postDelayed(this, 16);
    }
  };

  public StickyScrollView(Context context) {
    this(context, null);
  }

  public StickyScrollView(Context context, AttributeSet attrs) {
    this(context, attrs, android.R.attr.scrollViewStyle);
  }

  public StickyScrollView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setup();

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StickyScrollView, defStyle, 0);

    final float density = context.getResources().getDisplayMetrics().density;
    int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);

    mShadowHeight = a.getDimensionPixelSize(R.styleable.StickyScrollView_stuckShadowHeight,
        defaultShadowHeightInPix);

    int shadowDrawableRes = a.getResourceId(R.styleable.StickyScrollView_stuckShadowDrawable, -1);

    if (shadowDrawableRes != -1) {
      mShadowDrawable = context.getResources().getDrawable(shadowDrawableRes);
    }

    a.recycle();

  }

  /**
   * Sets the height of the shadow drawable in pixels.
   *
   * @param height
   */
  public void setShadowHeight(int height) {
    mShadowHeight = height;
  }

  public void setup() {
    stickyViews = new ArrayList<View>();
  }

  private int getLeftForViewRelativeOnlyChild(View v) {
    int left = v.getLeft();
    while (v.getParent() != getChildAt(0)) {
      v = (View) v.getParent();
      left += v.getLeft();
    }
    return left;
  }

  private int getTopForViewRelativeOnlyChild(View v) {
    int top = v.getTop();
    while (v.getParent() != getChildAt(0)) {
      v = (View) v.getParent();
      top += v.getTop();
    }
    return top;
  }

  private int getRightForViewRelativeOnlyChild(View v) {
    int right = v.getRight();
    while (v.getParent() != getChildAt(0)) {
      v = (View) v.getParent();
      right += v.getRight();
    }
    return right;
  }

  private int getBottomForViewRelativeOnlyChild(View v) {
    int bottom = v.getBottom();
    while (v.getParent() != getChildAt(0)) {
      v = (View) v.getParent();
      bottom += v.getBottom();
    }
    return bottom;
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    if (!clipToPaddingHasBeenSet) {
      clippingToPadding = true;
    }
    notifyHierarchyChanged();
  }

  @Override
  public void setClipToPadding(boolean clipToPadding) {
    super.setClipToPadding(clipToPadding);
    clippingToPadding = clipToPadding;
    clipToPaddingHasBeenSet = true;
  }

  @Override
  public void addView(View child) {
    super.addView(child);
    findStickyViews(child);
  }

  @Override
  public void addView(View child, int index) {
    super.addView(child, index);
    findStickyViews(child);
  }

  @Override
  public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
    super.addView(child, index, params);
    findStickyViews(child);
  }

  @Override
  public void addView(View child, int width, int height) {
    super.addView(child, width, height);
    findStickyViews(child);
  }

  @Override
  public void addView(View child, android.view.ViewGroup.LayoutParams params) {
    super.addView(child, params);
    findStickyViews(child);
  }

  @Override
  protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    if (currentlyStickingView != null) {
      canvas.save();
      canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() + stickyViewTopOffset
          + (clippingToPadding ? getPaddingTop() : 0));

      canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth() - stickyViewLeftOffset,
          currentlyStickingView.getHeight() + mShadowHeight + 1);

      if (mShadowDrawable != null) {
        int left = 0;
        int right = currentlyStickingView.getWidth();
        int top = currentlyStickingView.getHeight();
        int bottom = currentlyStickingView.getHeight() + mShadowHeight;
        mShadowDrawable.setBounds(left, top, right, bottom);
        mShadowDrawable.draw(canvas);
      }

      canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth(),
          currentlyStickingView.getHeight());
      if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
        showView(currentlyStickingView);
        currentlyStickingView.draw(canvas);
        hideView(currentlyStickingView);
      } else {
        currentlyStickingView.draw(canvas);
      }
      canvas.restore();
    }
  }

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
      redirectTouchesToStickyView = true;
    }

    if (redirectTouchesToStickyView) {
      redirectTouchesToStickyView = currentlyStickingView != null;
      if (redirectTouchesToStickyView) {
        redirectTouchesToStickyView = ev.getY() <= (currentlyStickingView.getHeight() + stickyViewTopOffset)
            && ev.getX() >= getLeftForViewRelativeOnlyChild(currentlyStickingView)
            && ev.getX() <= getRightForViewRelativeOnlyChild(currentlyStickingView);
      }
    } else if (currentlyStickingView == null) {
      redirectTouchesToStickyView = false;
    }
    if (redirectTouchesToStickyView) {
      ev.offsetLocation(0, -1
          * ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));

      // XKJ add TODO: remove this
      currentlyStickingView.invalidate();
    }
    return super.dispatchTouchEvent(ev);
  }

  private boolean hasNotDoneActionDown = true;

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (redirectTouchesToStickyView) {
      ev.offsetLocation(0,
          ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));
    }

    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
      hasNotDoneActionDown = false;
    }

    if (hasNotDoneActionDown) {
      MotionEvent down = MotionEvent.obtain(ev);
      down.setAction(MotionEvent.ACTION_DOWN);
      super.onTouchEvent(down);
      hasNotDoneActionDown = false;
    }

    if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
      hasNotDoneActionDown = true;
    }

    return super.onTouchEvent(ev);
  }

  @Override
  protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);
    doTheStickyThing();
    if (mOnScrollHandler != null) {
      mOnScrollHandler.onScrollChanged(l, t, oldl, oldt);
    }
    int maxScroll = getChildAt(0).getHeight() - getHeight();
    if (getChildCount() > 0 && t == maxScroll) {
      if (mOnScrollToEnd != null) {
        mOnScrollToEnd.onScrollToEnd();
      }
    }
  }

  public void setOnScrollListener(OnScrollChangedListener handler) {
    mOnScrollHandler = handler;
  }

  public interface OnScrollChangedListener {
    public void onScrollChanged(int l, int t, int oldl, int oldt);
  }

  public interface IOnScrollToEnd {
    public void onScrollToEnd();
  }

  public void setOnScrollToEndListener(IOnScrollToEnd handler) {
    mOnScrollToEnd = handler;
  }

  private void doTheStickyThing() {
    View viewThatShouldStick = null;
    View approachingView = null;
    for (View v : stickyViews) {
      int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop())
          - MIN_STICK_TOP;// add 50dp

      if (viewTop <= 0) {
        if (viewThatShouldStick == null
            || viewTop > (getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0
            : getPaddingTop()))) {
          viewThatShouldStick = v;
        }
      } else {
        if (approachingView == null
            || viewTop < (getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0
            : getPaddingTop()))) {
          approachingView = v;
        }
      }
    }
    if (viewThatShouldStick != null) {
      stickyViewTopOffset = approachingView == null ? MIN_STICK_TOP : Math.min(MIN_STICK_TOP,
          getTopForViewRelativeOnlyChild(approachingView) - getScrollY()
              + (clippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight());
      if (viewThatShouldStick != currentlyStickingView) {
        if (currentlyStickingView != null) {
          stopStickingCurrentlyStickingView();
        }
        // only compute the left offset when we start sticking.
        stickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);
        startStickingView(viewThatShouldStick);
      }
    } else if (currentlyStickingView != null) {
      stopStickingCurrentlyStickingView();
    }
  }

  private void startStickingView(View viewThatShouldStick) {
    currentlyStickingView = viewThatShouldStick;
    if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
      hideView(currentlyStickingView);
    }
    if (((String) currentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)) {
      post(invalidateRunnable);
    }
  }

  private void stopStickingCurrentlyStickingView() {
    if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
      showView(currentlyStickingView);
    }
    currentlyStickingView = null;
    removeCallbacks(invalidateRunnable);
  }

  /**
   * Notify that the sticky attribute has been added or removed from one or
   * more views in the View hierarchy
   */
  public void notifyStickyAttributeChanged() {
    notifyHierarchyChanged();
  }

  private void notifyHierarchyChanged() {
    if (currentlyStickingView != null) {
      stopStickingCurrentlyStickingView();
    }
    stickyViews.clear();
    findStickyViews(getChildAt(0));
    doTheStickyThing();
    invalidate();
  }

  private void findStickyViews(View v) {
    if (v instanceof ViewGroup) {
      ViewGroup vg = (ViewGroup) v;
      for (int i = 0; i < vg.getChildCount(); i++) {
        String tag = getStringTagForView(vg.getChildAt(i));
        if (tag != null && tag.contains(STICKY_TAG)) {
          stickyViews.add(vg.getChildAt(i));
        } else if (vg.getChildAt(i) instanceof ViewGroup) {
          findStickyViews(vg.getChildAt(i));
        }
      }
    } else {
      String tag = (String) v.getTag();
      if (tag != null && tag.contains(STICKY_TAG)) {
        stickyViews.add(v);
      }
    }
  }

  private String getStringTagForView(View v) {
    Object tagObject = v.getTag();
    return String.valueOf(tagObject);
  }

  private void hideView(View v) {
    if (Build.VERSION.SDK_INT >= 11) {
      v.setAlpha(0);
    } else {
      AlphaAnimation anim = new AlphaAnimation(1, 0);
      anim.setDuration(0);
      anim.setFillAfter(true);
      v.startAnimation(anim);
    }
  }

  private void showView(View v) {
    if (Build.VERSION.SDK_INT >= 11) {
      v.setAlpha(1);
    } else {
      AlphaAnimation anim = new AlphaAnimation(0, 1);
      anim.setDuration(0);
      anim.setFillAfter(true);
      v.startAnimation(anim);
    }
  }

  /**
   * 设置悬浮高度
   * @param height
   */
  public void setStickTop(int height) {
    MIN_STICK_TOP = height;
  }

  /**
   * 解决vviewpager在scrollview滑动冲突的问题
   */
  // 滑动距离及坐标
  private float xDistance, yDistance, xLast, yLast;

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        xDistance = yDistance = 0f;
        xLast = ev.getX();
        yLast = ev.getY();
        break;
      case MotionEvent.ACTION_MOVE:
        final float curX = ev.getX();
        final float curY = ev.getY();

        xDistance += Math.abs(curX - xLast);
        yDistance += Math.abs(curY - yLast);
//        com.ihaveu.utils.Log.i("test", "curx:"+curX+",cury:"+curY+",xlast:"+xLast+",ylast:"+yLast);
//        xLast = curX;
//        yLast = curY;

        if (xDistance > yDistance) {
          return false;
        }

    }
    return super.onInterceptTouchEvent(ev);
  }
}

接下来是调用自定义控件了,用到两个关键的方法。StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title)和llTitle.setBackgroundColor(Color.argb((int) alpha, 227, 29, 26))分别设置状态栏和标题栏的颜色。

 package com.xiaoyuan;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.jaeger.library.StatusBarUtil;

public class MainActivity extends AppCompatActivity implements View.OnClickListener, StickyScrollView.OnScrollChangedListener {

  TextView oneTextView, twoTextView;
  private StickyScrollView stickyScrollView;
  private int height;
  private LinearLayout llContent;
  private RelativeLayout llTitle;
  private FrameLayout frameLayout;
  private TextView title;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
    initListeners();
  }

  /**
   * 初始化View
   */
  private void initView() {
    stickyScrollView = (StickyScrollView) findViewById(R.id.scrollView);
    frameLayout = (FrameLayout) findViewById(R.id.tabMainContainer);
    title = (TextView) findViewById(R.id.title);
    oneTextView = (TextView) findViewById(R.id.infoText);
    llContent = (LinearLayout) findViewById(R.id.ll_content);
    llTitle = (RelativeLayout) findViewById(R.id.ll_good_detail);
    oneTextView.setOnClickListener(this);
    twoTextView = (TextView) findViewById(R.id.secondText);
    twoTextView.setOnClickListener(this);

    stickyScrollView.setOnScrollListener(this);
    StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title);
    FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) llTitle.getLayoutParams();
    params.setMargins(0, getStatusHeight(), 0, 0);
    llTitle.setLayoutParams(params);

    //默认设置一个Frg
    getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment.newInstance()).commit();
  }

  /**
   * 获取状态栏高度
   *
   * @return
   */
  private int getStatusHeight() {
    int resourceId = MainActivity.this.getResources().getIdentifier("status_bar_height", "dimen", "android");
    return getResources().getDimensionPixelSize(resourceId);

  }

  @Override
  public void onClick(View v) {
    if (v.getId() == R.id.infoText) {
      getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment.newInstance()).commit();
    } else if (v.getId() == R.id.secondText) {
      getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment1.newInstance()).commit();

    }
  }

  private void initListeners() {
    //获取内容总高度
    final ViewTreeObserver vto = llContent.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        height = llContent.getHeight();
        //注意要移除
        llContent.getViewTreeObserver()
            .removeGlobalOnLayoutListener(this);

      }
    });

    //获取Fragment高度
    ViewTreeObserver viewTreeObserver = frameLayout.getViewTreeObserver();
    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        height = height - frameLayout.getHeight();
        //注意要移除
        frameLayout.getViewTreeObserver()
            .removeGlobalOnLayoutListener(this);
      }
    });

    //获取title高度
    ViewTreeObserver viewTreeObserver1 = llTitle.getViewTreeObserver();
    viewTreeObserver1.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        height = height - llTitle.getHeight() - getStatusHeight();//计算滑动的总距离
        stickyScrollView.setStickTop(llTitle.getHeight() + getStatusHeight());//设置距离多少悬浮
        //注意要移除
        llTitle.getViewTreeObserver()
            .removeGlobalOnLayoutListener(this);
      }
    });

  }

  @Override
  public void onScrollChanged(int l, int t, int oldl, int oldt) {
    if (t <= 0) {
      llTitle.setBackgroundColor(Color.argb((int) 0, 255, 255, 255));

    } else if (t > 0 && t <= height) {
      float scale = (float) t / height;
      int alpha = (int) (255 * scale);
      llTitle.setBackgroundColor(Color.argb((int) alpha, 227, 29, 26));//设置标题栏的透明度及颜色
      StatusBarUtil.setTranslucentForImageView(MainActivity.this, alpha, title);//设置状态栏的透明度
    } else { StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title);
      llTitle.setBackgroundColor(Color.argb((int) 255, 227, 29, 26));
      StatusBarUtil.setTranslucentForImageView(MainActivity.this, 255, title);
    }
  }
}

最后demo下载:demo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

时间: 2016-12-14

Android沉浸式状态栏实现示例

应用市场上App越来越多的出现沉浸式状态栏的设计(如下图所示)状态栏和导航栏具有相同的颜色.Android在4.4开始对于该种效果的支持,而在4.4之下,状态栏只是黑框,无法控制.同时在4.4和5.0及其之上的版本对该种效果的支持又有所差异,因此要实现该种效果,可以将4.4归为一类,5.0及其之上归为一类.接下来,我们将一步步来在4.4和5.0及其之上来实现如下所示效果. 导航栏问题 在Android中,顶部导航栏目前常用的两种实现方式,一个是通过Toolbar,一个是通过自定义View的方式来

Android透明化和沉浸式状态栏实践及源码分析

本文所提到的透明状态栏其实指的是将顶部的导航栏延伸到状态栏,使之浑然一体(Google官方建议状态栏颜色比导航栏的颜色略深一点),并不代表一定不设置背景色,比如导航栏是白色,则可设置状态栏为白色,视情况而定. 相比于iOS系统,Android系统对于状态栏的设置就显得稍微复杂了一点.Android系统提供了API 19以上对状态栏的设置接口,而直到API 23以上才提供对于icon颜色的设置,还有就是各家厂商(如魅族,小米等)对于状态栏的有自己的定制,对于需要使用浅色背景状态栏的应用,没处理好的

Android 高仿QQ 沉浸式状态栏

前言: 在进入今天正题前,还是老样子先谈谈感想吧,最近感觉整个都失去了方向感,好迷茫!找工作又失败了,难道Android真的饱和了?这两天我一直没出门,除了下楼哪外卖就是宅宿舍了,静想了许久,我还是不能忘了初心,我相信我找不到工作的原因有很多,最关键的还是要技术够硬才行啊,奔跑吧孩子!接下来我就给大家介绍怎样快速打造沉浸式状态栏吧,虽然感觉有点相见恨晚,但其实不完! 一:何为沉浸式状态栏? 沉浸式状态栏是Google从Android 4.4开始,给我们开发者提供的一套能透明的系统ui样式,这样样

Android App仿QQ制作Material Design风格沉浸式状态栏

一.概述 近期注意到QQ新版使用了沉浸式状态栏,ok,先声明一下效果图: 恩,接下来正题. 首先只有大于等于4.4版本支持这个半透明状态栏的效果,但是4.4和5.0的显示效果有一定的差异,所有本文内容为: 1.如何实现半透明状态栏效果在大于4.4版本之上. 2.如何让4.4的效果与5.0的效果尽可能一致. 先贴下模拟器效果图,以便和实现过程中做下对比 4.4 模拟器 5.x 真机 二.实现半透明状态栏 因为本例使用了NavigationView,所以布局代码稍多,当然如果你不需要,可以自己进行筛

Android 沉浸式状态栏与隐藏导航栏实例详解

1 前言 一般我们在Android的APP开发中,APP的界面如下: 可以看到,有状态栏.ActionBar(ToolBar).导航栏等,一般来说,APP实现沉浸式有三种需求:沉浸式状态栏,隐藏导航栏,APP全屏 沉浸式状态栏是指状态栏与ActionBar颜色相匹配, 隐藏导航栏不用多说,就是将导航栏隐藏,去掉下面的黑条. APP全屏是指将状态栏与导航栏都隐藏,例如很多游戏界面,都是APP全屏. 所以,在做这一步时,关键要问清楚产品狗的需求,免得白费功夫. 下面,分别来介绍这三种方式的实现. 2

Android沉浸式状态栏实现

苹果上的UI基本上都是这个效果,然而Android机上的顶部状态栏总是和app的主题颜色不搭.还好如今的api19以上的版本,我们也能做出这样的效果. 第一步: // 需要setContentView之前调用 private void setTranslucentStatus() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 透明状态栏 getWindow().addFlags( WindowManager.Lay

Android 实现沉浸式状态栏的方法

沉浸式状态栏的来源就是很多手机用的是实体按键,没有虚拟键,于是开了沉浸模式就只有状态栏消失了.于是沉浸模式成了沉浸式状态栏. 我们先来看下具体的效果 开启沉浸模式后,状态栏消失,从顶部向下滑动,状态栏出现,退出沉浸模式,状态栏也出现了. 我们的代码基于前一篇文章.首先是两个开启沉浸模式和关闭沉浸模式的函数 @SuppressLint("NewApi") public static void hideSystemUI(View view) { view.setSystemUiVisibi

另外两种Android沉浸式状态栏实现思路

关于沉浸式状态栏相信大家都不陌生,IOS系统很早就有,android5.0及以后版本都支持给状态栏着色,而目前android主流版本还是4.4,网上通用实现4.4(API19)沉浸式状态栏也都是依赖于可以将状态栏变为透明的属性,再为其着色,主要实现代码: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout

Android沉浸式状态栏设计的实例代码

本文介绍了android沉浸式状态栏,分享给大家,希望对大家有帮助 一.概述 现在主流的App设计风格很多都用到了Materail Design,今天我们就来简单的实现一下改变状态栏颜色.让状态栏透明这两种效果. 二.实现状态栏设置颜色 我们写一个工具类StatusBarUtils 代码如下: /** * 设置状态栏颜色 * * @param activity */ public static void setStatusColor(Activity activity, int color) {

Android之沉浸式状态栏的实现方法、状态栏透明

现在越来越多的软件都开始使用沉浸式状态栏了,下面总结一下沉浸式状态栏的两种使用方法 注意!沉浸式状态栏只支持安卓4.4及以上的版本 状态栏:4.4上是渐变色,5.0上是完全透明,本文模拟器为4.4演示 效果图: 注意!两种方法的区别: 第一种:为顶部栏跟随当前activity的布局文件的背景的颜色,使用方便,不过也有点问题就是,如果有底部虚拟导航键的话,导航键的背景跟顶部的颜色一样,比如: 第二种:是通过设置顶部栏的颜色来显示的,可以解决第一种的不足,比如: 第一种使用方法: 第一.首先在val

Android实现沉浸式导航栏实例代码

废话不多说了,直接给大家贴代码了,具体代码如下所示: private SystemBarTintManager tintManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // getWindow().addFlags(WindowManager.Layo

Android实现沉浸式状态栏

前段时间,项目中用到了沉浸式的状态栏,在此记录一下,代码如下: package com.jackie.immersive; import android.os.Build; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends AppCompatActivity { @Overrid

Android实现沉浸式状态栏功能

Android中实现沉浸式状态栏的功能,供大家参考,具体内容如下 1. 先上效果图,实现沉浸式状态栏有两种方式,一种是通过写Theme主题的方式,另一种是写代码的方式.若要使多个页面出现沉浸式状态栏,则使用主题的方式更方便,如果只要使单个页面出现,则使用代码方式更好!当然了,看个人喜好而去. 2. 先来介绍写主题的方式 2.1 先在res包下新建values-v19和values-v21两个包,为了兼容Android高低版本 2.2 然后分别在包中新建styles.xml文件 2.2.1 val

Android实现沉浸式通知栏通知栏背景颜色跟随app导航栏背景颜色而改变

最近好多app都已经满足了沉浸式通知栏, 所谓沉浸式通知栏:就是把用来导航的各种界面操作空间隐藏在以程序内容为主的情景中,通过相对"隐形"的界面来达到把用户可视范围最大化地用到内容本身上. 而最新安卓4.4系统的通知栏沉浸模式就是在软件打开的时候通知栏和软件顶部颜色融为一体,这样不仅可以使软件和系统本身更加融为一体. 就是手机的通知栏的颜色不再是白色.黑色简单的两种了,本人用的小米4手机,米4手机中的自带软件都支持沉浸式通知栏, 举个例子:大家可以看一下自己的qq,它的标题的背景颜色是

详解Android通知栏沉浸式/透明化完整解决方案

Google在Android 4.4版本加入了半透明的界面样式,在Android 5.0的时候推出了Material Design的概念. 这些样式的加入使得原本死板.丑陋.和App颜色不一致的通知栏变得更亲和.顺眼.用户体验更友好. 作者是常年做对日项目的,对日项目以界面简洁功能强大而著称.最近客户要求UI方面做一些改变,让App看上去给用户感觉更友好.所以就提到了Android 4.4以后的通知栏问题. 网上关于通知栏的文章铺天盖地,什么沉浸式,什么半透明...挺会拽词.也不乏有Androi

Android编程中沉浸式状态栏的三种实现方式详解

本文实例讲述了Android编程中沉浸式状态栏的三种实现方式.分享给大家供大家参考,具体如下: 沉浸式状态栏 Google从android kitkat(Android 4.4)开始,给我们开发者提供了一套能透明的系统ui样式给状态栏和导航栏,这样的话就不用向以前那样每天面对着黑乎乎的上下两条黑栏了,还可以调成跟Activity一样的样式,形成一个完整的主题,和IOS7.0以上系统一样了. 首先看下效果 首先看下第一种方式 系统的方式沉浸式状态栏实现 步奏一 //当系统版本为4.4或者4.4以上

Android 4.4以上"沉浸式"状态栏效果的实现方法

什么是沉浸式状态栏? 沉浸式状态栏意思指状态栏的颜色随着软件颜色而改变,使状态栏和软件颜色保持一致,沉浸其中!当我们打开应用程序时,不会再因为看到应用程序和状态栏的黑边相隔开而感到十分难看.沉浸式状态栏由于其能给用户群体带来极佳的用户体验,已经在越来越多的应用上得到了体现. 实现原理 从4.4后系统增加了透明状态栏的特性WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 一旦添加上这个属性后,那么布局中的内容DecorView就会自动填充到状态栏