溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android應(yīng)用中的View出現(xiàn)滑動沖突如何解決

發(fā)布時(shí)間:2020-11-23 16:39:36 來源:億速云 閱讀:444 作者:Leah 欄目:移動開發(fā)

本篇文章給大家分享的是有關(guān)Android應(yīng)用中的View出現(xiàn)滑動沖突如何解決,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

1、外部滑動方向和內(nèi)部滑動方向不一致

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

情況1的解決思路

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

外部攔截法

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

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

上面代碼是外部攔截法的典型邏輯,針對不同的滑動沖突,只需要修改父容器需要當(dāng)前點(diǎn)擊事件的條件即可,其他均不需要修改。我們在描述下:在onInterceptTouchEvent方法中,首先是ACTION_DOWN事件,父容器必須返回false,即不攔截ACTION_DOWN事件,這是因?yàn)橐坏└溉萜鲾r截ACTION_DOWN,那么后續(xù)的ACTION_MOVE和ACTION_UP都會直接交給父容器處理,這時(shí)候事件就沒法傳遞給子元素了;其次是ACTION_MOVE事件,這個(gè)事件可以根據(jù)需要來決定是否需要攔截。

下面來看一個(gè)具體的實(shí)例,這個(gè)實(shí)現(xiàn)模擬ViewPager的效果,我們定義一個(gè)全新的控件,名稱叫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);
  }
}

在這個(gè)代碼中,我們創(chuàng)建了3個(gè)ListView然后將其添加到我們自定義控件的。這里HorizontalScrollView是父容器,ListView是子View。下面我們就使用外部攔截法來實(shí)現(xiàn)HorizontalScrollView,代碼如下:

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

  //記錄上次滑動的坐標(biāo)
  private int mLastX = 0;
  private int mLastY = 0;
  private WindowManager wm;
  //子View的個(gè)數(shù)
  private int mChildCount;
  private int mScreenWidth;
  //自定義控件橫向?qū)挾?
  private int mMeasureWidth;
  //滑動加載下一個(gè)界面的閾值
  private int mCrital;
  //滑動輔助類
  private Scroller mScroller;
  //當(dāng)前展示的子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) {
    //讀取屏幕相關(guān)的長寬
    wm = ((Activity)context).getWindowManager();
    mScreenWidth = wm.getDefaultDisplay().getWidth();
    mCrital=mScreenWidth/4;
    mScroller=new Scroller(context);
    showViewIndex=1;
  }

  /**
   * 重新事件攔截機(jī)制
   * 我們分析了view的事件分發(fā),我們知道點(diǎn)擊事件的分發(fā)順序是 通過父布局分發(fā),如果父布局沒有攔截,即onInterceptTouchEvent返回false,
   * 才會傳遞給子View。所以我們就可以利用onInterceptTouchEvent()這個(gè)方法來進(jìn)行事件的攔截。來看一下代碼
   * 此處使用外部攔截法
   * @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()){
      //按下事件不要攔截,否則后續(xù)事件都會給ViewGroup處理
      case MotionEvent.ACTION_DOWN:
        intercept=false;
        if(!mScroller.isFinished()){
          mScroller.abortAnimation();
          intercept=true;
        }
        break;
      case MotionEvent.ACTION_MOVE:
        //如果是橫向移動就進(jìn)行攔截,否則不攔截
        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的左側(cè)邊框和當(dāng)前內(nèi)容左側(cè)邊框之間的距離
         */
        int scrollX=getScrollX();
        if(scrollX-deltaX>0
            && (scrollX-deltaX)<=(mMeasureWidth-mScreenWidth)) {
          scrollBy(-deltaX, 0);
        }
        break;
      case MotionEvent.ACTION_UP:
        scrollX=getScrollX();
        int dx;
        //計(jì)算滑動的差值,如果超過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毫秒內(nèi)滑動dx距離,效果就是慢慢滑動
    mScroller.startScroll(getScrollX(), 0, dx, 0, 1000);
    invalidate();
  }

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

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

以上就是Android應(yīng)用中的View出現(xiàn)滑動沖突如何解決,小編相信有部分知識點(diǎn)可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI