核心代码如下:
能够吸顶的前提是,NestedScrollView的内容高度足够用,得能够滑动到顶部。
package com.baina.coordinatorlayoutdemo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.widget.NestedScrollView;
public class TestNestedScrollViewActivity extends Activity {
private String TAG = "JPush";
// 滑动面板距离顶部的距离
private int topDistancePx = 0;
private NestedScrollView scrollView;
private TextView contentTv;
private Handler handler = new Handler();
private int scrollDy = 0;
private int scrollOldDy = 0;
private boolean isDrag = true;
private boolean result = false;
private Runnable runnableAnimate = new Runnable() {
@Override
public void run() {
if (scrollDy < topDistancePx && scrollDy > 0) {
result = true;
scrollAnimate(scrollDy);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_nested_scroll_view);
initView();
initEvent();
}
private void initEvent() {
final long delayTime = 50;
contentTv.setText(generateFruitContent("好甜的西瓜"));
scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
scrollDy = scrollY;
scrollOldDy = oldScrollY;
handler.removeCallbacksAndMessages(null);
if (!isDrag) {
handler.postDelayed(runnableAnimate, delayTime);
}
// Log.i(TAG, "animate: 滑动的距离:" + scrollY);
// 滑动的距离,正数-向上;负数-向下
// int distant = scrollDy - scrollOldDy;
}
});
scrollView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onTouch: 触摸事件按下,起始距离:" + scrollOldDy);
isDrag = true;
result = false;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "onTouch: 手抬起");
isDrag = false;
if (scrollOldDy < topDistancePx && scrollDy > 0) {
result = true;
scrollAnimate(scrollDy);
} else {
result = false;
}
break;
}
return result;
}
});
}
private void initView() {
scrollView = findViewById(R.id.scrollView);
contentTv = findViewById(R.id.tv_content);
// 滑动面板距离顶部的距离(dp)
int topDistanceDp = 200;
topDistancePx = dp2px(topDistanceDp);
Log.i(TAG, "animate: 距离顶部:" + topDistancePx);
}
private String generateFruitContent(String fruitName) {
StringBuilder fruitContent = new StringBuilder();
for (int i = 0; i < 300; i++) {
fruitContent.append(fruitName);
fruitContent.append("@ ");
fruitContent.append(i);
fruitContent.append(" @");
}
return fruitContent.toString();
}
private void scrollAnimate(int scrollY) {
Log.i(TAG, "scrollAnimate: 竖直滑动的距离:" + scrollY);
if (!isDrag && scrollY < topDistancePx) {
if (scrollY > topDistancePx / 3) {
// 吸附顶部
Log.i(TAG, "scrollAnimate: 吸顶动画");
scrollView.smoothScrollTo(0, topDistancePx);
} else {
// 回弹到底部
Log.i(TAG, "scrollAnimate: 回弹底部动画");
scrollView.smoothScrollTo(0, 0);
}
}
}
private int dp2px(float dpValue) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
<?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"
android:orientation="vertical"
tools:context=".TestNestedScrollViewActivity">
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="200dp"
android:orientation="vertical"
android:background="#C6C0C6">
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</RelativeLayout>