ScrollView+RecyclerView实现悬停导航栏

效果预览

设计思路

主页布局

    个人喜欢用LinearLayout做最外层布局,任何情况下。。。
    这里需要先用到ScrollView,等下再使用重写的ScrollView类来取代它

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:gravity="center"
xmlns:android="http://schemas.android.com/apk/res/android">
<ScrollView
android:id="@+id/myScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 顶部-->
<LinearLayout
android:gravity="center"
android:orientation="vertical"
android:background="#0000FF"
android:layout_width="match_parent"
android:layout_height="200dp">
<TextView
android:text="顶部"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 导航栏-->
<!-- 这里给导航栏加了tag标签,方便找到它-->
<LinearLayout
android:gravity="center"
android:background="#FFFFE0"
android:layout_width="match_parent"
android:tag="NavBar"
android:layout_height="60dp">
<TextView
android:text="顶部"
android:textColor="@color/black"
android:textStyle="bold"
android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

重写ScrollView

    创建MyScrollView类并继承ScrollView,重写ScrollView的前三个构造器,重写onLayout()方法,重写dispatchDraw()方法,在类中创建接口供外部继承调用

MyScrollView.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.xin.FixBar;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;

public class MyScrollView extends ScrollView {
public MyScrollView(Context context) {
super(context);
}

public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

private View view;//用作储存状态栏的View

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed){
// 得到滚动布局中的LinearLayout
LinearLayout linearLayout = (LinearLayout) getChildAt(0);
if (linearLayout != null){
// 遍历滚动布局子View LinearLayout的所有子View
for (int i = 0; i < linearLayout.getChildCount(); i++){
// 将标签为NavBar的View赋值给实例变量view
if (linearLayout.getChildAt(i).getTag() != null && linearLayout.getChildAt(i).getTag().toString().equals("NavBar")){
view = linearLayout.getChildAt(i);
break;
}
}
}
else {
Toast.makeText(getContext(),"至少有一个LinearLayout作为ScrollView的子View",Toast.LENGTH_SHORT).show();
}
}
}

@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// 当getScrollY()与view.getTop()相等时,说明状态栏滑动到了顶部
if (getScrollY() >= view.getTop()){
if (onNavBarListener != null){
onNavBarListener.OnFix();//固定状态栏
}
canvas.save();
canvas.translate(0,getScrollY());
canvas.clipRect(0,0,view.getMeasuredWidth(),view.getMeasuredHeight());//设置画布的显示范围
view.draw(canvas);//绘制设置的canvas
canvas.restore();//恢复到canvas.save()保存时的状态
}
// 状态栏未接触到顶部,则不设置为其固定
else {
if (onNavBarListener != null){
onNavBarListener.OnReset();//取消固定状态栏
}
}
}

// 使在Activity中可以继承或直接调用接口对悬浮状态栏进行设置
private OnNavBarListener onNavBarListener;

public void setOnNavBarListener(OnNavBarListener onNavBarListener){
this.onNavBarListener = onNavBarListener;
}

public interface OnNavBarListener{
void OnFix();//固定状态栏
void OnReset();//取消固定状态栏
}
}

    然后把activity_main.xml中的ScrollView控件替换为我们重写后的MyScrollView,注意使用完整的类名

1
2
3
4
5
6
<com.xin.FixBar.MyScrollView
android:id="@+id/myScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">

</com.xin.FixBar.MyScrollView>

创建item的布局

item.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="60dp"
android:layout_width="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:text="文本"
android:id="@+id/item_text"
android:layout_marginLeft="20dp"
android:textSize="16sp"
android:textColor="@color/black"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

创建MyAdapter

    创建MyAdapter类,继承Recycler.Adapter给RecylcerView创建适配器

MyAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.xin.FixBar;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private String[] data;

public MyAdapter(Context context,String[] data){
this.context = context;
this.data = data;
}

@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = View.inflate(context,R.layout.item,null);
return new MyViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.item_view.setText(data[position]);
}

@Override
public int getItemCount() {
return data.length;
}

public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView item_view;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
item_view =itemView.findViewById(R.id.item_text);
}
}
}

在MainActivity中调用

都在注释里了,啥也不想写。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.xin.FixBar;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
private MyScrollView myScrollView;
private RecyclerView recyclerView;
private String[] data;
private boolean navBar = false;//navbar的悬浮状态
private MyAdapter myAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();//初始化
setRecycler();//设置列表
setScroll();//设置滚动
}

// 设置滚动
private void setScroll() {
myScrollView.setOnNavBarListener(new MyScrollView.OnNavBarListener() {
@Override
// 设置为悬浮状态
public void OnFix() {
if (!navBar){
navBar = true;
recyclerView.setNestedScrollingEnabled(true);//允许recyclerView滑动
Log.d("setScroll", "OnReset: recyclerView已允许");
}
}

@Override
// 取消悬浮状态
public void OnReset() {
if (navBar){
navBar = false;
recyclerView.setNestedScrollingEnabled(false);//禁止recyclerView滑动
Log.d("setScroll", "OnReset: recyclerView已禁止");
}
}
});
}

// 设置列表
private void setRecycler() {
LinearLayoutManager layoutManager = new LinearLayoutManager(this);//实例化一个布局管理器
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);//设置布局方向
recyclerView.setLayoutManager(layoutManager);//给Recycler添加布局
// 设置RecyclerView的高度
int pxHeight = this.getResources().getDisplayMetrics().heightPixels;//获取屏幕高度的px
int StatusBarHeight = (int) (60 * this.getResources().getDisplayMetrics().density + 0.5f);//状态栏的px高度
recyclerView.getLayoutParams().height = pxHeight - StatusBarHeight;//得到RecyclerView的高度并设置
myAdapter = new MyAdapter(this,data);//实例化适配器
recyclerView.setAdapter(myAdapter);//设置适配器

recyclerView.setNestedScrollingEnabled(false);//设置recycler为不可滑动,解决滑动冲突
// 给RecyclerView添加滑动监听,当滑动至顶部时,禁用RecyclerView滑动
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
// 滑动停止时调用
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 当recyclerView滑动到顶部时,canScrollVertically(-1)返回false,禁用它的滑动
if (!recyclerView.canScrollVertically(-1)){
recyclerView.setNestedScrollingEnabled(false);
}
}
});
}

// 初始化
private void init() {
myScrollView = findViewById(R.id.myScrollView);
recyclerView = findViewById(R.id.recyclerView);
data = new String[]{
"测试1","测试2","测试3","测试4","测试5","测试6","测试7","测试8","测试9","测试10","测试11","测试12","测试13","测试14",
"测试1","测试2","测试3","测试4","测试5","测试6","测试7","测试8","测试9","测试10","测试11","测试12","测试13","测试14",
"测试1","测试2","测试3","测试4","测试5","测试6","测试7","测试8","测试9","测试10","测试11","测试12","测试13","测试14",
"测试1","测试2","测试3","测试4","测试5","测试6","测试7","测试8","测试9","测试10","测试11","测试12","测试13","测试14",
"测试1","测试2","测试3","测试4","测试5","测试6","测试7","测试8","测试9","测试10","测试11","测试12","测试13","测试14"
};
}
}

到这里就搞定了

源代码的话,github地址:https://github.com/xxinPro/fixNavBar