Search code examples
androidandroid-layoutandroid-custom-viewbutterknife

clicklisteners in a customview after onConfigurationChanged


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;
    }
}

Solution

  • 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);
            }
    }