Fullscreen translucent custom Dialog that ignores touches and dispatches them to underlaying views

StackOverflow https://stackoverflow.com/questions/23673762

Domanda

The title can be a little misleading, but I can't put my question in one line.

The Idea

I am building an on-screen tutorial for my application that shows a message on the top part of every activity with some instructions. The message should overlay the ABS and everything and it takes about 20% from the top of the screen. In order to overlay the ABS and everything else, i created a custom dialog that has a translucent background and spans the entire screen.

This is the code for the Custom Dialog

public class TutorialDialog extends Dialog
{

public TutorialDialog(final Context context)
{   
    super(context);   
}


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);


    this.requestWindowFeature(Window.FEATURE_NO_TITLE);
    // Making dialog content transparent.
    this.getWindow().setBackgroundDrawable(
            new ColorDrawable(Color.TRANSPARENT));
    // Removing window dim normally visible when dialog are shown.
    this.getWindow().clearFlags(
            WindowManager.LayoutParams.FLAG_DIM_BEHIND);


    // Setting position of content, relative to window.
    WindowManager.LayoutParams params = this.getWindow().getAttributes();
    params.gravity = Gravity.TOP | Gravity.LEFT;
    params.x = 100;
    params.y = 20;
    // If user taps anywhere on the screen, dialog will be cancelled.
    this.setCancelable(false);

    this.setContentView(R.layout.tutorial_overlay_dialog);  





}

The XML file for the Dialog is pretty simple, with a text box and a Close button:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<TextView
    android:id="@+id/txtHint"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:background="@android:color/darker_gray"
    android:padding="3dp"
    android:text="Explain what to do here"
    android:textColor="@android:color/white" />

  <Button
    android:id="@+id/btnTutorialClose"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:layout_alignParentRight="true"
    android:text="Close" />

</RelativeLayout>

The Problem

I want the Custom Dialog to ignore the touch events and forward them to the views behind the dialog. I could do this by adding this line to the creation of the Dialog

getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);

However, this line will forward ALL touch events, and i want to have a close button in the dialog that will dismiss the tutorial.

TL;DR version

I want to intercept all touch events in my Dialog, except the ones that are inside my "Close" button, and forward all the rest to the underlaying views.

If my question sounds a bit complicated please ask me and i'll explain more about any part. Thank you very much !

Update

According to this SO question, if i override the onTouchEvent method in my custom dialog and return false for all touch events, they will be handled by the "Children views". I tried it and it didn't work, because i think the underlaying views are not exactly children views, are they ?

È stato utile?

Soluzione

I figured out how to solve this issue, thanks to help from different people.

In order to intercept all touch event, process them (determine where the touch event occured) and then pass them to the underlaying Activity, I had to override the dispatchTouchEvent

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
    case MotionEvent.ACTION_UP: {
        if (isClose) {
            // Tests if the touch event was inside the Close button boundries
            if (Utils.inViewBounds(btnClose, (int) ev.getRawX(),(int) ev.getRawY())) {
                this.dismiss();
                return true;
            }

        }

        else {
            isClose = false;
            callingActivity.dispatchTouchEvent(ev);
        }

        return false;
    }
    case MotionEvent.ACTION_DOWN: {
        if (Utils.inViewBounds(btnClose, (int) ev.getRawX(),
                (int) ev.getRawY())) {
            isClose = true;
            return true;
        }

        else {
            callingActivity.dispatchTouchEvent(ev);
        }

        return false;

    }

    }

    callingActivity.dispatchTouchEvent(ev);
    return false;
}

The important code in this section is the callingActivity.dispatchTouchEvent(ev) This code will propagate the touch event to the Activity that started the dialog. Then the Activity can process the touch event in a normal way.

Altri suggerimenti

Since your using the flag FLAG_NOT_TOUCHABLE, All the events are passed on so you cant really control which view should intercept it because the flag is working in the context of the entire window.

But however there is a work around

  1. Get the Coordinates of the button on the screen with the size of the button to map a rectangular region on the screen.
  2. Now Add a Touch listener or Gesture listener and get the position of the touch events on your screen, If the touch event is inside the rectangular region that you mapped earlier of the button then call the buttons click listener programmatically like this btnTutorialClose.performClick();

EDIT

1> Try extending any layout and overrode onInterceptTouchEvent to see if you get any events

2> Attach a listener on the onCreateView like this

Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_slide, container, false);
    LinearLayout layout = (LinearLayout)rootView.findViewById(R.id.layout)// get your root  layout
    layout.setOnTouchListener(new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return false;
        }
    });
return rootView;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top