Android中View的滑動衝突——Android開發藝術探索筆記
阿新 • • 發佈:2019-02-06
介紹
相信開發Android的人都會有這種體會:從網上下載的demo執行的好好的,但是隻要出現了滑動衝突,Demo就無法正常工作了。但是不用擔心,解決滑動衝突有固定的模式,常見的有內部攔截和外部攔截兩種,只要按照這個模式來就可以順利解決。本文會涉及到View事件分發的相關知識,關於事件分發請參考http://blog.csdn.net/l664675249/article/details/50738102
常見的滑動衝突場景
示例
處理規則
對於場景1,處理規則為:當用戶左右滑動時,讓外部的View攔截點選事件,當用戶上下滑動時,讓內部的View攔截點選事件。當產生滑動時,根據滑動的起始點與終點座標位置,如果垂直方向滑動距離大,就判斷為垂直滑動,否則判斷為水平滑動。其他兩種情況處理方法相似,都是從業務需求上得出相應的規則。
解決方法
外部攔截髮
所有的點選事件都先經過父容器攔截處理,如果父容器需要攔截就攔截,不需要就傳給內部的View。虛擬碼如下
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break ;
}
case MotionEvent.ACTION_MOVE: {
if (滿足父容器的攔截要求) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false ;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
注:
ACTION_DOWN這個事件是不能攔截的,因為一旦攔截後續的事件都會由父容器處理了。
內部攔截法
父容器不攔截任何事件,所有事件都傳給子元素。如果子元素需要此事件就直接消耗,否則就交給父容器進行處理。完成這個功能需要配合requestDisallowInterceptTouchEvent()方法才可。這個方法表示是否讓父容器攔截事件。虛擬碼如下:
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
if (滿足父容器的攔截要求) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
注
父容器預設攔截除了ACTION_DOWN以外的其他事件,這樣子當元素呼叫parent.requestDisallowInterceptTouchEvent(false)時,父元素才能攔截所需的事件。
總結
- 解決滑動衝突有兩種方法,推薦外部攔截法,實現起來簡單。
- 本文以場景1為例做了講解,場景2,3的做法與1類似,都是根據業務需要制定處理規則。
示例效果的原始碼
Activity
public class DemoActivity_1 extends Activity {
private static final String TAG = "DemoActivity_1";
private HorizontalScrollViewEx mListContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.demo_1);
Log.d(TAG, "onCreate");
initView();
}
private void initView() {
LayoutInflater inflater = getLayoutInflater();
mListContainer = (HorizontalScrollViewEx) findViewById(R.id.container);
final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
final int screenHeight = MyUtils.getScreenMetrics(this).heightPixels;
for (int i = 0; i < 3; i++) {
ViewGroup layout = (ViewGroup) inflater.inflate(
R.layout.content_layout, mListContainer, false);
layout.getLayoutParams().width = screenWidth;
TextView textView = (TextView) layout.findViewById(R.id.title);
textView.setText("page " + (i + 1));
layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0));
createList(layout);
mListContainer.addView(layout);
}
}
private void createList(ViewGroup layout) {
ListView listView = (ListView) layout.findViewById(R.id.list);
ArrayList<String> datas = new ArrayList<String>();
for (int i = 0; i < 50; i++) {
datas.add("name " + i);
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.content_list_item, R.id.name, datas);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(DemoActivity_1.this, "click item",
Toast.LENGTH_SHORT).show();
}
});
}
}
水平滑動的View
public class HorizontalScrollViewEx extends ViewGroup {
private static final String TAG = "HorizontalScrollViewEx";
private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
// 分別記錄上次滑動的座標
private int mLastX = 0;
private int mLastY = 0;
// 分別記錄上次滑動的座標(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
public HorizontalScrollViewEx(Context context) {
super(context);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
Log.d(TAG, "onInterceptTouchEvent: ACTION_DOWN");
intercepted = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
Log.d(TAG, "onInterceptTouchEvent: ACTION_MOVE");
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
Log.d(TAG, "intercepted=" + intercepted);
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
@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: {
Log.d(TAG, "onTouchEvent: ACTION_DOWN");
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
}
case MotionEvent.ACTION_MOVE: {
Log.d(TAG, "onTouchEvent: ACTION_MOVE");
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "onTouchEvent: deltaX" + deltaX);
scrollBy(-deltaX, 0);
break;
}
case MotionEvent.ACTION_UP: {
int scrollX = getScrollX();
int scrollToChildIndex = scrollX / mChildWidth;
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
//滑的速度到達閾值就認為需要進入下一頁
if (Math.abs(xVelocity) >= 100) {
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
//滑動的距離超過一半,就進入下一頁
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
//保證在0頁和最後一頁滑動時不會越界
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
//沒有達到進入下一頁的要求,恢復原樣
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
Log.d(TAG, "onTouchEvent: dx = " + dx);
mVelocityTracker.clear();
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
@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) {
final View childView = getChildAt(0);
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measuredWidth, heightSpaceSize);
} else {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}