2

I am trying to get the ViewDragHelper to work with a vertical LinearLayout, - Container - which has

A ---- List View
B ---- horizontal linear layout 
C ---- Support Map Fragment

as children views.

A has layout_height="0dp". Pulling down on B, should proportionally increase the height of A, there by revealing the contents on the ListView, thereby automatically re positioning B and resizing C in the process.

The following is the code for the Container LinearLayout.

public class MyLinearLayout extends LinearLayout {

private LinearLayout headerLayout;
private ListView filtersListView;

private ViewDragHelper viewDragHelper;
private int MARGIN;

public MyLinearLayout (Context context) {
    super(context);
    init();
}

public MyLinearLayout (Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public MyLinearLayout (Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init() {
    viewDragHelper = ViewDragHelper.create(this, 1, new DragHelperCallback());
    float GESTURE_THRESHOLD_DP = 10.0f;
    float scale = getResources().getDisplayMetrics().density;

    MARGIN = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f);
}

@Override
protected void onFinishInflate() {
    Log.i("Places", "onFinishInflate");
    super.onFinishInflate();

    headerLayout = (LinearLayout) findViewById(R.id.header);
    filtersListView = (ListView) findViewById(R.id.filters_list);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.i("Places", "onInterceptTouchEvent");
    return viewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.i("Places", "onTouchEvent");
    viewDragHelper.processTouchEvent(event);

    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) event.getRawY());
    layoutParams.setMargins(MARGIN, MARGIN, MARGIN, MARGIN);
    filtersListView.setLayoutParams(layoutParams);

    return true;
}

private class DragHelperCallback extends ViewDragHelper.Callback {

    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        Log.i("Places", "tryCaptureView " + (child == headerLayout));
        return child == headerLayout;
    }

    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        Log.i("Places", "onViewPositionChanged " + changedView.getClass().getName());
        super.onViewPositionChanged(changedView, left, top, dx, dy);
    }

    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        Log.i("Places", "onViewReleased " + releasedChild.getClass().getName());
        super.onViewReleased(releasedChild, xvel, yvel);
    }
}
}

I have looked at the the following.

ViewDragHelper: how to use it?

Community
  • 1
  • 1
Nihal Kenkre
  • 21
  • 1
  • 3

1 Answers1

2

Your ViewDragHelper is a bit off. Specifically you're missing the clampViewPositionVertical() override, which is required to enable dragging of the view at all. Right now, your ViewDragHelper is actually doing no work. You are probably getting some motion because you are manipulating the LayoutParams directly for every onTouchEvent(), which will also cause some problems.

You likely want your callback code to look more like this:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return viewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    viewDragHelper.processTouchEvent(event);

    return true;
}

private class DragHelperCallback extends ViewDragHelper.Callback {

    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        //Never go below fully visible
        final int bottomBound = getHeight() - child.getHeight();
        //Never go above the top
        final int topBound = 0;

        return Math.max(Math.min(top, bottomBound), topBound);
    }

    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        //Capture touches to the header view
        return child == headerLayout;
    }

    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        if (dy == 0) return;

        //React to the position change of the header by modifying layout
        LinearLayout.LayoutParams layoutParams = (LayoutParams) filtersListView.getLayoutParams();
        layoutParams.height += dy;
        filtersListView.setLayoutParams(layoutParams);
    }
}

This sets the drag bounds of your "header view" to move freely between the top of the container view and the bottom (clamping it so it doesn't go off-screen). By itself, this will only move the header view. Moving your LayoutParams code into onViewPositionChanged() lets your top/bottom views react to the exact drag distance.

Two Warnings:

  1. Don't add margins to your LayoutParams. The way you are using them to constantly modify proportions will cause the dragging to jump. If you need to inset the ListView content, use padding or another nested container.
  2. Changing the layout of an entire view continuously like this can be very expensive (layout passes are not cheap, and you trigger one every time you modify LayoutParams). This may work for a very simple layout, but if your view gets more complex you will likely see performance suffer on older hardware.
devunwired
  • 62,780
  • 12
  • 127
  • 139
  • If we have to update the height of parent then what would be the best way to update it. Apart from setting layout params. Because offSetTopAndBottom it just translate the view cooridinates. If you could elaborate more on both warnings that would be of great help – Akshay Mukadam Mar 09 '20 at 14:38