问题的恢复:将布局上的触摸事件发送到他的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发出的事件)移动了一个。到目前为止,一切都在按预期工作。
在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%的解决方案。我们来形容它:
解决方案包括在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;
}
将手枪添加到手动滚动中并不容易。基本是添加一个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
注释中解释的另一个解决方案应该是将FrameEayout中的MotionEvent发送到他的WebView兄弟
mFrameLayout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouchEvent(MotionEvent ev) {
return mWebView.onTouchEvent(MotionEvent ev);
}
});
此解决方案的问题是滚动距离不与默认WebView onTouchEvent相同。它太慢了:滚动时的距离小于它应该的距离,并且发生了一些闪烁。
会采取任何建议或其他解决方案。
答案 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);
}
});