as the documentation for TouchGesture says as follows:
Capturing touch events for a single view
As an alternative to onTouchEvent(), you can attach an View.OnTouchListener object to any View object using the setOnTouchListener() method. This makes it possible to to listen for touch events without subclassing an existing View. For example:
View myView = findViewById(R.id.my_view);
myView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
// ... Respond to touch events
return true;
}
});
Beware of creating a listener that returns false for the ACTION_DOWN event. If you do this, the listener will not be called for the subsequent ACTION_MOVE and ACTION_UP string of events. This is because ACTION_DOWN is the starting point for all touch events.
But returning false for the onTouch() method calls subsequent events ACTION_MOVE AND ACTION_UP and returning true is not calling the following events such as ACTION_MOVE AND ACTION_CANCEL. This look counter part from the documentation.
my code :
/**
* Setting Touch Listener to Tabs <br/>
* ReSelecting the tabs calls the touch listener and open the Default/Initial Screen for the tab.
*/
protected void setTabListeners() {
if (mTabHelper.getTabHost() != null) {
final TabWidget tabWidget = mTabHelper.getTabHost().getTabWidget();
int tabsCount = tabWidget.getChildCount();
for (int i = 0; i < tabsCount; i++) {
mLogger.info("count = " + i);
tabWidget.getChildAt(i).setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mLogger.info("event "+event.getAction());
return false;
}
});
}
}
Returning false means that you won't have consumed the event and it's up for grabs by anything else along the chain.
I would recommend that you take the time to watch the talk by Dave Smith on how touch events work on Android and how they are passed down through to child views to consume the events. It's actually the inverse of what most people would expect.
This should clear up any other questions you have.
I have a ListView inside ScrollView. I can enable scroll of ListView by
listView.getParent().requestDisallowInterCeptTouchEvent(true);
But the problem is when i scroll up in listView and it reaches top it should scroll to parent view i.e. parent scroll has to work . How can i do this ? any suggestion please.
listView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
return true; // Indicates that this has been handled by you and will not be forwarded further.
}
return false;
}
});
OR
To make the View unselectable just get the view and .setClickable(false)
OR
listView.setScrollContainer(false);
You can override ScrollView class and insert these methods inside:
private boolean isScrollEnabled = true;
public void enableScroll(boolean isScrollEnabled ) {
this.isScrollEnabled = isScrollEnabled ;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isScrollEnabled) {
return super.onInterceptTouchEvent(ev);
} else {
return false;
}
}
This is the cleanest solution to achieve this. You only call scrollView.enableScroll(true) to enable scrolling or scrollView.enableScroll(false) to disable it.
I would suggest to embed your upper view i.e any viewgroup above list view into listview header. ListView has a method, listview.addHeaderView(). That way you would be able to scroll your list (Whole View) even on small size display and you don't need scrollview.
I am making an alarm clock.
I want to make an activity which on the layout part is empty (exept a photo on the background)
I want to do, that if i touch anywhere on the screen, the music will stop.
I thought about making the img as a imageview...
but it dosent strach on the screen when I do it (even if the parameters are on the whole screen)
help?
in your layout verify that :
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
then try to use onTouchListener
then try :
yourActivityLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
// action to do
return true;//always return true to consume event
}
});
Do this way to put touch event on Whole Activity
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// do your work here
return true;
} else {
return false;
}
}
}
Have your Activity or Fragment implement OnClickListener, and then assign it as your click listener to every view and or layout.
in the first line of the function just run some logic,
#override
public void onClick(view v)
{
if(isMusicPlaying)
stopMusic();
// here run the rest of your logic
if (v == someButton){}
}
If you want if touch the layout , set click listener to the layout it self
You may play with WindowManager and overlaying your layout over everything else(may also overlay status bar and other system UI)
WindowManager instance has addVieW() method. With right layout params it produces described result
Try this, Pass your topmost parent as argument for this method
Eg: stopMusicOnTouch(yourParentView);
public void stopMusicOnTouch(View view) {
//Set up touch listener for non-text box views to hide keyboard.
if(!(view instanceof ImageView)) {
view.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
// Stop music here
return false;
}
});
}
//If a layout container, iterate over children and seed recursion.
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
View innerView = ((ViewGroup) view).getChildAt(i);
stopMusicOnTouch(innerView);
}
}
}
At the end,I made a button for the whole screen and did it transperent (but visible)
it worked perefectly, and I suggest it to others!
I have read a few questions regarding this topic on SO but haven't really found a solid answer to it.
I have a framelayout that I stack multiple custom views on, however the onTouch event only works with the top view. (the custom views are all the same view with the same onTouch event, just multiple of them)
FrameLayout
customView[2] <--- this is the last view added and the only one that receives the event
customView[1]
customView[0]
I'm testing it on Android 2.2 and am wondering if there is any way for the other views below to know where the touch happened?
EDIT (Adding some code)
I'm adding some code to hopefully help explain where I'm running into issues. At first I just automatically had the onTouchEvent return true. This made it so that the last view (in my case customerView[2]) would be the only one generating a value.
However, once I added the method to set the onTouchEvent to return true or false, now the only view returning a generated value is customView[0].
I hope this clears up what I am asking. I'm rather new to this and I appreciate you taking the time to explain it (and of course I appreciate your patience).
Also, I realize that my TextView's don't update with the value on each touchEvent, I'm working on fixing that.
My Activity:
public class MyActivity extend Activity {
CustomView[] customView;
TextView[] textView;
int numViews 3;
//FrameLayout and Params created
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for(int i = 0; i < numViews; i++) {
customView[i] = new CustomView(this, i);
//Allows the onTouch to be handled by all Views - View[0] is the bottom view
if(i == 0) {
customView[i].setTouchBool(true); //set view's onTouch to return true
} else {
customView[i].setTouchBool(false); //set view's onTouch to return false
}
//Set TextView to display the number generated by the CustomView
textView[i].setText(Double.toString(customView[i].getGeneratedNumber()));
//Add views to main layout
frame.addView(textView[i]);
frame.addView(customView[i]);
}
}
}
My View:
public class CustomView extends View {
boolean onTouchHandler = true;
int xVal = 0, yVal = 0;
int index;
double generatedNum = 0;
public CustomView(Context context) {
this(context, 0);
this.index = 0;
}
public CustomView(Context context, int index) {
super(context);
this.index = index;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch(action) {
case MotionEvent.ACTION_DOWN: {
//do logic
}
case MotionEvent.ACTION_MOVE: {
//do logic
}
case MotionEvent.ACTION_UP: {
xVal = (int) ev.getX();
yVal = (int) ev.getY();
generateNumber(xVal, yVal, index);
break;
}
}
return onTouchHandler;
}
private void generateNumber(int x, int y, int index) {
if(index == 0) {
generatedNum = (x / 2) * (y / 2) + 64;
} else {
generatedNum = (x / 2) * (y / 2) + (index * 128);
}
}
public double getGeneratedNumber() {
return generatedNum;
}
public boolean setTouchBool(boolean b) {
this.onTouchHandler = b;
}
}
Android will cascade down the views calling onTouchEvent on each one until it receives a true from one of them. If you want a touch event to be handled by all of them, then return false until it reaches the last one.
EDIT:
Ok. If I understand correctly, you have a single top view containing a bunch of child views one layer deep. My original answer was assuming that you had three custom views that were on top of each other in the ViewGroup's hierarchy (View3 is a child of View2. View2 is a child of View1. View1 is a child of ParentView). You want the user's touch event on the parent view to get sent to all of it's children.
If that's the case, AFAIK, there is no view in Android's API that allows that. So, you'll have to make a custom view that does it.
OK, I haven't tested this, so please tell me if it works and if it's what you're trying. Create a custom class that extends whatever object frame is, then override the onTouch method like so.
#Override
public boolean onTouchEvent(MotionEvent ev) {
for(int i = 0; i < this.getChildCount(); i++){
this.getChildAt(i).dispatchTouchEvent(ev);
}
return true;
}
Now, keep the same logic that your custom views have, except they should all return false because your parent view will not receive the onTouch event unless they do as stated in my previous answer
note: with this implementation, the child view that the user actually touches will fire twice because the logic will go
fire child touch event -> return false -> fire parent touch event -> fire child touch event again
I know this question is very old, but I had the same problem and solved it by creating my own Layout to determine which child is actually touched.
I therefore iterate over the children of my custom layout and check if the user actually clicked on the view. The collision detection is handled in the custom view's onTouch() method. (Collision detection is done by intersecting a Region() with the event's x,y coordinates. For me this was convennient because I drew the custom view with a Path())
Here is a kotlin code snippet from my custom layout for better understanding:
class CustomLayout(context: Context, attrs: AttributeSet) :
RelativeLayout(context, attrs){
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
if(ev.action != MotionEvent.ACTION_UP){
return true
}
//Iterate over child view and search for the right child that should handle this touch event
for (i in childCount - 1 downTo 0) {
val child = getChildAt(i)
if (!viewTouched(child, ev)) {
continue
}
//Do something
Timber.d("Touched view: ${child.id}")
}
return true
}
private fun viewTouched(child: View, ev: MotionEvent) : Boolean {
child as OnTouchListener
//onTouch() does the collision detection
return child.onTouch(child, ev)
}
I have some views that I make visible upon a button press. I want them to disappear if I click outside of those views.
How would this be done on Android?
Also, I realize that the "back button" can also assist Android users with this - I might use that as a secondary way to close the views - but some of the tablets aren't even using a 'physical' back button anymore, it has been very de-emphasized.
An easy/stupid way:
Create a dummy empty view (let's say ImageView with no source), make it fill parent
If it is clicked, then do what you want to do.
You need to have the root tag in your XML file to be a RelativeLayout. It will contain two element: your dummy view (set its position to align the Parent Top). The other one is your original view containing the views and the button (this view might be a LinearLayout or whatever you make it. don't forget to set its position to align the Parent Top)
Hope this will help you, Good Luck !
Find the view rectangle, and then detect whether the click event is outside the view.
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Rect viewRect = new Rect();
mTooltip.getGlobalVisibleRect(viewRect);
if (!viewRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
setVisibility(View.GONE);
}
return true;
}
If you want to use the touch event other place, try
return super.dispatchTouchEvent(ev);
This is an old question but I thought I'd give an answer that isn't based on onTouch events. As was suggested by RedLeader it's also possible to achieve this using focus events. I had a case where I needed to show and hide a bunch of buttons arranged in a custom popup, ie the buttons were all placed in the same ViewGroup. Some things you need to do to make this work:
The view group that you wish to hide needs to have View.setFocusableInTouchMode(true) set. This can also be set in XML using android:focusableintouchmode.
Your view root, i.e. the root of your entire layout, probably some kind of Linear or Relative Layout, also needs to be able to be focusable as per #1 above
When the view group is shown you call View.requestFocus() to give it focus.
Your view group need to either override View.onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) or implement your own OnFocusChangeListener and use View.setOnFocusChangeListener()
When the user taps outside your view focus is transferred to either the view root (since you set it as focusable in #2) or to another view that inherently is focusable (EditText or similar)
When you detect focus loss using one of the methods in #4 you know that focus has be transferred to something outside your view group and you can hide it.
I guess this solution doesn't work in all scenarios, but it worked in my specific case and it sounds as if it could work for the OP as well.
I've been looking for a way to close my view when touching outside and none of these methods fit my needs really well. I did find a solution and will just post it here in case anyone is interested.
I have a base activity which pretty much all my activities extend. In it I have:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (myViewIsVisible()){
closeMyView();
return true;
}
return super.dispatchTouchEvent(ev);
}
So if my view is visible it will just close, and if not it will behave like a normal touch event. Not sure if it's the best way to do it, but it seems to work for me.
base on Kai Wang answer : i suggest first check visibility of Your view , base on my scenario when user clicked on fab myView become visible and then when user click outside myView disappears
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Rect viewRect = new Rect();
myView.getGlobalVisibleRect(viewRect);
if (myView.getVisibility() == View.VISIBLE && !viewRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
goneAnim(myView);
return true;
}
return super.dispatchTouchEvent(ev);
}
I needed the specific ability to not only remove a view when clicking outside it, but also allow the click to pass through to the activity normally. For example, I have a separate layout, notification_bar.xml, that I need to dynamically inflate and add to whatever the current activity is when needed.
If I create an overlay view the size of the screen to receive any clicks outside of the notification_bar view and remove both these views on a click, the parent view (the main view of the activity) has still not received any clicks, which means, when the notification_bar is visible, it takes two clicks to click a button (one to dismiss the notification_bar view, and one to click the button).
To solve this, you can just create your own DismissViewGroup that extends ViewGroup and overrides the following method:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ViewParent parent = getParent();
if(parent != null && parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(this);
}
return super.onInterceptTouchEvent(ev);
}
And then your dynamically added view will look a little like:
<com.example.DismissViewGroup android:id="#+id/touch_interceptor_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent" ...
<LinearLayout android:id="#+id/notification_bar_view" ...
This will allow you to interact with the view, and the moment you click outside the view, you both dismiss the view and interact normally with the activity.
Implement onTouchListener(). Check that the coordinates of the touch are outside of the coordinates of your view.
There is probably some kind of way to do it with onFocus(), etc. - But I don't know it.
Step 1: Make a wrapper view by Fragmelayout which will cover your main layout.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- This is your main layout-->
</RelativeLayout>
<View
android:id="#+id/v_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- This is the wrapper layout-->
</View>
</FrameLayout>
Step 2: Now add logic in your java code like that -
View viewOverlay = findViewById(R.id.v_overlay);
View childView = findViewByID(R.id.childView);
Button button = findViewByID(R.id.button);
viewOverlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
childView.setVisibility(View.GONE);
view.setVisibility(View.GONE);
}
});
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
childView.setVisibility(View.VISIBLE);
// Make the wrapper view visible now after making the child view visible for handling the
// main visibility task.
viewOverlay.setVisibility(View.VISIBLE);
}
});
To hide the view when click performs outside the view:
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
if (isMenuVisible) {
if (!isWithinViewBounds(ev.rawX.toInt(), ev.rawY.toInt())) {
hideYourView()
return true
}
}
return super.dispatchTouchEvent(ev)
}
create a method to get the bounds(height & width) of your view, so when you click outside of your view it will hide the view and when click on the view will not hide:
private fun isWithinViewBounds(xPoint: Int, yPoint: Int): Boolean {
val l = IntArray(2)
llYourView.getLocationOnScreen(l)
val x = l[0]
val y = l[1]
val w: Int = llYourView.width
val h: Int = llYourView.height
return !(xPoint < x || xPoint > x + w || yPoint < y || yPoint > y + h)
}
I've created custom ViewGroup to display info box anchored to another view (popup balloon).
Child view is actual info box, BalloonView is fullscreen for absolute positioning of child, and intercepting touch.
public BalloonView(View anchor, View child) {
super(anchor.getContext());
//calculate popup position relative to anchor and do stuff
init(...);
//receive child via constructor, or inflate/create default one
this.child = child;
//this.child = inflate(...);
//this.child = new SomeView(anchor.getContext());
addView(child);
//this way I don't need to create intermediate ViewGroup to hold my View
//but it is fullscreen (good for dialogs and absolute positioning)
//if you need relative positioning, see #iturki answer above
((ViewGroup) anchor.getRootView()).addView(this);
}
private void dismiss() {
((ViewGroup) getParent()).removeView(this);
}
Handle clicks inside child:
child.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//write your code here to handle clicks inside
}
});
To dismiss my View by click outside WITHOUT delegating touch to underlying View:
BalloonView.this.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
To dismiss my View by click outside WITH delegating touch to underlying View:
#Override
public boolean onTouchEvent(MotionEvent event) {
dismiss();
return false; //allows underlying View to handle touch
}
To dismiss on Back button pressed:
//do this in constructor to be able to intercept key
setFocusableInTouchMode(true);
requestFocus();
#Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
dismiss();
return true;
}
return super.onKeyPreIme(keyCode, event);
}
I want to share my solution which I think it could be useful if :
you are able to add a custom ViewGroup as root layout
also the view which you want to disappear can be a custom one.
First, we create a custom ViewGroup to intercept touch events:
class OutsideTouchDispatcherLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val rect = Rect()
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (ev.action == MotionEvent.ACTION_DOWN) {
val x = ev.x.roundToInt()
val y = ev.y.roundToInt()
traverse { view ->
if (view is OutsideTouchInterceptor) {
view.getGlobalVisibleRect(rect)
val isOutside = rect.contains(x, y).not()
if (isOutside) {
view.interceptOutsideTouch(ev)
}
}
}
}
return false
}
interface OutsideTouchInterceptor {
fun interceptOutsideTouch(ev: MotionEvent)
}
}
fun ViewGroup.traverse(process: (View) -> Unit) {
for (i in 0 until childCount) {
val child = getChildAt(i)
process(child)
if (child is ViewGroup) {
child.traverse(process)
}
}
}
As you see, OutsideTouchDispatcherLayout intercepts touch events and informs each descendent view which implenets OutsideTouchInterceptor that some touch event occured outside of that view.
Here is how the descendent view could handle this event. Notice that it must implement OutsideTouchInterceptor interface:
class OutsideTouchInterceptorView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr),
OutsideTouchDispatcherLayout.OutsideTouchInterceptor {
override fun interceptOutsideTouch(ev: MotionEvent) {
visibility = GONE
}
}
Then you have outside touch detection easily just by a child-parent relation:
<?xml version="1.0" encoding="utf-8"?>
<com.example.touchinterceptor.OutsideTouchDispatcherLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.touchinterceptor.OutsideTouchInterceptorView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#eee"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.example.touchinterceptor.OutsideTouchDispatcherLayout>
Here's a simple approach to get your work done:
Step 1: Create an ID for the outside container of your element for which you want to generate a click outside event.
In my case, it is a Linear Layout for which I've given id as 'outsideContainer'
Step 2: Set an onTouchListener for that outside container which will simply act as a click outside event for your inner elements!
outsideContainer.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// perform your intended action for click outside here
Toast.makeText(YourActivity.this, "Clicked outside!", Toast.LENGTH_SHORT).show();
return false;
}
}
);
Wrapper layout that notifies us when a click occurred outside a given view:
class OutsideClickConstraintLayout(context: Context, attrs: AttributeSet?) :
ConstraintLayout(context, attrs) {
private var viewOutsideClickListenerMap = mutableMapOf<View, () -> Unit>()
fun setOnOutsideClickListenerForView(view: View, listener: () -> Unit) {
viewOutsideClickListenerMap[view] = listener
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
viewOutsideClickListenerMap.forEach { (view, function) ->
if (isMotionEventOutsideView(view, ev)) function.invoke()
}
return super.onInterceptTouchEvent(ev)
}
private fun isMotionEventOutsideView(view: View, motionEvent: MotionEvent): Boolean {
val viewRectangle = Rect()
view.getGlobalVisibleRect(viewRectangle)
return !viewRectangle.contains(motionEvent.rawX.toInt(), motionEvent.rawY.toInt())
}
}
Usage:
....
outsideClickContainerView.setOnOutsideClickListenerForView(someView) {
// handle click outside someView
}
....
thank #ituki for idea
FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:clickable="true">
<LinearLayout
android:clickable="true" // not trigger
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#FFF"
android:orientation="vertical"
android:padding="20dp">
...............
</LinearLayout>
</FrameLayout>
and java code
mContainer = (View) view.findViewById(R.id.search_container);
mContainer.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
Log.d("aaaaa", "outsite");
return true;
}
return false;
}
});
it's work when touch outside LinearLayout