最近在读Android开发艺术探索,本文作为自己对view的滑动冲突的理解和实践记录
而滑动冲突,需要了解Android的事件分发机制,如果这个还有些疑惑的地方,请参考这篇文章,以及其中的参考文章
还需要View的Measure和Layout的相关知识View的Measure流程总结
自定义view注意
1.如果直接继承view,此时wrap_content和使用match_parent效果一样.需要在onMeasure()
中处理AT_MOST
条件,处理wrap_content.
2.margin
要在onLayout中设定,padding
需要在 onDraw中设定
3.刷新回调,停止县城或者动画 在view.onDetachedFromWindow
4.在dispatchTouchEvent
和TouchEvent
中处理好滑动事件.
滑动冲突的种类
场景一:外部和内部俩层滑动方向不一致
场景二:外部和内部俩层滑动方向一致
场景三:主要是针对场景一和二的嵌套
滑动处理千篇一律, 只要你找到什么时候父控件滑动,什么时候子空间滑动.然后再父布局中,选择 是否自己处理onInterceptTouchEvent()
,就好了.
Android开发艺术探索中有两种方式,分别为外部拦截发,和内部拦截法.我上面说的是外部拦截法(感觉这个好用些).
具体的内容,大家可以看 Android开发艺术探索第三章第五节相关内容.
情形1的处理
先上效果图
下面是自定义的view,解决了上述情景1的问题.
/**
自定义滑动viewPager
* Created by chenchangjun on 17/7/14.
*/
public class HorizontalScrollView extends ViewGroup {
private static final String TAG = HorizontalScrollView.class.getSimpleName();
private int mChildWidth = 1;
private int mChildIndex = 1;
private int mLastX;
private int mLastY;
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
/**
* Scroller只是个计算器,处理滑动效果的,例如ViewPager,listview等的内部类
*/
private Scroller mScroller;
/**
* 速度获取器
*/
private VelocityTracker mVelocityTracker;
private int mChildrenCount;
private void init() {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:
break;
}
Log.d(TAG, "intercept=" + intercept);
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
scrollBy(-deltaX, 0);//视觉上向右滑动,相对的,view横向向左移动.
break;
case MotionEvent.ACTION_UP:
int scrollX = getScrollX();
// int srcollToChildIndex=scrollX/mChildWidth;
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 30) { //当一秒滑动像素大于30像素的时候,
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;//计算pager下标mChildIndex.如果手指从右向左,则xVelocity为负,mChildIndex+1;反之,易然.
} else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
int dex=0;
/* if (mChildIndex >= getChildCount()) {
mChildIndex = 0;
} else if (mChildIndex < 0) {
mChildIndex = getChildCount() - 1;
} else {
dex = mChildIndex * mChildWidth - scrollX;
}*/
mChildIndex=Math.max(0,Math.min(mChildIndex,mChildrenCount-1));
dex = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dex, 0);
mVelocityTracker.clear();
break;
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenCount = childCount;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
mChildWidth = child.getMeasuredWidth();
child.layout(childLeft, 0, childLeft + child.getMeasuredWidth(), child.getMeasuredHeight());
childLeft += mChildWidth;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0) {
setMeasuredDimension(0, 0);
} else if (heightSpecMode == MeasureSpec.AT_MOST && widthSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);//因为这里的child都是同类,所以偷懒~取第一个测量尺寸就够啦
measuredWidth = childView.getMeasuredWidth() * childCount;
measuredHeight = childView.getMeasuredHeight() * childCount;
setMeasuredDimension(measuredWidth, measuredHeight);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);//因为这里的child都是同类,所以偷懒~取第一个测量尺寸就够啦
measuredHeight = childView.getMeasuredHeight() * childCount;
setMeasuredDimension(widthSpaceSize, measuredHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);//因为这里的child都是同类,所以偷懒~取第一个测量尺寸就够啦
measuredWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measuredWidth, heightSpaceSize);
} else {
setMeasuredDimension(widthSpaceSize, heightSpaceSize);
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
public HorizontalScrollView(Context context) {
super(context);
init();
}
public HorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
}
总结
对于第一种,思路是在ACTION_MOVE
的时候,判断x偏移量是否大于y的偏移量.如果大于,就page++,.
对于第二种,需要判断子view是否滑动到了顶部,或者底部,如果是,让父控件滑动即可.
对于第三种,需要结合第一种,和第二种进行判断.
在判断滑动冲突的过程中,重点放在 InterceptTouchEvent()中,还有TouchEvent种进行处理.