AnderWeb / discreteSeekBar

Apache License 2.0
2.11k stars 402 forks source link

Using discreteSeekBar with PopupWindow #16

Open paynd opened 9 years ago

paynd commented 9 years ago

Hi! I found the bug when I tried to use this widget into my floating menu that was implemented using PopupWindow.

WindowManager BadTokenException: Unable to add window -- token android.view.ViewRootImpl is not valid; is your activity running?

It fall on PopupIndicator:166 when I tried to

 mWindowManager.addView(mPopupView, p);

Looks like parent view`s token (so parent are Popupwindow) are wrong. When I tried to use as parent other view (for example root view of my activity) I got wrong placement of PopupIndicator. so it goes :(

AnderWeb commented 9 years ago

Providing a sample code to reproduce the issue would be very helpful. I don't understand the "When I tried to use as parent other view (for example root view of my activity) I got wrong placement of PopupIndicator" part.

paynd commented 9 years ago

Sorry for my bad english. Code to reproduce:

    private PopupWindow mShapeDialog = null;
    public void showShapeSettingsDialog() {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        LinearLayout container = (LinearLayout) inflater.inflate(R.layout.dialog_shape_settings, null);
//      mShapeDialog = new PopupWindow(getContext());
        mShapeDialog = new PopupWindow( SmartObjLayer.this, 1000, 1500, true);

        DiscreteSeekBar discreteSeekBar1 = (DiscreteSeekBar) container.findViewById(R.id.discrete1);
        discreteSeekBar1.setProgress(Math.round(mSelectedView.getCurrentFillColorAlpha() * 100 / 255));
        mShapeDialog.showAtLocation(SmartObjLayer.this, Gravity.NO_GRAVITY, 0, 0);
    }

I`ve tried to fix it by adding this to DiscreteSeekBar

    private View tokenView = null;
    public void setTokenView(View tokenView) {
        this.tokenView = tokenView;
    }

and modifying this method:

   private void showFloater() {
         if (!isInEditMode()) {
             mThumb.animateToPressed();
            if(tokenView!=null){
                mIndicator.showIndicator(tokenView, mThumb.getBounds());
            }else {
                mIndicator.showIndicator(this, mThumb.getBounds());
            }
             notifyBubble(true);
         }
     }

As a tokenView I used my root view. I get rid of BadTokenException but PopupIndicator was placed in wrong place.

AnderWeb commented 9 years ago

Oh, I see. A PopupWindow cannot be parent of another PopupWindow so you need a bit of hackery here, so both PopupWindows use the same token:

Instead a method to pass a "View tokenView", create a method to pass an "IBinder windowToken"

    private IBinder token = null;
    public void setToken(IBinder token) {
        this.token = token;
    }

then, showFloater would be this way:

    private void showFloater() {
        if (!isInEditMode()) {
            mThumb.animateToPressed();
            if (token != null) {
                mIndicator.showIndicatorWithToken(this, mThumb.getBounds(), token);
            } else {
                mIndicator.showIndicator(this, mThumb.getBounds());
            }
            notifyBubble(true);
        }
    }

You need to create this method inside PopupIndicator.java

    public void showIndicatorWithToken(View parent, Rect touchBounds, IBinder windowToken) {
        if (isShowing()) {
            mPopupView.mMarker.animateOpen();
            return;
        }

        if (windowToken != null) {
            WindowManager.LayoutParams p = createPopupLayout(windowToken);

            p.gravity = Gravity.TOP | GravityCompat.START;
            updateLayoutParamsForPosiion(parent, p, touchBounds.bottom);
            mShowing = true;

            translateViewIntoPosition(touchBounds.centerX());
            invokePopup(p);
        }
    }

and finally, when you create your popup:

    private PopupWindow mShapeDialog = null;
    public void showShapeSettingsDialog() {
        View yourPopupAnchorView = SmartObjLayer.this;
        LayoutInflater inflater = LayoutInflater.from(getContext());
        LinearLayout container = (LinearLayout) inflater.inflate(R.layout.dialog_shape_settings, null);
        mShapeDialog = new PopupWindow(container, 1000, 1500, true);

        DiscreteSeekBar discreteSeekBar1 = (DiscreteSeekBar) container.findViewById(R.id.discrete1);
        discreteSeekBar1.setToken(yourPopupAnchorView.getWindowToken());
        discreteSeekBar1.setProgress(Math.round(mSelectedView.getCurrentFillColorAlpha() * 100 / 255));
        mShapeDialog.showAtLocation(yourPopupAnchorView, Gravity.NO_GRAVITY, 0, 0);
    }
AnderWeb commented 9 years ago

Anyways, unless your settings popup is constantly being shown/hidden, You should better create a DialogFragment instead of a PopupWindow.

paynd commented 9 years ago

Unfortunately I cant use DialogFragment here. :( And thank you for your fix! It works, but I still have problem with positioning of indicator when I place popup window

mShapeDialog.showAtLocation(SmartObjLayer.this, Gravity.NO_GRAVITY, locationX, locationY);

May be I`ll try some offset correction.

P.S. Also I've found an article http://www.androiddesignpatterns.com/2013/07/binders-window-tokens.html IBinder principles isn't clear for me now.