Android ScrollView 滑动实现标题栏渐变背景色
在 Android 应用开发中,顶部导航栏或标题栏随着页面滚动产生颜色渐变效果是一种常见且优雅的 UI 交互设计。这种设计通常用于列表页(List Page),当用户向上滑动时,标题栏从透明逐渐变为实色,增强视觉层次感。
本文将详细介绍如何通过自定义 ScrollView 监听滚动事件,结合透明度计算,实现标题栏背景色的平滑渐变效果。内容涵盖核心类编写、Activity 逻辑处理、布局配置以及注意事项。
一、核心原理
实现该效果的核心在于获取 ScrollView 的垂直滚动距离(Y 轴偏移量),并根据该距离与顶部图片高度的比例,动态计算背景颜色的 Alpha 通道值。
- 监听滚动:标准 ScrollView 不直接提供滚动监听接口,需继承并重写
onScrollChanged方法。 - 高度测量:获取顶部图片(Header Image)的高度,作为渐变的基准高度。
- 颜色插值:
- 当滚动距离 Y <= 0 时,背景完全透明。
- 当 0 < Y <= HeaderHeight 时,Alpha 值随 Y 线性增加。
- 当 Y > HeaderHeight 时,背景完全不透明。
二、核心类实现 (ObservableScrollView)
我们需要一个能够暴露滚动事件的自定义 ScrollView 控件。
package com.example.titlebarcolor;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
/**
* 带滚动监听的 ScrollView
*/
public class ObservableScrollView extends ScrollView {
public interface ScrollViewListener {
void onScrollChanged(ObservableScrollView scrollView, int x, int y,
int oldx, int oldy);
}
private ScrollViewListener scrollViewListener = null;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
}
说明
- 定义了一个内部接口
ScrollViewListener,用于回调滚动状态。 - 重写了
onScrollChanged方法,确保每次滚动都会通知注册的监听器。 - 提供了多种构造函数以兼容不同的 XML 加载方式。
三、具体使用 (MainActivity.java)
在 Activity 中初始化视图,绑定监听器,并根据滚动位置更新标题栏颜色。
package com.example.titlebarcolor;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity implements ObservableScrollView.ScrollViewListener {
private ObservableScrollView scrollView;
private ListView listView;
private ImageView imageView;
private TextView textView;
private int imageHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化视图引用
scrollView = findViewById(R.id.scrollview);
listView = findViewById(R.id.listview);
imageView = findViewById(R.id.imageview);
textView = findViewById(R.id.textview);
initListeners();
initData();
}
private void initListeners() {
// 使用 ViewTreeObserver 获取图片高度,避免过早测量导致高度为 0
ViewTreeObserver vto = imageView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
imageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
imageHeight = imageView.getHeight();
// 设置滚动监听
scrollView.setScrollViewListener(MainActivity.this);
}
});
}
private void initData() {
ArrayAdapter<String> adapter = new ArrayAdapter<>(
MainActivity.this,
android.R.layout.simple_list_item_1,
getResources().getStringArray(R.array.data)
);
listView.setAdapter(adapter);
}
@Override
public void onScrollChanged(ObservableScrollView scrollView, int x, int y,
int oldx, int oldy) {
// 根据滚动距离 y 计算透明度
if (y <= 0) {
// 滚动到顶部以上,背景完全透明
textView.setBackgroundColor(Color.argb(0, 227, 29, 26));
} else if (y > 0 && y <= imageHeight) {
// 滚动过程中,计算 Alpha 值
float scale = (float) y / imageHeight;
float alpha = 255 * scale;
// 设置半透明背景,仿知乎滑动效果
textView.setBackgroundColor(Color.argb((int) alpha, 227, 29, 26));
} else {
// 滚动超过图片高度,背景完全不透明
textView.setBackgroundColor(Color.argb(255, 227, 29, 26));
}
}
}
关键逻辑解析
- ViewTreeObserver:在
onCreate中直接获取getHeight()可能返回 0,因为此时视图尚未完成测量。使用OnGlobalLayoutListener可确保在布局完成后获取准确高度。 - Color.argb:使用 ARGB 模式构建颜色。前两个参数分别为 Alpha(透明度)和 RGB 分量。Alpha 范围是 0-255。
- 线性插值:
scale = y / imageHeight将滚动距离归一化为 0.0 到 1.0 之间,再乘以 255 得到 Alpha 值。
四、布局文件配置 (activity_main.xml)
布局结构需要包含自定义 ScrollView、顶部图片区域、列表区域以及悬浮的标题栏。
<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"
tools:context=".MainActivity">
<com.example.titlebarcolor.ObservableScrollView
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/imageview"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@drawable/header_bg" />
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</com.example.titlebarcolor.ObservableScrollView>
<!-- 标题栏,位于 ScrollView 之上 -->
<TextView
android:id="@+id/textview"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center"
android:text="我是标题"
android:textSize="18sp"
android:textColor="#FFFFFF"
android:background="#00000000" />
</RelativeLayout>
布局要点
- 相对定位:
TextView使用RelativeLayout的子元素,默认位于最上层,覆盖在 ScrollView 内容之上。 - ScrollView 属性:
android:scrollbars="none"隐藏滚动条以获得更干净的视觉效果。 - 图片资源:
header_bg需替换为实际的项目资源文件。
五、常见问题与优化建议
1. 性能优化
- 在
onScrollChanged中频繁调用setBackgroundColor可能会引起重绘。如果列表项较多,建议仅在颜色值发生变化时才更新背景。 - 对于复杂的动画效果,可考虑使用
ValueAnimator进行插值,但本例中的简单计算对性能影响较小。
2. 状态栏适配
- 如果标题栏需要与系统状态栏融合,需注意
TextView的paddingTop设置,确保不被状态栏遮挡。 - 在 Android 5.0+ 版本中,可通过
WindowInsets或fitsSystemWindows属性自动适配。
3. 兼容性处理
ViewTreeObserver在 API 11 及以上推荐使用addOnGlobalLayoutListener,低版本需注意移除监听器防止内存泄漏。- 代码中已包含
removeGlobalOnLayoutListener,确保不会造成内存泄漏。
4. 颜色选择
- 示例中使用红色系 (
227, 29, 26),实际项目中应根据品牌色调整 RGB 值。 - 若需支持深色模式,建议在
values-night/colors.xml中定义对应的颜色资源。
六、总结
通过自定义 ScrollView 监听滚动事件并结合简单的数学计算,我们可以轻松实现标题栏背景色的渐变效果。这种方法不仅代码量少,而且兼容性好,适用于大多数 Android 列表场景。开发者只需注意视图测量的时机以及颜色值的动态计算即可。

