As a quick solution for our problem Android SDK 11 offers us set of holographic themes (more commonly known as themes) among which there are Theme_Holo_Dialog and Theme_Holo_DialogWhenLarge (as well as few derived from them themes stylized for light colors and w.o. action bar). We won't deepen in these topics - I think every one faced with these stuff (besides, internal implementation of these themes deserves own investigation). Just note that using these themes we got quite nice dialogs stylized for holographic themes.
This dialog boxes were quite limited in styling - to perform even small changes in dialog positioning or appearance sometimes developer had to engaged in a tedious search for correct property in appropriate style. Obviously, we can't have 2 activity in foreground, so the question about the 2 open dialogs at the same time no longer relevant. Also do not forget the 'heaviness' of Activities. But support of activity may be a partial compensation for previous inconvenience.
Let's look at alternative approach (in my opinion unjustly deprived of attention) - PopupWindow. Been present from very first sdk, PopupWindow wasn't very popular (at least, I could not find more information than simple example how to create simple popup window). In my opinion, this is very configurable container, which gives us much more advantages than activity-dialog, so let's look on it closer.
First of all, there are 2 views to hold content:
private View mContentView;
private View mPopupView;
Depending on whether was mBackground provided or not (using PopupWindow#setBackgroundDrawable) mPopupView will be assigned whole mContentView or even composite PopupViewContainer (FrameLayout actually):
private void preparePopup(WindowManager.LayoutParams p) {
...
if (mBackground != null) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
int height = ViewGroup.LayoutParams.MATCH_PARENT;
if (layoutParams != null &&
layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
// when a background is available, we embed the content view
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.setBackgroundDrawable(mBackground);
popupViewContainer.addView(mContentView, listParams);
mPopupView = popupViewContainer;
} else {
mPopupView = mContentView;
}
...
}
PopupViewContainer is a key point to answer on frequently asked question 'why do we need set setBackgroundDrawable to close our popup window when outside area is touched?' - PopupViewContainer is listening to the touch event and dismisses popup window:
@Override
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
if ((event.getAction() == MotionEvent.ACTION_DOWN)
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
return true;
} else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
dismiss();
return true;
} else {
return super.onTouchEvent(event);
}
}
Content of PopupWindow is added to Activity by WindowManager (ViewManager#addView method is used). We are not limited in number of PopupWindows that can be added to page. But since ViewManager#addView creates new 'layer' for every PopupWindow they will be closed (in case of touch at outside area) in reverse order about how they were added.From the fact that content of PopupWindow is View follows that we can apply animations to dialog box (using, for example, View#startAnimation). It may be useful when we want to show/hide dialog box with move effect or something like this (this often happens when we copy the behavior of ios apps :))
Finally, very often problem when you tried to create to show PopupWindow from activity lifecycle method:
E/AndroidRuntime( 524): java.lang.RuntimeException: Unable to resume activity {packagename/packagename.ActivityName}: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
For those who haven't faced this problem here is solution: add your popup window showing to the message queue (using post method) to be run after all the lifecycle methods finish:
viewRoot.post(new Runnable() {
public void run() {
popupWindow.showAtLocation(viewRoot, Gravity.TOP | Gravity.LEFT, 0, 0);
}
});
Looks like this is essential requirement. I couldn't find explanation why this happens, looks like we have to wait until everything in Activity is set up. Summing up, I want to say, that PopupWindow is a great choice when you need lightweight (and configurable) mechanism to communicate with user.
No comments:
Post a Comment