3、NestedScrollingParent和NestedScrollingChild对应
这两个接口的回调方法api,如下:
SCROLL_STATE_IDLE 0, 最后是RecyclerView滚动停止状态。
SCROLL_STATE_DRAGGING 1, 先是手指拖拽的状态
SCROLL_STATE_SETTLING 2,再是手指松开但是RecyclerView还在滑动
public interface NestedScrollingParent {
boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes);
void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int nestedScrollAxes);
void onStopNestedScroll(@NonNull View target);
void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);
boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);
boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);
int getNestedScrollAxes();
}
public interface NestedScrollingChild {
void setNestedScrollingEnabled(boolean enable);
boolean isNestedScrollingEnabled();
boolean startNestedScroll(int axes);
void stopNestedScroll();
boolean hasNestedScrollingParent();
boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow);
boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);
boolean dispatchNestedPreFling(float var1, float var2);
boolean dispatchNestedFling(float var1, float var2, boolean var3);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
父接口的回调和子接口的回调,两者的方法有明显的对应关系。
这样实现了子接口就可以在需要的时候调用接口的方法,如
stopNestedScroll
,这样对应在父接口
onStopNestedScroll
也就会被回调。
4、响应者
上文说了父容器是接受的一方,但它并不是真正意义上的响应者,响应者是谁取决于
Behavior
的定义。
本例中的父容器是
CoordinatorLayout
,我们就可以自定义一个
Behavior
继承
CoordinatorLayout.Behavior
,然后在对应的父容器回调方法中加入自己想要的逻辑。
值得一提的是,你即可以指定设置了
Behavior
的控件本身响应,也可以指定该父容器下的其他子容器(控件)响应,无论这个
Behavior
设置给哪个子容器(控件)。
示例如下:
public class ScaleBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
...
}
}
当然Android本身也有很多定义好的
Behavior
可以直接使用,这里就不赘述了。
最后将这个
Behavior
设置到布局中,就可以正常使用了。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout/>
<FrameLayout
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior">
<android.support.v7.widget.RecyclerView/>
<RelativeLayout>
...
</RelativeLayout>
</FrameLayout
app:layout_behavior=".ScaleBehavior">
<android.support.design.widget.FloatingActionButton/>
</android.support.design.widget.CoordinatorLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
示例
本例主要
-
使用
RecyclerView
做示范。(将项目启动页改为
MainActivity
查看)
-
自定义了一个实现了
NestedScrollingChild
接口的
ListView
做示范。
-
自定义
Behavior
示范。
效果如下
总布局代码如下
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
app:title="ToolBar" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior">
<pers.owen.recyclerview.NestedListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:listitem="@layout/item_feed" />
<RelativeLayout
android:id="@+id/suspension_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<pers.owen.recyclerview.CircleImageView
android:id="@+id/iv_avatar"
android:layout_width="44dp"
android:layout_height="44dp"
android:padding="8dp"
android:src="@drawable/avatar1" />
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginLeft="8dp"
android:layout_toRightOf="@id/iv_avatar"
android:gravity="center_vertical"
android:text="粥可温"
android:textSize="12sp" />
<View
android:id="@+id/top_divider"
android:layout_width="match_parent"
android:layout_height="0.2dp"
android:layout_below="@id/tv_nickname"
android:background="#33000000" />
</RelativeLayout>
</FrameLayout>
<android.support.design.widget.FloatingActionButton
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="bottom|right"
android:layout_margin="16dp"
app:layout_behavior=".ScaleBehavior" />
</android.support.design.widget.CoordinatorLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
NestedListView代码如下
public class NestedListView extends ListView implements NestedScrollingChild {
private NestedScrollingChildHelper mChildHelper;
private int mLastY;
private final int[] mScrollOffset = new int[2];
private final int[] mScrollConsumed = new int[2];
private int mNestedOffsetY;
public NestedListView(Context context) {
super(context);
init();
}
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public NestedListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
@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);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int y = (int) ev.getY();
ev.offsetLocation(0, mNestedOffsetY);
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastY = y;
mNestedOffsetY = 0;
this.startNestedScroll((ViewCompat.SCROLL_AXIS_VERTICAL));
break;
case MotionEvent.ACTION_MOVE:
int dy = mLastY - y;
int oldY = getScrollY();
if (dispatchNestedPreScroll(0, dy, mScrollConsumed, mScrollOffset)) {
dy -= mScrollConsumed[1];
ev.offsetLocation(0, -mScrollOffset[1]);
mNestedOffsetY += mScrollOffset[1];
}
mLastY = y - mScrollOffset[1];
int newScrollY = oldY + dy;
dy -= newScrollY - oldY;
if (dispatchNestedScroll(0, newScrollY - dy, 0, dy, mScrollOffset)) {
ev.offsetLocation(0, mScrollOffset[1]);
mNestedOffsetY += mScrollOffset[1];
mLastY -= mScrollOffset[1];
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
stopNestedScroll();
break;
}
return super.onTouchEvent(ev);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
ScaleBehavior代码如下
public class ScaleBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
private Interpolator interpolator;
private boolean isRunning;
public ScaleBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
interpolator = new AccelerateDecelerateInterpolator();
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
Log.e("test", dyConsumed + " " + dyUnconsumed);
if (dyConsumed > 0 && !isRunning && child.getVisibility() == View.VISIBLE) {
scaleHide(child);
} else if (dyConsumed < 0 && !isRunning && child.getVisibility() == View.INVISIBLE) {
scaleShow(child);
}
}
private void scaleShow(final V child) {
child.setVisibility(View.VISIBLE);
ViewCompat.animate(child).alpha(1).scaleX(1).scaleY(1).setInterpolator(interpolator)
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
isRunning = true;
}
@Override
public void onAnimationEnd(View view) {
isRunning = false;
}
@Override
public void onAnimationCancel(View view) {
isRunning = false;
}
}).setDuration(500).start();
}
private void scaleHide(final V child) {
ViewCompat.animate(child).alpha(0).scaleX(0).scaleY(0).setInterpolator(interpolator)
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
isRunning = true;
}
@Override
public void onAnimationEnd(View view) {
isRunning = false;
child.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationCancel(View view) {
isRunning = false;
}
}).setDuration(500).start();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
推荐阅读
Android 11新特性,Scoped Storage又有了新花样
About
本文Demo
UI系列文章一览