I have this custom view that is being added to the WindowManager as a System Alert which should function as a fullscreen overlay to block certain parts of the phone when the app is running.
This works great when the overlay is shown it blocks the phone window fullscreen. Now I want to support rotation changes.
This part also works fine, I have 3 layout files in layout
layout-land
and layout-h600dp-land
and when I rotate the phone it changes to the correct layout. The problem I have with this is after onConfigurationChanged(Configuration newConfig)
is called and I inflate the layout again all the click listeners are gone so none of the buttons in the views react to clicks. The button's pressed state does change when I tap on them but none of the onClickListeners are being triggered. Before the orientation change all the buttons do work. I have been using Butterknife to reduce the boilerplate code for onClicklisteners and findViewById's but I already changed that to findViewById and adding a clicklistener on the view manually which doesn't make a difference.
And now for some code. The service adds the view to the windowmanager
public class OverlayService extends Service {
public void showOverlay() {
startForeground();
mUiHandler.post(new Runnable() {
@Override
public void run() {
if (!DrivingOverlay.isShowing()) {
if (mOverlay == null) {
mOverlay = new Overlay(OverlayService.this);
mOverlay.setTag(OVERLAY_TAG);
mOverlay.setId(R.id.overlay);
}
addView(mOverlay);
}
});
}
private void addView(@NonNull final View view) {
try {
final WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
windowManager.addView(view, Overlay.LAYOUT_PARAMS);
} catch (IllegalStateException error) {
Log.e(TAG, Log.getStackTraceString(error));
}
}
}
The Custom View which is the overlay.
public class Overlay extends FrameLayout {
private static boolean sIsOverlayShowing;
public static final WindowManager.LayoutParams LAYOUT_PARAMS = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
public Overlay(Context context) {
super(context);
init(context);
}
public Overlay(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public Overlay(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public Overlay(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
protected void init(Context context) {
inflate(context, R.layout.driving_overlay, this);
if (!isInEditMode()) {
ButterKnife.bind(this);
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(TAG, "onConfigurationChanged");
init(getContext());
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setIsOverlayShowing(true);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
setIsOverlayShowing(false);
}
@Override
public void onViewAttachedToWindow(View v) {
setIsOverlayShowing(true);
}
@Override
public void onViewDetachedFromWindow(View v) {
setIsOverlayShowing(false);
}
public static boolean isShowing() {
return sIsOverlayShowing;
}
public static void setIsOverlayShowing(boolean isOverlayShowing) {
sIsOverlayShowing = isOverlayShowing;
}
}
Finally figured it out.
Calling inflate(context, R.layout.driving_overlay, this)
in the init adds the view that is being inflated to the root view in this case to Overlay
which is a FrameLayout
so each rotation was inflating a new layout and adding it to the root view so after the a rotation the Overlay class had more than 1 child view. The OnClickListeners would bind the child views button's at position 0 to the OnClickListeners in the class and it would show the child view that was newly inflated at position 1 or higher. So after adding this a check and removing obsolete views the OnClickListeners are working again.
protected void init(Context context) {
if (getChildCount() >= 1) {
removeViewAt(0);
}
final View view = inflate(context, R.layout.driving_overlay, this);
if (!isInEditMode()) {
ButterKnife.bind(this, view);
}
}