How to handle touch outside the view in Android?

限于喜欢 提交于 2019-11-30 15:04:42

问题


I've found implementation of "Undo Bar" used in Gmail application for Android. "UndoBar" is basically a View that is displayed on top of the layout.

Unfortunately it's not complete - it has no functionality of dismissing bar by touching screen outside the bar.

I've implemented FrameLayout that overrides onInterceptTouchEvent to handle bar dismissing but touching Action Bar does nothing.

Is there any way to handle such events from Action Bar?

Below there is an Image with "UndoBar"shown. What I want to achieve to handle touch in Action bar represented by red dot.


回答1:


Try to override dispatchTouchEvent of your activity.

dispatchTouchEvent(MotionEvent event){

     int x= event.getRawX();
     int y= event.getRawY();


         if(/*check bounds of your view*/){
          // set your views visiblity to gone or what you want. 
         }
      //for prevent consuming the event.
      return super().dispatchTouchEvent(event);    
}

update

dispatchTouchEvent(MotionEvent event){

     int x= event.getRawX();
     int y= event.getRawY();

      return super().dispatchTouchEvent(event)||[YourView].onTouch(event);    
}



回答2:


To catch touch events for the whole screen including the ActionBar add a view to the Window.

View overlayView = new View(this);
WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.gravity = Gravity.TOP;
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
p.token = overlayView.getWindowToken();
overlayView.setOnTouchListener(new OnTouchListener() {
    public boolean onTouch(View view, MotionEvent event) {
        // Get the action bar
        int actionBarHeight = actionBar.getHeight();
        if ((event.getAction() == MotionEvent.ACTION_DOWN)
            && (event.getRawY() < actionBarHeight)) {
            // Touch inside the actionBar so let's consume it
            // Do something
            return true;
        }
        return false;
    }
});
WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mWindowManager.addView(overlayView, p);

Hope this helps.




回答3:


You should just override Activity's dispatchTouchEvent(ev: MotionEvent) method


Look at this example of dismissing indefinite Snackbar

class MainActivity : AppCompatActivity() {

private var snackbar: Snackbar? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)

    // show indefinite snackbar
    snackbar = Snackbar.make(coordinator_layout, "Hello world!", Snackbar.LENGTH_INDEFINITE).apply {
        show()
    }
}

/**
 * On each touch event:
 * Check is [snackbar] present and displayed
 * and dismiss it if user touched anywhere outside it's bounds
 */
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    // dismiss shown snackbar if user tapped anywhere outside snackbar
    snackbar?.takeIf { it.isShown }?.run {
        val touchPoint = Point(Math.round(ev.rawX), Math.round(ev.rawY))
        if (!isPointInsideViewBounds(view, touchPoint)) {
            dismiss()
            snackbar = null // set snackbar to null to prevent this block being executed twice
        }
    }

    // call super
    return super.dispatchTouchEvent(ev)
}

/**
 * Defines bounds of displayed view and check is it contains [Point]
 * @param view View to define bounds
 * @param point Point to check inside bounds
 * @return `true` if view bounds contains point, `false` - otherwise
 */
private fun isPointInsideViewBounds(view: View, point: Point): Boolean = Rect().run {
    // get view rectangle
    view.getDrawingRect(this)

    // apply offset
    IntArray(2).also { locationOnScreen ->
        view.getLocationOnScreen(locationOnScreen)
        offset(locationOnScreen[0], locationOnScreen[1])
    }

    // check is rectangle contains point
    contains(point.x, point.y)
}
}        

Or check out the full gist




回答4:


Have you tried an onTouchEvent on your FrameLayout..?

I haven't tried it but i think its makes sense to me that in case of MotionEvent on that layout ACTION_OUTSIDE action should get fired.

Try the following.

public boolean onTouchEvent(MotionEvent event)  
{  

       if(event.getAction() == MotionEvent.ACTION_OUTSIDE){  

             undoBar.setVisibility(View.GONE);
       }  
       return true;  
}  



回答5:


There are 2 possible solutions.

As I mentioned in the comments you can either implement a scroll listener on your ListView and simple called hideUndoBar(true) on the slightest scroll.

OR

You can modify the UndoBarController. You'll notice that the undo bar is simply a View slap a OnFocusChange listener onto the View in the constructor and in the show method setFocus to the view.

In your OnFocusChange check to see if the view has lost focus and call hideUndoBar(true).

Update

I've created a Gist here https://gist.github.com/atgheb/5551961 showing how to change the UndoBarController to add the feature where it hides when it loses focus.

I haven't test it but I dont see why it won't work.




回答6:


params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                PixelFormat.TRANSPARENT);

what you actually need is :

WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH

Documentation says:

Window flag: if you have set FLAG_NOT_TOUCH_MODAL, you can set this flag to receive a single special MotionEvent with the action MotionEvent.ACTION_OUTSIDE for touches that occur outside of your window.




回答7:


I wasn't satisfied with any of the answers I found here. My solution is to attach a touch listener to the parent view like so:

((View)getParent()).setOnTouchListener((v, event) -> {
    return true;
});



回答8:


You can get all the touch events in your Activity by using the below code. I guess this code would get the touches from ActionBar as well.

public class MainActivity extends Activity {
...
// This example shows an Activity, but you would use the same approach if
// you were subclassing a View.
@Override
public boolean onTouchEvent(MotionEvent event){ 

    int action = MotionEventCompat.getActionMasked(event);

    switch(action) {
        case (MotionEvent.ACTION_DOWN) :
            Log.d(DEBUG_TAG,"Action was DOWN");
            return true;
        case (MotionEvent.ACTION_MOVE) :
            Log.d(DEBUG_TAG,"Action was MOVE");
            return true;
        case (MotionEvent.ACTION_UP) :
            Log.d(DEBUG_TAG,"Action was UP");
            return true;
        case (MotionEvent.ACTION_CANCEL) :
            Log.d(DEBUG_TAG,"Action was CANCEL");
            return true;
        case (MotionEvent.ACTION_OUTSIDE) :
            Log.d(DEBUG_TAG,"Movement occurred outside bounds " +
                    "of current screen element");
            return true;      
        default : 
            return super.onTouchEvent(event);
    }      
}


来源:https://stackoverflow.com/questions/16155285/how-to-handle-touch-outside-the-view-in-android

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!