Android - 子视图触摸位置不正确

时间:2016-10-28 08:10:47

标签: android ontouchevent viewdraghelper

我有RelativeLayout,其ViewDragHelper用于移动视图。我必须使用layout(boolean changed, int left, int top, int right, int bottom)来更新拖动时的视图大小,因为里面的视图需要调整大小。一切正常。现在里面的儿童观点都有他们的触摸位置都错了。按钮的工作方式就好像它们位于屏幕顶部而不是它们的位置。

private void changeDragViewPosition(int top, float verticalMovementFactor) {
    int right = calculateViewRightPosition(verticalMovementFactor);
    int left = right - mainViewLayoutParams.width;
    int bottom = top + mainViewLayoutParams.height;

    mainView.layout(left, top, right, bottom);
}

这是我用来移动视图的代码。 mainView本身具有正确的触摸位置,此视图中的子视图是问题所在。我有什么遗失的吗?

修改

以下是我正在使用的视图。

public class MinimizableView extends RelativeLayout {

private static final int DEFAULT_MINIMIZED_MARGIN = 2;
private static final int DEFAULT_SCALE_FACTOR = 2;
private static final int MIN_SLIDING_DISTANCE_ON_CLICK = 10;

private View mainView;
private View unmovableView;
private ArrayList<View> otherViews;
private ArrayList<Integer> initialPositions;

private LayoutParams mainViewLayoutParams;

private int verticalDragRange;
private int mainViewOriginalWidth;
private int mainViewOriginalHeight;
private float scaleFactor = DEFAULT_SCALE_FACTOR;
private float minimizedMargin;
private boolean firstLayoutPass = true;
private int mainViewInitialPosition;
private float lastTouchActionDownXPosition;

private ViewDragHelper viewDragHelper;

private MinimizableViewListener listener;

public interface MinimizableViewListener {
    void onMinimized();

    void onMaximized();

    void onClosed();
}

public MinimizableView(Context context) {
    super(context);

    init(context);
}

public MinimizableView(Context context, AttributeSet attrs) {
    super(context, attrs);

    init(context);
}

public MinimizableView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    init(context);
}

private void init(Context context) {
    minimizedMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MINIMIZED_MARGIN, getResources().getDisplayMetrics());

    ViewCompat.requestApplyInsets(this);
}

private ViewDragHelper.Callback viewDragHelperCallback = new ViewDragHelper.Callback() {

    private static final int MINIMUM_DX_FOR_HORIZONTAL_DRAG = 5;
    private static final int MINIMUM_DY_FOR_VERTICAL_DRAG = 15;
    private static final float X_MIN_VELOCITY = 1500;
    private static final float Y_MIN_VELOCITY = 1000;

    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        return child.equals(mainView);
    }

    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        if (!isMainViewAtBottom()) {
            float verticalMovementFactor = (top - mainViewInitialPosition) / (float) verticalDragRange;

            changeDragViewScale(verticalMovementFactor);
            changeDragViewPosition(top, verticalMovementFactor);
            changeSecondViewAlpha(verticalMovementFactor);
            changeSecondViewPosition(verticalMovementFactor);
            changeUnmovableViewAlpha(verticalMovementFactor);
        }
    }

    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        super.onViewReleased(releasedChild, xvel, yvel);

        if (isMainViewAtBottom() && !isViewAtRight(releasedChild)) {
            triggerOnReleaseActionsWhileHorizontalDrag(xvel);
        } else {
            triggerOnReleaseActionsWhileVerticalDrag(yvel);
        }
    }

    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        int newTop = verticalDragRange + mainViewInitialPosition;
        if (isMinimized() && Math.abs(dy) >= MINIMUM_DY_FOR_VERTICAL_DRAG || (!isMinimized() && !isMainViewAtBottom())) {
            final int topBound = getPaddingTop() + mainViewInitialPosition;
            final int bottomBound = verticalDragRange + mainViewInitialPosition;

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

    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        int newLeft = mainView.getLeft();
        if ((isMinimized() && Math.abs(dx) > MINIMUM_DX_FOR_HORIZONTAL_DRAG) || (isMainViewAtBottom() && !isViewAtRight(mainView))) {
            newLeft = left;
        }
        return newLeft;
    }

    private void triggerOnReleaseActionsWhileHorizontalDrag(float xvel) {
        if (xvel < 0 && xvel <= -X_MIN_VELOCITY) {
            closeToLeft();
        } else if (xvel > 0 && xvel >= X_MIN_VELOCITY) {
            closeToRight();
        } else {
            if (isNextToLeftBound(mainView)) {
                closeToLeft();
            } else if (isNextToRightBound(mainView)) {
                closeToRight();
            } else {
                minimize();
            }
        }
    }

    private void triggerOnReleaseActionsWhileVerticalDrag(float xvel) {
        if (xvel < 0 && xvel <= -Y_MIN_VELOCITY) {
            maximize();
        } else if (xvel > 0 && xvel >= Y_MIN_VELOCITY) {
            minimize();
        } else {
            if (isDragViewAboveTheMiddle(mainView)) {
                maximize();
            } else {
                minimize();
            }
        }
    }
};

private void changeDragViewScale(float verticalMovementFactor) {
    mainViewLayoutParams.width = (int) (mainViewOriginalWidth * (1 - (verticalMovementFactor / scaleFactor)));
    mainViewLayoutParams.height = (int) (mainViewOriginalHeight * (1 - (verticalMovementFactor / scaleFactor)));

    mainView.setLayoutParams(mainViewLayoutParams);
}

private void changeDragViewPosition(int top, float verticalMovementFactor) {
    int right = calculateViewRightPosition(verticalMovementFactor);
    int left = right - mainViewLayoutParams.width;
    int bottom = top + mainViewLayoutParams.height;

    mainView.layout(left, top, right, bottom);
}

private void changeSecondViewAlpha(float verticalMovementFactor) {
    for (int i = 0; i < otherViews.size(); i++) {
        otherViews.get(i).setAlpha(1 - verticalMovementFactor);
    }
}

private void changeSecondViewPosition(float verticalMovementFactor) {
    int newTop;
    int initialTop;
    for (int i = 0; i < otherViews.size(); i++) {
        initialTop = initialPositions.get(i);
        newTop = (int) (initialTop + ((getBottom() - initialTop) * verticalMovementFactor));

        otherViews.get(i).setY(newTop);
    }
}

private void changeUnmovableViewAlpha(float verticalMovementFactor) {
    unmovableView.setAlpha(1 - verticalMovementFactor);
}

private int calculateViewRightPosition(float verticalMoveFactor) {
    return (int) (mainViewOriginalWidth - minimizedMargin * verticalMoveFactor);
}

private boolean isDragViewAboveTheMiddle(View view) {
    int parentHeight = getHeight();
    float viewYPosition = view.getY() + (view.getHeight() * 0.5f);

    return viewYPosition < (parentHeight * 0.5);
}

private boolean isMainViewAtTop() {
    return mainView.getTop() == mainViewInitialPosition;
}

private boolean isMainViewAtBottom() {
    return mainView.getBottom() >= getBottom() - getPaddingBottom() - minimizedMargin - 1;
}

private boolean isViewAtRight(View view) {
    return view.getRight() + minimizedMargin + 10 >= getWidth() - 10;
}

private void closeToLeft() {
    if (viewDragHelper.smoothSlideViewTo(mainView, -mainViewOriginalWidth, getHeight() - getMinHeightPlusMargin())) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
    if (listener != null) {
        listener.onClosed();
    }
}

private int getMinHeightPlusMargin() {
    return (int) (mainViewOriginalHeight * (1 - 1 / scaleFactor) + minimizedMargin);
}

private int getMinWidthPlusMargin() {
    return (int) (mainViewOriginalWidth * (1 - 1 / scaleFactor) + minimizedMargin);
}

private void closeToRight() {
    if (viewDragHelper.smoothSlideViewTo(mainView, mainViewOriginalWidth, getHeight() - getMinHeightPlusMargin())) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
    if (listener != null) {
        listener.onClosed();
    }
}

private boolean isNextToLeftBound(View view) {
    return (view.getLeft() - minimizedMargin) < getWidth() * 0.05;
}

private boolean isNextToRightBound(View view) {
    return (view.getLeft() - minimizedMargin) > getWidth() * 0.75;
}

private boolean isViewHit(View view, int x, int y) {
    int[] viewLocation = new int[2];
    view.getLocationOnScreen(viewLocation);
    int[] parentLocation = new int[2];
    this.getLocationOnScreen(parentLocation);
    int screenX = parentLocation[0] + x;
    int screenY = parentLocation[1] + y;
    return screenX >= viewLocation[0]
            && screenX < viewLocation[0] + view.getWidth()
            && screenY >= viewLocation[1]
            && screenY < viewLocation[1] + view.getHeight();
}

private static final int INVALID_POINTER = -1;

private int activePointerId;

@Override
public boolean onTouchEvent(MotionEvent event) {
    int actionMasked = MotionEventCompat.getActionMasked(event);
    if ((actionMasked & MotionEventCompat.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
        activePointerId = MotionEventCompat.getPointerId(event, actionMasked);
    }
    if (activePointerId == INVALID_POINTER) {
        return false;
    }
    viewDragHelper.processTouchEvent(event);
    if (isClosed()) {
        return false;
    }
    boolean isDragViewHit = isViewHit(mainView, (int) event.getX(), (int) event.getY());
    boolean isSecondViewHit = false;
    for (int i = 0; i < otherViews.size(); i++) {
        if (isViewHit(otherViews.get(i), (int) event.getX(), (int) event.getY())) {
            isSecondViewHit = true;
            break;
        }
    }
    analyzeTouchToMaximizeIfNeeded(event, isDragViewHit);
    if (isMaximized()) {
        mainView.dispatchTouchEvent(event);
    } else {
        mainView.dispatchTouchEvent(cloneMotionEventWithAction(event, MotionEvent.ACTION_CANCEL));
    }
    return isDragViewHit || isSecondViewHit;
}

private void analyzeTouchToMaximizeIfNeeded(MotionEvent ev, boolean isDragViewHit) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastTouchActionDownXPosition = ev.getX();
            break;
        case MotionEvent.ACTION_UP:
            float clickOffset = ev.getX() - lastTouchActionDownXPosition;
            if (shouldMaximizeOnClick(ev, clickOffset, isDragViewHit)) {
                if (isMinimized()) {
                    maximize();
                }
            }
            break;
        default:
            break;
    }
}

public boolean shouldMaximizeOnClick(MotionEvent ev, float deltaX, boolean isDragViewHit) {
    return (Math.abs(deltaX) < MIN_SLIDING_DISTANCE_ON_CLICK) && ev.getAction() != MotionEvent.ACTION_MOVE && isDragViewHit;
}

private MotionEvent cloneMotionEventWithAction(MotionEvent event, int action) {
    return MotionEvent.obtain(event.getDownTime(), event.getEventTime(), action, event.getX(), event.getY(), event.getMetaState());
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (!isEnabled()) {
        return false;
    }
    switch (MotionEventCompat.getActionMasked(ev) & MotionEventCompat.ACTION_MASK) {
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            viewDragHelper.cancel();
            return false;
        case MotionEvent.ACTION_DOWN:
            int index = MotionEventCompat.getActionIndex(ev);
            activePointerId = MotionEventCompat.getPointerId(ev, index);
            if (activePointerId == INVALID_POINTER) {
                return false;
            }
            break;
        default:
            break;
    }
    boolean interceptTap = viewDragHelper.isViewUnder(mainView, (int) ev.getX(), (int) ev.getY());
    return viewDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;
}

@Override
protected void onFinishInflate() {
    super.onFinishInflate();

    otherViews = new ArrayList<>();
    initialPositions = new ArrayList<>();

    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        if (child instanceof DragView) {
            mainView = child;
        } else if (child instanceof UnmovableView) {
            unmovableView = child;
        } else {
            otherViews.add(child);
        }
    }

    viewDragHelper = ViewDragHelper.create(this, 1, viewDragHelperCallback);

    mainViewLayoutParams = (LayoutParams) mainView.getLayoutParams();
}

@Override
public void computeScroll() {
    if (!isInEditMode() && viewDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    if (isInEditMode() || firstLayoutPass) {
        super.onLayout(changed, left, top, right, bottom);

        mainViewInitialPosition = mainView.getTop();
        verticalDragRange = getMeasuredHeight() - mainViewInitialPosition - getMinHeightPlusMargin() - getPaddingTop() - getPaddingBottom();
        for (int i = 0; i < otherViews.size(); i++) {
            initialPositions.add(otherViews.get(i).getTop());
        }

        firstLayoutPass = false;
    } else if (isMainViewAtTop()) {
        mainView.layout(left, mainViewInitialPosition, right, mainViewInitialPosition + mainViewOriginalHeight);

        changeUnmovableViewAlpha(0);

        setLayoutPositions(0, left, right, bottom, true);
    } else {
        setLayoutPositions(mainView.getTop() / (float) verticalDragRange, left, right, bottom, false);
    }
}

private void setLayoutPositions(float offsetFactor, int left, int right, int bottom, boolean setY) {
    int newTop;
    int initialTop;
    for (int i = 0; i < otherViews.size(); i++) {
        View view = otherViews.get(i);

        initialTop = initialPositions.get(i);
        newTop = (int) (initialTop + ((bottom - initialTop) * offsetFactor));

        view.layout(left, newTop, right, newTop + view.getHeight());
        if (setY) {
            view.setY(newTop);
        }
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    if (mainViewOriginalWidth == 0) {
        mainViewOriginalWidth = mainView.getMeasuredWidth();
        mainViewOriginalHeight = mainView.getMeasuredHeight();
    }
}

private boolean smoothSlideTo(float slideOffset) {
    final int topBound = mainViewInitialPosition + getPaddingTop();
    int x = (int) (slideOffset * (getWidth() - getMinWidthPlusMargin()));
    int y = (int) ((slideOffset * verticalDragRange) + topBound);
    if (viewDragHelper.smoothSlideViewTo(mainView, x, y)) {
        ViewCompat.postInvalidateOnAnimation(this);
        return true;
    }
    return false;
}

@Override
public void setPadding(int left, int top, int right, int bottom) {
    super.setPadding(left, top, right, bottom);

    mainViewInitialPosition = mainView.getTop();
    verticalDragRange = getMeasuredHeight() - mainViewInitialPosition - getMinHeightPlusMargin() - getPaddingTop() - getPaddingBottom();
    for (int i = 0; i < otherViews.size(); i++) {
        initialPositions.add(otherViews.get(i).getTop());
    }
}

// --------- PUBLIC METHODS ---------- //
public void maximize() {
    smoothSlideTo(0);
    if (listener != null) {
        listener.onMaximized();
    }
}

public void minimize() {
    smoothSlideTo(1);
    if (listener != null) {
        listener.onMinimized();
    }
}

public boolean isMinimized() {
    return isMainViewAtBottom() && isViewAtRight(mainView);
}

public boolean isMaximized() {
    return mainView.getTop() == mainViewInitialPosition;
}

public boolean isClosed() {
    return mainView.getRight() <= 0 && mainView.getLeft() >= getWidth();
}

public void setMinimizableViewListener(MinimizableViewListener listener) {
    this.listener = listener;
}

public void hide() {
    changeDragViewScale(1);
    changeDragViewPosition(verticalDragRange + mainViewInitialPosition - getMinHeightPlusMargin(), 1);
    minimize();
    setVisibility(GONE);
}

public void show() {
    setVisibility(VISIBLE);
    post(new Runnable() {
        @Override
        public void run() {
            maximize();
        }
    });
}

}

正如您所看到的,我已经覆盖了onTouch方法,它运行正常。我在mainView内也有一个按钮,它也是RelativeLayout

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lycatv.dragviewtest.MainActivity">

<android.support.v7.widget.Toolbar
    android:id="@+id/actionBar1"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary" />

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/actionBar1"
    android:fitsSystemWindows="true"
    android:text="Hello World!" />

<com.lycatv.dragviewtest.MinimizableView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="56dp">

    <com.lycatv.dragviewtest.UnmovableView
        android:id="@+id/actionBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimaryDark" />
    </com.lycatv.dragviewtest.UnmovableView>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/actionBar"
        android:background="#1a1e39" />

    <FrameLayout
        android:id="@+id/frameLayout1"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_below="@+id/actionBar" />

    <com.lycatv.dragviewtest.DragView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/frameLayout1">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:src="#000000" />

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="CheckBox"
            android:textColor="@android:color/white" />
    </com.lycatv.dragviewtest.DragView>
</com.lycatv.dragviewtest.MinimizableView>

按下CheckBox时,ARRAY=(one two three four five) 不起作用。相反,当你按下它时,它会起作用。无论从顶部偏移的是多少。

1 个答案:

答案 0 :(得分:1)

我不会假装理解你的代码所做的一切。但是,我确实注意到onLayout中的某些内容几乎肯定是错误的。您发布的代码是:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    if (isInEditMode() || firstLayoutPass) {
        super.onLayout(changed, left, top, right, bottom);

        mainViewInitialPosition = mainView.getTop();
        verticalDragRange = getMeasuredHeight() - mainViewInitialPosition - getMinHeightPlusMargin() - getPaddingTop() - getPaddingBottom();
        for (int i = 0; i < otherViews.size(); i++) {
            initialPositions.add(otherViews.get(i).getTop());
        }

        firstLayoutPass = false;
    } else if (isMainViewAtTop()) {
        mainView.layout(left, mainViewInitialPosition, right, mainViewInitialPosition + mainViewOriginalHeight);

        changeUnmovableViewAlpha(0);

        setLayoutPositions(0, left, right, bottom, true);
    } else {
        setLayoutPositions(mainView.getTop() / (float) verticalDragRange, left, right, bottom, false);
    }
}

此代码中几乎可以肯定的错误在于您使用layout方法:

mainView.layout(left, mainViewInitialPosition, right, mainViewInitialPosition + mainViewOriginalHeight);

您使用参数值leftright来布置mainView(以及setLayoutPositions的子视图)。 这是错误的。请注意,lefttoprightbottom是您父视图的相对位置({{1 }});不应使用直接来布局您的子视图,因为子视图相对于MinimizableView,而不是MinimizableView的父级。

例如,在布局MinimizableView时,您传递到mainView的值将被解释为相对于layout()。这意味着左偏移量应为0(加填充), MinimizableView。 Diddo for left。布局调用看起来应该更像:

right

请注意,上述内容不考虑填充。