Last active
December 13, 2021 03:15
-
-
Save pythoncat1024/c38eb383021ff92551ed495c2a58f78e to your computer and use it in GitHub Desktop.
嵌套滑动
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
实现效果。类似给 RecyclerView 添加了一个 headerView.
注意这里的 onMeasure , 很巧妙的解决了 嵌套滑动布局中 RecyclerView 高度显示的问题。