I have created a button at the center of the layout with width 50dp.If I touch the screen at left extreme side of the layout(where the is no button) and hold down (Keep pressing ) till I reach the button in center then it should detect the touch .How can I do that .I have tried both onCLicklistener() and onTouchListener() but I still cannot detect the touch .
Basically like gesture but I thought button had a way of detecting that.
button.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// Toast text : entered within button
return false;
}
}))
If we set Click or Touch listener to Button which is 50dp width at center, then the listener will get callback only if the user click/touch initiating from button.
While in your problem case, You are initiating click/touch from outside and coming to button.
So, button listener will not get callback from Android Framework
For your requirement to work, we need to add some logic, i have tried to add it here :
activity_main.xml :
<RelativeLayout android:id="#+id/parentLayout" ... >
<Button android:id="#+id/buttonCenter ... />
</RelativeLayout>
MainActivity.java :
// apply touch listener to parentLayout
button = (Button) findViewById(R.id.buttonCenter);
parent = (RelativeLayout) findViewById(R.id.parentLayout);
parent.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("tag","in onTouch...");
checkTouch(event);
return true;
}
});
// check if touch entered button area
// save button left, right, top and bottom edge
// update : This is API i found on google documentation
float[] params;
button.getLocationOnScreen(params);
public void checkTouch(MotionEvent event) {
x = event.getX();
y = event.getY();
if(x >= param[0] && x <= (param[0]+button.getWidth())) {
if(y >= param[1] && y <= (param[1]+button.getHeight())) {
Log.d("tag","this touch is in button area");
// do what you want to do when touch/click comes in button area
}
}
}
Actually when you place a touch MotionEvent.ACTION_DOWN event is dispatched on the view which you touch and as you move MotionEvent.ACTION_MOVE events will be dispatched for every pixel point moved and until you release viz. until MotionEvent.ACTION_UP event is dispatched on that same view the events will not be handed over to any other view.
That is why it is not detecting on the button since it is actually the event of its parent view. So write an ontouchlistener on the parent and in that handle the touch according to the event.getRawX() & event.getRawY() positions in pixels.
I have a RecyclerView with a LinearLayoutManager and an Adapter:
#Override public int getItemViewType(int position) {
return position == 0? R.layout.header : R.layout.item;
}
Here's header.xml:
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="#dimen/header_height"
/>
What I want to achieve is to have a hole in the RecyclerView where I can click through to anything behind the RecyclerView. I tried a lot of combinations the following attributes to no avail:
android:background="#android:color/transparent"
android:background="#null"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:longClickable="false"
How could I make the first item transparent (allowing touch events to anything behind it) in the list, but let it still occupy the space?
You could set the OnClickListener/OnTouchListener of all of your "holes" to the parent of the view behind the RecyclerView and delegate any of the MotionEvent and touch events to that parent ViewGroup's touch handling.
Update by TWiStErRob:
class HeaderViewHolder extends RecyclerView.ViewHolder {
public HeaderViewHolder(View view) {
super(view);
view.setOnTouchListener(new OnTouchListener() {
#Override public boolean onTouch(View v, MotionEvent event) {
// http://stackoverflow.com/questions/8121491/is-it-possible-to-add-a-scrollable-textview-to-a-listview
v.getParent().requestDisallowInterceptTouchEvent(true); // needed for complex gestures
// simple tap works without the above line as well
return behindView.dispatchTouchEvent(event); // onTouchEvent won't work
}
});
There's nothing special needed in the XML, just plain views (none of the attributes in the question).
v.getParent() is the RecyclerView and that long method stops it from starting a scroll gesture while holding your finger on the "hole".
My "hole" view in the list also has a semi-transparent background, but that doesn't matter because we're hand-delivering the touches.
I have a problem I am struggling with a while now.
I have a Layout with a Button and a container in it.
<FrameLayout ... >
<Button
android:id="#+id/button"
...
/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/overlayContainer"/>
</FrameLayout>
My goal is that as I long-press the button, I attach a custom view MyCustomViewto the container and keep the finger pressed.
All the following (ACTION_MOVE, ACTION_UP) events should then ideally be dispatched to and evaluated by MyCustomView.
MyCustomView works like a circular flyout menu: it overlays, dims the background, and shows some options. You then slide your pressed finger to the option, lift it up, and it triggers a result.
mButton.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
// attach custom view to overlayContainer
// simplified code for demonstration
overlayContainer.addView(new MyCustomView());
return true;
}
});
Right now I don't see any option to "steal" the ACTION_DOWN-Event (which is required to start the event flow to a view) from the Button as I'm above it.
Nor does it work to manually generate and dispatch a ACTION_DOWN-Event in MyCustomView as I attach it.
While researching I found this post here, it basically is the same requirement, but for iOS (also does not provide an elegant solution, other that an click capturing overlay view) ): How to preserve touch event after new view is added by long press
Note that I want to avoid some kind of global overlay over the main view, I would like the solution to be as pluggable and portable as possible.
Thanks for any suggestions.
To answer my own question after the hint in the comments:
I solved it using a bare stripped version of TouchDelegate (had to extend it, since it unfortunetaly is no interface - setTouchDelegate only accepts TouchDelegate (sub)classes. Not 100% clean, but works great.
public class CustomTouchDelegate extends TouchDelegate {
private View mDelegateView;
public CustomTouchDelegate(View delegateView) {
super(new Rect(), delegateView);
mDelegateView = delegateView;
}
public boolean onTouchEvent(MotionEvent event) {
return mDelegateView.dispatchTouchEvent(event);
}
}
Then in my onLongClick method:
mButton.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
// attach custom view to overlayContainer, simplified for demonstration
MyCustomView myMenuView = new MyCustomView()
mButton.setTouchDelegate(new CustomTouchDelegate(myMenuView));
// What's left out here is to mButton.setTouchDelegate = null,
// as soon as the temporary Overlay View is removed
overlayContainer.addView(myMenuView);
return true;
}
});
This way, all my ACTION_MOVE events from the Button are delegated to MyCustomView (and may or may not need some translation of the coordinates) - et voilĂ .
Thanks to pskink for the hint.
I'm having a FrameLayout that has an extended ImageView (github) as a child. When I set an onClick()-Event to the FrameLayout it won't be triggered. The reason appears to be the onTouch() method's return value.
If I set the ACTION_DOWN's return value to false the event is passed along properly - but then the Multitouch functionalities break. Also running performClick() in the ACTION_UP event comes to nothing.
How to handle those events correctly?
Currently I solved the issue by manually passing the click event to the view's parents:
new GestureDetector( context, new GestureDetector.SimpleOnGestureListener()
{
#Override
public boolean onSingleTapConfirmed( MotionEvent event )
{
View view = MultitouchImageView.this;
if( !performClick() )
{
while( view.getParent() instanceof View )
{
view = (View) view.getParent();
if( view.performClick() )
{
return true;
}
}
return false;
}
return true;
}
...
}
Doing the same thing for an occuring LongPress event. It works the way I need it but I don't like the solution..
Try setting the parent FrameLayout to receive touches before the ImageView child.
You can do this by adding
android:descendantFocusability="beforeDescendants"
to the xml of the parent.
e.g.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:descendantFocusability="beforeDescendants">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
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