[Android]通过setOnTouchListener实现移动View功能 [Android]通过setOnTouchListener实现移动View功能

[Android]通过setOnTouchListener实现移动View功能

需求

有时需要移动View,以前写的代码不太好,就上网上找,也不好用,和我自己写的都有的问题是View 会比光标的位置靠右下方。以前都忍了,偏就偏吧,现在决心要改,也可能是因为在这个特殊时期比较闲吧。

实现

正如上面所说,靠右下方,而且相比于靠右,靠下的程度更大。这很难不让人想到是状态栏造成的影响,而且单单是状态栏都还不够,还有一个ToolBar,所以要点在于如何去除这个高度。


    /**
     * Returns the original raw Y coordinate of this event.  For touch
     * events on the screen, this is the original location of the event
     * on the screen, before it had been adjusted for the containing   window
     * and views.
     *
     * @see #getY(int)
     * @see #AXIS_Y
     */
    public final float getRawY() {
        return nativeGetRawAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
    }

getRawX 或者getRawY 返回的都是相对于整个屏幕的,在测试时,如果不加限制,甚至可以滑动到状态栏,事件一直有效。

    /**
     * {@link #getY(int)} for the first pointer index (may be an
     * arbitrary pointer identifier).
     *
     * @see #AXIS_Y
     */
    public final float getY() {
        return nativeGetAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
    }

可是看这个getX 函数根本不明白说的什么意思,甚至它们的实现完全一样,可能是mNativePtr 或者HISTORY_CURRENT 有什么不同吧,不管了。到网上查,是相对这个我们设置触摸事件的View 的位置。

在每次设定位置时,需要的是设置当前的View 相对于父View 的座标,在每次移动时getRawY 的值需要减去getY 的值,得到的值就是当前view相对整个屏幕的位置,在减去当前view 相对于父窗体的位置,这样得到的值就是父窗体相对于整个屏幕的位置,最后得到的值是跟我们没有关系的,在获得当前view 需要的位置时在减去这个值。父窗体是不会动的,所以后者的这个位置可以在MotionEvent.ACTION_DOWN 时获取,同样的,getY 的值也需要在这个时候获取,因为光标相对于当前的View 的位置也是不能改变的。
像这样:

case MotionEvent.ACTION_DOWN:
    x = event.getX();
    y = event.getY();
    left = event.getRawX() - view.getLeft() - x;
    top = event.getRawY() - view.getTop() - y;
    return true;

当用户只是点击时,是不会有MotionEvent.ACTION_MOVE 事件的,所以在这个事件下记录当前用户是否进行的是移动操作,而不是在上面的。

case MotionEvent.ACTION_MOVE:
    if (!moved) {
        moved = true;
    }
    setXPosition(event.getRawX() - x - left);
    setYPosition(event.getRawY() - y - top);
    return true;

我们用光标相对于整个屏幕的位置减去父窗体的位置,减去光标相对于父窗体的位置,就是当前view 的左上角相对于父窗体的位置。

当用户擡手时,判断是否是移动了,如果移动了,那就不是点击事件,而且这个移动操作也结束了,恢复原样,如果不是,那就是点击事件,就调用performClick 函数。

case MotionEvent.ACTION_UP:
    if (moved) {
        moved = false;
    } else {
        v.performClick();
    }
    return true;

至于那个performClick

    /**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */

这样onClickListener 就能够正常运行了,而不是被我们阻止了。

这还没有完:

    /**
     * Entry point for {@link #performClick()} - other methods on View should call it instead of
     * {@code performClick()} directly to make sure the autofill manager is notified when
     * necessary (as subclasses could extend {@code performClick()} without calling the parent's
     * method).
     */
    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

他提醒我不让我直接调用performClick ,而是使用performClickInternal ,说是为了保证autofill manager 工作。可是这个函数是个私有函数,可能说的不是我的这种情况吧。

至于perfomClick 的返回值,感觉不重要就不管了。

最后

如果是悬浮窗,view.getTop() 返回的总是0,所以需要通过LayoutParams来获取这个top 的值,并且left不再需要。


    WindowManager.LayoutParams layoutParams= (WindowManager.LayoutParams) v.getLayoutParams();
    top = event.getRawY() -layoutParams.y- y;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章