Dispatch Touch event from DialogFragment view to parent activity view
Asked Answered
D

6

6

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;
        }
    });
Deponent answered 3/6, 2014 at 20:27 Comment(1)
You may want to drop the DialogFragment and implement it as a floating View directly in your custom view(a sub child) that handles the touch events. If I'm not mistaken you can't pass those touches like you're trying to do now.Oleum
D
3

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.

Deponent answered 6/6, 2014 at 22:27 Comment(2)
My original thought was of a simple view(for easier positioning) but you can use a Fragment. A DialogFragment could also be embedded in the layout(like a normal fragment) by not using the show() method and using instead a FragmentTransaction(so you can use the current class that you have).Oleum
Please add this as an answer so I can assign the bounty to you.Deponent
M
4

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.

Multiform answered 17/10, 2019 at 8:10 Comment(1)
Yes and not, this solution is not flawless - at least for me when I tap on the input underneath this touch_outside it does not show keyboard... Does anyone have a solution?Koontz
D
3

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.

Deponent answered 6/6, 2014 at 22:27 Comment(2)
My original thought was of a simple view(for easier positioning) but you can use a Fragment. A DialogFragment could also be embedded in the layout(like a normal fragment) by not using the show() method and using instead a FragmentTransaction(so you can use the current class that you have).Oleum
Please add this as an answer so I can assign the bounty to you.Deponent
A
2

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);
}
Airfield answered 6/6, 2019 at 12:19 Comment(1)
@Finn Marquardt I have a similar use case. Doesn't your getDialog().getWindow() methods work within the DialogFragment dimensions? I thought the question was about capturing a touch outside of the DialogFragment.Aforethought
H
0

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 :)

Hebetic answered 6/6, 2014 at 6:55 Comment(1)
Sorry, but this is not an option for me. I have a complex layout on dialogfragment and callbacks to parent activity.Deponent
A
0

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.

Analyse answered 6/6, 2014 at 10:42 Comment(1)
The first idea sounded really good and seemed to be exactly what I need... but it is not working. I have updated the question with some sample code. My guess is that the dialog fragments acts different from other views.Deponent
H
0

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).

Henderson answered 6/6, 2014 at 21:46 Comment(2)
Thanks Rarw for your answer. I implemented your answer but it does not work as I need it to. The activity does receive the callback but the actual touch does not go to activity's view, so basically no interaction with the buttons.Deponent
No problem. You can do this with your original set up. I've done it before. The issue seems to be that you are not actually capturing the touch as you think. Some log tags will show where the touch stops and we can trouble shoot from there. I saw you switched to fragment but there should be no need to do so.Henderson

© 2022 - 2024 — McMap. All rights reserved.