这篇文章会用NestedScrolling机制做一个实例,此实例代码参考自:http://blog.csdn.net/al4fun/article/details/53889075
我在源代码的基础上,删减了些代码,加了些注释
例子效果如下:先贴出布局文件代码:
布局文件很简单,其中MyParent和MyChild分别是实现了NestedScrollingParent和NestedScrollingChild接口的父容器和子元素,这两个View都继承自LinearLayout。
我们先来看看MyChild是怎样实现的:
public class MyChild extends LinearLayout implements NestedScrollingChild {
NestedScrollingChildHelper nscp;
int lastY;
//这两个数组用来接收父容器传过来的参数
int[] consumed;
int[] offsetWindow;
int showHeight;
public MyChild(Context context) {
this(context,null);
}
public MyChild(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//第一次测量,因为是wrap_content,测量出来的只是父容器除了ImageView和TextView剩余的高度
//此次测量只是为了求得剩余的高度
//如果没有第二次测量,那么下面的文字就会显示不出来
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
showHeight = getMeasuredHeight();
//现在我们把MeasureSpec设置为UNSPECIFIED,这样MyChild的高度就没有限制了,也就能显示全部的文字了
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int y = (int) event.getRawY();
int dy = y-lastY;
lastY = y;
//开启NestedScrolling机制,如果找到了匹配的父容器,那么就与父容器配合消费掉滑动距离
if(startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)){
//dy是我们传过去的滑动的距离,父容器可以根据逻辑来选择要不要消费,消费多少
dispatchNestedPreScroll(0,dy,consumed,offsetWindow);
scrollBy(0,-dy);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
//scrollBy内部调用scrollTo,我们不能滑出去,也不能滑的太下面,我们要修正这些情况
@Override
public void scrollTo(@Px int x, @Px int y) {
int maxY = getMeasuredHeight()-showHeight;
if(y>maxY){
y=maxY;
}
else if(y<0){
y=0;
}
super.scrollTo(x, y);
}
//这里使用单例模式提供Helper,我发现如果没有单例模式,机制就会失效
//原因我大致的猜到了,但是我还不能具体的表达出来,如果有人知道,请在评论区留下言
private NestedScrollingChildHelper getNscp(){
if(nscp == null){
nscp = new NestedScrollingChildHelper(this);
nscp.setNestedScrollingEnabled(true);
return nscp;
}else {
return nscp;
}
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
getNscp().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return getNscp().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return getNscp().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
getNscp().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return getNscp().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return getNscp().dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return getNscp().dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return getNscp().dispatchNestedFling(velocityX,velocityY,consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getNscp().dispatchNestedPreFling(velocityX,velocityY);
}
}
大多数的函数我们都用帮助类的同名函数处理了,其余的代码我已经写上了详细的注释,不难看懂。
接下看下MyParent的实现:
public class MyParent extends LinearLayout implements NestedScrollingParent {
NestedScrollingParentHelper nsp;
ImageView iv;
MyChild nsc;
int ivHeight;
public MyParent(Context context) {
this(context,null);
}
public MyParent(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
//创建一个Helper类
nsp = new NestedScrollingParentHelper(this);
}
//拿到父容器里面的三个子View
@Override
protected void onFinishInflate() {
super.onFinishInflate();
iv = (ImageView) getChildAt(0);
nsc = (MyChild) getChildAt(2);
//拿到ImageView的高度
iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if(ivHeight<=0){
ivHeight = iv.getMeasuredHeight();
}
}
});
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
//参数里target是实现了NestedScrolling机制的子元素,这个子元素可以不是父容器的直接子元素
//child是包含了target的View,这个View是父容器的直接子元素
if(target instanceof MyChild){
return true;
}
return false;
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
//dy是子View传过来的,来询问父容器是不是要消费他,要的话,就把dy放进consumed数组,表示我消费了
//其中consumed数组,consumed[0]表示x方向的距离,consumed[1]表示y方向的距离
if(showImg(dy)||hideImg(dy)/*这里根据业务逻辑来判断*/){
scrollBy(0,-dy);
consumed[1] = dy;
}
}
private boolean hideImg(int dy) {
//上拉的时候,判断是不是要隐藏图片
if(dy<0){
if(getScrollY()0){
if(nsc.getScrollY()==0){
return true;
}
}
return false;
}
//scrollBy内部调用scrollTo,我们父容器不能滑出去,也不能滑的太下面,我们要修正这些情况
@Override
public void scrollTo(@Px int x, @Px int y) {
if(y>ivHeight){
y = ivHeight;
}
else if(y<0){
y=0;
}
super.scrollTo(x,y);
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
nsp.onNestedScrollAccepted(child,target,nestedScrollAxes);
}
@Override
public void onStopNestedScroll(View target) {
nsp.onStopNestedScroll(target);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
@Override
public int getNestedScrollAxes() {
return 0;
}
}
和MyChild一样,大多数函数都用帮助类的同名函数处理了,其余的也写上了详细的注释。
好了,没了。
完整代码地址:https://github.com/ChenTianSaber/NestedScrollingDemo
结束:
这篇文章先到此结束。
感觉写的好干,因为大多数地方都被帮助类实现了,而且例子也简单,没有什么一步一步的步骤。大家可以看懂了之后,自己写一遍。
接下来我们会分析一下NestedScrolling的源码,来看看它们是怎么做到相互配合的。