How to draw over view content via PopupWindow in Android

PopupWindow

From API level 1 and onward you can place your content into a PopupWindow, which is a new temporary window in which you can place views that will be displayed on top of the current activity window. PopupWindow can be shown anywhere onscreen, either by providing an explicit location or by providing an existing view that the PopupWindow should be anchored to.

PopupWindow is used to show floating view on display at specified position, but without inserting or otherwise modifying the existing view hierarchy. It’s a floating container that appears on top of current activity. PopupWindow can have their own layout and can be set after inflating with setContentView(View).

From API level 18 and onward you can use the newer ViewOverlay to draw content on top of your views. ViewOverlay allows you to add any number of Drawable objects to a private layer managed by the parent view. Those objects will be drawn on top of the corresponding view as long as their bounds are within the bounds of the parent.

In order to draw content on top of our view hierarchy, we first need to create the content to display. Following listing (file res/layout/popup.xml) constructs a simple group of views that will be the content of our PopupWindow.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="This is a PopupWindow" />
    <EditText
    android:layout_width="250dp"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal" />
    <Button
    android:id="@+id/btnClose"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="Close" />
</LinearLayout>

When we display this content in a pop-up anchored to a view, by default the PopupWindow will display just below the view, left-aligned. However, if there is not enough space below the view to display the PopupWindow, it will be displayed above the anchor view instead. To make the pop-up visually distinct in both cases, we can provide a custom background drawable that switches on the android:state_above_ anchor attribute. Following listing (file res/drawable/popup_background.xml) illustrates the custom drawables we will be using for this example.

<?xml version="1.0" encoding="utf-8"?>
<selector
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:state_above_anchor="true"
        android:drawable="@drawable/android_popup_bg_top" />
    <!-- Default State -->
    <item
        android:drawable="@drawable/android_popup_bg_bottom" />
</selector>
android_popup_bg_bottom.9.png android_popup_bg_top.9.png

Following listings (file res/layout/activity_main.xml) illustrates an example activity and layout that construct and display a PopupWindow in response to a button click. In this example, the PopupWindow will be shown anchored to the button that was clicked.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
    <Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Show PopupWindow"
    android:onClick="onShowWindowClick" />
    <Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:text="Show PopupWindow"
    android:onClick="onShowWindowClick" />
</FrameLayout>

Some words about events in PopupWindow.

  • setTouchInterceptor. Default behavior is not to allow any elements in the PopupWindow to be interactive, but to enable touch events to be delivered directly to the PopupWindow. All outside touches will be delivered to the main Activity window.
  • setFocusable. Call setFocusable() to enable elements in the PopupWindow to take focus, which will also enable the behavior of dismissing the PopupWindow on any outside touch.
  • setOutsideTouchable. Call setOutsideTouchable() if you want to enable outside touches to auto-dismiss the PopupWindow but don't want elements inside the PopupWindow to take focus.

Following is activity displaying a PopupWindow.

public class MainActivity extends Activity
    private PopupWindow popup;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //inflate the popup content layout; we do not have access
        //to the parent view yet, so we pass null as the
        //container view parameter.

        View popupContent = getLayoutInflater().inflate(R.layout.popup, null);
        popup = new PopupWindow();

        //popup should wrap content view
        popup.setWindowLayoutMode(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT);
        popup.setHeight(250);
        popup.setWidth(350);

        //set content and background
        popup.setContentView(popupContent);
        popup.setBackgroundDrawable(getResources().getDrawable(R.drawable.popup_background));

        popupContent.findViewById(R.id.btnClose).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                popup.dismiss();
            }
        });

        popup.setTouchInterceptor(this);
        popup.setFocusable(true);
        popup.setOutsideTouchable(true);
    }

    @Override
    protected void onPause() {
        super.onPause();
        popup.dismiss();
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //Handle direct touch events passed to the PopupWindow
        return false;
    }

    public void onShowWindowClick(View v) {
        if (popup.isShowing()) {
            popup.dismiss();
        } else {
            //Show the PopupWindow anchored to the button we
            //pressed. It will be displayed below the button
            //if there's room, otherwise above.
            popup.showAsDropDown(v);
        }
    }
}

In this example, we create a simple layout with two buttons, both set to trigger the same action. When either button is clicked, the PopupWindow will be displayed anchored to that view by using the showAsDropDown() method.

A PopupWindow can also be shown at a specific location by using its showAtLocation() method instead. Similar to showAsDropDown() , this method takes a View parameter, but it is used only to get window information.

popup.showAtLocation(layout, Gravity.CENTER, 0, 0);

After playing with the previous example, you may have noticed that the PopupWindow has a default animation associated with it when it is shown or dismissed. This can be customized or removed by passing a new resource via setAnimationStyle(). This method takes a resource ID referencing a style that defines a pair of animations, one for the window entrance and another for the window exit. So, set a custom animation enter/exit pair, or 0 to disable animations. You can also use animation styles defined in the platform, such as android.R.style.Animation_Toast.

Following listing (file res/values/styles.xml) illustrates the style resource we need to create in order to customize the `PopupWindow animation.

<resources>
<!-- Define this element below any existing themes -->
<style name="PopupAnimation">
    <item name="android:windowEnterAnimation">
        @android:anim/slide_in_left</item>
    <item name="android:windowExitAnimation">
        @android:anim/slide_out_right</item>
</style>
</resources>

Add following snippet to MainActivity.java file.

popup.setAnimationStyle(R.style.PopupAnimation);

Result

activity_popup.png

ViewOverlay

From API level 18 and onward you can draw content over your views via ViewOverlay. ViewOverlay, and its cousin ViewGroupOverlay, allows you to add any number of drawable objects to be drawn on top of the view. Applications cannot create a ViewOverlay directly, and instead obtain a ViewOverlay by calling getOverlay() on any view in the hierarchy. Views are constrained to drawing within their bounds, so any content in an overlay whose location extends outside the hosting view’s bounds will be clipped.

comments powered by Disqus