Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 淺談Android View滑動沖突的解決方法

淺談Android View滑動沖突的解決方法

編輯:關於Android編程

引言

這一篇文章我們就通過介紹滑動沖突的規則和一個實例來更加深入的學習View的事件分發機制。

1、外部滑動方向和內部滑動方向不一致

考慮這樣一種場景,開發中我們經常使用ViewPager和Fragment配合使用所組成的頁面滑動效果,很多主流的應用都會使用這樣的效果。在這種效果中,可以使用左右滑動來切換界面,而每一個界面裡面往往又都是ListView這樣的控件。本來這種情況是存在滑動沖突的,只是ViewPager內部處理了這種滑動沖突。如果我們不使用ViewPager而是使用ScrollView,那麼滑動沖突就需要我們自己來處理,否者造成的後果就是內外兩層只有一層能滑動。

情況1的解決思路

對於第一種情況的解決思路是這樣的:當用戶左右滑動時,需要讓外層的View攔截點擊事件。當用戶上下滑動時,需要讓內部的View攔截點擊事件(外層的View不攔截點擊事件),這時候我們就可以根據它們的特性來解決滑動沖突。在這裡我們可以根據滑動時水平滑動還是垂直滑動來判斷誰來攔截點擊事件。下面先介紹一種通用的解決滑動沖突的方法。

外部攔截法

外部攔截法是指:點擊事件都經過父容器的攔截處理,如果父容器需要處理此事件就進行攔截,否者不攔截交給子View進行處理。這種方法比較符合點擊事件的分發機制。外部攔截法需要重寫父容器的onInterceptTouchEvent方法,在內部做相應的攔截即可。這種方法的偽代碼如下:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  int x=(int)ev.getX();
  int y=(int)ev.getY();
  boolean intercept=false;
  switch (ev.getAction()){
    //按下事件不要攔截,否則後續事件都會給ViewGroup處理
    case MotionEvent.ACTION_DOWN:
      intercept=false;
      break;
    case MotionEvent.ACTION_MOVE:
      //如果是橫向移動就進行攔截,否則不攔截
      int deltaX=x-mLastX;
      int deltaY=y-mLastY;
      if(父容器需要當前點擊事件){
        intercept=true;
      }else {
        intercept=false;
      }
      break;
    case MotionEvent.ACTION_UP:
      intercept=false;
      break;
  }
  mLastX = x;
  mLastY = y;
  return intercept;
}

上面代碼是外部攔截法的典型邏輯,針對不同的滑動沖突,只需要修改父容器需要當前點擊事件的條件即可,其他均不需要修改。我們在描述下:在onInterceptTouchEvent方法中,首先是ACTION_DOWN事件,父容器必須返回false,即不攔截ACTION_DOWN事件,這是因為一旦父容器攔截ACTION_DOWN,那麼後續的ACTION_MOVE和ACTION_UP都會直接交給父容器處理,這時候事件就沒法傳遞給子元素了;其次是ACTION_MOVE事件,這個事件可以根據需要來決定是否需要攔截。

下面來看一個具體的實例,這個實現模擬ViewPager的效果,我們定義一個全新的控件,名稱叫HorizontalScrollView。具體代碼如下:

1、我們先看Activity中的代碼:

public class MainActivity extends Activity{

  private HorizontalScrollView mListContainer;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    initView();
  }

  private void initView() {
    LayoutInflater inflater = getLayoutInflater();
    mListContainer = (HorizontalScrollView) findViewById(R.id.container);
    final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
    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<>();
    for (int i = 0; i < 50; i++) {
      datas.add("name " + i);
    }

    ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.content_list_item, R.id.name, datas);
    listView.setAdapter(adapter);
  }
}

在這個代碼中,我們創建了3個ListView然後將其添加到我們自定義控件的。這裡HorizontalScrollView是父容器,ListView是子View。下面我們就使用外部攔截法來實現HorizontalScrollView,代碼如下:

/**
 * 橫向布局控件
 * 模擬經典滑動沖突
 * 我們此處使用ScrollView來模擬ViewPager,那麼必須手動處理滑動沖突,否則內外兩層只能有一層滑動,那就是滑動沖突。另外內部左右滑動,外部上下滑動也同樣屬於該類
 */
public class HorizontalScrollView extends ViewGroup {

  //記錄上次滑動的坐標
  private int mLastX = 0;
  private int mLastY = 0;
  private WindowManager wm;
  //子View的個數
  private int mChildCount;
  private int mScreenWidth;
  //自定義控件橫向寬度
  private int mMeasureWidth;
  //滑動加載下一個界面的阈值
  private int mCrital;
  //滑動輔助類
  private Scroller mScroller;
  //當前展示的子View的索引
  private int showViewIndex;

  public HorizontalScrollView(Context context){
    this(context,null);
  }

  public HorizontalScrollView(Context context, AttributeSet attributeSet){
    super(context,attributeSet);
    init(context);
  }

  /**
   * 初始化
   * @param context
   */
  public void init(Context context) {
    //讀取屏幕相關的長寬
    wm = ((Activity)context).getWindowManager();
    mScreenWidth = wm.getDefaultDisplay().getWidth();
    mCrital=mScreenWidth/4;
    mScroller=new Scroller(context);
    showViewIndex=1;
  }

  /**
   * 重新事件攔截機制
   * 我們分析了view的事件分發,我們知道點擊事件的分發順序是 通過父布局分發,如果父布局沒有攔截,即onInterceptTouchEvent返回false,
   * 才會傳遞給子View。所以我們就可以利用onInterceptTouchEvent()這個方法來進行事件的攔截。來看一下代碼
   * 此處使用外部攔截法
   * @param ev
   * @return
   */
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    int x=(int)ev.getX();
    int y=(int)ev.getY();
    boolean intercept=false;
    switch (ev.getAction()){
      //按下事件不要攔截,否則後續事件都會給ViewGroup處理
      case MotionEvent.ACTION_DOWN:
        intercept=false;
        if(!mScroller.isFinished()){
          mScroller.abortAnimation();
          intercept=true;
        }
        break;
      case MotionEvent.ACTION_MOVE:
        //如果是橫向移動就進行攔截,否則不攔截
        int deltaX=x-mLastX;
        int deltaY=y-mLastY;
        if(Math.abs(deltaX)>Math.abs(deltaY)){
          intercept=true;
        }else {
          intercept=false;
        }
        break;
      case MotionEvent.ACTION_UP:
        intercept=false;
        break;
    }
    mLastX = x;
    mLastY = y;
    return intercept;
  }


  @Override
  public boolean onTouchEvent(MotionEvent 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;
        /**
         * scrollX是指ViewGroup的左側邊框和當前內容左側邊框之間的距離
         */
        int scrollX=getScrollX();
        if(scrollX-deltaX>0
            && (scrollX-deltaX)<=(mMeasureWidth-mScreenWidth)) {
          scrollBy(-deltaX, 0);
        }
        break;
      case MotionEvent.ACTION_UP:
        scrollX=getScrollX();
        int dx;
        //計算滑動的差值,如果超過1/4就滑動到下一頁
        int subScrollX=scrollX-((showViewIndex-1)*mScreenWidth);
        if(Math.abs(subScrollX)>=mCrital){
          boolean next=scrollX>(showViewIndex-1)*mScreenWidth;
          if(showViewIndex<3 && next) {
            showViewIndex++;
          }else {
            showViewIndex--;
          }
        }
        dx=(showViewIndex - 1) * mScreenWidth - scrollX;
        smoothScrollByDx(dx);
        break;
    }
    mLastX = x;
    mLastY = y;
    return true;
  }


  /**
   * 緩慢滾動到指定位置
   * @param dx
   */
  private void smoothScrollByDx(int dx) {
    //在1000毫秒內滑動dx距離,效果就是慢慢滑動
    mScroller.startScroll(getScrollX(), 0, dx, 0, 1000);
    invalidate();
  }

  @Override
  public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
      postInvalidate();
    }
  }
}

從上面代碼中,我們看到我們只是很簡單的采用橫向滑動距離和垂直滑動距離進行比較來判斷滑動方向。在滑動過程中,當水平方向的距離大時就判斷為水平滑動,否者就是垂直滑動。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved