2

Using something similar to the answer of this question, I've tried to disable the ViewPager swipe action for when the user is swiping over a particular item. The view in question is a scrollable chart from the MPAndroidChart library, so naturally I don't want the view pager interfering with the scrolling of the chart.

The issue I am having is that the ViewPager will often have "onInterceptTouch" invoked before the onTouchListener is invoked on my desired view.

In this segment of code, I'm recording when the view is pressed/unpressed:

private long lastDown;

private long lastUp;

...

public void foo(){

    barChart.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {

        System.out.println("AAA");

        if(event.getAction() == MotionEvent.ACTION_DOWN){
          lastDown = System.currentTimeMillis();
        }else if(event.getAction() == MotionEvent.ACTION_UP){
          lastUp = System.currentTimeMillis();
        }

    return false;
      }
    });
}

In this segment of code, I determine if the view is selected:

  public boolean isGraphTouched(){
    return lastDown > lastUp;
  }

And in this segment of code I'm overriding the onInterceptTouchEvent method:

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    System.out.println("BBB");
    return isGraphSelected() ? super.onInterceptTouchEvent(ev) : false;
  }

And if you take note of the printlines, the onInterceptTouchEvent method is called before...

Println

The only way I can think of getting around this is to make a method which checks if the graph exists at the coordinates of the motion event (although I'm not sure if this is even possible) and then use that to determine if the pager will be swipable or not.

Philipp Jahoda
  • 50,880
  • 24
  • 180
  • 187
Top Cat
  • 350
  • 1
  • 7
  • 15

2 Answers2

5

I managed to make it work using the function parent.requestDisallowInterceptTouchEvent(true); being placed inside the child view onTouchEvent(). This way the View does not allow none of his parents to interecpt his touch events in case a scroll happened and was to be handled by the ViewPager.

However in my case I had a ViewPager with a draggable custom views inside it which I wanted to move without that the ViewPager changes page.

My solution in terms of code: (Kotlin)

view.setOnTouchListener { v, event ->
    parent.requestDisallowInterceptTouchEvent(true);
    //Drag and drop handling is here and the rest of the event logic
}

I hope this will help you as well.

MarounG
  • 164
  • 3
  • 8
0

I've managed to solve my problem by incorporating this answer from another post. The code allows you to get the coordinates of a given view and compare them to your own coordinates.

Inside of my CustomViewPager class, I implemented what I mentioned above:

  private boolean isPointInsideView(float x, float y, View view) {
    int location[] = new int[2];
    view.getLocationOnScreen(location);
    int viewX = location[0];
    int viewY = location[1];

    return ((x > viewX && x < (viewX + view.getWidth())) && (y > viewY && y < (viewY + view.getHeight())));
  }

I then had a method which returns a boolean, which checks a couple of conditions and returns true/false on if the pager should be able to be swiped:

  public boolean canSwipe(float x, float y) {
    boolean canSwipe = true;
    if (launchActivity.isReady(MainScreenPagerAdapter.STAT_PAGE)) {
      FragmentStatistics fragmentStatistics = (FragmentStatistics) launchActivity.getPageAdapter().instantiateItem(this, MainScreenPagerAdapter.STAT_PAGE);
      View chart = fragmentStatistics.getView().findViewById(R.id.chart);
      canSwipe = !isPointInsideView(x, y, chart) || !fragmentStatistics.isGraphPannable();
    }
    return canSwipe;
  }

And then, of course, I overwrote the onInterceptTouchEvent like so:

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    return canSwipe(ev.getX(), ev.getY()) ? super.onInterceptTouchEvent(ev) : false;
  }

And now, the graph can be fully panned without the Pager interfering with it at all.

Full CustomViewPager code:

public class CustomViewPager extends ViewPager {

  /** Reference to the launch activity */
  private LaunchActivity launchActivity;

  /**
   * Constructor to call the super constructor
   *
   * @param context The application context
   * @param attrs   The attributes
   */
  public CustomViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  /**
   * Sets object reference for the {@code launchActivity}
   *
   * @param launchActivity The LaunchActivity to be set
   */
  public void set(LaunchActivity launchActivity) {
    this.launchActivity = launchActivity;
  }

  /**
   * Determines if the pager can be swiped based off the x and y inputs provided, as well as if the
   * barchart can be panned or not.
   *
   * @param x The x coordinate to check
   * @param y The y coordinate to check
   * @return True if the ViewPager will continue with normal swiping action.
   */
  public boolean canSwipe(float x, float y) {
    boolean canSwipe = true;
    if (launchActivity.isReady(MainScreenPagerAdapter.STAT_PAGE)) {
      FragmentStatistics fragmentStatistics = (FragmentStatistics) launchActivity.getPageAdapter().instantiateItem(this, MainScreenPagerAdapter.STAT_PAGE);
      View chart = fragmentStatistics.getView().findViewById(R.id.chart);
      canSwipe = !isPointInsideView(x, y, chart) || !fragmentStatistics.isGraphPannable();
    }
    return canSwipe;
  }

  /**
   * Takes x and y coordinates and compares them to the coordinates of the passed view. Returns true if the passed coordinates
   * are within the range of the {@code view}
   *
   * @param x    The x coordinate to compare
   * @param y    The y coordinate to compare
   * @param view The view to check the coordinates of
   * @return True if the x and y coordinates match that of the view
   */
  private boolean isPointInsideView(float x, float y, View view) {
    int location[] = new int[2];
    view.getLocationOnScreen(location);
    int viewX = location[0];
    int viewY = location[1];

    // point is inside view bounds
    return ((x > viewX && x < (viewX + view.getWidth())) && (y > viewY && y < (viewY + view.getHeight())));
  }

  /**
   * Override of the onInterceptTouchEvent which allows swiping to be disabled when chart is selected
   *
   * @param ev The MotionEvent object
   * @return Call to super if true, otherwise returns false
   */
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    return canSwipe(ev.getX(), ev.getY()) ? super.onInterceptTouchEvent(ev) : false;
  }

}
Community
  • 1
  • 1
Top Cat
  • 350
  • 1
  • 7
  • 15
  • 1
    Too complex of a solution for a problem that's already solved in the Android SDK itself. Check how SeekBar goes around this issue, Unbugiful's answer is how it should be done – Ihor Klimov Sep 11 '19 at 04:53
  • 1
    @IhorKlimov This was years ago mate, but I've updated the accepted answer anyway. – Top Cat Sep 13 '19 at 11:29