I am using google maps API and I have some code that tries to capture the position of the center of the map after the user has dragged it.
MapView mv = ...;
mv.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
GeoPoint pt = mv.getMapCenter();
//do something with the point
return A;
}
return B;
}
});
Now my problem is with the return values:
if B is false, the map gets dragged but I only see the ACTION_DOWN event and ACTION_UP is never triggered - which I understand
if B is true, I receive the ACTION_UP event, but the map is not dragged
it seems that whether A is true or false does not make a difference
What I want is to receive the ACTION_UP event AND to have the map dragged.
What am I missing here?
Here's one solution: Instead of using the default MapView, use a custom MapView which has a GestureDetector, basically, this lets you create a custom event listener for your map which helps you avoid the issue of messing up the dragging etc. and moreover gives you are vast number of interaction options compared to the default MapView. A couple of months ago I'd faced a similar problem and so I decided to implement the solution I just mentioned. Here's the code for the custom MapView called TapControlledMapView and the code for the interface for the custom listener is provided at the bottom : http://pastebin.com/kUrm9zFg.
So to implement the listener, all you need to do is use the following code in your mapactivity class (Oh and in case you didn't know this, you have to declare the following in your MapActivity layout XML file since you are using a custom MapView:
<?xml version="1.0" encoding="utf-8"?>
//The bottom line was the tricky business (You get ClassCastExceptions and whatnot)
<NAMEOFYOURPROJECT.TapControlledMapView (ex: com.android.googlemapsapp.TapControlledMapView)
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:apiKey="API_KEY_HERE"
/>
and use the following code in your MapActivity class.
mapView.setOnSingleTapListener(new OnSingleTapListener() {
#Override
public boolean onSingleTap(MotionEvent arg1) {
if(arg1.getAction() == MotionEvent.ACTION_UP)
{
GeoPoint pt = mv.getMapCenter();
// do something with the point.
return ***true***;
}
return true;
});
Let me know how it goes.
Following #ViswaPatel's idea, I create a new MapView class, extending MapView, which overrides the onTouchEvent method and manages its own listeners:
public class CustomMapView extends MapView {
private OnTouchListener lst;
public CustomMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public CustomMapView(Context context, String apiKey) {
super(context, apiKey);
}
public void setOnActionUpListener(OnTouchListener lst) {
this.lst = lst;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (lst != null && event.getAction() == MotionEvent.ACTION_UP) {
lst.onTouch(this, event); // return value ignored
}
return true;
}
}
The calling code is:
mv.setOnActionUpListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
final GeoPoint pt = mv.getMapView().getMapCenter();
//doing my stuff here
return true; //ignored anyway
}
});
}
Related
For practicing purpose, I am create a customized Tooltip control. To use the Tooltip control, a hosting UIControl (e.g. a Button) will be assigned to my Tooltip control, and I want my Tooltip control is be able to listen to the mouse press event on the hosting control (i.e. the Button), and show / dismiss itself accordingly.
I am having problem finding a way to listening to mouse events of the hosting control. I tried:
Set the Hosting Control's setOnTouchListener, this works, but it will override the existing OnTouchListener of the Hosting Control, thus undeserable.
Go to the Hosting Control's ViewGroup, and add a **Observer to the ViewGroup. But there is no way to observe the mouse event on the ViewGroup.
So is listening to other control's mouse event doable from a custom view, if so, what's the recommended way to implement it ?
Thanks.
I also thought of another way to do it, as followed:
Get the ViewGroup of the hosting control;
In the ViewGroup, add a transparent view to listen to the mouse event.
In the handler of mouse event of the transparent view, check whether the mouse event is happened on the Hosting Control.
If happened on the Hosting Control, respond correspondingly.
I will try this approach after I post my question, but it seems to be resource-intensive way of implementing something seemingly straightforward.
I will let you know if this approach works or not, any comment / thought is very appreciated.
Thanks ~!
Try to use this approach. I've already tried this approach with OnClickListener and it works great.
public class CustomButton extends Button {
private OnTouchListener outsideListener;
private OnTouchListener innerListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (outsideListener != null) {
outsideListener.onTouch(v, event);
}
//some code here ...
}
};
#Override
public void setOnTouchListener(OnTouchListener listener) {
outsideListener = listener;
}
public CustomButton(Context context) {
super(context);
super.setOnTouchListener(innerListener);
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
super.setOnTouchListener(innerListener);
}
public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
super.setOnTouchListener(innerListener);
}
}
as I mentioned in my question, I thought of a way to implement what I wanted. I am sharing my way of implementing here. But still, it seems to be resource-intensive way of solving seemingly simple problem. If you have an easier solution, or any comment, please leave a comment. Much Appreciated ~!
My custom MaterialTooltip class:
public class MaterialToolTip {
/// Implementation
}
An example of how to use my MaterialToolTip class:
public class MainActivity extends AppCompatActivity {
MaterialToolTip toolTip;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// anchorButton is the button to which ToolTip will be added to.
Button anchorButton = (Button)findViewById(R.id.button);
this.toolTip = new MaterialToolTip.Builder(this)
//.anchorView property is used to specify the view that will use this tooltip
.anchorView(anchorButton)
.maxWidth(R.dimen.simpletooltip_max_width)
.build(); // that's all the consumer needs to do, as soon as the tooltip is attached to the View, the tooltip decided when to show / dismiss by listening to View's event.
}
}
Within my MaterialToolTip class:
Create a transparent view that listens to mouse event
#SuppressLint("ViewConstructor")
public class ToolTipPressInterceptView extends View {
OnAnchorViewMouseEventListener mListener;
ToolTipPressInterceptView(Context context, View anchorView) {
super(context);
//get the anchorView's onScreenLocation
int[] output = new int[2];
anchorView.getLocationOnScreen(output);
anchorViewRect = new RectF(output[0], output[1], output[0] + anchorView.getMeasuredWidth(), output[1] + anchorView.getMeasuredHeight()) ;
//set the dimension to match parent
this.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
this.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
if (anchorViewRect.contains(event.getX(), event.getY())){
mListener.onMouseEvent(AnchorViewMouseEventType.PRESSED);
}
}
case MotionEvent.ACTION_UP:{
if (anchorViewRect.contains(event.getX(), event.getY())){
mListener.onMouseEvent(AnchorViewMouseEventType.RELEASED);
}
}
}
return false;
}
});
}
}
Get the ViewGroup of the hosting control, and add the transparent view to the ViewGroup;
private MaterialToolTip(Builder builder){
//.. Other initialisation logic
//.. add the TransparentView to the ViewGroup;
mRootView = (ViewGroup)mAnchorView.getRootView();
ToolTipPressInterceptView view = new ToolTipPressInterceptView(mContext, mAnchorView);
//mAnchorViewTouchListener is listener to Mouse Event
view.setOnTouchListener(mAnchorViewTouchListener);
mRootView.addView(view);
}
If happened on the Hosting Control, respond correspondingly.
private final View.OnTouchListener mAnchorViewTouchListener = new
View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event){
int action = MotionEventCompat.getActionMasked(event);
switch(action) {
case (MotionEvent.ACTION_DOWN) :
// show
show();
case (MotionEvent.ACTION_UP) :
// dismiss
dismiss();
}
return false; //return false so the other handlers is able to
}
};
I am using a ViewPager with a TouchImageView inside it and it works great, (I have used this solution in many of my Android apps).
However I have an app for which there are many other controls on the same screen so they are all inside a scrollview control.
In this scenario I see the scrollview does not play nice and I am not able to pan within the zoomed image. When I use my finger to pan upward or downward the entire page scrolls instead of the image panning.
So here is what I am trying to do....
Inside the TouchImageView I detect Zoom Begin and Zoom End and have created an interface to make a callback to my Activity onZoomBegin() and onZoomEnd() methods.
In the onZoomBegin() method I want to disable the scrollview from responding to any touch events and in onZoomEnd() I can re-enable it.
So far here are the things I have tried doing in the onZoomBegin() method for which none are working....
scrollView.setEnabled(false);
scrollView.requestDisallowInterceptTouchEvent(true);
also I have tried the answer to a similar question which was to takeover the onTouchListener like such:
scrollView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
This does stop the scrollview from scrolling but the scrollview is still intercepting the touch events cause the image still will not pan up or down.
I've tried checking nestedScrollingEnabled in the layout designer, no joy....
I just want to know is there a way to totally disable a scrollview and then re-enable it from responding to touch events?
I found this answer on another question somewhere but by the time I realized it was the solution to my problem (answer to my question) then I lost reference to it. I will keep looking so I can edit this post to give credit where credit is due.
public class CustomScrollView extends ScrollView {
// true if we can scroll the ScrollView
// false if we cannot scroll
private boolean scrollable = true;
public CustomScrollView(Context context) {
super(context);
}
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setScrollingEnabled(boolean scrollable) {
this.scrollable = scrollable;
}
public boolean isScrollable() {
return scrollable;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// if we can scroll pass the event to the superclass
if (scrollable)
return super.onTouchEvent(ev);
// only continue to handle the touch event if scrolling enabled
return false; // scrollable is always false at this point
default:
return super.onTouchEvent(ev);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Don't do anything with intercepted touch events if
// we are not scrollable
if (!scrollable)
return false;
else
return super.onInterceptTouchEvent(ev);
}
}
This part I just figured out for myself.... In the TouchImageView I added a callback interface which is called when a zoom begins and ends so in my Activity I only had to do this:
private class OnZoomListener implements TouchImageView.OnZoomListener {
#Override
public void onZoomBegin() {
isZoomed = true;
scrollView.scrollTo(0, 0);
scrollView.setScrollingEnabled(false); // <-- disables scrollview
hideImageControls();
sizeViewPager();
}
#Override
public void onZoomEnd() {
scrollView.setScrollingEnabled(true); // <-- enables scrollview
showImageControls();
isZoomed = false;
}
}
Hi i'm working on a Battleships game in Android and currently i'm trying to implement the ship positioning activity.
I have a custum view with onDraw representing the board on which you position the ships.
I want to be able to rotate ships by singletapping them and drag a ship by longclicking it. The thing is i can't just use onClick and onLongClick because i need to know where was the click on the canvas. I tried using onTouch but that didn't work. I also tried using GestureDetector but it just meseed up everything.
Do you have any suggestions on how to approach this logic?
i need to know where was the click on the canvas
You have a custom view, hence you can easily use GestureDetector.SimpleOnGestureListener. Just override the onTouchEvent() of your CustomView and use the onLongPress of the GestureDetector. I would suggest you to handle this within the CustomView itself, rather than do it in Activity or Fragment. This would keep things modularized.
You can follow the code below to get this done:
CustomView.java
public class CustomView extends View {
private GestureDetectorCompat mGestureDetector;
private LongPressGestureListener longPressGestureListener;
CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
longPressGestureListener= new LongPressGestureListener(this);
mGestureDetector = new GestureDetectorCompat(context, longPressGestureListener);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
// Handle any other event here, if not long press.
return true;
}
}
LongPressGestureListener.java
public class LongPressGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
// e will give you the location and everything else you want
// This is where you will be doing whatever you want to.
int eIndex = MotionEventCompat.getActionIndex(e);
float eX = MotionEventCompat.getX(e, eIndex);
float eY = MotionEventCompat.getY(e, eIndex);
Log.d("X:Y = " + eX + " : " + eY);
}
#Override
public boolean onDown(MotionEvent e) {
return true;
}
}
You will need to use View.OnTouchListener.
Set the touch listener to your canvas with view.setOnTouchListener(listener).
Implement your touch listener. You will need to implement the onTouch(View v, MotionEvent event) method. In this method, you will have access to the touch event, and you will be able to decide, if it is a simple click, a long press, etc, and do the appropriate action.
You can read more about it in this answer on SO.
Solved
The goal I wanted to achieve is make the ViewPager to be more easier to scroll. I have solved it the by editing the original ViewPager source code instead of extending like below.
How can i return touch coordinates to original reference frame for any child views?Here is the ViewPager java file i always return y = 0 to make it scroll whenever x change just like the launcher's home screen.
My qusetion is that i need to return the real touch motion to the child views of the ViewPager
public class myViewPager extends ViewPager
{
public myViewPager(Context context) {
super(context);
}
public myViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* always return y = 0 to make it scroll whenever x change
*/
private MotionEvent changeY(MotionEvent ev) {
ev.setLocation(ev.getX(), 0);
return ev;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev){
// how to return touch coordinates to original reference frame for any child views ?
return ???;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(changeY(ev));
}
}
How about changing it in dispatchTouchEvent? Like this:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(changeY(ev));
}
I was not very clean your problem, hope this can help you.
I have a custom WebView and I want to simulate a click when I get a MotionEvent.ACTION_DOWN. I don't want to have any kind of input on the WebView , I must not even make it clickable(.setClickable(false)). So what i have done is override the onTouchEvent() in my custom WebView to return false.
this works fine but I'm missing the click. I have seen in the source code of WebView that it send messages to class called WebViewCore but this communication is done differently in every version of android.
Does anyone knows how can I programmatically send a click to a webView?
Here is what I'm trying to do:
public class VoidWebView extends WebView {
public VoidWebView(Context context) {
super(context);
}
public VoidWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
//tell the webview that a click has been performed , it doesn't matter where the click happened
}
return false;
}
}
I think you are looking for the performClick() method of the View.
You can get some more information here.
Pass up and down event to super class.
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP){
super.onTouchEvent(event);
return true;
}
return false;
}