package com.vortex.scrolltest;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.OverScroller;
import android.widget.TextView;
/**
* Created by Administrator (chenPS) on 2019/7/29.
*/
public class MyNestedScrollParent extends LinearLayout implements NestedScrollingParent {
private ImageView iv;
private TextView tv;
private MyNestedScrollChild mMyNestedScrollChild;
private NestedScrollingParentHelper mParentHelper;
private OverScroller mScroller;
private int ivHeight;
private int tvHeight;
public MyNestedScrollParent(Context context) {
super(context);
}
public MyNestedScrollParent(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mParentHelper = new NestedScrollingParentHelper(this);
mScroller = new OverScroller(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
iv = (ImageView) getChildAt(0);
tv = (TextView) getChildAt(1);
mMyNestedScrollChild = (MyNestedScrollChild) getChildAt(2);
iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (ivHeight <= 0) {
ivHeight = iv.getMeasuredHeight();
}
}
});
tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (tvHeight <= 0) {
tvHeight = tv.getMeasuredHeight();
}
}
});
}
/**
*
* @param dy 滚动距离 大于0 是网上滚 逐渐显示图片
* @return getscrollY的值是逐渐减小的。一旦为0说明滑到顶部了
*/
private boolean showIv(int dy) {
if (dy > 0) {
Log.v("test",getScrollY()+" showIv");
if (getScrollY() > 0 && mMyNestedScrollChild.getScrollY() == 0) {
return true;
}
}
return false;
}
/**
*
* @param dy 滚动距离 小于0 是向下滑滚 逐渐隐藏图片
* @return getscrollY是父的滑动距离 一旦滑动到iv的高度 说明 已经完全不见了,就不需要在滑动父布局了
* getscrolly 的值是逐渐增大的
*/
private boolean hideIv(int dy) {
if (dy < 0) {
Log.v("test",getScrollY()+" showIv");
if (getScrollY() < ivHeight) {
return true;
}
}
return false;
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
if (target instanceof MyNestedScrollChild) {
return true;
}
return false;
}
private void fling(int velocityY) {
mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, ivHeight);
invalidate();
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
mParentHelper.onNestedScrollAccepted(child, target, axes);
}
@Override
public void onStopNestedScroll(View child) {
mParentHelper.onStopNestedScroll(child);
}
//先与 child 滚动
/**
* 在显示 隐藏图片的过程中 也代表这个时候 需要显示 /隐藏的是 父布局的移动效果
* 不需要去操纵 子布局的滑动
* 下滑 dy小于0 上滑dy大于0
*/
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
if (showIv(dy) || hideIv(dy)) {
Log.v("test", "onNestedPreScroll 先于child滚动" + dy);
scrollBy(0, -dy);
consumed[1] = dy;
}
}
// child滚动后
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
Log.d("test","onNestedScroll"+dyUnconsumed);
if (dyUnconsumed > 0) {
scrollBy(0, -dyUnconsumed);
}
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
if (getScrollY() >= 0 && getScrollY() < ivHeight) {
fling((int) velocityY);
return true;
}
return false;
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
if (!consumed) {
fling((int) velocityX);
return true;
}
return false;
}
@Override
public int getNestedScrollAxes() {
return mParentHelper.getNestedScrollAxes();
}
@Override
public void scrollTo(int x, int y) {
if (y < 0) {
y = 0;
}
if (y > ivHeight) {
y = ivHeight;
}
super.scrollTo(x, y);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}
private int lastY;
private VelocityTracker mVelocityTracker;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = (int) event.getRawY();
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int y = (int) event.getRawY();
int dy = y - lastY;
lastY = y;
scrollTo(0, -dy);
break;
case MotionEvent.ACTION_UP:
mVelocityTracker.computeCurrentVelocity(1000);
int vy = (int) mVelocityTracker.getYVelocity();
fling(-vy);
break;
}
return true;
}
}
package com.vortex.scrolltest;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.OverScroller;
/**
* Created by Administrator (chenPS) on 2019/7/29.
*/
public class MyNestedScrollChild extends LinearLayout implements NestedScrollingChild {
private NestedScrollingChildHelper mChildHelper;
private OverScroller mScroller;
private int maxScrollY;
private VelocityTracker mVelocityTracker;
private int lastY;
private final int[] offset = new int[2]; //偏移量
private final int[] consumed = new int[2]; //消费
public MyNestedScrollChild(Context context) {
super(context);
}
public MyNestedScrollChild(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
mScroller = new OverScroller(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int contentHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
measureChild(view, widthMeasureSpec, heightMeasureSpec);
contentHeight += view.getMeasuredHeight();
}
int parentHeight = ((ViewGroup) getParent()).getMeasuredHeight();
int pinTopHeight = (int) (getResources().getDisplayMetrics().density * 50 + 0.5); //50dp转 对应的像素 固定头的高度
int visibleHieght = parentHeight - pinTopHeight;
maxScrollY = contentHeight - visibleHieght;
setMeasuredDimension(getMeasuredWidth(), visibleHieght);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = (int) event.getRawY();
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int y = (int) event.getRawY();
int dy = y - lastY;
lastY = y;
/**
* public boolean startNestedScroll(int axes);
开启嵌套滚动流程(实际上是进行了一些嵌套滚动前准备工作)。
当找到了能够配合当前子view进行嵌套滚动的父view时,返回值为true
(Returns:true if a cooperative parent was found and nested scrolling
has been enabled for the current gesture)。
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
在子view自己进行滚动之前调用此方法,询问父view是否要在子view之前进行滚动。
此方法的前两个参数用于告诉父View此次要滚动的距离;而第三第四个参数用于子view获取父view消费掉的距离和父view位置的偏移量。
第一第二个参数为输入参数,即常规的函数参数,调用函数的时候我们需要为其传递确切的值。而第三第四个参数为输出参数,调用函数时我们只需要传递容器(在这里就是两个数组),在调用结束后,我们就可以从容器中获取函数输出的值。
如果parent消费了一部分或全部距离,则此方法返回true。
*/
if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) && //找到了配合滑动的父view
dispatchNestedPreScroll(0, dy, consumed, offset)) { //父类消费距离
/**
* 猜测下 这两个数组 第一位表示 x 第二位表示y值
*/
int remain = dy - consumed[1];
if (remain != 0) {
scrollBy(0, -remain);
}
} else {
scrollBy(0, -dy);
}
break;
case MotionEvent.ACTION_UP:
mVelocityTracker.computeCurrentVelocity(1000);
float vy = mVelocityTracker.getYVelocity();
if (!dispatchNestedPreFling(0, -vy)) { // 如果父类没有消费滑动事件 自己滑动
fling(-vy);
}
break;
}
return true;
}
private void fling(float velocityY) {
mScroller.fling(0, getScrollY(), 0, (int) velocityY, 0, 0, 0, maxScrollY);
invalidate();
}
@Override
public void scrollTo(int x, int y) {
if (y > maxScrollY) {
y = maxScrollY;
}
if (y < 0) {
y = 0;
}
super.scrollTo(x, y);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) { //如果还在滑动
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return mChildHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return mChildHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
}
笔记
这里有个bug 上滑 会有个白色的底部空白。暂时放下
参考文章
https://blog.csdn.net/mchenys/article/details/80041618