Question

Here is how my layout looks like:

enter image description here

I have a parent activity which has a custom view (view1 that handle onTouch events by itself) and 2 buttons (view2 and view3). The DialogFragment has a visible layout shown and the rest is transparent. My dialog fragments look like this:

public class FragmentText extends DialogFragment{

  public static FragmentText newInstance() {
        FragmentText frag = new FragmentText();

        frag.setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Translucent_NoTitleBar);
        return frag;
    }

  @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);

        // instantiate the custom layout
        final View layout = inflater.inflate(R.layout.fragment_layout, null);

        .....
        }

}

and the layout file looks like this:

<com.TransparentView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layMain"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_marginBottom="70dp"
    android:background="@color/transparent"
    android:gravity="bottom"
    android:orientation="vertical"
    android:paddingBottom="70dp" >

    <LinearLayout
        android:id="@+id/layContent"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_margin="5dp"
        android:background="@drawable/bk_text_edit"
        android:orientation="vertical"
        android:padding="@dimen/margin" >

          all my layouts and buttons
    </LinearLayout>
</com.TransparentView>

and

public class TransparentView extends LinearLayout {

    @SuppressLint("NewApi")
    public TransparentView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public TransparentView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TransparentView(Context context) {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false; // event get propagated
    }

}

When the user presses outside the visible layout of the DialogFragment I want to: 1. dismiss the dialog fragment 2. pass the onTouch to the parent activity and allow the user to interact with the views.

So basically if I drag my finger over View1, I want to dismiss the dialog and continue my dragging interaction against view1.

Is this possible to achieve ?

LE: also this does not work:

layMain.setOnTouchListener(new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            getActivity().dispatchTouchEvent(event);

            return false;
        }
    });
Was it helpful?

Solution

In the end, as Luksprog suggested, I ended up with ditching DialogFragment. I did a test using simple Fragment which is hosted in an activity's FrameLayout so it looks just like an overlay on where I need it. In this way I still get all the interaction I need with the rest of the views.

Thank you all for your support.

OTHER TIPS

even though you ditched DialogFragment this might help others.

you need to get the actual decor view and intercept the touch event there:

 @Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    getDialog().getWindow().setBackgroundDrawableResource(android.R.color.transparent);


    getDialog().getWindow().getDecorView().setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            getActivity().dispatchTouchEvent(event);
            return false;
        }
    });

    return super.onCreateView(inflater, container, savedInstanceState);
}

I would use an Alert dialog instead for you to get the desired effect:

AlertDialog.Builder Dialog = new AlertDialog.Builder(this);
        Dialog.setMessage("Text you wish to enter in the dialog"));
//Use this if you would like to include a button at the bottom of the alert dialog. Otherwise just leave it blank.
        Dialog.setNeutralButton("Back to App", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
            }
        });
        Dialog.show();

Hope this helps :)

You may override boolean onInterceptTouchEvent(MotionEvent ev) in your dialog's transparent view, catch event, send it where you need, dismiss dialog and return false (which means that touch event wasn't consumed).

Or, if you don't want to override View class, you may override boolean dispatchTouchEvent (MotionEvent ev) in your dialog and try to find out where the click took place by analysing MotionEvent object.

If you are already successfully capturing the touch event in your LinearLayout, which I understand to be the transparent view surrounding the Dialog then you should be able to accomplish this with an interface back to the parent Activity and a call to a public method within your dialog class. I would make the following modifications:

First - define an interface within your TransparentView and add a public method to establish a callback to your parent activity.

public class TransparentView extends LinearLayout {

   public interface TouchCallback{
       public void onMotionEvent(MotionEvent e);
   }

   private TouchCallback mCallback;

   @SuppressLint("NewApi")
   public TransparentView(Context context, AttributeSet attrs, int defStyle) {
       super(context, attrs, defStyle);
   }

    public TransparentView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TransparentView(Context context) {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        callback.onMotionEvent(ev);
        return false; // event get propagated
    }

    public setCallback(TouchCallback callback){
        mCallback = callback;
    }

}

The idea is to catch the motion event and manually pass it back to your hosting activity. Once you pass the motion event back to the activity all you need to do is dismiss the dialog. DialogFragment already has a dismiss method built in so as long as you keep a reference to the dialog with an instance variable you should be able to do the following

@Override
public void onMotionEvent(MotionEvent e){
    mDialog.dismiss();
}

If that does not work simply add a method in your dialog class and call that. From what I can tell from your example we don't really even need to do anything with the motion event captured by the transparent view because all we want to do is close the dialog when a touch occurs. As the callback should only be activated when a touch happens there's nothing to check for (though you could expand this later to implement a Handler and only close the dialog if the touch lasted for a specified amount of time).

I had the same problem with BottomSheetDialog. I solved my problem:

final BottomSheetDialog dialog = getDialog();
final View touchOutside = dialog.findViewById(R.id.touch_outside);
touchOutside.setOnTouchListener((v, event) -> {
    getActivity().dispatchTouchEvent(event);
    return false;
});

So looks like solution of Finn should work correctly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top