I have an Osmdroid MapView. Even though I have set
mapView.setClickable(false);
mapView.setFocusable(false);
the map can still be moved around. Is there a simple way to disable all interactions with the map view?
A simple solution is to do like #Schrieveslaach but with the mapView:
mapView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
My solution is similar to #schrieveslaach and #sagix, but I just extend base MapView class and add new functionality:
class DisabledMapView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MapView(context, attrs) {
private var isUserInteractionEnabled = true
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
if (isUserInteractionEnabled.not()) {
return false
}
return super.dispatchTouchEvent(event)
}
fun setUserInteractionEnabled(isUserInteractionEnabled: Boolean) {
this.isUserInteractionEnabled = isUserInteractionEnabled
}
}
I've found a solution. You need to handle the touch events directly by setting a OnTouchListener. For example,
public class MapViewLayout extends RelativeLayout {
private MapView mapView;
/**
* #see #setDetachedMode(boolean)
*/
private boolean detachedMode;
// implement initialization of your layout...
private void setUpMapView() {
mapView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (detachedMode) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// if you want to fire another event
}
// Is detached mode is active all other touch handler
// should not be invoked, so just return true
return true;
}
return false;
}
});
}
/**
* Sets the detached mode. In detached mode no interactions will be passed to the map, the map
* will be static (no movement, no zooming, etc).
*
* #param detachedMode
*/
public void setDetachedMode(boolean detachedMode) {
this.detachedMode = detachedMode;
}
}
You could try:
mapView.setEnabled(false);
Which should disable all interactions with the map view
Related
I am trying to make a 3D app for Android using OpenGL ES 3.0 but I have an issue when going from one state to another (in a state machine). When switching state, the OpenGL context seems to be lost in computer memory. This only happens when I push a button that will trigger state switching.
I think it is due to thread creation when switching state in Android (even though in a desktop java application, I don't recall seeing such an issue). Correct me if I'm wrong.
The exact message that appears (in red) in the debugger is :
E/libEGL: call to OpenGL ES API with no current context (logged once
per thread)
I made it so that if I don't press any key, the state will be switched after 20 sec. In that case the message does not appear (i.e. no red message if the user is not the one triggering the switch). But after the automatic switch, if I press a button I get a (normally colored) message in the debugger that states the following :
D/CompatibilityChangeReporter: Compat change id reported: 147798919;
UID 10060; state: ENABLED
Not sure if it's an issue. All I know is that if a use a workaround to track which keys are pressed (array of boolean describing each key state, with each index of the element of the table being used to represent the keycode), the keys being pressed after the switch of state get stuck on pressed (values in the table for the keys that are pressed are true).
Apologies in advance for the (probably) long code that I have to post. I tried my best to strip away and test so that the issue remains as much as I could. That's also why I wrote the whole explaination in the beginning
code-listing 1: The GLSurfaceView
public class OpenGLView extends GLSurfaceView
{
private OpenGLRenderer renderer;
public OpenGLView(Context context)
{
super(context);
init(context);
}
public OpenGLView(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
private void init(Context context)
{
setEGLContextClientVersion(3);
setPreserveEGLContextOnPause(true);
renderer = new OpenGLRenderer();
setRenderer(renderer);
Display.getInstance().setContext(context);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
renderer.onKeyDown(keyCode, event);
return super.onKeyDown(keyCode, event);
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
renderer.onKeyUp(keyCode, event);
return super.onKeyUp(keyCode, event);
}
}
code-listing 2: the GLSurfaceView.Renderer
public class OpenGLRenderer implements GLSurfaceView.Renderer
{
private final StateMachine gameStateMachine = StateMachine.getInstance();
public void onSurfaceCreated (GL10 glUnused, EGLConfig config ) {/* empty */}
public void onSurfaceChanged ( GL10 glUnused, int width, int height )
{
gameStateMachine.pushState(new HomeState(gameStateMachine));
}
public void onDrawFrame ( GL10 glUnused )
{
gameStateMachine.update();
gameStateMachine.render();
}
public void onKeyDown(int keyCode, KeyEvent event)
{
gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyCode);
}
public void onKeyUp(int keyCode, KeyEvent event)
{
gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyCode);
}
}
code-listing 3: HomeState.java (first state in the game state machine)
public class HomeState extends State {
public HomeState(StateMachine gsm) { super(gsm); }
#Override
public void onEnter() {/* empty */}
#Override
public void onExit() {/* empty */}
#Override
public void handleEvent(EventType type, int keyCode)
{
if(type == EventType.KEY_PRESSED)
{
switch (keyCode)
{
case KeyEvent.KEYCODE_DPAD_LEFT:
gameStateMachine.pushState(new InGameState(gameStateMachine));
}
}
}
long sometime = SystemClock.uptimeMillis();
#Override
public void update()
{
Log.d("DEBUG", ""+(SystemClock.uptimeMillis() - sometime));
if(SystemClock.uptimeMillis() - sometime > 20000)
{
gameStateMachine.pushState(new InGameState(gameStateMachine));
}
}
#Override
public void render() {/* empty */}
}
code-listing 4: inGameState.java (state one is switching to)
public class InGameState extends State {
public InGameState(StateMachine gsm) { super(gsm); }
#Override
public void onEnter()
{
glCreateShader(GL_VERTEX_SHADER);
}
#Override
public void onExit() {/* empty */}
#Override
public void handleEvent(EventType type, int keyCode)
{
if(type == EventType.KEY_PRESSED)
{
Toast.makeText(Display.getInstance().getContext(), "key pressed in InGameState!", Toast.LENGTH_SHORT).show();
}
}
#Override
public void update() {/* empty */}
#Override
public void render() {/* empty */}
}
Note that when there is no OpenGL call in the second game state, the red message doesn't appear. But as soon as I make any OpenGL call in the second game state, the red message appears when the user triggers the switch.
Also note that, like I mentioned previously, the red message only appears when I directly check the keyCode/KeyEvent given by Android-Java. If I do something like the following code, the red message will not appear but key pressed will be stuck (through software).
code-listing 5: workaround GLSurfaceView.Renderer
public class OpenGLRenderer implements GLSurfaceView.Renderer
{
private final StateMachine gameStateMachine = StateMachine.getInstance();
private final boolean[] keyStates = new boolean[1024];
public void onSurfaceCreated (GL10 glUnused, EGLConfig config )
{
for(boolean b : keyStates)
{
b = false;
}
}
/* see code-listing 2 */
public void onDrawFrame ( GL10 glUnused )
{
gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyStates);
gameStateMachine.update();
gameStateMachine.render();
}
public void onKeyDown(int keyCode, KeyEvent event)
{
keyStates[keyCode] = true;
}
public void onKeyUp(int keyCode, KeyEvent event)
{
keyStates[keyCode] = false;
}
}
When you're using GLSurfaceView, all of your interactions with OpenGLES should be downstream from OpenGLRenderer's onDrawFrame, as that's called on the thread with the OpenGLES context bound.
onKeyDown is called from some other Android thread with no OpenGLES context. If I'm following your code correctly, then it eventually invokes InGameState's onEnter function which calls glCreateShader which fails because there's no OpenGLES context.
Your workaround seems like the right sort of idea. I would recommend that you have onKeyDown and onKeyUp append to a queue of input events, which can be processed in your onDrawFrame. I think that's a more robust approach than your array of boolean keyStates as you can process input in order, and easily extend the system to have touch events when you need to.
I have two views which are siblings of each other. They both cover the full screen. So one is behind the other. If the upper one gets touched (onTouch), I delegate the touch events to the one underneath it (with dispatchTouchEvent).
But sometimes I want to delay that delegation, till the next time onTouch gets called. But somehow that does not work.
An example to clarify:
To viewA - which is in front of viewB - I have applied the following (simplified) code:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
int touchType = event.getActionMasked();
if (touchType == MotionEvent.ACTION_DOWN) {
savedEvent = event;
return true; // I also tried returning false here
} else {
if (savedEvent != null) {
viewB.dispatchTouchEvent(savedEvent);
savedEvent = null;
}
viewB.dispatchTouchEvent(event);
return true; // I also tried returning false here
}
}
});
To test the dispatchTouchEvent call, I have the following code for viewB
viewB.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent event) {
Log.d("test", "test"); // this code gets logged, so it is being called, but the view seems to not execute any of the touch events
return true;
}
});
When I change to code for viewA to this:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
viewB.dispatchTouchEvent(event);
return true;
}
});
everything works just fine.
But the thing is that for my use case, I sometimes have to call the dispatchTouchEvent method with the event parameter outside its originating onTouch method, so to speak.
Is this even possible? And if yes, how?
So I found out what prevents it from working. If you manually call dispatchTouchEvent you should pass through an event that you created yourself with MotionEvent.obtain().
Working example:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
int touchType = event.getActionMasked();
if (touchType == MotionEvent.ACTION_DOWN) {
// do not do this, somehow passing on a reference of the event directly does not work:
// savedEvent = event;
// this works though:
savedEvent = MotionEvent.obtain(event);
return true;
} else {
if (savedEvent != null) {
viewB.dispatchTouchEvent(savedEvent);
savedEvent = null;
}
viewB.dispatchTouchEvent(event);
return true;
}
}
});
Although I don't understand why that does work and just passing the event reference directly does not, I tested this and it does work perfectly.
I have only a simple MyGestureOverlayView extends GestureOverlayView in my layout; I want that when the user swipes and the GestureOverlayView recognizes the gesture, it doesn't change color. I want that the color will change only when I call overlay.setGestureColor(int color), but in fact this method changes the color that will have the next gesture when recognized by the super class, not while the user is swiping.
So my question is: how can I change the color of the gesture while the user is performing the gesture?
public class MyGestureOverlayView extends GestureOverlayView implements
GestureOverlayView.OnGesturePerformedListener,
GestureOverlayView.OnGestureListener {
private static final String TAG = MyGestureOverlayView.class.getName();
private GiocoActivity mContext;
private boolean mRecognized;
private GestureLibrary gestures;
public MyGestureOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
if (context instanceof GiocoActivity)
mContext = (GiocoActivity) context;
gestures = GestureLibraries.fromRawResource(mContext, R.raw.gesture);
if (!gestures.load())
mContext.finish();
addOnGesturePerformedListener(this);
addOnGestureListener(this);
}
#Override
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
ArrayList<Prediction> predictions = gestures.recognize(gesture);
Log.d(TAG, "Gesture riconosciuta!");
// Sorry for my English :)
if (predictions.size() > 0 && predictions.get(0).score > 3.0) {
overlay.setGestureColor(Color.GREEN);
// The color doesn't change here, but it will change in the next gesture
String action = predictions.get(0).name;
mRecognized = true;
Toast.makeText(mContext, action, Toast.LENGTH_SHORT).show();
}
else {
overlay.setGestureColor(Color.RED);
// The color doesn't change here, but it will change in the next gesture
mRecognized = false;
}
Log.d(TAG, "Fine riconoscimento Gesture; esito: "+ mRecognized);
}
#Override
public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
Log.d(TAG, "Gesture iniziata!");
}
#Override
public void onGesture(GestureOverlayView overlay, MotionEvent event) {
Log.d(TAG, "Gesture in corso!");
if (mRecognized) // Always false for the first gesture
overlay.setGestureColor(Color.GREEN); // Same as before
else
overlay.setGestureColor(Color.RED); // Same as before
}
#Override
public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
Log.d(TAG, "Gesture finita!");
}
#Override
public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
Log.d(TAG, "Gesture cancellata!");
}
}
Besides, the callback method onGesturePerformed is called after onGestureEnded, so the boolean mRecognized in onGesture() is always false whem I perform a gesture for the first time and when it'll be recognized from the super class it'll be displayed red in any case (both recognized or not by my onGesturePerformedListener).
Sorry if I was complicate, this is my first question, and I found only one question similar to this, but it has no answers.
Thanks for your time.
I am creating an android app in Java in which I have a lot of <TextView> around the screen, all of them with onTouchListeners defined. They are wrapped in a <ScrollView> because they occupy more space than available in the screen.
My problem is: when I scroll the app, up/down, by touching at the screen and moving my finger up/down, the scroll works as expected but the onTouchListener of the touched <TextView> is also fired (which is probably expected as well) - I don't want that to happen though. I want the onTouchListener to be ignored when I'm touching the screen to scroll it.
How can I accomplish this? I don't want my function to run when the user is scrolling and "accidentally" fires the onTouchListener on a certain <TextView>.
After searching more, I found this solution by Stimsoni. The idea is to check if the time between the ACTION_DOWN and ACTION_UP events is lower or higher than the value given by ViewConfiguration.getTapTimeout().
From the documentation:
[Returns] the duration in milliseconds we will wait to see if a touch event is a tap or a scroll. If the user does not move within this interval, it is considered to be a tap.
Code:
view.setOnTouchListener(new OnTouchListener() {
private long startClickTime;
#Override
public boolean onTouch(View view, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
startClickTime = System.currentTimeMillis();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (System.currentTimeMillis() - startClickTime < ViewConfiguration.getTapTimeout()) {
// Touch was a simple tap. Do whatever.
} else {
// Touch was a not a simple tap.
}
}
return true;
}
});
I had the same problem as you, and I solved it with ACTION_CANCEL.
motionEvent.getActionMasked() is equal to ACTION_CANCEL when an action perceived previously (like ACTION_DOWN in your case) is "canceled" now by other gestures like scrolling, etc. your code may be like this:
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent e) {
if (e.getActionMasked() == MotionEvent.ACTION_DOWN) {
// perceive a touch action.
} else if(e.getActionMasked() == MotionEvent.ACTION_UP ||
e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
// ignore the perceived action.
}
}
I hope this helps.
I had a similar problem but with one TextView, search led me here. The text-content potentially takes up more space than available on screen.
Simple working example: bpmcounter-android (Kotlin)
class MainActivity : AppCompatActivity() {
inner class GestureTap : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean {
// Do your buttonClick stuff here. Any scrolling action will be ignored
return true
}
}
#SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.textView)
textView.movementMethod = ScrollingMovementMethod()
val gestureDetector = GestureDetector(this, GestureTap())
textView.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
}
}
1 METHOD:
I figured out that the best method to do this is detecting the first touch saving the points x and y and then confront it with the second touch. If the distance between the first click and the second one is quite close (I put 10% as an approximation) then the touch a simple click otherwise is a scrolling movement.
/**
* determine whether two numbers are "approximately equal" by seeing if they
* are within a certain "tolerance percentage," with `tolerancePercentage` given
* as a percentage (such as 10.0 meaning "10%").
*
* #param tolerancePercentage 1 = 1%, 2.5 = 2.5%, etc.
*/
fun approximatelyEqual(desiredValue: Float, actualValue: Float, tolerancePercentage: Float): Boolean {
val diff = Math.abs(desiredValue - actualValue) // 1000 - 950 = 50
val tolerance = tolerancePercentage / 100 * desiredValue // 20/100*1000 = 200
return diff < tolerance // 50<200 = true
}
var xPoint = 0f
var yPoint = 0f
#SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action) {
MotionEvent.ACTION_DOWN -> {
xPoint = event.x
yPoint = event.y
return true
}
MotionEvent.ACTION_UP -> {
if (!approximatelyEqual(xPoint, event.x, 10f) || !approximatelyEqual(yPoint, event.y, 10f)) {
//scrolling
} else {
//simple click
}
}
}
return false
}
2 METHOD:
Another way to do the same thing is by using the GestureDetector class:
interface GestureInterface {
fun setOnScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float)
fun onClick(e: MotionEvent)
}
class MyGestureDetector(val gestureInterfacePar: GestureInterface) : SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean {
gestureInterfacePar.onClick(e)
return false
}
override fun onLongPress(e: MotionEvent) {}
override fun onDoubleTap(e: MotionEvent): Boolean {
return false
}
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
return false
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
return false
}
override fun onShowPress(e: MotionEvent) {
}
override fun onDown(e: MotionEvent): Boolean {
return true
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
gestureInterfacePar.setOnScroll(e1, e2, distanceX, distanceY)
return false
}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
return super.onFling(e1, e2, velocityX, velocityY)
}
}
and finally, bind it with your view:
val mGestureDetector = GestureDetector(context, MyGestureDetector(object : GestureInterface {
override fun setOnScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float) {
//handle the scroll
}
override fun onClick(e: MotionEvent) {
//handle the single click
}
}))
view.setOnTouchListener(OnTouchListener { v, event -> mGestureDetector.onTouchEvent(event) })
You can identify moving action like this:
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_MOVE)
{
}
return false;
}
});
Worked for me :
View.OnTouchListener() {
#Override
public boolean onTouch(View v,MotionEvent event)
{
if(event.getAction()!=MotionEvent.ACTION_DOWN)
{
// Before touch
}
else {
// When touched
}
return true
});
You dont need to go for such cómplicated method for capturing a "click" event. Just for this method :-
//Inside on touch listener of course :-
KOTLIN :-
if(event.action == MotionEvent.ACTION_UP && event.action != MotionEvent.ACTION_MOVE) {
// Click has been made...
// Some code
}
JAVA :-
Just replace event.action with event.getAction()
This works for me 😉
Is there a way to intercept input in a NativeActivity before it gets dispatched to the AInputQueue in native code? The reason I need to intercept input in Java is to support gamepad/joystick events that I can't capture using any of the android/input.h functions, ie. MotionEvent.getAxisValue(MotionEvent.AXIS_RZ).
This following does not work (my manifest correctly points to my derived NativeActivity class):
public class CustomNativeActivity extends NativeActivity
{
private View.OnTouchListener touchListener = new View.OnTouchListener() {
public boolean onTouch (View v, MotionEvent event)
{
// This is never called!
System.out.println("onTouch");
return false;
}
};
public void setContentView(View view)
{
// This method is called, but registering a touch listener does nothing!
view.setOnTouchListener(touchListener);
super.setContentView(view);
}
public boolean dispatchTouchEvent(MotionEvent ev)
{
// This is never called either!
System.out.println("dispatchTouchEvent!");
return super.dispatchTouchEvent(ev);
}
}