一.滑动冲突场景
继续讲述上篇场景一的滑动冲突,定义出模拟场景和解决方案(内部拦截法)。跟上篇一样定义一个可以横向滑动的父容器,子View定义一个RecycleView可以垂直滑动。
二.重点代码
- 父容器ViewGroup
只需重写父容器onInterceptTouchEvent方法,如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean isIntercept = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//不能拦截Down事件,后续事件自己处理,导致子View永远处理不了事件
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
//设置为true:当子View 调用 requestDisallowInterceptTouchEvent(false)申请父容器不拦截事件为(false)
//此时此事件交回给父容器,所以父容器需要自己消耗掉返回true。
/*疑点:这里move事件已经拦截了,那怎么会传递后续事件到子View呢?
子View需要在down事件的时候调用requestDisallowInterceptTouchEvent(true),
表示父容器down事件之后的系列事件将不要拦截(这里跟事件分发流程有点不同)*/
isIntercept = true;
break;
}
return isIntercept;
}
- 子类View
重写dispatchTouchEvent方法,如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//请求父容器不拦截后续系列事件,
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
float moveX = ev.getX();
float moveY = ev.getY();
if (Math.abs(moveX- mLastX)>Math.abs(moveY- mLastY)) {
//请父类容器不拦截事件为false,即父容器会拦截之后系列事件
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = ev.getX();
mLastY = ev.getY();
return super.dispatchTouchEvent(ev);
}
-
正常处理
-
不使用 requestDisallowInterceptTouchEvent 处理
if (Math.abs(moveX- mLastX)>Math.abs(moveY- mLastY)) {
//请父类容器不拦截事件为false,即父容器会拦截之后系列事件
//getParent().requestDisallowInterceptTouchEvent(false);
}
三.具体代码
1.定义一个可以横向滑动的容器HorizontalScrollViewGroup02,冲突处理采用内部部拦截法,(跟上篇ViewGroup一样,只是onInterceptTouchEvent方法有差异)代码入下:
public class HorizontalScrollViewGroup02 extends ViewGroup {
private final static String TAG = "HorizontalScrollview";
private Scroller mScroller;
private float mDownX;
float interceptDownX = 0;
float interceptDownY = 0;
private VelocityTracker mVelocityTracker;//速度跟踪
private int mChildWidth;
private int mChildeIndex;
private int mChildCount;
public HorizontalScrollViewGroup02(Context context) {
this(context, null);
}
public HorizontalScrollViewGroup02(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childCount = getChildCount();
//1.先测量子view
measureChildren(widthMeasureSpec, heightMeasureSpec);
//2.自身测量
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
if (childCount == 0) {
//子view 数量为0 宽高为父容器剩余空间
setMeasuredDimension(sizeWidth, sizeWidth);
} else if (modeHeight == MeasureSpec.AT_MOST && modeWidth == MeasureSpec.AT_MOST) {
View childAt = getChildAt(0);
//横向排布效果 默认取第一个view的高度
setMeasuredDimension(childCount * childAt.getMeasuredWidth(), childAt.getMeasuredHeight());
} else if (modeHeight == MeasureSpec.AT_MOST) {
setMeasuredDimension(sizeWidth, getChildAt(0).getHeight());
} else if (modeWidth == MeasureSpec.AT_MOST) {
setMeasuredDimension(childCount * getChildAt(0).getMeasuredWidth(), sizeHeight);
} else {
setMeasuredDimension(sizeWidth, sizeWidth);
}
}
/**
* 确定子view位置
*
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
mChildCount = getChildCount();
for (int i = 0; i < mChildCount; i++) {
View childAt = getChildAt(i);
int measuredWidth = childAt.getMeasuredWidth();
mChildWidth = measuredWidth;
//宽:累计子view的宽度 高:取子view高度
childAt.layout(childLeft, 0, childLeft + childAt.getMeasuredWidth(), childAt.getMeasuredHeight());
childLeft += childAt.getMeasuredWidth();
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
float moveX = ev.getX();
Log.d(TAG, "onTouchEvent: mDownX:" + mDownX + ",moveX:" + moveX);
//为负数:scrollBy移动的是控件里的内容,是相对滑动方向的反方向
scrollBy((int) -(moveX - mDownX), 0);
break;
case MotionEvent.ACTION_UP:
//优化:为了滑动到view边界
measureVelocity(ev);
break;
}
mDownX = ev.getX();
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean isIntercept = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//不能拦截Down事件,后续事件自己处理,导致子View永远处理不了事件
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
//设置为true:当子View 调用 requestDisallowInterceptTouchEvent(false)申请父容器不拦截事件为(false)
//此时此事件交回给父容器,所以父容器需要自己消耗掉返回true。
/*疑点:这里move事件已经拦截了,那怎么会传递后续事件到子View呢?
子View需要在down事件的时候调用requestDisallowInterceptTouchEvent(true),
表示父容器down事件之后的系列事件将不要拦截(这里跟事件分发流程有点不同)*/
isIntercept = true;
break;
}
return isIntercept;
}
private void smoothScrollBy(int dx) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
}
/**
* 模板写法
*/
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
private void measureVelocity(MotionEvent ev) {
mVelocityTracker.addMovement(ev);
int scroll = getScrollX();
//1秒内滑动
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) > 50) {
mChildeIndex = xVelocity > 0 ? mChildeIndex - 1 : mChildeIndex + 1;
} else {
mChildeIndex = (scroll + mChildWidth / 2) / mChildWidth;
}
mChildeIndex = Math.max(0, Math.min(mChildeIndex, mChildCount - 1));
int dx = mChildeIndex * mChildWidth - scroll;
smoothScrollBy(dx);
mVelocityTracker.clear();
}
}
2.定义子View,继承RecycleView,重写dispatchTouchEvent方法:
public class InternalInteceptRecycleView extends RecyclerView {
private float mLastX;
private float mLastY;
public InternalInteceptRecycleView(Context context) {
this(context, null);
}
public InternalInteceptRecycleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//请求父容器不拦截后续系列事件,
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
float moveX = ev.getX();
float moveY = ev.getY();
if (Math.abs(moveX- mLastX)>Math.abs(moveY- mLastY)) {
//请父类容器不拦截事件为false,即父容器会拦截之后系列事件
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = ev.getX();
mLastY = ev.getY();
return super.dispatchTouchEvent(ev);
}
}
3.定义测试 HorizontalAndVerticalScrollActivity02
- HorizontalAndVerticalScrollActivity02 界面
/**
* 内部拦截法
*/
public class HorizontalAndVerticalScrollActivity02 extends AppCompatActivity{
@Bind(R.id.rv)
RecyclerView mRv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_horizontal_scroll02);
ButterKnife.bind(this, this);
initData();
}
private void initData() {
ArrayList<String> strings = new ArrayList<>();
for (int i = 1; i < 81; i++) {
strings.add("第"+i+"个");
}
mRv.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
NormalTextAdapter normalTextAdapter = new NormalTextAdapter(strings);
mRv.setAdapter(normalTextAdapter);
}
@Override
protected void onDestroy() {
super.onDestroy();
ButterKnife.unbind(this);
}
}
- HorizontalAndVerticalScrollActivity02 布局
<?xml version="1.0" encoding="utf-8"?>
<com.example.robertluozizhao.framecollectdemo.view.custom.HorizontalScrollViewGroup02
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/green"
android:gravity="center"
android:text="222222222222"
android:textSize="30px"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/green"
android:gravity="center"
android:text="33333333333"
android:textSize="30px"/>
<com.example.robertluozizhao.framecollectdemo.view.custom.InternalInteceptRecycleView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray"/>
</com.example.robertluozizhao.framecollectdemo.view.custom.HorizontalScrollViewGroup02>
笔记:
内部拦截法
1.作为ViewGroup重写onInterceptTouchEvent方法,拦截除down以外事件;
2.作为View重写dispatchTouchEvent方法,需要配合parent.requestDisallowInterceptTouchEvent的方法:down事件时,申请父容器不拦截事件parent.requestDisallowInterceptTouchEvent(true),事件由子View处理;后续根据需求设置parent.requestDisallowInterceptTouchEvent(false),后续事件将由父容器处理