自定義View解決滑動衝突
最近在讀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種進行處理.