Skip to content

Instantly share code, notes, and snippets.

@pythoncat1024
Last active December 13, 2021 03:15
Show Gist options
  • Save pythoncat1024/c38eb383021ff92551ed495c2a58f78e to your computer and use it in GitHub Desktop.
Save pythoncat1024/c38eb383021ff92551ed495c2a58f78e to your computer and use it in GitHub Desktop.
嵌套滑动
<com.python.cat.mvvm.widgets.NestedLinearLayout
android:id="@+id/nested_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:visibleGone="@{!isLoading}">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/banner"
android:scaleType="centerCrop"
android:src="@drawable/time" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/articles_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</com.python.cat.mvvm.widgets.NestedLinearLayout>
package com.python.cat.mvvm.widgets;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.OverScroller;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import com.apkfuns.logutils.LogUtils;
/**
* <a href="https://blog.csdn.net/lmj623565791/article/details/52204039">Android 嵌套滑动 hongyang</a>
*/
public class NestedLinearLayout extends LinearLayout {
private OverScroller mScroller;
private int mTouchSlop;
private int mMaximumVelocity;
private int mMinimumVelocity;
private VelocityTracker mVelocityTracker;
private float mLastY;
private boolean mDragging;
private int mTapTimeout;
private int mLongPressTimeout;
private long mDownTime;
private void init(Context context) {
mScroller = new OverScroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMaximumVelocity = ViewConfiguration.get(context)
.getScaledMaximumFlingVelocity();
mMinimumVelocity = ViewConfiguration.get(context)
.getScaledMinimumFlingVelocity();
mTapTimeout = ViewConfiguration.getTapTimeout();
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
}
public NestedLinearLayout(Context context) {
super(context);
init(context);
}
public NestedLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public NestedLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public NestedLinearLayout(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int hs = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY);
int ws = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
// 我自己实现的,跟 hongyang 的不一样。
// 让下面的那个 recyclerView/ListView 尺寸与自己相同即可。
// 否则默认情况下,recyclerView 的高度是 parent - child[0] 的高度
measureChild(getChildAt(1), ws, hs);
}
/**
* onStartNestedScroll该方法,一定要按照自己的需求返回true,
* 该方法决定了当前控件是否能接收到其内部View(非并非是直接子View)滑动时的参数;
* 假设你只涉及到纵向滑动,这里可以根据nestedScrollAxes这个参数,进行纵向判断。
*/
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
// return super.onStartNestedScroll(child, target, nestedScrollAxes);
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
super.onNestedScrollAccepted(child, target, nestedScrollAxes);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
/**
* onNestedPreScroll 该方法的会传入内部View移动的dx,dy,如果你需要消耗一定的dx,dy,
* 就通过最后一个参数consumed进行指定,例如我要消耗一半的dy,就可以写consumed[1]=dy/2
*/
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(target, dx, dy, consumed);
int firstChildHeight = getChildAt(0).getHeight();
LogUtils.e("firstChildHeight=" + firstChildHeight);
boolean hiddenTop = dy > 0 && getScrollY() < firstChildHeight;
boolean showTop = dy < 0 && getScrollY() > 0
&& !target.canScrollVertically(-1);
if (hiddenTop || showTop) {
scrollBy(0, dy);
consumed[1] = dy;
}
}
/**
* onNestedFling 你可以捕获对内部View的fling事件,如果return true则表示拦截掉内部View的事件。
*/
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY,
boolean consumed) {
// return super.onNestedFling(target, velocityX, velocityY, consumed);
int firstChildHeight = getChildAt(0).getHeight();
LogUtils.e("firstChildHeight=" + firstChildHeight);
if (getScrollY() >= firstChildHeight)
return false;
fling((int) velocityY);
return true;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return super.onNestedPreFling(target, velocityX, velocityY);
}
@Override
public int getNestedScrollAxes() {
return super.getNestedScrollAxes();
}
public void fling(int velocityY) {
int firstChildHeight = getChildAt(0).getHeight();
LogUtils.d("firstChildHeight=" + firstChildHeight);
mScroller.fling(0, getScrollY(), 0, velocityY,
0, 0, 0, getHeight());
invalidate();
}
@Override
public void scrollTo(int x, int y) {
int firstChildHeight = getChildAt(0).getHeight();
LogUtils.i("firstChildHeight=" + firstChildHeight);
if (y < 0) {
y = 0;
}
if (y > firstChildHeight) {
y = firstChildHeight;
}
if (y != getScrollY()) {
super.scrollTo(x, y);
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
invalidate();
}
}
// 因为本身的继承 LinearLayout,导致滑动 child[0] 的时候,并不让自己滑动
@Override
public boolean performClick() {
return super.performClick();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(event);
int action = event.getAction();
float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished())
mScroller.abortAnimation();
mLastY = y;
mDownTime = SystemClock.elapsedRealtime();
return true;
case MotionEvent.ACTION_MOVE:
float dy = y - mLastY;
if (!mDragging && Math.abs(dy) > mTouchSlop) {
mDragging = true;
}
if (mDragging) {
scrollBy(0, (int) -dy);
}
mLastY = y;
break;
case MotionEvent.ACTION_CANCEL:
handleClicks();
mDragging = false;
recycleVelocityTracker();
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_UP:
handleClicks();
mDragging = false;
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityY = (int) mVelocityTracker.getYVelocity();
if (Math.abs(velocityY) > mMinimumVelocity) {
fling(-velocityY);
}
recycleVelocityTracker();
break;
}
return super.onTouchEvent(event);
}
private void handleClicks() {
long elapsedRealtime = SystemClock.elapsedRealtime();
if (!mDragging && (elapsedRealtime - mDownTime < mTapTimeout)) {
performClick();
} else if (!mDragging && (elapsedRealtime - mDownTime > mLongPressTimeout)) {
performLongClick();
}
}
private void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
}
@pythoncat1024
Copy link
Author

pythoncat1024 commented Aug 19, 2019

实现效果。类似给 RecyclerView 添加了一个 headerView.

注意这里的 onMeasure , 很巧妙的解决了 嵌套滑动布局中 RecyclerView 高度显示的问题。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment