You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动