打造更好的透明(沉浸?)状态栏
Posted ykb19891230
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了打造更好的透明(沉浸?)状态栏相关的知识,希望对你有一定的参考价值。
开篇之前
首先,这是github项目地址的传送门CollapsingView
有兴趣的童鞋可以去clone下来,尽量在真机上看效果,因为我不知道这工程怎么就不支持x86的模拟器。。。
再来看看6.0上面的截图和效果(as截取的,太大了o(╯□╰)o)
这个是截图
这个是6.0上透明后的效果
这个是6.0上状态栏标题栏同色效果
原理解析
看过一些大神的方法和思路,最主要的就是这篇博客Android 透明状态栏实现方案,不得不说,掘金上的文章都很有含金量!
其实这所有的艺术都源于下面这个属性
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
public static final int FLAG_TRANSLUCENT_STATUS
Added in API level 19
Window flag: request a translucent status bar with minimal system-provided background protection.
This flag can be controlled in your theme through the windowTranslucentStatus attribute; this attribute is automatically set for you in the standard translucent decor themes such as Theme_Holo_NoActionBar_TranslucentDecor, Theme_Holo_Light_NoActionBar_TranslucentDecor, Theme_DeviceDefault_NoActionBar_TranslucentDecor, and Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor.
When this flag is enabled for a window, it automatically sets the system UI visibility flags SYSTEM_UI_FLAG_LAYOUT_STABLE and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.
Constant Value: 67108864 (0x04000000)
SYSTEM_UI_FLAG_LAYOUT_STABLE Flag for setSystemUiVisibility(int): When using other layout flags, we would like a stable view of the content insets given to fitSystemWindows(Rect).
使用这个属性在加上没有ActionBar的主题,就可以实现你想要的各种透明(沉浸)效果了。
其实网上很多关于实现透明(沉浸)状态栏的文章,还有一些开源库,他们的方法也很简单,很好用,但是那也不能阻止我把这种效果做的更好用!
这里说明一下,这个自定义view里所附带的悬浮和缩放效果完全是因为项目需要,不需要的童鞋可以自行去掉,非常简单的代码——xml去掉对应的tag即可。
为什么是tag呢?因为像CollapsingToolbarLayout所使用的behavior太高端,像我这种菜鸟做不到啊。所以想到了用tag来标识每个子view的属性。
//滚动属性的视图(ScrollView)
private final String TAG_SCROLL = getResources().getString(R.string.tag_collapsingScroll);
//Listview
private final String TAG_LIST = getResources().getString(R.string.tag_collapsingList);
//需要缩放属性的视图
private final String TAG_SCALE = getResources().getString(R.string.tag_collapsingScale);
//标题栏
private final String TAG_HEADER = getResources().getString(R.string.tag_collapsingHeader);
//真正的内容视图,不含标题栏
private final String TAG_CONTENT = getResources().getString(R.string.tag_collapsingContent);
//需要悬浮的视图(目前仅支持悬浮在标题栏下面)
private final String TAG_FLOAT = getResources().getString(R.string.tag_collapsingFloat);
布局的要点:
1.滚动缩放和悬浮须包含ObservableScrollView或ListView和一个标题栏布局,ObservableScrollView其实就是多了一个暴露onScrollChange方法里的参数回调接口的ScrollView,你可以随便自定义;
2.每个View的tag一定是你定义的@string/tag_…对应的字符串,比如ObservableScrollView对应@string/tag_collapsingScroll,因为我是靠tag来查找视图的;
3.标题栏布局必须在最顶层。
4.activity主题需为NoActionBar相关
5.需要透明状态栏一定要记得加这句代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
6.如果需要内容显示在标题栏之下,必须给你的真正的内容视图加上如下tag(当然你可以自己随便改字符串)
private final String TAG_CONTENT =getResources().getString(R.string.tag_collapsingContent);
以及在CollapsingLayout里加上自定义属性
app:needTranslucentStatus="false"
其实这些要点从代码里也能看出来,大家去仔细看看github上的代码和布局就能发现端倪。
代码篇
ObservableScrollView.java。
ObservableScrollView其实就是多了一个暴露onScrollChange方法里的参数回调接口的ScrollView,你可以随便自定义。
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ykbjson.app.collapsingview.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;
import java.util.ArrayList;
/**
* 包名:com.ykbjson.app.collapsingview.widget
* 描述:自定义scrollview,为了向外部提供onScrollChanged方法的回调参数
* 创建者:yankebin
* 日期:2016/10/8
*/
public class ObservableScrollView extends ScrollView
private ArrayList<Callbacks> mCallbacks = new ArrayList<>();
private boolean interceptTouchAnyWay;
public ObservableScrollView(Context context)
this(context, null);
public ObservableScrollView(Context context, AttributeSet attrs)
super(context, attrs);
setOverScrollMode(OVER_SCROLL_NEVER);
public ObservableScrollView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
setOverScrollMode(OVER_SCROLL_NEVER);
public void setInterceptTouchAnyWay(boolean interceptTouchAnyWay)
this.interceptTouchAnyWay = interceptTouchAnyWay;
/**
* 重写此方法让scrollview一直拦截touch事件,解决当scrollview内容没超出屏幕时也能拦截touch事件
* 因为源码 onInterceptTouchEvent 里有如下说明
* Don't try to intercept touch if we can't scroll anyway.
* if (getScrollY() == 0 && !canScrollVertically(1))
* return false;
*
*/
@Override
public boolean canScrollVertically(int direction)
return interceptTouchAnyWay||super.canScrollVertically(direction);
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
super.onScrollChanged(l, t, oldl, oldt);
handleOnScrollCallback(l, t, oldl, oldt);
@Override
public boolean onTouchEvent(MotionEvent ev)
if (!mCallbacks.isEmpty())
switch (ev.getActionMasked())
case MotionEvent.ACTION_DOWN:
handleOnDownMotionEvent();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handleOnUpOrCancelMotionEvent();
break;
return super.onTouchEvent(ev);
@Override
public int computeVerticalScrollRange()
return super.computeVerticalScrollRange();
private void handleOnDownMotionEvent()
if (checktock())
return;
for (Callbacks callbacks : mCallbacks)
callbacks.onDownMotionEvent();
private void handleOnUpOrCancelMotionEvent()
if (checktock())
return;
for (Callbacks callbacks : mCallbacks)
callbacks.onUpOrCancelMotionEvent();
private void handleOnScrollCallback(int l, int t, int oldl, int oldt)
if (checktock())
return;
for (Callbacks callbacks : mCallbacks)
callbacks.onScrollCallback(l, t, oldl, oldt);
private boolean checktock()
return mCallbacks.isEmpty();
public boolean addScrollCallbacks(Callbacks listener)
return !mCallbacks.contains(listener) && mCallbacks.add(listener);
public boolean removeScrollCallbacks(Callbacks listener)
return mCallbacks.contains(listener) && mCallbacks.remove(listener);
public interface Callbacks
public void onScrollCallback(int l, int t, int oldl, int oldt);
public void onDownMotionEvent();
public void onUpOrCancelMotionEvent();
CollapsingLayout.java。
CollapsingLayout其实就是把一些实现都包含在了里面,用户只要参照一定的规则布局便可以实现多种效果,里面注释很多的。
package com.ykbjson.app.collapsingview.widget;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.nineoldandroids.view.ViewHelper;
import com.ykbjson.app.collapsingview.R;
import com.ykbjson.app.collapsingview.utils.Util;
/**
* 包名:com.ykbjson.app.collapsingview.widget
* 描述:透明状态栏视图
* 创建者:yankebin
* 日期:2016/10/8
*/
public class CollapsingLayout extends FrameLayout implements AbsListView.OnScrollListener, ObservableScrollView.Callbacks
/**
* 收缩系数回调接口
*/
public interface OnCollapsingCallback
/**
* 收缩系数变化
*
* @param t 竖直方向的滚动距离
* @param coefficient 计算出的收缩系数[0,1]
*/
void onCollapsing(float t, float coefficient);
private final String TAG_SCROLL = getResources().getString(R.string.tag_collapsingScroll);
private final String TAG_SCALE = getResources().getString(R.string.tag_collapsingScale);
private final String TAG_HEADER = getResources().getString(R.string.tag_collapsingHeader);
private final String TAG_CONTENT = getResources().getString(R.string.tag_collapsingContent);
private final String TAG_FLOAT = getResources().getString(R.string.tag_collapsingFloat);
/**
* 加载状态栏和header的容器
*/
private LinearLayout headerLayout;
/**
* 伪装状态栏
*/
private ImageView fillingStatusView;
/**
* 收缩系数回调接口
*/
private OnCollapsingCallback collapsingCallback;
/**
* 防止多次加载header布局
*/
private boolean isHandled;
/**
* 收缩系数的基础高度,默认为statusbar+titlebar的高度*2
*/
private int collapsingHeight;
/**
* 手指在滚动视图上上一次记录的y值
*/
private float lastMoveY;
/**
* 是在缩放
*/
private boolean mScaling;
/**
* 缩放视图
*/
private View scaleView;
/**
* 悬浮视图
*/
private View floatView;
/**
* 悬浮视图顶部距离
*/
private float floatViewTop;
/**
* 缩放视图初始宽度
*/
private int scaleWidth;
/**
* 缩放视图初始高度
*/
private int scaleHeight;
/**
* 是否在执行动画
*/
private boolean isAnimation;
private int statusBarColorRes;
private float statusBarAlpha;
private boolean needTranslucentStatus;
private boolean needPullRefresh;
public CollapsingLayout(Context context)
this(context, null);
public CollapsingLayout(Context context, AttributeSet attrs)
this(context, attrs, 0);
public CollapsingLayout(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
setTag(getResources().getString(R.string.tag_collapsingRoot));
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CollapsingLayout);
statusBarColorRes = typedArray.getResourceId(R.styleable.CollapsingLayout_statusBarColor,
R.color.sip_gray_dark);
statusBarAlpha = typedArray.getFloat(R.styleable.CollapsingLayout_statusBarAlpha, 0.0f);
needTranslucentStatus = typedArray.getBoolean(
R.styleable.CollapsingLayout_needTranslucentStatus, true);
needPullRefresh = typedArray.getBoolean(
R.styleable.CollapsingLayout_needPullRefresh, false);
typedArray.recycle();
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
super.onLayout(changed, left, top, right, bottom);
if (changed && !isHandled)
isHandled = true;
//此处可以取到缩放视图的宽高,不在此处去替换原布局子view,解决header的布局不能正常显示的问题
View scaleView = findViewWithTag(TAG_SCALE);
if (null != scaleView)
scaleWidth = scaleView.getMeasuredWidth();
scaleHeight = scaleView.getMeasuredHeight();
//悬浮视图
floatView = findViewWithTag(TAG_FLOAT);
if (null != floatView)
floatViewTop = getChildTop(floatView, floatView.getTop()) - headerLayout.getMeasuredHeight();
@Override
protected void onFinishInflate()
super.onFinishInflate();
//可以解决进入页面时布局错乱的问题
handleLayout(getContext());
//当scrollview外部包含下拉刷新这类的视图和不需要透明状态栏以及内容要显示在标题之下时,不能拦截touch事件
if (needPullRefresh)
ObservableScrollView observableScrollView = findObservableScrollView(this);
if (null != observableScrollView)
observableScrollView.setInterceptTouchAnyWay(false);
@Override
protected void attachViewToParent(View child, int index, ViewGroup.LayoutParams params)
super.attachViewToParent(child, index, params);
@Override
protected void onAttachedToWindow()
super.onAttachedToWindow();
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
final float scrollY = getViewScrollY(view);
final float coefficient = Math.min(scrollY * 1.0f / collapsingHeight, 1);
handleScroll(scrollY, coefficient);
@Override
public void onScrollCallback(int l, int t, int oldl, int oldt)
final float coefficient = Math.min(t * 1.0f / collapsingHeight, 1);
handleScroll(t, coefficient);
@Override
public void onDownMotionEvent()
@Override
public void onUpOrCancelMotionEvent()
/**
* 重置放大的视图
*/
private void restScaleView()
final ViewGroup.LayoutParams lp = scaleView.getLayoutParams();
final float w = scaleView.getLayoutParams().width;// 图片当前宽度
final float h = scaleView.getLayoutParams().height;// 图片当前高度
// 设置动画
ValueAnimator anim = ObjectAnimator.ofFloat(0.0F, 1.0F).setDuration(200);
anim.addListener(new Animator.AnimatorListener()
@Override
public void onAnimationStart(Animator animation)
isAnimation = true;
@Override
public void onAnimationEnd(Animator animation)
isAnimation = false;
checkScaleViewAttr();
@Override
public void onAnimationCancel(Animator animation)
isAnimation = false;
checkScaleViewAttr();
@Override
public void onAnimationRepeat(Animator animation)
);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
float cVal = (Float) animation.getAnimatedValue();
lp.width = (int) (w - (w - scaleWidth) * cVal);
lp.height = (int) (h - (h - scaleHeight) * cVal);
scaleView.setLayoutParams(lp);
);
anim.start();
/**
* 处理加载后的布局
*
* @param context 上下文
*/
private void handleLayout(Context context)
View headerView = findViewWithTag(TAG_HEADER);
if (null == headerView)
throw new IllegalArgumentException("can not find header view with tag @string/tag_collapsingHeader");
removeView(headerView);
headerLayout = new LinearLayout(context);
headerLayout.setOrientation(LinearLayout.VERTICAL);
fillingStatusView = new ImageView(context);
fillingStatusView.setBackgroundResource(statusBarColorRes);
fillingStatusView.setAlpha(statusBarAlpha);
headerLayout.addView(fillingStatusView, new LinearLayout.LayoutParams(-1, Util.getStatusBarHeight()));
headerLayout.addView(headerView, headerView.getLayoutParams());
addView(headerLayout, new LayoutParams(-1, -2));
//sdk版本小于4.4不需要额外的状态栏
if (!isSupport())
fillingStatusView.setVisibility(GONE);
//强制测量以获取其高度
headerLayout.measure(0, 0);
setCollapsingHeight(headerLayout.getMeasuredHeight() << 1);
View scrollView = findViewWithTag(TAG_SCROLL);
if (null != scrollView)
if (scrollView instanceof ObservableScrollView)
((ObservableScrollView) scrollView).addScrollCallbacks(this);
else if (scrollView instanceof AbsListView)
((AbsListView) scrollView).setOnScrollListener(this);
else
throw new IllegalArgumentException("only support AbsListView or ScrollView");
handleScale(scrollView);
//contentview padding 兼容不需要沉浸的页面
if (!needTranslucentStatus)
View content = findViewWithTag(TAG_CONTENT);
if (null == content)
return;
content.setPadding(0, headerLayout.getMeasuredHeight(), 0, 0);
/**
* 找出ObservableScrollView
*
* @param viewGroup
*/
public ObservableScrollView findObservableScrollView(ViewGroup viewGroup)
if (viewGroup instanceof ObservableScrollView)
return (ObservableScrollView) viewGroup;
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++)
View view = viewGroup.getChildAt(i);
if (view instanceof ViewGroup)
return findObservableScrollView((ViewGroup) view);
return null;
/**
* 处理缩放
*
* @param scrollView 处理touch事件的view
*/
private void handleScale(final View scrollView)
scaleView = findViewWithTag(TAG_SCALE);
if (null == scaleView)
return;
scrollView.setOnTouchListener(new OnTouchListener()
@Override
public boolean onTouch(View v, MotionEvent event)
if (isAnimation)
return true;
switch (event.getAction())
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mScaling)
//手指离开后恢复图片
restScaleView();
//防止上次沒缩放时记录了位置,导致下次点击时距离计算错误
resetScaleFlag();
break;
case MotionEvent.ACTION_MOVE:
final int scrollY = getScrollY(scrollView);
if (!mScaling)
if (lastMoveY == 0 && scrollY == 0)
lastMoveY = event.getY();// 滚动到顶部时记录位置,否则正常返回
break;
// 当前位置比记录位置要小,正常返回
if (event.getY() - lastMoveY < 0)
//防止快速回退时没能减掉最后一部分位移距离
resetScaleFlag();
checkScaleViewAttr();
break;
int distance = (int) ((event.getY() - lastMoveY) * 0.6); // 滚动距离乘以一个系数
//滚动距离负数,不缩放
if (distance < 0)
resetScaleFlag();
break;
//这里是防止还没滚动到顶部时touch事件被拦截,导致缩放距离计算不准确的问题
if (scrollY == 0)
// 处理放大
handleScale(distance);
return true;
return false;
);
/**
* 重置缩放相关标志位参数,解决多次上下滑动时缩放距离不准确的问题
*/
private void resetScaleFlag()
mScaling = false;
lastMoveY = 0;
/***
* 矫正缩放视图宽高,防止快速回退时没能减掉最后一部分位移距离
*/
private void checkScaleViewAttr()
ViewGroup.LayoutParams scaleViewLayoutParams = scaleView.getLayoutParams();
if (scaleViewLayoutParams.width != scaleWidth ||
scaleViewLayoutParams.height != scaleHeight)
scaleViewLayoutParams.width = scaleWidth;
scaleViewLayoutParams.height = scaleHeight;
scaleView.setLayoutParams(scaleViewLayoutParams);
/**
* 处理放大
*
* @param distance 放大增量
*/
private void handleScale(int distance)
ViewGroup.LayoutParams scaleViewLayoutParams = scaleView.getLayoutParams();
mScaling = true;
scaleViewLayoutParams.width = scaleWidth + distance;
scaleViewLayoutParams.height = scaleHeight + distance;
scaleView.setLayoutParams(scaleViewLayoutParams);
/**
* 获取滚动视图滚动距离
*
* @param view 滚动视图
* @return
*/
private int getScrollY(View view)
if (null == view)
return -1;
if (view instanceof AbsListView)
return getViewScrollY((AbsListView) view);
return view.getScrollY();
/**
* 设置渐变的高度基数
*
* @param collapsingHeight 高度基数
*/
public void setCollapsingHeight(int collapsingHeight)
this.collapsingHeight = collapsingHeight;
/**
* 获取AbsListView当前的滚动距离
*
* @param absListView AbsListView
* @return 当前的滚动距离
*/
private int getViewScrollY(AbsListView absListView)
View c = absListView.getChildAt(0);//第一个可见的view
if (c == null)
return 0;
int top = c.getTop() + absListView.getPaddingTop();
return -top;
/**
* 处理滚动
*
* @param coefficient 滚动系数
*/
private void handleScroll(float scrollY, float coefficient)
if (null != collapsingCallback)
collapsingCallback.onCollapsing(scrollY, coefficient);
//悬浮视图
if (null != floatView)
if (floatViewTop <= scrollY)
ViewHelper.setTranslationY(floatView, -floatViewTop + scrollY);
else
ViewHelper.setTranslationY(floatView, 0);
/**
* 找到view距离顶部的距离
*
* @param view
* @param height
* @return
*/
private int getChildTop(View view, int height)
ViewParent parent = view.getParent();
if (null == parent || this == parent)
return height;
ViewGroup parentView = (ViewGroup) parent;
height += parentView.getTop();
return getChildTop(parentView, height);
/**
* 设置透明回调接口
*
* @param collapsingCallback 透明回调接口
*/
public void setCollapsingCallback(OnCollapsingCallback collapsingCallback)
this.collapsingCallback = collapsingCallback;
/**
* 设置状态栏背景颜色
*
* @param color 颜色值
*/
public void setStatusBarColor(@ColorInt int color)
fillingStatusView.setBackgroundColor(color);
/**
* 设置状态栏背景图片
*
* @param drawableRes 图片资源id
*/
public void setStatusBarBackground(@DrawableRes int drawableRes)
fillingStatusView.setBackgroundResource(drawableRes);
/**
* header布局
*
* @return header布局
*/
public LinearLayout getHeaderView()
return headerLayout;
/**
* 返回缩放视图
*
* @return
*/
public View getScaleView()
return findViewWithTag(TAG_SCALE);
/**
* statusbar的替代view
*
* @return statusbar的替代view
*/
public ImageView getFillingStatusView()
return fillingStatusView;
public void setHeaderLayoutBgColor(@ColorRes int color)
headerLayout.setBackgroundResource(color);
public void setHeaderLayoutBgDrawable(@DrawableRes int drawable)
headerLayout.setBackgroundResource(drawable);
/**
* 是否支持当前版本
*
* @return 是否支持
*/
private boolean isSupport()
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
/**
* 是否沉浸了状态栏
*
* @return
*/
private boolean isTranslucentStatus()
return isSupport() && needTranslucentStatus;
剩下的就是style了,大家可随意扩展
<declare-styleable name="CollapsingLayout">
<attr name="statusBarColor" format="reference" />
<attr name="statusBarAlpha" format="float" />
<attr name="needTranslucentStatus" format="boolean" />
<attr name="needPullRefresh" format="boolean" />
</declare-styleable>
请大家原谅我这蹩脚的叙述能力和排版能力,其实我很想说的很清楚,但是文学水平也就这个样子了,请大家多多包涵。但是我相信大家结合github上的demo和这篇博客看的话,应该不会有太多的问题的,代码是最好的老师。
以上是关于打造更好的透明(沉浸?)状态栏的主要内容,如果未能解决你的问题,请参考以下文章
android沉浸式状态栏变色状态栏透明状态栏修改状态栏颜色及透明