gpo
gpo

Reputation: 3689

Android non modal PopupWindow

Please note: This issue is about Android PopupWindow, not Dialog.

I'm trying to make an Android PopupWindow non modal:

I've set the touch listener on the content view in the PopupWindow to test if the event is outside of the PopupWindow, and if so to return false. I would expect then the MotionEvent to be propagated to the view where I clicked. But it seems that since those views are outside of the current window then they don't receive the event.

How can I propagate the event to the view located where I clicked even if it's outside of the PopupWindow?

public class NonModalPopupWindow extends PopupWindow {

private static final String TAG = "NonModalPopupWindow";

public NonModalPopupWindow(View contentView, int width, int height) {
    super(width, height);
    setContentView(contentView);
    contentView.setOnTouchListener(createOnTouchListener());
    setFocusable(true);
}

private View.OnTouchListener createOnTouchListener() {
    return new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            final int x = (int) event.getX();
            final int y = (int) event.getY();

            final boolean isTouchOutside = (x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight());
            if (isTouchOutside || event.getAction() == MotionEvent.ACTION_OUTSIDE) {
                Log.e(TAG, "Touched OUTSIDE");
                // How to propagate this event so that it's received by the View shown 
                // At the location where I clicked, outside of the PopupWindow? 
                return false;
            } else {
                Log.e(TAG, "Touched INSIDE");
                return true;
            }
        }
    };
}

}

Upvotes: 2

Views: 1291

Answers (3)

Phlip
Phlip

Reputation: 5343

For Android versions >=29, you can tell the PopupWindow to transmit touches to the view behind it using

myPopup.setTouchModal(false);

The touch goes thru, and the popup stays up.

I don't know if that lets you ALSO touch INSIDE the popup, because my popup has no controls. But it's still a cleaner solution than playing with lots of Listeners...

Upvotes: 0

albertdadze
albertdadze

Reputation: 59

Just an addition for DialogFragment peradventure you don't want to use an activity or Theme.Dialog

Chooser.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:layout_margin="@dimen/activity_horizontal_margin"
    android:background="@drawable/dialog_bg">

    <RelativeLayout
        android:id="@+id/layout_Camera"
        android:layout_width="0dp"
        android:layout_weight = "0.25"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/camera"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_centerHorizontal="true"
            android:src="@drawable/ic_menu_camera_holo_dark" />

        <TextView
            android:id="@+id/tvCamera"
            android:layout_margin="@dimen/margin_very_small"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:textAlignment="center"
            android:layout_below="@id/camera"
            android:text="Camera"/>

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/layout_Gallery"
        android:layout_width="0dp"
        android:layout_weight ="0.25"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/gallery"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_centerHorizontal="true"
            android:src="@drawable/ic_menu_photo_gallery" />

        <TextView
            android:id="@+id/tvGallery"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:textAlignment="center"
            android:layout_below="@id/gallery"
            android:text="Gallery"/>
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/layout_RemovePhoto"
        android:layout_width="0dp"
        android:layout_weight="0.25"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/removePhoto"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_centerHorizontal="true"
            android:src="@drawable/ic_menu_remove"/>

        <TextView
            android:id="@+id/tvRemovePhoto"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:textAlignment="center"
            android:layout_below="@id/removePhoto"
            android:text="Remove Photo"/>

    </RelativeLayout>

</LinearLayout>

Transparent background in the drawable folder

dialog_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid
        android:color="@color/off_white"/>

    <corners
        android:radius="15dp" />

    <padding
        android:left="20dp"
        android:top="20dp"
        android:right="20dp"
        android:bottom="20dp" />

    <margin
        android:left="50dp"
        android:bottom="50dp"
        android:right="50dp"
        android:top="50dp"
        />
</shape>

PhotoChooserFragment

public class photoChooserFragment
        extends DialogFragment {

    onItemSelectedListener listener;
    ImageView imgCamera, imgGallery, imgRemovePhoto;
    TextView tvCamera, tvGallery, tvRemovePhoto;

    public interface onItemSelectedListener {
        void onItemSelected(int position);
    }

    public void onItemSelected(int position) {
         listener.onItemSelected(position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Activities containing this fragment must implement its listener.
        if (!(activity instanceof onItemSelectedListener)) {
            throw new IllegalStateException(
                    "Activity must implement fragment's callbacks.");
        }

        listener = (onItemSelectedListener) activity;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.chooser, null, false);

        imgCamera = view.findViewById(R.id.camera);
        imgGallery = view.findViewById(R.id.gallery);
        imgRemovePhoto = view.findViewById(R.id.removePhoto);

        tvCamera = view.findViewById(R.id.tvCamera);
        tvGallery = view.findViewById(R.id.tvGallery);
        tvRemovePhoto = view.findViewById(R.id.tvRemovePhoto);

        Window window = getDialog().getWindow();
        window.requestFeature(Window.FEATURE_NO_TITLE);
        window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

        WindowManager.LayoutParams windowLP = window.getAttributes();
        //window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
        //       WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
        window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
        //window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        window.setGravity(Gravity.BOTTOM);
        windowLP.gravity = Gravity.BOTTOM;
        window.setAttributes(windowLP);

        getDialog().setCanceledOnTouchOutside(true);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        imgCamera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                onItemSelected(1);
            }
        });

        tvCamera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                onItemSelected(1);
            }
        });

        imgGallery.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                onItemSelected(2);
            }
        });

        tvGallery.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                onItemSelected(2);
            }
        });

        imgRemovePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                onItemSelected(3);
            }
        });

        tvRemovePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                onItemSelected(3);
            }
        });
    }

    @Override
    public void onDetach() {
        super.onDetach();
        // Reset the active listener interface to null.
        listener = null;
    }
}

Then in your activity add the interface

implements  photoChooserFragment.onItemSelectedListener

and ensure it is implemented

 @Override
    public  void onItemSelected(int position){
        switch (position){
            case 1: //camera
                GetImageFromCamera();
                break;
            case 2: //gallery
                ChooseImageFromGallery();
                break;
        }
    }

Now you can call the fragment, a button will do, in mine I used a floatingactionbutton

faBtnCamera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Show menu
                photoChooserFragment dialog = new photoChooserFragment();
                dialog.show(getSupportFragmentManager(), "chooser");
            }
        });

Upvotes: 0

wuyi
wuyi

Reputation: 227

try this:

setOutsideTouchable(true);

call this in your constructor

Upvotes: 1

Related Questions