<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                #### 3.5.3 滑動沖突的解決方式 在3.5.1節中描述了三種典型的滑動沖突場景,在本節將會一一分析各種場景并給出具體的解決方法。首先我們要分析第一種滑動沖突場景,這也是最簡單、最典型的一種滑動沖突,因為它的滑動規則比較簡單,不管多復雜的滑動沖突,它們之間的區別僅僅是滑動規則不同而已。拋開滑動規則不說,我們需要找到一種不依賴具體的滑動規則的通用的解決方法,在這里,我們就根據場景1的情況來得出通用的解決方案,然后場景2和場景3我們只需要修改有關滑動規則的邏輯即可。 上面說過,針對場景1中的滑動,我們可以根據滑動的距離差來進行判斷,這個距離差就是所謂的滑動規則。如果用ViewPager去實現場景1中的效果,我們不需要手動處理滑動沖突,因為ViewPager已經幫我們做了,但是這里為了更好地演示滑動沖突的解決思想,沒有采用ViewPager。其實在滑動過程中得到滑動的角度這個是相當簡單的,但是到底要怎么做才能將點擊事件交給合適的View去處理呢?這時就要用到3.4節所講述的事件分發機制了。針對滑動沖突,這里給出兩種解決滑動沖突的方式:外部攔截法和內部攔截法。 **1.外部攔截法** 所謂外部攔截法是指點擊事情都先經過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要此事件就不攔截,這樣就可以解決滑動沖突的問題,這種方法比較符合點擊事件的分發機制。外部攔截法需要重寫父容器的onInterceptTouchEvent方法,在內部做相應的攔截即可,這種方法的偽代碼如下所示。 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: { 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; } 上述代碼是外部攔截法的典型邏輯,針對不同的滑動沖突,只需要修改父容器需要當前點擊事件這個條件即可,其他均不需做修改并且也不能修改。這里對上述代碼再描述一下,在onInterceptTouchEvent方法中,首先是ACTION_DOWN這個事件,父容器必須返回false,即不攔截ACTION_DOWN事件,這是因為一旦父容器攔截了ACTION_DOWN,那么后續的ACTION_MOVE和ACTION_UP事件都會直接交由父容器處理,這個時候事件沒法再傳遞給子元素了;其次是ACTION_MOVE事件,這個事件可以根據需要來決定是否攔截,如果父容器需要攔截就返回true,否則返回false;最后是ACTION_UP事件,這里必須要返回false,因為ACTION_UP事件本身沒有太多意義。 考慮一種情況,假設事件交由子元素處理,如果父容器在ACTION_UP時返回了true,就會導致子元素無法接收到ACTION_UP事件,這個時候子元素中的onClick事件就無法觸發,但是父容器比較特殊,一旦它開始攔截任何一個事件,那么后續的事件都會交給它來處理,而ACTION_UP作為最后一個事件也必定可以傳遞給父容器,即便父容器的onInterceptTouchEvent方法在ACTION_UP時返回了false。 **2.內部攔截法** 內部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交由父容器進行處理,這種方法和Android中的事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起來較外部攔截法稍顯復雜。它的偽代碼如下,我們需要重寫子元素的dispatchTouchEvent方法: 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: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (父容器需要此類點擊事件)) { parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); } 上述代碼是內部攔截法的典型代碼,當面對不同的滑動策略時只需要修改里面的條件即可,其他不需要做改動而且也不能有改動。除了子元素需要做處理以外,父元素也要默認攔截除了ACTION_DOWN以外的其他事件,這樣當子元素調用parent.requestDisal-lowInterceptTouchEvent(false)方法時,父元素才能繼續攔截所需的事件。 為什么父容器不能攔截ACTION_DOWN事件呢?那是因為ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT這個標記位的控制,所以一旦父容器攔截ACTION_DOWN事件,那么所有的事件都無法傳遞到子元素中去,這樣內部攔截就無法起作用了。父元素所做的修改如下所示。 public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { return false; } else { return true; } } 下面通過一個實例來分別介紹這兩種方法。我們來實現一個類似于ViewPager中嵌套ListView的效果,為了制造滑動沖突,我們寫一個類似于ViewPager的控件即可,名字就叫HorizontalScrollViewEx,這個控件的具體實現思想會在第4章進行詳細介紹,這里只講述滑動沖突的部分。 為了實現ViewPager的效果,我們定義了一個類似于水平的LinearLayout的東西,只不過它可以水平滑動,初始化時我們在它的內部添加若干個ListView,這樣一來,由于它內部的Listview可以豎直滑動。而它本身又可以水平滑動,因此一個典型的滑動沖突場景就出現了,并且這種沖突屬于類型1的沖突。根據滑動策略,我們可以選擇水平和豎直的滑動距離差來解決滑動沖突。 首先來看一下Activity中的初始化代碼,如下所示。 public class DemoActivity_1 extends Activity { private static final String TAG = "SecondActivity"; 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).height- Pixels; 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); } } 上述初始化代碼很簡單,就是創建了3個ListView并且把ListView加入到我們自定義的HorizontalScrollViewEx中,這里HorizontalScrollViewEx是父容器,而ListView則是子元素,這里就不再多介紹了。 首先采用外部攔截法來解決這個問題,按照前面的分析,我們只需要修改父容器需要攔截事件的條件即可。對于本例來說,父容器的攔截條件就是滑動過程中水平距離差比豎直距離差大,在這種情況下,父容器就攔截當前點擊事件,根據這一條件進行相應修改,修改后的HorizontalScrollViewEx的onInterceptTouchEvent方法如下所示。 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: { intercepted = false; if (! mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; } case MotionEvent.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); mLastXIntercept = x; mLastYIntercept = y; return intercepted; } 從上面的代碼來看,它和外部攔截法的偽代碼的差別很小,只是把父容器的攔截條件換成了具體的邏輯。在滑動過程中,當水平方向的距離大時就判斷為水平滑動,為了能夠水平滑動所以讓父容器攔截事件;而豎直距離大時父容器就不攔截事件,于是事件就傳遞給了ListView,所以ListView也能上下滑動,如此滑動沖突就解決了。至于mScroller. abortAnimation()這一句話主要是為了優化滑動體驗而加入的。 考慮一種情況,如果此時用戶正在水平滑動,但是在水平滑動停止之前如果用戶再迅速進行豎直滑動,就會導致界面在水平方向無法滑動到終點從而處于一種中間狀態。為了避免這種不好的體驗,當水平方向正在滑動時,下一個序列的點擊事件仍然交給父容器處理,這樣水平方向就不會停留在中間狀態了。 下面是HorizontalScrollViewEx的具體實現,只展示了和滑動沖突相關的代碼: 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; … 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: { intercepted = false; if (! mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; } case MotionEvent.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: { if (! mScroller.isFinished()) { mScroller.abortAnimation(); } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; 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) >= 50) { mChildIndex = xVelocity>0? mChildIndex -1 : mChildIndex + 1; } else { mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); int dx = mChildIndex * mChildWidth - scrollX; smoothScrollBy(dx, 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 public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } … } 如果采用內部攔截法也是可以的,按照前面對內部攔截法的分析,我們只需要修改ListView的dispatchTouchEvent方法中的父容器的攔截邏輯,同時讓父容器攔截ACTION_MOVE和ACTION_UP事件即可。為了重寫ListView的dispatchTouchEvent方法,我們必須自定義一個ListView,稱為ListViewEx,然后對內部攔截法的模板代碼進行修改,根據需要,ListViewEx的實現如下所示。 public class ListViewEx extends ListView { private static final String TAG = "ListViewEx"; private HorizontalScrollViewEx2 mHorizontalScrollViewEx2; // 分別記錄上次滑動的坐標 private int mLastX = 0; private int mLastY = 0; … @Override public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent (true); break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (Math.abs(deltaX) > Math.abs(deltaY)) { mHorizontalScrollViewEx2.requestDisallowInterceptTouch- Event(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); } } 除了上面對ListView所做的修改,我們還需要修改HorizontalScrollViewEx的onInte-rceptTouchEvent方法,修改后的類暫且叫HorizontalScrollViewEx2,其onInterceptTouchEvent方法如下所示。 public boolean onInterceptTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { mLastX = x; mLastY = y; if (! mScroller.isFinished()) { mScroller.abortAnimation(); return true; } return false; } else { return true; } } 上面的代碼就是內部攔截法的示例,其中mScroller.abortAnimation()這一句不是必須的,在當前這種情形下主要是為了優化滑動體驗。從實現上來看,內部攔截法的操作要稍微復雜一些,因此推薦采用外部攔截法來解決常見的滑動沖突。 前面說過,只要我們根據場景1的情況來得出通用的解決方案,那么對于場景2和場景3來說我們只需要修改相關滑動規則的邏輯即可,下面我們就來演示如何利用場景1得出的通用的解決方案來解決更復雜的滑動沖突。這里只詳細分析場景2中的滑動沖突,對于場景3中的疊加型滑動沖突,由于它可以拆解為單一的滑動沖突,所以其滑動沖突的解決思想和場景1、場景2中的單一滑動沖突的解決思想一致,只需要分別解決每層之間的滑動沖突即可,再加上本書的篇幅有限,這里就不對場景3進行詳細分析了。 對于場景2來說,它的解決方法和場景1一樣,只是滑動規則不同而已,在前面我們已經得出了通用的解決方案,因此這里我們只需要替換父容器的攔截規則即可。注意,這里不再演示如何通過內部攔截法來解決場景2中的滑動沖突,因為內部攔截法沒有外部攔截法簡單易用,所以推薦采用外部攔截法來解決常見的滑動沖突。 下面通過一個實際的例子來分析場景2,首先我們提供一個可以上下滑動的父容器,這里就叫StickyLayout,它看起來就像是可以上下滑動的豎直的LinearLayout,然后在它的內部分別放一個Header和一個ListView,這樣內外兩層都能上下滑動,于是就形成了場景2中的滑動沖突了。當然這個StickyLayout是有滑動規則的:當Header顯示時或者ListView滑動到頂部時,由StickyLayout攔截事件;當Header隱藏時,這要分情況,如果ListView已經滑動到頂部并且當前手勢是向下滑動的話,這個時候還是StickyLayout攔截事件,其他情況則由ListView攔截事件。這種滑動規則看起來有點復雜,為了解決它們之間的滑動沖突,我們還是需要重寫父容器StickyLayout的onInterceptTouchEvent方法,至于ListView則不用做任何修改,我們來看一下StickyLayout的具體實現,滑動沖突相關的主要代碼如下所示。 public class StickyLayout extends LinearLayout { private int mTouchSlop; // 分別記錄上次滑動的坐標 private int mLastX = 0; private int mLastY = 0; // 分別記錄上次滑動的坐標(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; … @Override public boolean onInterceptTouchEvent(MotionEvent event) { int intercepted = 0; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mLastXIntercept = x; mLastYIntercept = y; mLastX = x; mLastY = y; intercepted = 0; break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (mDisallowInterceptTouchEventOnHeader && y <= getHeader- Height()) { intercepted = 0; } else if (Math.abs(deltaY) <= Math.abs(deltaX)) { intercepted = 0; }else if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) { intercepted = 1; } else if (mGiveUpTouchEventListener ! = null) { if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) { intercepted = 1; } } break; } case MotionEvent.ACTION_UP: { intercepted = 0; mLastXIntercept = mLastYIntercept = 0; break; } default: break; } if (DEBUG) { Log.d(TAG, "intercepted=" + intercepted); } return intercepted ! = 0 && mIsSticky; } @Override public boolean onTouchEvent(MotionEvent event) { if (! mIsSticky) { return true; } int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (DEBUG) { Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" + deltaY + " mlastY=" + mLastY); } mHeaderHeight += deltaY; setHeaderHeight(mHeaderHeight); break; } case MotionEvent.ACTION_UP: { // 這里做了一下判斷,當松開手的時候,會自動向兩邊滑動,具體向哪邊滑,要看當 前所處的位置 int destHeight = 0; if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) { destHeight = 0; mStatus = STATUS_COLLAPSED; } else { destHeight = mOriginalHeaderHeight; mStatus = STATUS_EXPANDED; } // 慢慢滑向終點 this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500); break; } default: break; } mLastX = x; mLastY = y; return true; } … } 從上面的代碼來看,這個StickyLayout的實現有點復雜,在第4章會詳細介紹這個自定義View的實現思想,這里先有大概的印象即可。下面我們主要看它的onIntercept-TouchEvent方法中對ACTION_MOVE的處理,如下所示。 case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (mDisallowInterceptTouchEventOnHeader && y <= getHeaderHeight()) { intercepted = 0; } else if (Math.abs(deltaY) <= Math.abs(deltaX)) { intercepted = 0; } else if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) { intercepted = 1; } else if (mGiveUpTouchEventListener ! = null) { if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) { intercepted = 1; } } break; } 我們來分析上面這段代碼的邏輯,這里的父容器是StickyLayout,子元素是ListView。首先,當事件落在Header上面時父容器不會攔截事件;接著,如果豎直距離差小于水平距離差,那么父容器也不會攔截事件;然后,當Header是展開狀態并且向上滑動時父容器攔截事件。另外一種情況,當ListView滑動到頂部了并且向下滑動時,父容器也會攔截事件,經過這些層層判斷就可以達到我們想要的效果了。另外,giveUpTouchEvent是一個接口方法,由外部實現,在本例中主要是用來判斷ListView是否滑動到頂部,它的具體實現如下: public boolean giveUpTouchEvent(MotionEvent event) { if (expandableListView.getFirstVisiblePosition() == 0) { View view = expandableListView.getChildAt(0); if (view ! = null && view.getTop() >= 0) { return true; } } return false; } 上面這個例子比較復雜,需要讀者多多體會其中的寫法和思想。到這里滑動沖突的解決方法就介紹完畢了,至于場景3中的滑動沖突,利用本節所給出的通用的方法是可以輕松解決的,讀者可以自己練習一下。在第4章會介紹View的底層工作原理,并且會介紹如何寫出一個好的自定義View。同時,在本節中所提到的兩個自定義View:Horizontal-ScrollViewEx和StickyLayout將會在第4章中進行詳細的介紹,它們的完整源碼請查看本書所提供的示例代碼。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看