前言
在前期做一些Android酷炫效果并遇到嵌套滑动问题的算是一大难点,没有标准的规范,开发人员根据自己的需求进行随意控制,导致做的一些组件缺少复用性,同时也不利于后期的维护。后期官方出了自己的嵌套滑动标准,主要由NestedScrollingChild、NestedScrollingChildHelper、NestedScrollingParent、NestedScrollingParentHelper进行控制,从而规范了嵌套滑动事件的处理标准。
下图展示的一个嵌套滑动的效果图,这里我们定义:
- A:表示蓝色部分视图,一个ViewGroup
- B:表示淡绿色部分视图,自定义的一个View
后面的描述我们都将用A和B表示。
们需要达到的效果是当触摸到B的时候,B滑动一段距离,然后再滑动A;接下来我们已两种不同的方式来实现该效果。
如何实现
方案一:使用传统的事件分发机制进行控制
要实现该效果,那么我们就需要在B中消费事件,同时满足一定条件后控制父视图A的滑动;这里我们定义一个view,重写onToucheEvent()进行事件消费处理:
public class DemoView extends View {
//最大滑动距离-需要根据自己需要进行控制
private int maxMoveDistance = 200;
public DemoView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
}
这里的onTouchEvent是我们处理的关键,因此详细介绍具体实现过程:
第一步: 处理Down事件
//做横向滑动,记录坐标即可
private int lastTouchX;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastTouchX = (int) event.getRawX();
ViewParent viewParent = getParent();
if(viewParent != null){
//请求父视图不拦截事件
viewParent.requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.onTouchEvent(event);
}
上面的代码已有注释,我们取出点击时的坐标,同时要求父View不拦截事件.
第二步: 处理Move事件
View的移动我们通过setTranslationX的方式来控制,移动的距离为当前移动X坐标与上一次按下或者移动的X坐标的差,即event.getRawX()-lastTouchX;在滑动的过程中,我们需要判断当前View达到相应临界点消费的滑动距离,没有消费完成则由父View消费,整个onTouch的代码如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
lastTouchX = (int) event.getRawX();
ViewParent viewParent = getParent();
if (viewParent != null) {
//请求父视图不拦截事件
viewParent.requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_MOVE: {
ViewParent viewParent = getParent();
if (viewParent != null) {
//请求父视图不拦截事件
viewParent.requestDisallowInterceptTouchEvent(true);
}
int dx = (int) (event.getRawX() - lastTouchX);
//判断当前View能够移动的最大距离
int oldViewX = (int) getTranslationX();
int viewTranslationX = oldViewX + dx;
if (viewTranslationX < 0) {
setTranslationX(0);
} else if (viewTranslationX > maxMoveDistance) {
setTranslationX(maxMoveDistance);
} else {
setTranslationX(viewTranslationX);
}
//判断当前View是否已经消费完滑动的距离,没有消费完则交由父View处理
int dxConsumed = (int) (getTranslationX() - oldViewX);
int dxUnConsumed = dx - dxConsumed;
if (dxUnConsumed != 0) {
if (viewParent != null) {
ViewGroup viewGroup = (ViewGroup) viewParent;
viewGroup.setTranslationX(viewGroup.getTranslationX() + dxUnConsumed);
}
}
lastTouchX = (int) event.getRawX();
break;
}
}
return true;
}
第三步: 验证结果
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="100dp">
<View
android:layout_width="150dp"
android:layout_height="50dp"
android:background="@color/colorPrimary"></View>
<com.water.view.demo.DemoView