Android: implement gesture container

This commit is contained in:
王劲鹏 2021-09-22 10:28:34 +08:00 committed by osborn
parent ac6a027f02
commit 50d9377960
3 changed files with 344 additions and 0 deletions

View File

@ -42,6 +42,7 @@ import pub.doric.plugin.StatusBarPlugin;
import pub.doric.plugin.StoragePlugin; import pub.doric.plugin.StoragePlugin;
import pub.doric.refresh.RefreshableNode; import pub.doric.refresh.RefreshableNode;
import pub.doric.shader.DraggableNode; import pub.doric.shader.DraggableNode;
import pub.doric.shader.GestureContainerNode;
import pub.doric.shader.HLayoutNode; import pub.doric.shader.HLayoutNode;
import pub.doric.shader.ImageNode; import pub.doric.shader.ImageNode;
import pub.doric.shader.InputNode; import pub.doric.shader.InputNode;
@ -121,6 +122,7 @@ public class DoricRegistry {
this.registerViewNode(DraggableNode.class); this.registerViewNode(DraggableNode.class);
this.registerViewNode(SwitchNode.class); this.registerViewNode(SwitchNode.class);
this.registerViewNode(FlexNode.class); this.registerViewNode(FlexNode.class);
this.registerViewNode(GestureContainerNode.class);
initRegistry(this); initRegistry(this);
doricJSEngine.setEnvironmentValue(DoricSingleton.getInstance().envMap); doricJSEngine.setEnvironmentValue(DoricSingleton.getInstance().envMap);
DoricSingleton.getInstance().registries.add(new WeakReference<>(this)); DoricSingleton.getInstance().registries.add(new WeakReference<>(this));

View File

@ -0,0 +1,270 @@
/*
* Copyright [2021] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pub.doric.shader;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.FrameLayout;
import com.github.pengfeizhou.jscore.JSValue;
import pub.doric.DoricContext;
import pub.doric.extension.bridge.DoricPlugin;
import pub.doric.utils.DoricUtils;
/**
* @Description: pub.doric.shader
* @Author: jingpeng.wang
* @CreateDate: 2021-09-16
*/
@DoricPlugin(name = "GestureContainer")
public class GestureContainerNode extends StackNode {
private enum SwipeOrientation {
LEFT(0), RIGHT(1), TOP(2), BOTTOM(3);
private final int value;
SwipeOrientation(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
private String onSingleTap;
private String onDoubleTap;
private String onLongPress;
private String onPinch;
private String onPan;
private String onRotate;
private String onSwipe;
public GestureContainerNode(DoricContext doricContext) {
super(doricContext);
}
@Override
protected FrameLayout build() {
return new GestureContainerView(getContext());
}
@Override
protected void blend(FrameLayout view, String name, JSValue prop) {
if ("onSingleTap".equals(name)) {
if (prop.isString()) {
onSingleTap = prop.asString().value();
} else {
onSingleTap = null;
}
} else if ("onDoubleTap".equals(name)) {
if (prop.isString()) {
onDoubleTap = prop.asString().value();
} else {
onDoubleTap = null;
}
} else if ("onLongPress".equals(name)) {
if (prop.isString()) {
onLongPress = prop.asString().value();
} else {
onLongPress = null;
}
} else if ("onPinch".equals(name)) {
if (prop.isString()) {
onPinch = prop.asString().value();
} else {
onPinch = null;
}
} else if ("onPan".equals(name)) {
if (prop.isString()) {
onPan = prop.asString().value();
} else {
onPan = null;
}
} else if ("onRotate".equals(name)) {
if (prop.isString()) {
onRotate = prop.asString().value();
} else {
onRotate = null;
}
} else if ("onSwipe".equals(name)) {
if (prop.isString()) {
onSwipe = prop.asString().value();
} else {
onSwipe = null;
}
} else {
super.blend(view, name, prop);
}
}
private class GestureContainerView extends FrameLayout {
private final GestureDetector gestureDetector;
private final ScaleGestureDetector scaleGestureDetector;
private final RotateGestureDetector rotateGestureDetector;
public GestureContainerView(Context context) {
super(context);
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
private static final int SWIPE_THRESHOLD = 100;
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (onSingleTap != null)
callJSResponse(onSingleTap);
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (onDoubleTap != null)
callJSResponse(onDoubleTap);
return super.onDoubleTap(e);
}
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
if (onLongPress != null)
callJSResponse(onLongPress);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (scaleGestureDetector.isInProgress()) {
// don't allow scrolling while scaling
return false;
}
// handle scrolling
if (onPan != null)
callJSResponse(onPan, DoricUtils.px2dp(distanceX), DoricUtils.px2dp(distanceY));
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
boolean result = false;
try {
float diffY = e2.getY() - e1.getY();
float diffX = e2.getX() - e1.getX();
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
onSwipeRight();
} else {
onSwipeLeft();
}
result = true;
}
} else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeBottom();
} else {
onSwipeTop();
}
result = true;
}
} catch (Exception exception) {
exception.printStackTrace();
}
return result;
}
});
scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
if (onPinch != null)
callJSResponse(onPinch, scaleGestureDetector.getScaleFactor());
return false;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
}
});
rotateGestureDetector = new RotateGestureDetector(context, new RotateGestureDetector.OnRotateGestureListener() {
@Override
public boolean onRotate(float degrees, float focusX, float focusY) {
if (onRotate != null)
callJSResponse(onRotate, degrees * Math.PI / 180f);
return false;
}
});
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
// handle touch event conflict when in a scroll view or other similar containers
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
// handle gesture
boolean scaleRet = scaleGestureDetector.onTouchEvent(event);
boolean rotateRet = rotateGestureDetector.onTouchEvent(event);
boolean commonRet = gestureDetector.onTouchEvent(event);
return (scaleRet || rotateRet || commonRet) || super.onTouchEvent(event);
}
private void onSwipeLeft() {
if (onSwipe != null)
callJSResponse(onSwipe, SwipeOrientation.LEFT.value);
}
private void onSwipeRight() {
if (onSwipe != null)
callJSResponse(onSwipe, SwipeOrientation.RIGHT.value);
}
private void onSwipeTop() {
if (onSwipe != null)
callJSResponse(onSwipe, SwipeOrientation.TOP.value);
}
private void onSwipeBottom() {
if (onSwipe != null)
callJSResponse(onSwipe, SwipeOrientation.BOTTOM.value);
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright [2021] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pub.doric.shader;
import android.content.Context;
import android.view.MotionEvent;
/**
* @Description: pub.doric.shader
* @Author: jingpeng.wang
* @CreateDate: 2021-09-16
*/
public class RotateGestureDetector {
public interface OnRotateGestureListener {
boolean onRotate(float degrees, float focusX, float focusY);
}
public static class SimpleOnRotateGestureDetector implements OnRotateGestureListener {
@Override
public boolean onRotate(float degrees, float focusX, float focusY) {
return false;
}
}
private static float RADIAN_TO_DEGREES = (float) (180.0 / Math.PI);
private OnRotateGestureListener listener;
private float prevX = 0.0f;
private float prevY = 0.0f;
private float prevTan;
public RotateGestureDetector(Context context, OnRotateGestureListener listener) {
this.listener = listener;
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getPointerCount() == 2 && event.getActionMasked() == MotionEvent.ACTION_MOVE) {
boolean result = true;
float x = event.getX(1) - event.getX(0);
float y = event.getY(1) - event.getY(0);
float focusX = (event.getX(1) + event.getX(0)) * 0.5f;
float focusY = (event.getY(1) + event.getY(0)) * 0.5f;
float tan = (float) Math.atan2(y, x);
if (prevX != 0.0f && prevY != 0.0f) {
result = listener.onRotate((tan - prevTan) * RADIAN_TO_DEGREES, focusX, focusY);
}
prevX = x;
prevY = y;
prevTan = tan;
return result;
} else {
prevX = prevY = prevTan = 0.0f;
return true;
}
}
}