从一个视图发送触摸事件到他的WebView兄弟

时间:2014-12-22 09:58:28

标签: android android-event

问题的恢复:将布局上的触摸事件发送到他的WebView兄弟,实现与默认WebView滚动相同的滚动(使用fling)

我在这个xml之后的WebView上有一个frameLayout:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">


    <com.app.ObservableWebView
        android:layout_width="match_parent" android:layout_height="match_parent"
        android:id="@+id/observableWebView">

    </com.app.ObservableWebView>


    <FrameLayout
        android:layout_width="match_parent" android:layout_height="200dp"
        android:id="@+id/frameLayout">

    </FrameLayout>

</RelativeLayout>

在应用程序开始时,一个空的html占位符放在WebView Dom的某个位置,他的位置由JavascriptInterface给出。此位置使用像素比率转换,FrameLayout位于frameLayout上方。 当WebView内容移动时,占位符使用FrameLayout(从Observable WebView发出的事件)移动了一个。到目前为止,一切都在按预期工作。

layout schema

在frameLayout中我需要点击一下它,所以我按照这一步设置了一个TouchListener:

private boolean mIsNativeClick = false;
private float mStartNativeX;
private float mStartNativeY;
private final float SCROLL_THRESHOLD = 10;

private void init(){
    mRootFrameLayout.setOnTouchListener(this);
}


@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            mStartNativeX = event.getX();
            mStartNativeY = event.getY();
            mIsNativeClick = true;
            return true;
        case MotionEvent.ACTION_MOVE:
            if (mIsNativeClick && (Math.abs(mStartNativeX - event.getX()) > SCROLL_THRESHOLD
                    || Math.abs(mStartNativeY - event.getY()) > SCROLL_THRESHOLD)) {
                mIsNativeClick = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            if (mIsNativeClick) {
                // my action on touch up goes here
                return true;
            }
    }

    return false;
}

触摸事件正确地传递给听众,一切正常。除了滚动FrameLayout时WebView没有滚动,因为它是frameLayout的兄弟:它不是父/子。

显而易见的解决方案是为View.onTouchEvent或侦听器设置correclty return true / false,以便Android将事件分派到三个Latout Tree中的下一个视图。但是因为我需要在FrameLayout中处理down / up事件,所以在MotionEvent.ACTION_DOWN停止调度下一个事件时开始返回true。

在上周搜索StackOverFlow时,我已经实现了50%的解决方案。我们来形容它:


步骤1:在FrameLayout移动事件

上进行WebView滚动

解决方案包括在FrameLayout上拦截事件并根据滚动移动/事件设置WebView的滚动位置。使用此解决方案的问题是滚动的 fling事件无法管理(fling:当用户滑动并从屏幕上移开手指时,它会产生惯性效果滚动继续移动一些ms)。步骤2中解释了添加fling。

这是代码段:( FrameLayout自定义视图的一部分)

private int movY;
private float mStartNativeX;
private float mStartNativeY;

@Override
public boolean onInterceptTouchEvent ( MotionEvent event ) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mStartNativeX = event.getX();
            mStartNativeY = event.getY();
            break;
        case MotionEvent.ACTION_SCROLL:
            Log.d(LOG_TAG, " scroll event");
            break;
    }
    return super.onInterceptTouchEvent(event);
}


@Override
public boolean onTouchEvent(MotionEvent event) {

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.d(LOG_TAG, "down event : " + Float.toString(mStartNativeY));
            break;
        case MotionEvent.ACTION_MOVE:
            movY = (int) ((int) mStartNativeY - event.getY());

            mWebView.scrollBy(0, movY);

            Log.d(LOG_TAG, "move event : " + Integer.toString(movY));
            return true;
        case MotionEvent.ACTION_UP:
            Log.d(LOG_TAG, "up event : ");
            break;
        default:
            break;
    }
    return true;
}

第2步:投掷

将手枪添加到手动滚动中并不容易。基本是添加一个GestureDetector和一个Scroller。所以我有一个用Scroller实现Runnable的类来管理fling效果。在GestureDetector Listener的onFling上调用此类。

private float mStartNativeY;
private GestureDetector mGestureDetector;
int movY;

@Override
public boolean onInterceptTouchEvent ( MotionEvent event ) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mStartNativeY = event.getY();
            break;
        case MotionEvent.ACTION_SCROLL:
            Log.d(LOG_TAG, " scroll event");
            break;
    }
    return super.onInterceptTouchEvent(event);
}

@Override
public boolean onTouchEvent(MotionEvent event) {

    mGestureDetector.onTouchEvent(event);

    // 1.) remember DOWN event ALWAYS as this is important start for every gesture
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.d(LOG_TAG, "down event : " + Float.toString(mStartNativeY));
            break;
        case MotionEvent.ACTION_MOVE:
            movY = (int) ((int) mStartNativeY - event.getY());
            mWebView.scrollBy(0, movY);
            Log.d(LOG_TAG, "move event : " + Integer.toString(movY));
            return true;
        case MotionEvent.ACTION_UP:
            Log.d(LOG_TAG, "up event : " + Integer.toString(mWebView.getScrollY()));
            return false;
        default:
            break;
    }
    return true;
}

// GestureDetector Listener

@Override
public void onLongPress(MotionEvent e) {

}

@Override
public boolean onDown(MotionEvent e) {
    Log.d(LOG_TAG, "onDown");
    return true; // else won't work

}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
                        float velocitX, float veloctiyY){

    return true;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

    new Flinger().start((int)velocityY);
    invalidate();

    return true;
}

@Override
public void onShowPress(MotionEvent e) {

}

@Override
public boolean onSingleTapUp(MotionEvent e) {
    return false;
}


private class Flinger implements Runnable {
    private final Scroller scroller;

    private int lastY = 0;

    Flinger() {
        scroller = new Scroller(getContext());
    }

    void start(int initialVelocity) {
        int initialY = mWebView.getScrollY();
        int maxY = Integer.MAX_VALUE; // or some appropriate max value in your code
        scroller.fling(0, initialY, 0, initialVelocity, 0, 10, 0, maxY);
        Log.i(LOG_TAG, "starting fling at " + initialY + ", velocity is " + initialVelocity + "");

        lastY = initialY;
        mWebView.post(this);
    }

    public void run() {
        if (scroller.isFinished()) {
            Log.i(LOG_TAG, "scroller is finished, done with fling");
            return;
        }

        boolean more = scroller.computeScrollOffset();
        int y = scroller.getCurrY();
        int diff = lastY - y;

        Log.d(LOG_TAG, "finger run : lasty : " + lastY +" y: " + y + " diff: "+Integer.toString(diff));

        if (diff != 0) {
            mWebView.scrollTo(0, scroller.getCurrY());
            lastY = y;
        }

        if (more) {
            mWebView.post(this);
        }
    }

    boolean isFlinging() {
        return !scroller.isFinished();
    }

    void forceFinished() {
        if (!scroller.isFinished()) {
            scroller.forceFinished(true);
        }
    }
}

问题:每次都应该投入使用。所以,如果我以1009的速度开始投掷,日志说:

starting fling at 3032, velocity is 1009
up event : 3032
finger run : lasty : 3032 y: 3047 diff: -15
finger run : lasty : 3047 y: 3063 diff: -16
finger run : lasty : 3063 y: 3078 diff: -15
finger run : lasty : 3078 y: 3090 diff: -12
finger run : lasty : 3090 y: 3102 diff: -12
finger run : lasty : 3102 y: 3106 diff: -4
finger run : lasty : 3106 y: 3110 diff: -4
finger run : lasty : 3110 y: 3113 diff: -3
finger run : lasty : 3113 y: 3116 diff: -3
finger run : lasty : 3116 y: 3118 diff: -2
finger run : lasty : 3118 y: 3119 diff: -1
finger run : lasty : 3119 y: 3120 diff: -1 

根据日志,投掷理论是有效但滚动的起点(-15)是不够的,它应该超过~100


编辑:替代解决方案:将调度事件发送到WebView

注释中解释的另一个解决方案应该是将FrameEayout中的MotionEvent发送到他的WebView兄弟

mFrameLayout.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return mWebView.onTouchEvent(MotionEvent ev);
    }
});

此解决方案的问题是滚动距离与默认WebView onTouchEvent相同。它太慢了:滚动时的距离小于它应该的距离,并且发生了一些闪烁


会采取任何建议或其他解决方案。

1 个答案:

答案 0 :(得分:2)

每个视图都有onTouchEvent方法,可以单独调用。

因此,为了将触摸事件传递给sibiling,我们只需要使用第一个视图的MotionEvent参数调用sibiling的onTouchEvent。

yourView.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //a sibiling MUST be final to be used in an OnTouchListener
        //if it's not, create a new final reference to it
        return sibiling.onTouchEvent(MotionEvent ev);
    }
});

更新:

根据评论中的对话,您希望同步不同维度的2个不同视图的滚动。具有不同的维度,仅仅将MotionEvent传递给webView是不够的。 But您可以使用此方法修改MotionEvent的X和Y坐标,然后再将其发送到sibiling:

ev.setLocation(float x, float y);

您需要根据Fragment和WebView的尺寸缩放片段的X和Y.我不知道什么类型的滚动因此缩放你精确需要,所以数学算法可能与我在下面写的不同(但这取决于你)。如果是这种情况,请考虑一下如何缩放MotionEvent

//you first need all dimensions
//if they're changing during runtime, you can do all this in OnTouchListener
final int fragWidth = fragment.getWidth();
final int fragHeight =  fragment.getHeight();
final int webWidith = webView.getWidth();
final int webHeight = webView.getHeight();

//then you calculate scale factors
final float scaleX = (float)webWidth / (float)fragWidth;
final float scaleY = (float)webHeight / (float)fragHeight;

//same old OnTouchListener
fragment.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        //calculate scaled coordinates
        float newX = ev.getX() * scaleX;
        float newY = ev.getY() * scaleY;

        //MODIFY the MotionEvent by setting the scaled coordinates
        ev.setLocation(newX, newY);

        //call WebView's onTouchEvent with the modified MotionEvent
        return webView.onTouchEvent(MotionEvent ev);
    }
});