I have a custom view class with a button and I let the user touch the screen so I can retrieve the coordinates. What I want to do is, whenever he clicks the button, to set its text to the coordinates of his touch. How do I do this?
public class TargetView extends RelativeLayout{
.
.
.
public float x=0;
public float y=0;
public TargetView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
((Activity)getContext())
.getLayoutInflater()
.inflate(R.layout.target_view, this, true);
Target=findViewById(R.id.target);
Undo=(Button)findViewById(R.id.undo_bt);
Undo.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
Log.d("x,y",(String.valueOf(x)+","+String.valueOf(y)));
Undo.setText(String.valueOf(x)+","+String.valueOf(y));
}
});
#Override
public boolean onTouchEvent(MotionEvent event){
switch (event.getAction()){
case (MotionEvent.ACTION_DOWN): {
x = event.getX();
y = event.getY();
}
return false;
}
.
.
.
}
EDIT
I have added a Log.d inside the button listener and it seems that the button gets clicked only before the user touches the rest of the screen and changes x,y (text changes to "0.0").
My .xml files:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<android.archer.test.TargetView
android:id="#+id/myTargetView"
android:layout_width="300dip"
android:layout_height="300dip"
android:layout_gravity="center_horizontal"
android:background="#drawable/target" >
</android.archer.test.TargetView>
</LinearLayout>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<View
android:id="#+id/target"
android:layout_width="300dp"
android:layout_height="350dip" />
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="#+id/undo_bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:text="-" />
</RelativeLayout>
- Return super.onTouchEvent(event) at the end of onTouchEvent method.
Eg:
#Override
public boolean onTouchEvent(MotionEvent event){
switch (event.getAction()){
case (MotionEvent.ACTION_DOWN): {
x = event.getX();
y = event.getY();
}
return super.onTouchEvent(event);
}
- Moreover, there is an hierarchy of views that Android maintains and the events are handled accordingly. Every View in the hierarchy gets the event, but only if the parent event has not consume it. Meaning - if you have a parent view that handles onTouchEvent() and returns true every time, then the child never receives the event.
Change return in your snippet:
#Override
public boolean onTouchEvent(MotionEvent event){
switch (event.getAction()){
case (MotionEvent.ACTION_DOWN): {
x = event.getX();
y = event.getY();
}
return false;
}
to return true;
And if possible, you can put log into your onTouchEvent to see if it can return the coordinates.
Related
suppose that I have a class for signUp button:
public class SignUp extends AppCompatButton {
public SignUp(Context context) {
this(context, null);
}
public SignUp(Context context, AttributeSet attrs) {
super(context, attrs, android.support.v7.appcompat.R.attr.buttonStyle);
setFocusable(true);
setFocusableInTouchMode(true);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
requestFocus();
this.setBackgroundColor(getResources().getColor(R.color.darkGreen));
return true;
case MotionEvent.ACTION_UP:
this.setBackgroundColor(getResources().getColor(R.color.green));
performClick();
return true;
}
return false;
}
#Override
public boolean performClick() {
super.performClick();
return false;
//TODO
}
and I have a button in my app called signUp and I have declared it like this in the XML file:
<com.example.John.myapplication.SignUp
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="#string/sign_up"
android:background="#color/green"
android:textColor="#color/whiteText"
app:layout_constraintTop_toBottomOf="#+id/verify_password"
android:layout_marginTop="40dp"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
android:id="#+id/sign_up"
android:textSize="20sp"
/>
now if the user touches the signUp button the color of the button would be dark green and when he releases it the color of the button would be green again. but I want to add this feature so that when the user touches the button and then drag his finger out of the button, the button color change to green but I can't.
none of the MotionEvent.ACTION_OUTSIDE,MotionEvent.ACTION_CANCEL and ... works.
what should I do?
and I don't want to check that the finger is outside of the button by coordinated because when the button is oval it is such a huge work.
Just use a selector.
https://developer.android.com/guide/topics/resources/color-list-resource
example
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#color/colorTextPrimaryDark" />
<item android:color="#color/colorAccent3" />
</selector>
No need for listeners here (and Action.DOWN is not dragging), please go through this section and let me know if you need more guidance -> https://developer.android.com/guide/topics/resources/color-list-resource
This -> http://www.zoftino.com/android-color-state-list-example -> seems to be a good example.
Use view bounds to compare the touch event boundaries, hereby sharing the modified code.
private Rect buttonBounds;
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
buttonBounds = new Rect(getLeft(), getTop(), getRight(), getBottom());
requestFocus();
this.setBackgroundColor(getResources().getColor(R.color.darkGreen));
return true;
case MotionEvent.ACTION_UP:
this.setBackgroundColor(getResources().getColor(R.color.green));
performClick();
return true;
case MotionEvent.ACTION_MOVE:
if(!buttonBounds.contains(getLeft() + (int) event.getX(), getTop() + (int) event.getY())){
// User moved outside bounds
this.setBackgroundColor(getResources().getColor(R.color.green));
return true;
}
}
return false;
}
You can use latest Material Button component introduced in latest support library. By default background color of button is your theme Accent Color.
Try this
<android.support.design.button.MaterialButton
android:id="#+id/btn__next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
app:backgroundTint="#color/colorAccent" /* Change colour for background */
app:rippleColor="#color/colorAccent" /* Change colour for selecting button */
android:text="#string/next" />
I need to open phone's contact book on the click of EditText's drawableRight. Click event on drawableRight is working fine But the problem is, when I click/touch on anywhere on EditText it is also execute click event and open contact list.
I take help for manage click event on drawableRight from here Please check this link.
I don't want to open contact list when I click on EditText, I only want to open it when I click drawableRight (image). So how solve this problem?
Here is my code:
EditText mobile_number;
mobile_number = (EditText)view.findViewById(R.id.mobile_number1);
mobile_number.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction()==MotionEvent.ACTION_UP){
if(event.getRawX()>=(mobile_number.getRight()-mobile_number.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width()));
{
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent,PICK_CONTACT);
return true;
}
}
return true;
}
});
Here is my layout code:
<LinearLayout
android:layout_width="fill_parent"
android:id="#+id/linearlayout_two1"
android:layout_below="#+id/linearlayout_one1"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginTop="20dp"
android:orientation="vertical"
>
<EditText
android:layout_width="300dp"
android:hint="Enter Your Mobile Number"
android:textSize="15sp"
android:id="#+id/mobile_number1"
android:paddingLeft="30dp"
android:layout_height="wrap_content"
android:drawableRight="#drawable/editbox_icon"
/>
</LinearLayout>
Instead of using getRawX(), try replacing that line with
if (event.getX() >= (mobile_number.getWidth() - mobile_number
.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
EDIT: I believe View.getRight() returns the position of the right edge of the View relative to its parent, while TouchEvent.getRawX() returns the absolute X position on the screen.
EDIT AGAIN TO DEMONSTRATE MY POINT:
MainActivity.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.meremammal.www.edittextdrawable.MainActivity">
<!-- This layout is only here to demonstrate a situation that breaks the usage of getRawX() -->
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true">
<EditText
android:id="#+id/edit_text"
android:layout_width="400dp"
android:layout_height="wrap_content"
android:text="Hello World!"
android:drawableRight="#android:drawable/ic_input_add"/>
</RelativeLayout>
</RelativeLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
EditText mEditText;
Context mContext;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
mEditText = (EditText) findViewById(R.id.edit_text);
mEditText.setOnTouchListener(new View.OnTouchListener() {
private float touchX = 0;
#Override
public boolean onTouch(View v, MotionEvent event) {
int drawableLeft = mEditText.getRight() - mEditText
.getCompoundDrawables()[2].getBounds().width();
// This detects the location of touch on ACTION_DOWN, but because it is
// using getRawX() and getRight() and the EditText's parent is not at the
// left of the screen, it will respond when clicked in the middle of the
// EditText. Instead, use getX() and EditText.getWidth()
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getRawX() >= drawableLeft) {
touchX = event.getRawX();
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP && touchX >= drawableLeft) {
Toast.makeText(mContext, "Clicked Button", Toast.LENGTH_SHORT).show();
touchX = 0;
return true;
} else {
return mEditText.onTouchEvent(event);
}
}
});
}
}
You don't have access to the right image as far my knowledge, unless you create custom EditText class. I suggest to use a RelativeLayout, with one editText and one imageView, and set OnClickListener over the image view as below:
<RelativeLayout
android:id="#+id/rlSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:drawable/edit_text"
android:padding="5dip" >
<EditText
android:id="#+id/txtSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="#+id/imgSearch"
android:background="#00000000"
android:ems="10"/>
<ImageView
android:id="#+id/imgSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="#drawable/btnsearch" />
</RelativeLayout>
Make a Linear layout with horizontal orientation and add a edit-text and image-view with proper weight..
Then Give on click for each item separately.....
Just chage the last return to false. Also you have to remove the comma ; at the end of if statement.
I have a StreetViewPanoramaView inside a linearlayout inside a scrollview:
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scrollview">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/linear">
<com.google.android.gms.maps.StreetViewPanoramaView
android:id="#+id/street_view_panorama"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<other views - textviews etc>
</LinearLayout>
</ScrollView>
The StreetViewPanoramaView allows a user to move the streetview camera up and down and left and right. I want the scrollview to stop intercepting the user touch events when the user is moving the camera. Currently, if the user moves the camera in the StreetViewPanoramaView, the scrollview intercept the touch and moves the scrollview instead.
I tried this code from https://mobiarch.wordpress.com/2013/11/21/prevent-touch-event-theft-in-android/ but its doesn't seem to be working (scrollview is still taking the touch events):
mStreetViewPanoramaView = (StreetViewPanoramaView) findViewById(R.id.street_view_panorama);
LinearLayout.LayoutParams streetViewLp =
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, displaymetrics.widthPixels/3);
mStreetViewPanoramaView.setLayoutParams(streetViewLp);
disableTouchTheft(mStreetViewPanoramaView);
public static void disableTouchTheft(View view) {
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
view.getParent().getParent().requestDisallowInterceptTouchEvent(true);
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
view.getParent().getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return false;
}
});
}
EDIT:
Things I have tried:
I have made a custom view and imported the code dispatchTouchEvent into my activity code:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
int[] loc = {0, 0};
mStreetViewPanoramaView.getLocationOnScreen(loc);
Rect rect = new Rect(loc[0], loc[1] , loc[0] + mStreetViewPanoramaView.getWidth(), loc[1] + mStreetViewPanoramaView.getHeight());
if (rect.contains((int) event.getRawX(), (int) event.getRawY())) {
Log.e("stretview", "dispatched");
return mStreetViewPanoramaView.dispatchTouchEvent(event);
/* return true;*/
}else{
//Continue with touch event
return super.dispatchTouchEvent(event);
}
}
public class CustomStreetViewPanoramaView extends StreetViewPanoramaView {
public CustomStreetViewPanoramaView(Context context) {
super(context);
}
public CustomStreetViewPanoramaView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomStreetViewPanoramaView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public CustomStreetViewPanoramaView(Context context, StreetViewPanoramaOptions options) {
super(context, options);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return false; // Do not intercept touch event, let the child handle it
}
}
The code appears to half-work - it appears that the StreetViewParnoramaView will obtain the touch events for the top half of the view but the bottom half does not obtain any touch events.
My full layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:id="#+id/container"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scrollview">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/linearlayout">
<com.example.simon.customshapes.CustomStreetViewPanoramaView
android:id="#+id/street_view_panorama"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="500dp"
android:id="#+id/card"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello card"
android:layout_gravity="center" />
</android.support.v7.widget.CardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save panorama"
android:padding="8dp"
android:id="#+id/savepano"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</ScrollView>
</LinearLayout>
The answer of Johann is the right path but still not working - this one will work
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (streetView != null) {
streetView.getGlobalVisibleRect(rect);
if (rect.contains((int) event.getRawX(), (int) event.getRawY())) {
event.offsetLocation(-rect.left, -rect.top);
streetView.dispatchTouchEvent(event);
return true;
} else {
//Continue with touch event
return super.dispatchTouchEvent(event);
}
} else {
return super.dispatchTouchEvent(event);
}
}
dispatchTouchEvent of the activity and move the event after offsets it to the streetView
Can you try the following...
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
int[] loc = {0, 0};
mStreetViewPanoramaView.getLocationOnScreen(loc);
Rect rect = new Rect(loc[0], loc[1] , loc[0] + mStreetViewPanoramaView.getWidth(), loc[1] + mStreetViewPanoramaView.getHeight());
if (rect.contains((int) event.getRawX(), (int) event.getRawY())) {
mStreetViewPanoramaView.dispatchTouchEvent(event);
return true;
}else{
//Continue with touch event
return super.dispatchTouchEvent(event);
}
}
This hopefully will check if the touch was on the panoramaview and handle accordingly.
I have frame layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp" >
<LinearLayout
android:id="#+id/widgetView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="10dp"
android:gravity="center" >
</LinearLayout>
<LinearLayout
android:id="#+id/widgetOverlayFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false" >
</LinearLayout>
First layout contains widget layout, and second one is transparent and receives onLongClick for dragging method.That works, the problem is when i want to interact with widget, to click something, click event is taken by overlayFrame. How can i pass click event trough overlayFrame to widgetView?
EDIT:
Now I'm trying to attach GestureLister to overlayFrame LinearLayout, to somehow see difference between MotionEvent that represent single click and long click. The problem that I'm facing is that OnLongPress in gesture listener is always called whatever I do, single click or long click.
Is there an explanation for this behavior? Tnx!
In my case I added flag to layoutParams of my view, which should have passed a touch event under:
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
This way view will ignore all touches (like view with visibility GONE)
Instead of using GestureListener you can override the onTouchListener in this way.
This will call longPress when the timer runs out and if an up comes in between it will cancel the LongPress
CountDownTimer c;
long time=5000;
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
c= new CountDownTimer(5000, 1000) {
public void onTick(long millisUntilFinished) {
time=millisUntilFinished;
}
public void onFinish() {
Log.i("","LONG PRESS");
}}.start();
break;
case MotionEvent.ACTION_UP:
if (time>0) {
Log.i("example","short click");
c.cancel();
}
break;
}
return true;
}
Maybe setting the overlay's attribute android:focusable="false" or android:focusableInTouchMode="false" would be enough.
You could also make your own overlay widget and override its onInterceptTouchEvent method to return false always.
public class NotTouchableLinearLayout extends LinearLayout {
// Override constructors
// ...
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
}
Instead of setting a GestureListener on the top layout, you should create your own class that extends LinearLayout and to override it's onTouchEvent method.
There you can implement the logic of long click \ short click etc.
The click events will first be sent to the widget layout, and only if it doesn't handle them (hence it's onTouchEvent returns false), you will get them on the top layout.
edit:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp" >
<LinearLayout
android:id="#+id/widgetView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="10dp"
android:gravity="center" >
</LinearLayout>
<com.example.touchtesting.MyLinearLayout
android:id="#+id/widgetOverlayFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false" >
</com.example.touchtesting.MyLinearLayout>
</FrameLayout>
change the com.example.touchtesting to the name of your package.
and this is the class:
public class MyLinearLayout extends LinearLayout{
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
private long startClick = 0;
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startClick = ev.getEventTime();
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (ev.getEventTime() - startClick < 500) {
Log.i("example","short click");
}
else {
Log.i("example","long click");
}
break;
}
return true;
}
}
To get the long clicks to pass through the widgetView LinearLayout add android:longClickable="true". (For ordinary clicks add android:clickable="true")
So widgetView becomes:
<LinearLayout
android:id="#+id/widgetView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="10dp"
android:gravity="center"
android:longClickable="true">
</LinearLayout>
I have some code that I wrote to implement a vertical swipe on a
Gallery widget. It works great in Android 1.5 and 1.6 but does not
work in Android 2.2 (I have yet to try it with 2.1).
public class SwipeUpDetector extends SimpleOnGestureListener
implements OnTouchListener
{
private GestureDetector m_detector;
public SwipeUpDetector()
{
m_detector = new GestureDetector(m_context, this);
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
if (Math.abs(e1.getX() - e2.getX()) < s_swipeMaxOffPath &&
e1.getY() - e2.getY() >= s_swipeMinDistance &&
Math.abs(velocityY) >= s_swipeMinVelocity)
{
int pos = m_gallery.pointToPosition((int)e1.getX(), (int)e2.getY());
startAnimation(pos);
return true;
}
return false;
}
#Override
public boolean onTouch(View v, MotionEvent event)
{
return m_detector == null ? false : m_detector.onTouchEvent(event);
}
}
And to be able to get my gallery to detect the onFling I have the
following:
m_gallery.setOnTouchListener(new SwipeUpDetector());
In Android 1.5 and 1.6 this works great. In Android 2.2 onFling() is
never called. In looking around on Google and StackOverflow I found
one possible solution was to implement onDown() and return true.
However, I am also listening to single clicks and have a context menu
listener set up on this gallery. When I implement onDown() and return
true I do indeed get the swipe to work. But when I do this the
context menu doesn't display on a long click and the single clicks
don't work either... Clicking on items in the gallery cause the
gallery to jump around and I don't get any feedback when I click on an
item in the gallery. It just immediately makes that item the selected
item and moves it to the center.
I looked at the API differences report between 1.6, 2.1, and 2.2 and
didn't see anthing of significance that could have caused this to
break...
What am I doing wrong?
EDIT:
It might also be helpful to know that the gallery is nested inside a couple layouts as follows (this isn't a complete layout... it is just intended to show the hierarchy of where this Gallery lives):
<ScrollView>
<LinearLayout>
<RelativeLayout> <!-- This relative layout is a custom one that I subclassed -->
<Gallery />
</RelativeLayout>
</LinearLayout>
</ScrollView>
EDIT #2:
Here are the requested layouts... There are two of them, for reusability purposes. Here is the first one, which is the main activity's layout:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myns="http://com.magouyaware/appswipe"
android:id="#+id/main_layout_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center_horizontal"
android:scrollbarAlwaysDrawVerticalTrack="false"
>
<LinearLayout
android:id="#+id/appdocks_layout_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginTop="10dp"
android:layout_gravity="center"
android:orientation="vertical"
android:gravity="center"
android:background="#null"
>
<com.magouyaware.appswipe.TitledGallery
android:id="#+id/running_gallery_layout_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
myns:gallery_title="#string/running_title"
/>
<com.magouyaware.appswipe.TitledGallery
android:id="#+id/recent_gallery_layout_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
myns:gallery_title="#string/recent_title"
/>
<com.magouyaware.appswipe.TitledGallery
android:id="#+id/favs_gallery_layout_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
myns:gallery_title="#string/favs_title"
/>
<com.magouyaware.appswipe.TitledGallery
android:id="#+id/service_gallery_layout_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
myns:gallery_title="#string/service_title"
/>
<com.magouyaware.appswipe.TitledGallery
android:id="#+id/process_gallery_layout_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
myns:gallery_title="#string/process_title"
/>
<include
android:id="#+id/indeterminate_progress_layout_id"
layout="#layout/indeterminate_progress_layout"
/>
</LinearLayout>
</ScrollView>
And here is the layout file for com.magouyaware.appswipe.TitledGallery... This is nothing more than a RelativeLayout subclass for the purpose of controlling several views as a single item in the code and for reusability:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/titled_gallery_main_layout_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
android:background="#null"
>
<LinearLayout
android:id="#+id/titled_gallery_expansion_layout_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:focusable="true"
android:clickable="true"
android:gravity="center_vertical"
>
<ImageView
android:id="#+id/titled_gallery_expansion_image_id"
android:layout_width="20dp"
android:layout_height="20dp"
android:duplicateParentState="true"
android:clickable="false"
/>
<TextView
style="#style/TitleText"
android:id="#+id/titled_gallery_title_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:paddingLeft="1sp"
android:paddingRight="10sp"
android:textColor="#drawable/titled_gallery_text_color_selector"
android:duplicateParentState="true"
android:clickable="false"
/>
</LinearLayout>
<Gallery
android:id="#+id/titled_gallery_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#id/titled_gallery_expansion_layout_id"
android:layout_alignWithParentIfMissing="true"
android:spacing="5sp"
android:clipChildren="false"
android:clipToPadding="false"
android:unselectedAlpha=".5"
android:focusable="false"
/>
<TextView
style="#style/SubTitleText"
android:id="#+id/titled_gallery_current_text_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#id/titled_gallery_id"
android:layout_alignWithParentIfMissing="true"
android:gravity="center_horizontal"
/>
</RelativeLayout>
I was able to receive single/double clicks and long click as well by implementing onSingleTapConfirmed, onDoubleTap and onLongPress in my implementation of SimpleOnGestureListener (while returning true from onDown).
Concerning why we should override onDown method. I think it is related to the issue #8233. It was reported one year ago against 2.1 version. Since only 10 people starred it so far I guess it would not be fixed in the near future.
UPDATE
It turned out that the issue was caused by the combination of ScrollView and Gallery and usage of OnTouchListener. Gallery itself implements OnGestureListener and encapsulates GestureDetector which is disabled when we set our OnTouchListener, resulting in a strange gallery behavior sometimes. On the other hand if we just subclass Gallery component and perform long-click/swipe detection in its onLongPress/onFling methods the parent ScrollView will intercept vertical move events preventing onFling call for such events. The solution is to override Gallery.dispatchTouchEvent and call requestDisallowInterceptTouchEvent(true) for gallery parent.
To summarize: if you want to detect swipes (long-, double-clicks etc.) for the Gallery (and possibly place it inside the ScrollView) use the custom component provided below instead of GestureDetector/OnTouchListener.
public class FlingGallery extends android.widget.Gallery implements OnDoubleTapListener {
private static final int SWIPE_MIN_VELOCITY = 30; // 30dp, set to the desired value
private static final int SWIPE_MIN_DISTANCE = 50; // 50dp, set to the desired value
private static final int SWIPE_MAX_OFF_PATH = 40; // 40dp, set to the desired value
private final float mSwipeMinDistance;
private final float mSwipeMaxOffPath;
private final float mSwipeMinVelocity;
public FlingGallery(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
float density = context.getResources().getDisplayMetrics().density;
this.mSwipeMinDistance = density * SWIPE_MIN_DISTANCE;
this.mSwipeMaxOffPath = density * SWIPE_MAX_OFF_PATH;
this.mSwipeMinVelocity = density * SWIPE_MIN_VELOCITY;
}
public FlingGallery(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.galleryStyle);
}
public FlingGallery(Context context) {
this(context, null);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final ViewParent parent;
if (ev.getAction() == MotionEvent.ACTION_MOVE && (parent = getParent()) != null) {
parent.requestDisallowInterceptTouchEvent(true); // this will be passed up to the root view, i.e. ScrollView in our case
}
return super.dispatchTouchEvent(ev);
}
#Override
public boolean onDoubleTap(MotionEvent e) {
// Your double-tap handler...
return true;
}
#Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// Your single-tap handler...
return true;
}
#Override
public void onLongPress(MotionEvent event) {
// Your long-press handler...
super.onLongPress(event);
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (e1 == null) {
return super.onFling(e1, e2, velocityX, velocityY);
}
float dx = e2.getX() - e1.getX();
float dy = e2.getY() - e1.getY();
if (abs(dx) < mSwipeMaxOffPath && abs(velocityY) > mSwipeMinVelocity && abs(dy) > mSwipeMinDistance) {
if (dy > 0) {
// Your from-top-to-bottom handler...
} else {
// Your from-bottom-to-top handler...
}
} else if (abs(dy) < mSwipeMaxOffPath && abs(velocityX) > mSwipeMinVelocity && abs(dx) > mSwipeMinDistance) {
if (dx > 0) {
// Your from-left-to-right handler...
} else {
// Your from-right-to-left handler...
}
}
return super.onFling(e1, e2, velocityX, velocityY);
}
}
If you do not handle the down you will not get any event (scroll, fling, up) linked to this down event. So you must return true.
I tried to understand why but I failed yet. Maybe since SimpleOnGestureListener returns false by default, and some new 2.2 optimizations in the outer Layout feels you do not want the event. You are not a valid target anymore for the chain of events.
To get your longPress working, can't you implement the onLongPress event in your detector and call the code that makes your menu appear?