能夠經過重寫getChildDrawingOrder方法去改變遍歷規則。java
滑動衝突的本質實際上是一個策略問題,在開發中咱們一般都是經過在子View中去調用requestDisallowInterceptTouchEvent方法配合父View中的onInterceptTouchEvent方法去使用。android
下邊給出一個例子:git
public class MyLayout extends LinearLayout{
public MyLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE: //表示父類須要攔截
return true;
default:
break;
}
return false; //若是設置攔截,除了down,其餘都是父類處理
}
}
複製代碼
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);//禁用父View的攔截方法。
break;
case MotionEvent.ACTION_MOVE:
if(知足條件){
getParent().requestDisallowInterceptTouchEvent(false);//解除父View的攔截的禁用。
}
}
return true;
}
}
複製代碼
在ViewGroup的dispatchTouchEvent中咱們能夠看到以下代碼:github
// 發生ACTION_DOWN事件或者已經發生過ACTION_DOWN,而且將mFirstTouchTarget賦值,才進入此區域,主要功能是攔截器
final boolean intercepted;
//onInterceptTouchEvent返回true後以後將再也不執行onInterceptTouchEvent方法,由於其將mFirstTouchTarget字段置爲了null。
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
//disallowIntercept:是否禁用事件攔截的功能(默認是false),即不由用
//能夠在子View經過調用requestDisallowInterceptTouchEvent方法對這個值進行修改,不讓該View攔截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//默認狀況下會進入該方法
if (!disallowIntercept) {
//調用攔截方法
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 當沒有觸摸targets,且不是down事件時,開始持續攔截觸摸。
intercepted = true;
}
複製代碼
獲取事件時需調用MotionEvent.getActionMasked()而不是MotionEvent.getAction(),只有MotionEvent.getActionMasked()能夠支持多點觸控。bash
常見值:ide
默認的event.getX()其實能夠理解爲 event.getX(0),這是針對於一根手指的狀況,再多點觸控的狀況下咱們須要經過調用event.getX(int index)來傳入參數以區別當前是第幾根手指在進行移動 (這裏的index是會變的,可是手指的ID是不會變的,咱們須要經過ID找到對應手指的index)。佈局
多點觸控通常寫法實例: github.com/rengwuxian/…動畫
大體可分爲下邊三個方法(只有layout方法是能夠真正改變View座標位置)ui
對View進行從新佈局定位。在onTouchEvent()方法中得到控件滑動先後的偏移。而後經過layout方法從新設置。this
// 視圖座標方式
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 記錄觸摸點座標
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 計算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 在當前left、top、right、bottom的基礎上加上偏移量
layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
// offsetLeftAndRight(offsetX);
// offsetTopAndBottom(offsetY);
break;
}
return true;
}
複製代碼
本質是View內容的移動,須要經過父容器的該方法來滑動當前View,Scroller: 平滑滑動,經過重載computeScroll(),使用scrollTo/scrollBy完成滑動效果,Scroller只是一個移動的機制,真正仍是須要調用去scrollTo/scrollBy去進行移動。
Scroll中與之相關的各類API中的參數都要跟實際咱們認知相反,好比想往自身右邊移動100不是去調用scrollerBy(100,0)而是調用scrollerBy(-100,0)。
public class ScrollButton extends android.support.v7.widget.AppCompatButton {
Scroller scroller;
int direction = -1;
public ScrollButton(Context context) {
this(context,null);
}
public ScrollButton(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public ScrollButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
scroller = new Scroller(context);
}
@Override
public void computeScroll() {
if(scroller!=null){
if(scroller.computeScrollOffset()){//判斷scroll是否完成
((View) getParent()).scrollTo(
scroller.getCurrX(),scroller.getCurrY()
);//執行本段位移
invalidate();//進行下段位移
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
scroller.startScroll(((int) getX()), ((int) getY()), ((int) getX())*direction,
((int) getY())*direction);//開始位移,真正開始是在下面的invalidate
direction*=-1;//改變方向
invalidate();//開始執行位移
break;
}
return super.onTouchEvent(event);
}
}
複製代碼
動畫對View進行滑動: setTranslationX,setTranslationY。
當手指按下時因爲scrollView中onInterceptTouchEvent沒對down事件進行攔截同時button的onTouchEvent是默認返回true的(clickable=true)那麼button首先會消耗down事件,當咱們手指移動時會觸發MOVE事件,這時ScrollView的攔截事件將進行攔截(onInterceptTouchEvent在MOVE時返回true)同時會發送 CANCLE事件給button(CANCLE的觸發時機是父View進行攔截後會發送給原先處理事件的子View通知它不要處理後續事件了),以後的MOVE和UP事件將直接交由ScrollView的onTouchEvent去處理同時其自身的onInterceptTouchEvent不會再被觸發(onInterceptTouchEvent返回true後將不被調用)。
不能響應。
當手指移動時在View的OnTouchEvent的MOVE事件中會不斷檢測當前手指是否在View區域內,若是出了View區域的話那麼會將mPressed這個標誌位置爲false,當手指擡起時在UP事件中若是mPressed爲false的話將不會觸發任何響應(必定要注意的是會觸發MOVE和UP事件,由於一個View在DOWN事件返回true後後續的事件序列都會交給其去處理,只不過在這種狀況下沒有任何響應效果)。
View的onTouchEvent:
public boolean onTouchEvent(MotionEvent event) {
....
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//若是mPrivateFlags爲false則prepressed爲false,將不會執行後續UP事件中的任何邏輯
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
....
}
break;
...
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
//判斷手指是否在View的區域中
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
//若是手指移出View區域將改變mPrivateFlags
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
複製代碼