Aegis
Aegis

Reputation: 5791

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

Upvotes: 0

Views: 587

Answers (2)

Aegis
Aegis

Reputation: 5791

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

Upvotes: 1

Prashant
Prashant

Reputation: 1056

Assuming you have not restricted the Restarting of activity due to change in
the orientation. When a device s configuration gets changed in your case    
Orientation, its user interface has to be updated according to the new   
configuration. 

Being the primary component for interaction, it can be updated with some      
attributes to handle changes. Default behavior of Activity when device gets 
rotated is it gets destroyed and restarted again. This may be your issue.

To handle the rotation of activity use android:configChanges = "orientation"  
attribute in manifest for your activity.

Upvotes: 0

Related Questions