这个示例来自:http://blog.csdn.net/lmj121212/article/details/52974427
自己在原项目代码基础上做了精简和梳理,添加了注释以帮助理解,下载地址:项目代码(已整理)
此项目中的NestedScrollingParent和NestedScrollingChild都是自定义view,理解这个项目有助于我们理清NestedScrolling机制的工作原理。项目效果如下:
直接说代码吧:
布局文件很简单,MyNestedScrollParent是一个自定义的LinearLayout,实现了NestedScrollingParent接口,其子元素从上到下依次为一个ImageView、一个TextView和一个MyNestedScrollChild,其中MyNestedScrollChild实现了NestedScrollingChild接口。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lmj.com.mynestscroll.MainActivity">
<com.lmj.com.mynestscroll.view.MyNestedScrollParent
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#f0f"
android:text="@string/topStr" />
<com.lmj.com.mynestscroll.view.MyNestedScrollChild
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/contenttv"
android:textColor="#f0f" />
</com.lmj.com.mynestscroll.view.MyNestedScrollChild>
</com.lmj.com.mynestscroll.view.MyNestedScrollParent>
</RelativeLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
在onTouchEvent()
中先后调用了startNestedScroll()
和dispatchNestedPreScroll()
方法,具体逻辑在注释中已经说的很清楚。
至于NestedScrollingChild接口的实现则是完全借助了NestedScrollingChildHelper帮助类。
public class MyNestedScrollChild extends LinearLayout implements NestedScrollingChild {
private NestedScrollingChildHelper mScrollingChildHelper;
private final int[] offset = new int[2];
private final int[] consumed = new int[2];
private int lastY;
private int showHeight;
public MyNestedScrollChild(Context context) {
super(context);
}
public MyNestedScrollChild(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//第一次测量,因为布局文件中高度是wrap_content,因此测量模式为ATMOST,即高度不能超过父控件的剩余空间
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
showHeight = getMeasuredHeight();
//第二次测量,对高度没有任何限制,那么测量出来的就是完全展示内容所需要的高度
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = (int) e.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int y = (int) (e.getRawY());
int dy = y - lastY;
lastY = y;
if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) //如果找到了支持嵌套滚动的父类
&& dispatchNestedPreScroll(0, dy, consumed, offset)) {//父类进行了一部分滚动
int remain = dy - consumed[1];//获取滚动的剩余距离
if (remain != 0) {
scrollBy(0, -remain);
}
} else {
scrollBy(0, -dy);
}
}
return true;
}
//scrollBy内部会调用scrollTo
//限制滚动范围
@Override
public void scrollTo(int x, int y) {
int MaxY = getMeasuredHeight() - showHeight;
if (y > MaxY) {
y = MaxY;
}
if (y < 0) {
y = 0;
}
super.scrollTo(x, y);
}
private NestedScrollingChildHelper getScrollingChildHelper() {
if (mScrollingChildHelper == null) {
mScrollingChildHelper = new NestedScrollingChildHelper(this);
mScrollingChildHelper.setNestedScrollingEnabled(true);
}
return mScrollingChildHelper;
}
//以下为接口实现--------------------------------------------------
@Override
public void setNestedScrollingEnabled(boolean enabled) {
getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return getScrollingChildHelper().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return getScrollingChildHelper().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
getScrollingChildHelper().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return getScrollingChildHelper().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}
}
在onStartNestedScroll()
中判断参数target是哪一个子view以及滚动的方向,然后决定是否要配合其进行嵌套滚动
在onNestedPreScroll()
中获取需要滚动的距离,根据情况决定自己是否要进行滚动,最后还要将自己滚动消费掉的距离存储在consumed数组中回传给child
public class MyNestedScrollParent extends LinearLayout implements NestedScrollingParent {
private ImageView img;
private TextView tv;
private MyNestedScrollChild nsc;
private NestedScrollingParentHelper mParentHelper;
private int imgHeight;
private int tvHeight;
public MyNestedScrollParent(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyNestedScrollParent(Context context) {
super(context);
init();
}
private void init() {
mParentHelper = new NestedScrollingParentHelper(this);
}
//获取子view
@Override
protected void onFinishInflate() {
img = (ImageView) getChildAt(0);
tv = (TextView) getChildAt(1);
nsc = (MyNestedScrollChild) getChildAt(2);
img.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (imgHeight <= 0) {
imgHeight = img.getMeasuredHeight();
}
}
});
tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (tvHeight <= 0) {
tvHeight = tv.getMeasuredHeight();
}
}
});
}
//接口实现--------------------------------------------------
//在此可以判断参数target是哪一个子view以及滚动的方向,然后决定是否要配合其进行嵌套滚动
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
if (target instanceof MyNestedScrollChild) {
return true;
}
return false;
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
}
@Override
public void onStopNestedScroll(View target) {
mParentHelper.onStopNestedScroll(target);
}
//先于child滚动
//前3个为输入参数,最后一个是输出参数
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
if (showImg(dy) || hideImg(dy)) {//如果需要显示或隐藏图片,即需要自己(parent)滚动
scrollBy(0, -dy);//滚动
consumed[1] = dy;//告诉child我消费了多少
}
}
//后于child滚动
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
}
//返回值:是否消费了fling
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
//返回值:是否消费了fling
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
@Override
public int getNestedScrollAxes() {
return mParentHelper.getNestedScrollAxes();
}
//--------------------------------------------------
//下拉的时候是否要向下滚动以显示图片
public boolean showImg(int dy) {
if (dy > 0) {
if (getScrollY() > 0 && nsc.getScrollY() == 0) {
return true;
}
}
return false;
}
//上拉的时候,是否要向上滚动,隐藏图片
public boolean hideImg(int dy) {
if (dy < 0) {
if (getScrollY() < imgHeight) {
return true;
}
}
return false;
}
//scrollBy内部会调用scrollTo
//限制滚动范围
@Override
public void scrollTo(int x, int y) {
if (y < 0) {
y = 0;
}
if (y > imgHeight) {
y = imgHeight;
}
super.scrollTo(x, y);
}
}
这个示例来自:http://blog.csdn.net/lmj623565791/article/details/43649913
代码下载地址:项目代码(已整理)
与第一个项目相比,此项目:
主要代码逻辑与第一个项目大同小异,因此不再赘述了。项目效果如下: