现在越来越多的app页面都有随着主体布局的滚动顶部标题栏渐变效果,有的是自定义的layout有的则是Toolbar,有的页面主体布局是ScrollView,有的则是RecycleView,今天我们就来一起实现这样的效果。
ScrollView 6.0以前没有scrollView.setOnScrollChangeListener(l)方法 所以要自定义scrollview的code,因为scrollview的滑动监听不兼容低版本.
1、继承ScrollView实现自定义的ObservableScrollView.java类,代码如下:
public class ObservableScrollView extends ScrollView {
/**
* 回调接口监听事件
*/
private OnObservableScrollViewListener mOnObservableScrollViewListener;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 添加回调接口,便于把滑动事件的数据向外抛
*/
public interface OnObservableScrollViewListener {
void onObservableScrollViewListener(int l, int t, int oldl, int oldt);
}
/**
* 注册回调接口监听事件
*
* @param onObservableScrollViewListener
*/
public void setOnObservableScrollViewListener(OnObservableScrollViewListener onObservableScrollViewListener) {
this.mOnObservableScrollViewListener = onObservableScrollViewListener;
}
/**
* 滑动监听
* This is called in response to an internal scroll in this view (i.e., the
* view scrolled its own contents). This is typically as a result of
* {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
* called.
*
* @param l Current horizontal scroll origin. 当前滑动的x轴距离
* @param t Current vertical scroll origin. 当前滑动的y轴距离
* @param oldl Previous horizontal scroll origin. 上一次滑动的x轴距离
* @param oldt Previous vertical scroll origin. 上一次滑动的y轴距离
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnObservableScrollViewListener != null) {
//将监听到的数据向外抛
mOnObservableScrollViewListener.onObservableScrollViewListener(l, t, oldl, oldt);
}
}
}
2、页面布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.administrator.myapplication.widgets.ObservableScrollView
android:id="@+id/sv_main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_main_topContent"
android:layout_width="match_parent"
android:layout_height="280dp"
android:background="#b5b433"
android:gravity="center"
android:src="@mipmap/ic_launcher"
android:text="我是头部"
android:textSize="22sp" />
<TextView
android:id="@+id/tvShow"
android:layout_width="match_parent"
android:layout_height="600dp"
android:background="#ffffff"
android:gravity="center"
android:src="@mipmap/ic_launcher"
android:text="我是内容"
android:textSize="22sp" />
</LinearLayout>
</com.example.administrator.myapplication.widgets.ObservableScrollView>
<include layout="@layout/include_header_itl" />
</RelativeLayout>
3、include_header_itl.xml为顶部标题栏布局文件,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_header_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00000000"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal"
android:paddingTop="25dp">
<ImageView
android:id="@+id/iv_header_left"
android:layout_width="40dp"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:paddingRight="12dp"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_header_title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:ellipsize="end"
android:gravity="center"
android:maxLines="2"
android:text="我是标题"
android:textColor="#ffffff"
android:textSize="16sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/iv_header_img"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_gravity="center"
android:paddingLeft="12dp"
android:paddingRight="8dp"
android:src="@mipmap/ic_launcher" />
</RelativeLayout>
</LinearLayout>
4、主页面逻辑代码:
public class FragmentTab02 extends BaseFragment implements ObservableScrollView.OnObservableScrollViewListener {
@BindView(R.id.sv_main_content)
ObservableScrollView mObservableScrollView;
@BindView(R.id.tv_main_topContent)
TextView tvImage; // 标题栏覆盖下的头部
@BindView(R.id.ll_header_content)
LinearLayout mHeaderContent; // 标题栏
@BindView(R.id.tvShow)
TextView tvShow; // 标题栏覆盖下的内容
private int mHeight;
@Override
protected int getContentViewId() {
return R.layout.fragment_tab02;
}
@Override
protected void initData() {
// 设置沉浸式效果,或者在fragment所依附的activity中设置也可以
setStatusBarTranslucent(getActivity());
//注册滑动监听
mObservableScrollView.setOnObservableScrollViewListener(Fragment2Tab02.this);
}
/**
* 我们可以利用OnGlobalLayoutListener来获得一个视图的真实高度
* 但是需要注意的是OnGlobalLayoutListener可能会被多次触发,因此在得到了高度之后,要将OnGlobalLayoutListener注销掉
* 注意在fragment中使用:
* 如果有多个fragment通过show和hide显示和隐藏,并且进入activity后显示的不是这个fragment,那么在onViewCreate()里调用此方法测得的宽高依然为0!
* 解决方案:
* 重写onHiddenChanged()方法,并在里面判断当fragment不隐藏的时候调用此方法即可正常获取,如下:
*/
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
final ViewTreeObserver viewTreeObserver = tvImage.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
tvImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mHeight = tvImage.getHeight() - mHeaderContent.getHeight();//这里取的高度应该为图片的高度-标题栏
}
});
}
}
/**
* 获取ObservableScrollView的滑动数据
*
* @param l
* @param t
* @param oldl
* @param oldt
*/
@Override
public void onObservableScrollViewListener(int l, int t, int oldl, int oldt) {
if (t <= 0) {
//顶部图处于最顶部,标题栏透明
mHeaderContent.setBackgroundColor(Color.argb(0, 48, 63, 159));
} else if (t > 0 && t < mHeight) {
//滑动过程中,渐变
float scale = (float) t / mHeight;//算出滑动距离比例
float alpha = (255 * scale);//得到透明度
mHeaderContent.setBackgroundColor(Color.argb((int) alpha, 48, 63, 159));
} else {
//过顶部图区域,标题栏定色
mHeaderContent.setBackgroundColor(Color.argb(255, 48, 63, 159));
}
}
@OnClick(R.id.tvShow)
public void onClick(){
startActivity(new Intent(mActivity, SearchActivity.class));
}
/**
* 设置activity全屏,状态栏透明,内容填充到状态栏中
*/
public static void setStatusBarTranslucent(Activity activity) {
// 5.0 以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
View decorView = activity.getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
}
final ViewTreeObserver viewTreeObserver = tvImage.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
tvImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mHeight = tvImage.getHeight() - mHeaderContent.getHeight();//这里取的高度应该为图片的高度-标题栏
}
});
使用上述代码获取控件宽高时,如果是在Activity中,那么可直接在onCreate方法中使用,但如果有多个fragment通过show和hide显示和隐藏,并且进入activity后显示的不是这个fragment,那么在onViewCreate()里调用此方法测得的宽高依然为0!,此例子就是,该效果是在第二个Fragment中实现的,并且该fragment也是通过show和hide控制显示和隐藏。
重写onHiddenChanged()方法,并在里面判断当fragment不隐藏的时候调用此方法即可正常获取,如上面的写法。
1,布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="txkj.xian.com.myapplication.activity.LayoutActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvShow"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
<RelativeLayout
android:id="@+id/toobar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#00000000"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#ffffff"
android:textSize="16dp"
android:text="我是标题"/>
</RelativeLayout>
</RelativeLayout>
2,Activity中:
public class LayoutActivity extends AppCompatActivity {
private RecyclerView rvShow;
private RelativeLayout toobar;
private int mDistanceY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
rvShow = findViewById(R.id.rvShow);
toobar = findViewById(R.id.toobar);
LinearLayoutManager manager = new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
rvShow.setLayoutManager(manager);
List<String> list = new ArrayList<>();
for(int i=0;i<30;i++){
list.add("条目"+i);
}
MyAdapter adapter = new MyAdapter(R.layout.item,list);
rvShow.setAdapter(adapter);
setListener();
}
private void setListener() {
rvShow.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
//滑动的距离
mDistanceY += dy;
//toolbar的高度
int toolbarHeight = toobar.getBottom(); // getBottom()指标题栏底部距离父窗口的距离也就是标题栏的高度
//当滑动的距离 <= toolbar高度的时候,改变Toolbar背景色的透明度,达到渐变的效果
if (mDistanceY <= toolbarHeight) {
float scale = (float) mDistanceY / toolbarHeight;
float alpha = scale * 255;
toobar.setBackgroundColor(Color.argb((int) alpha, 63, 81, 181));
} else {
//上述虽然判断了滑动距离与toolbar高度相等的情况,但是实际测试时发现,标题栏的背景色
//很少能达到完全不透明的情况,所以这里又判断了滑动距离大于toolbar高度的情况,
//将标题栏的颜色设置为完全不透明状态
toobar.setBackgroundResource(R.color.colorPrimary); // #3F51B5
}
}
});
}
public class MyAdapter extends BaseQuickAdapter<String, BaseViewHolder> {
public MyAdapter(@LayoutRes int layoutResId, @Nullable List<String> data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, String item) {
// 赋值
helper.setText(R.id.content,item);
}
}
}
});