Android仿Foodpanda:ScrollView上下滑动时显示/隐藏视图(仅用TextView)
实现ScrollView滚动时显隐视图(无RecyclerView/ListView版)
嘿,这个需求其实很好实现!哪怕你的布局里只有ScrollView和TextView,咱们照样能复刻Foodpanda那种滚动时显示/隐藏顶部/底部栏的效果。下面我给你一步步拆解具体做法:
核心思路
监听ScrollView的滚动事件,记录滚动的位置变化,判断是向上滚动还是向下滚动,然后根据方向对目标布局(LinearLayout/RelativeLayout)执行显示或隐藏的动画操作,让过渡更自然。
第一步:准备布局文件
先写一个示例布局,结构大概是:根布局里包含要显隐的目标栏,再放一个ScrollView,ScrollView里是足够长的TextView(保证能滚动)。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 要显示/隐藏的目标布局(比如顶部导航栏) --> <LinearLayout android:id="@+id/target_layout" android:layout_width="match_parent" android:layout_height="56dp" android:background="#FF6200EE" android:gravity="center" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="顶部导航栏" android:textColor="#FFFFFF" android:textSize="18sp" /> </LinearLayout> <!-- 滚动容器:里面放你的TextView --> <ScrollView android:id="@+id/main_scrollview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/target_layout"> <TextView android:id="@+id/long_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingExtra="4dp" android:padding="16dp" android:text="@string/very_long_text" <!-- 这里放一段足够长的文本 --> android:textSize="16sp" /> </ScrollView> </RelativeLayout>
第二步:代码实现滚动监听与显隐逻辑
这里分两种情况:适配高版本API(23+)和兼容低版本API(<23),你可以根据自己的需求选择。
情况1:API 23及以上(直接用系统自带的监听)
在你的Activity/Fragment里,找到ScrollView和目标布局的实例,然后设置滚动监听:
Kotlin代码示例
import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { // 记录上一次滚动的Y位置 private var lastScrollY = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 设置ScrollView滚动监听 main_scrollview.setOnScrollChangeListener { _, _, scrollY, _, _ -> when { // 向下滚动:隐藏目标布局 scrollY > lastScrollY -> { if (target_layout.visibility == View.VISIBLE) { hideTargetLayout() } } // 向上滚动:显示目标布局 scrollY < lastScrollY -> { if (target_layout.visibility == View.GONE) { showTargetLayout() } } } // 更新上一次滚动位置 lastScrollY = scrollY } } // 隐藏布局的动画方法 private fun hideTargetLayout() { target_layout.animate() .translationY(-target_layout.height.toFloat()) .setDuration(300) // 动画时长300ms,和Foodpanda的流畅度接近 .withEndAction { target_layout.visibility = View.GONE } .start() } // 显示布局的动画方法 private fun showTargetLayout() { target_layout.visibility = View.VISIBLE target_layout.animate() .translationY(0f) .setDuration(300) .start() } }
Java代码示例
import android.os.Bundle; import android.view.View; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private int lastScrollY = 0; private View targetLayout; private ScrollView mainScrollView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); targetLayout = findViewById(R.id.target_layout); mainScrollView = findViewById(R.id.main_scrollview); mainScrollView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { if (scrollY > lastScrollY) { // 向下滚动,隐藏布局 if (targetLayout.getVisibility() == View.VISIBLE) { hideTargetLayout(); } } else { // 向上滚动,显示布局 if (targetLayout.getVisibility() == View.GONE) { showTargetLayout(); } } lastScrollY = scrollY; }); } private void hideTargetLayout() { targetLayout.animate() .translationY(-targetLayout.getHeight()) .setDuration(300) .withEndAction(() -> targetLayout.setVisibility(View.GONE)) .start(); } private void showTargetLayout() { targetLayout.setVisibility(View.VISIBLE); targetLayout.animate() .translationY(0) .setDuration(300) .start(); } }
情况2:兼容API低于23的版本
如果要支持Android 6.0以下的设备,需要自定义ScrollView来监听滚动事件:
1. 自定义ObservableScrollView类
import android.content.Context import android.util.AttributeSet import android.widget.ScrollView class ObservableScrollView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ScrollView(context, attrs, defStyleAttr) { private var onScrollChangeListener: OnScrollChangeListener? = null override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { super.onScrollChanged(l, t, oldl, oldt) onScrollChangeListener?.onScrollChange(this, l, t, oldl, oldt) } fun setOnScrollChangeListener(listener: OnScrollChangeListener) { this.onScrollChangeListener = listener } interface OnScrollChangeListener { fun onScrollChange(scrollView: ObservableScrollView, x: Int, y: Int, oldX: Int, oldY: Int) } }
2. 修改布局文件中的ScrollView
把原来的<ScrollView>换成自定义的<com.yourpackage.ObservableScrollView>(记得替换成你的包名)。
3. 在Activity中设置监听
逻辑和高版本基本一致,只是换成自定义的监听接口:
main_scrollview.setOnScrollChangeListener(object : ObservableScrollView.OnScrollChangeListener { override fun onScrollChange(scrollView: ObservableScrollView, x: Int, y: Int, oldX: Int, oldY: Int) { when { y > lastScrollY -> { if (target_layout.visibility == View.VISIBLE) hideTargetLayout() } y < lastScrollY -> { if (target_layout.visibility == View.GONE) showTargetLayout() } } lastScrollY = y } })
一些优化小细节
- 避免重复触发动画:判断目标布局当前的可见状态,只有在需要的时候才执行动画,防止滚动时反复触发。
- 滚动边界处理:如果滚动到顶部或底部,可以添加判断,比如当
scrollY == 0时强制显示布局,提升用户体验。 - 动画自定义:如果你不想用平移动画,也可以换成透明度动画(
alpha()),或者组合动画,效果都很赞。
这样就能完美实现类似Foodpanda的滚动显隐效果啦,哪怕只用ScrollView和TextView也完全没问题!
内容的提问来源于stack exchange,提问作者f982




