参考1
参考2
1. NestedScrolling是什么
NestedScrolling 是内部嵌套滑动机制。
由NestedScrollingParent,NestedScrollingParentHepler,NestedScrollingChild,NestedScrollingChildHepler组成
2. NestedScrolling解决了什么问题
解决了两个可以滑动的控件嵌套时出现的手势冲突的情况。
比如:ScrollView+ListView
ListView滑到顶部后,无法触发ScrollView的滑动
3. NestedScrolling的原理 (用了代理模式)
- NestedScrollingChild向外暴露方法,实际上具体的操作由NestedScrollingChildHepler完成。
-
NestedScrollingChildHelper最终会调用父容器NestedScrollingParent接口暴露出的方法,通知父容器做操作。
4. 源码分析
NestedScrollingChildHelper.startNestedScroll
会调用NestedScrollingParentHelper.onStartNestedScroll,如果返回true,还会调用NestedScrollingParentHelper.onNestedScrollAccepted。(其它的方法类似)
public class MyNestedScrollingChild extends LinearLayout implement NestedScrollingChild{
public boolean startNestedScroll(int axes) {
return mNestedScrollingChildHelper.startNestedScroll(axes);
}
}
public boolean startNestedScroll(@ScrollAxis int axes) {
return startNestedScroll(axes, TYPE_TOUCH);
}
public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
if (hasNestedScrollingParent(type)) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
setNestedScrollingParentForType(type, p);
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}
4. 使用示例
实现这样一个功能:Parent A下有一个不可滑动Child B和一个可滑动的Child C,向上拖动C,A和C一起动,C到置顶后,继续向上拖动,C的内容向上滑动
主要代码
public class MyNestedScrollingChild extends LinearLayout implements NestedScrollingChild {
private static final String TAG = "MyNestedScrollingChild";
private NestedScrollingChildHelper mNestedScrollingChildHelper;
private float mLastTouchX;
private float mLastTouchY;
private int[] mConsumed=new int[2];
private int[] mOffsetInWindow=new int[2];
public MyNestedScrollingChild(Context context) {
super(context, null);
}
public MyNestedScrollingChild(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
if (mNestedScrollingChildHelper == null) {
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
mNestedScrollingChildHelper.setNestedScrollingEnabled(true);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
float x=event.getX();
float y=event.getY();
switch (action){
case MotionEvent.ACTION_DOWN:
mLastTouchX=x;
mLastTouchY=y;
//关键点 通知Parent开始滑动
startNestedScroll(SCROLL_AXIS_HORIZONTAL);
break;
case MotionEvent.ACTION_MOVE:
int dx= (int) (mLastTouchX-x);
int dy= (int) (mLastTouchY-y);
mLastTouchX=x;
mLastTouchY=y;
//自己消耗事件前,将事件分发给Parent
//返回true表示Parent消耗了事件,其实是consume中是否有值大于1来决定的,消耗的偏移量在mConsumed中
if(dispatchNestedPreScroll(dx,dy,mConsumed, mOffsetInWindow)){//根据Parent的消耗再决定自己的消耗
Log.i(TAG, "Parent 消耗了 "+mConsumed[1]);
int leaveY=dy-mConsumed[1];
scrollBy(0,leaveY);
return true;
}
scrollBy(0,dy);
break;
}
return true;
}
}
public class MyNestedScrollingParent extends LinearLayout implements NestedScrollingParent {
private NestedScrollingParentHelper mNestedScrollingParentHelper;
private View mFirstView;
private View mSecondView;
public MyNestedScrollingParent(Context context) {
this(context,null);
}
public MyNestedScrollingParent(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
if (mNestedScrollingParentHelper == null) {
mNestedScrollingParentHelper=new NestedScrollingParentHelper(this);
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mFirstView=getChildAt(0);
mSecondView=getChildAt(1);
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return true;
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
if(consume(dy)){
consumed[1]=dy/2;
scrollBy(0,consumed[1]);
}
}
private boolean consume(int dy) {
if(dy < 0){
if(getScrollY() > 0 && mSecondView.getScrollY() <= 0){
return true;
}
}
if(dy > 0){
if(getScrollY() < mFirstView.getHeight()){
return true;
}
}
return false;
}
}
MyNestedScrollingChild.java
5.总结
- NestedScrollView的相关类 NestedScrollingParent,NestedScrollingParentHepler,NestedScrollingChild,NestedScrollingChildHepler
- 使用了代理模式,接口NestedScrollingParent,NestedScrollingChild,向外暴露接口,NestedScrollingParentHepler与NestedScrollingChildHepler负责具体实现
- 调用NestedScrollingChild的方法最终会触发NestedScrollingParent的相关方法(这体现了接口)
- NestedScrolling机制只负责滑动事件的通知,但是具体的滑动操作还是要自己实现
- 思考:为什么要使用 接口+代理模式的方式来实现NestedScrolling机制
- 所有视图都已经继承了View,所以只有采用接口的形式
- 接口中的大部分方法实现的逻辑都相似,所以采用了代理,让代理类完成具体的工作。