今天来写一个类似于qq空间的那种沉浸式效果。先来看看qq空间的这种效果
我们看到,头部局上拉的时候有个头布局的透明是从0变化到1,当你下拉的时候,头部局透明度又从1变化到0了。始终效果看起来还是不错的,当然这种效果要配合透明状态栏才好看。而且我们可以再很多应用各种会看到这种广告遮住头布局的方式。比如160的软件。
看起来效果还是挺酷炫的。现在我们就来讲讲他的实现方式吧。首先来看下demo实现的效果图
居然说到了透明状态栏,也说一下关于透明状态栏和沉浸式状态栏吧。我们首先说一下他的概念。
1.沉浸式全屏模式
隐藏status bar(状态栏)使屏幕全屏,让Activity接收所有的(整个屏幕的)触摸事件。相当于就是隐藏状态栏,让手机浏览进入全屏模式
2.透明化系统状态栏
透明化系统状态栏,使得布局侵入系统栏的后面,必须启用fitsSystemWindows属性来调整布局才不至于被系统栏覆盖。
透明状态栏的意思是指布局从状态栏开始。然后状态栏的一些东西比如电量那些基本信息会覆盖在布局上面。但透明状态栏是android 4.4及以上版本才有这种效果,4.4以下是不支持透明状态栏的。我们先看下不设置透明状态栏的效果。
布局文件是这样的
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/title_bg_color"
>
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimary"
android:gravity="center"
android:orientation="horizontal"
android:paddingBottom="9dp"
android:paddingTop="10dp"
/>
效果图:
此时没有设置状态栏,当我们设置透明状态栏的时候,会不一样。来看下设置透明状态栏的效果
设置透明状态栏中的两种方式
1.style文件中设置如下属性
然后在manifiest文件设置activity的theme属性中引用它,就可以了
2.代码中设置
//api大于19时设置才有效果
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
来解释一下这几种设置中的含义吧,大家参照一下下面的属性对照。
setSystemUiVisibility这个方法参数表示的状态比较多,具体如下:
1. View.SYSTEM_UI_FLAG_VISIBLE:显示状态栏,Activity不全屏显示(恢复到有状态的正常情况)。
2. View.INVISIBLE:隐藏状态栏,同时Activity会伸展全屏显示。
3. View.SYSTEM_UI_FLAG_FULLSCREEN:Activity全屏显示,且状态栏被隐藏覆盖掉。
4. View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:Activity全屏显示,但状态栏不会被隐藏覆盖,状态栏依然可见,Activity顶端布局部分会被状态遮住。
5. View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:效果同View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
6. View.SYSTEM_UI_LAYOUT_FLAGS:效果同View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
7. View.SYSTEM_UI_FLAG_HIDE_NAVIGATION:隐藏虚拟按键(导航栏)。有些手机会用虚拟按键来代替物理按键。
8. View.SYSTEM_UI_FLAG_LOW_PROFILE:状态栏显示处于低能显示状态(low profile模式),状态栏上一些图标显示会被隐藏。
* window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
Flag表明这个窗口负责绘制系统状态栏标题栏的背景。如果设置,系统bar绘制成透明背景,在这个窗口相应的地方会填充{@link Window#getStatusBarColor()}和 {@link Window#getNavigationBarColor()}对应的颜色。
但是大家有没有注意到,有时候布局内容从状态栏开始排布会很奇怪,比如内容布局的东西会被状态栏的信息覆盖。此时你想要的效果可能只是让状态栏变个颜色。这时候我们就需要这个属性了。将这个属性设置在根布局。
android:fitsSystemWindows="true"
这时候状态栏的颜色就会跟随主布局。而且内容布局将不会从状态栏开始排布,将会从状态栏之下开始排布。我们看下效果。
这才是我们想要的效果。当然有的人会说我想自定义状态颜色。当然可以。不过4.4系统没有方法自定义状态栏的颜色。5.1以上有方法可以设置状态栏的颜色,等会我会说。不过有个很好的开源库帮我们实现了。我们可以用它来自定义状态栏的颜色。就是SystemBarTinManger。关于这个开源库的使用方法也很简单。只需要导包使用就行,使用它的前提要前提是先要设置透明状态栏。有兴趣的大家去网上看看使用方法就行了,很简单的。接下来我们看下5.0之后设置状态栏颜色的方法。
1.在Manifest文件中配置Activity的theme,设置状态栏颜色,主题颜色等。
然后在文件中设置相应颜色即可。
2.下面是一个设置系统工具栏的比较通用的代码:
if(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
Windowwindow = getWindow();
//清除系统提供的默认保护色,After LOLLIPOP not translucent status bar
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
//设置系统UI的显示方式
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
//添加属性可以自定义设置系统工具栏颜色
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(color_status_bar);
window.setNavigationBarColor(color_navigation_bar);
}
其中getWindow.setStatusBarColor()要5.0以上才有效果。
好的,状态栏的知识就先说到这里了。我们来看看今天demo的实现吧。
1.实现原理
其实实现原理还是挺简单的。我们用FrameLayout作为根布局,然后设置透明状态栏,设置一个头布局。用头布局覆盖内容布局。接着在向下滑动或者向上滑动的时候,我们可以自行设置头布局的透明度。可以利用scrollview的滑动监听来设置。这样就可以看到头部局若隐若现的效果啦。
布局文件:
封装的帮助器类:
package com.example.administrator.mystatusbar;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import java.lang.reflect.Field;
public class ImmersiveHelper {
private Context mContext;
private int mAlpha;
private View mStatusBar;
private View mContentView;
private ObservableScrollView mScrollView;
private int mStatusHeight;
private int mTotalHeight;
private boolean isImmersived;
private ObservableScrollView.ScrollViewListener listener;
public ImmersiveHelper(Context mContext, View mStatusBar, final View mContentView, ObservableScrollView mScrollView) {
this.mContext = mContext;
this.mStatusBar = mStatusBar;
this.mContentView = mContentView;
this.mScrollView = mScrollView;
}
public void init(final Runnable runnable){
mStatusHeight = setStatusViewHeight(mContext,mStatusBar);
mContentView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (mContentView.getMeasuredHeight() != 0){
mTotalHeight = mStatusHeight + mContentView.getMeasuredHeight();
runnable.run();
}
mContentView.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
public void onRestoreData(Bundle bundle){
mTotalHeight = bundle.getInt("totalHeight");
}
public void onSaveData(Bundle bundle){
bundle.putInt("totalHeight",mTotalHeight);
}
public boolean isImmersived() {
return isImmersived;
}
public int getTotalHeight() {
return mTotalHeight;
}
/**
* 设置当前沉浸的状态
*/
public void setImmversive(boolean isOpen, final Runnable runnable){
if(isOpen){
if(!isImmersived) {
mContentView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int immerseHeight = mContext.getResources().getDisplayMetrics().widthPixels / 2 - mContentView.getMeasuredHeight() - mStatusHeight;
listener = setImmersiveEnabled(mScrollView, mStatusBar, mContentView, immerseHeight);
logD("setImmersive-->true");
isImmersived = true;
if(null != runnable){
runnable.run();
}
mScrollView.scrollTo(0,0);
mScrollView.invalidate();
mContentView.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
}else if(isImmersived){
mScrollView.scrollTo(0,0);
mScrollView.invalidate();
mScrollView.removeScrollViewListener(listener);
mAlpha = 255;
mStatusBar.getBackground().mutate().setAlpha(mAlpha);
mContentView.getBackground().mutate().setAlpha(mAlpha);
logD("setImmersive-->false");
isImmersived = false;
if(null != runnable){
runnable.run();
}
}
}
/**
* 设置沉浸式交互
* @param height 沉浸的总高度
*/
public ObservableScrollView.ScrollViewListener setImmersiveEnabled(ObservableScrollView scrollView, final View status, final View content, final int height){
mAlpha = 0;
content.getBackground().mutate().setAlpha(mAlpha);
status.getBackground().mutate().setAlpha(mAlpha);
ObservableScrollView.ScrollViewListener listener = new ObservableScrollView.ScrollViewListener() {
@Override
public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
int alpha = Math.max(0,Math.min(255,(int) (255 * (scrollView.getScrollY()) * 1.0f / height)));
if(mAlpha != alpha) {
content.getBackground().mutate().setAlpha(alpha);
status.getBackground().mutate().setAlpha(alpha);
mAlpha = alpha;
}
}
};
scrollView.addScrollViewListener(listener);
return listener;
}
private void logD(String text){
Log.d("ImmersiveHelper", text);
}
/**
* 设置需要充当状态栏的视图高度
* @param viewStatus 视图
*/
public static int setStatusViewHeight(Context mContext,View viewStatus){
int statusHeight = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT?getStatusBarHeight(mContext):0;
ViewGroup.LayoutParams params = viewStatus.getLayoutParams();
if(statusHeight != params.height) {
params.height = statusHeight;
viewStatus.requestLayout();
}
return statusHeight;
}
/**
* 获取状态栏高度
*
* @param context
* @return
*/
public static int getStatusBarHeight(Context context) {
/*Rect rect = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
return rect.top;*/
try {
Class c = Class.forName("com.android.internal.R$dimen");
Object obj = c.newInstance();
Field field = c.getField("status_bar_height");
int x = Integer.parseInt(field.get(obj).toString());
int y = context.getResources().getDimensionPixelSize(x);
return y;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
emptypackage com.example.administrator.mystatusbar;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.os.Handler;
public class MyStatusBar extends Activity {
ObservableScrollView scrollView;
LinearLayout contentView;
View stabarView;
MyStatusBar mContext;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.status_bar);
mContext = this;
initView();
}
private void initView(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
scrollView = (ObservableScrollView) findViewById(R.id.scrollView);
contentView = (LinearLayout) findViewById(R.id.ll_tab_top_search_parent);
stabarView = findViewById(R.id.view_status);
final ImmersiveHelper immersiveHelper = new ImmersiveHelper(mContext,stabarView,contentView,scrollView);
immersiveHelper.init(new Runnable() {
@Override
public void run() {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) scrollView.getLayoutParams();
params.topMargin = immersiveHelper.getTotalHeight();
scrollView.requestLayout();
}
});
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
immersiveHelper.setImmversive(true, new Runnable() {
@Override
public void run() {
if(immersiveHelper.isImmersived()){
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) scrollView.getLayoutParams();
params.topMargin = 0;
scrollView.requestLayout();
}else{
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) scrollView.getLayoutParams();
params.topMargin = immersiveHelper.getTotalHeight();
scrollView.requestLayout();
}
}
});
}
},3000);
}
}