Skip to content

Commit

Permalink
Merge branch 'popupmenu' of https://github.com/ksperling/ActionBarShe…
Browse files Browse the repository at this point in the history
…rlock into ksperling-popupmenu
  • Loading branch information
JakeWharton committed Jul 3, 2013
2 parents 5d80bb7 + 860539e commit 7b55d63
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,20 @@ public boolean tryShow() {

View anchor = mAnchorView;
if (anchor != null) {
final boolean addGlobalListener = mTreeObserver == null;
mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
((View_HasStateListenerSupport)anchor).addOnAttachStateChangeListener(this);
// Don't attach to the VTO unless the anchor itself is attached to avoid VTO-related leaks.
if (anchor.getWindowToken() != null) {
ViewTreeObserver vto = anchor.getViewTreeObserver();
if (vto != mTreeObserver) {
if (mTreeObserver != null && mTreeObserver.isAlive()) {
mTreeObserver.removeGlobalOnLayoutListener(this);
}
if ((mTreeObserver = vto) != null) {
vto.addOnGlobalLayoutListener(this);
}
}
} else if (anchor instanceof View_HasStateListenerSupport) {
((View_HasStateListenerSupport) anchor).addOnAttachStateChangeListener(this);
}
mPopup.setAnchorView(anchor);
} else {
return false;
Expand All @@ -141,11 +151,11 @@ public void onDismiss() {
mPopup = null;
mMenu.close();
if (mTreeObserver != null) {
if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
mTreeObserver.removeGlobalOnLayoutListener(this);
if (mTreeObserver.isAlive()) mTreeObserver.removeGlobalOnLayoutListener(this);
mTreeObserver = null;
} else if (mAnchorView instanceof View_HasStateListenerSupport) {
((View_HasStateListenerSupport) mAnchorView).removeOnAttachStateChangeListener(this);
}
((View_HasStateListenerSupport)mAnchorView).removeOnAttachStateChangeListener(this);
}

public boolean isShowing() {
Expand Down Expand Up @@ -207,15 +217,16 @@ public void onGlobalLayout() {

@Override
public void onViewAttachedToWindow(View v) {
((View_HasStateListenerSupport) v).removeOnAttachStateChangeListener(this);

// The anchor wasn't attached in tryShow(), attach to the ViewRoot VTO now.
if (mPopup != null && mTreeObserver == null) {
(mTreeObserver = v.getViewTreeObserver()).addOnGlobalLayoutListener(this);
}
}

@Override
public void onViewDetachedFromWindow(View v) {
if (mTreeObserver != null) {
if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver();
mTreeObserver.removeGlobalOnLayoutListener(this);
}
((View_HasStateListenerSupport)v).removeOnAttachStateChangeListener(this);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class IcsListPopupWindow {
private static final int EXPAND_LIST_TIMEOUT = 250;

private Context mContext;
private PopupWindow mPopup;
private final PopupWindowCompat mPopup;
private ListAdapter mAdapter;
private DropDownListView mDropDownList;

Expand Down Expand Up @@ -80,17 +80,17 @@ public IcsListPopupWindow(Context context) {

public IcsListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
mContext = context;
mPopup = new PopupWindow(context, attrs, defStyleAttr);
mPopup = new PopupWindowCompat(context, attrs, defStyleAttr);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}

public IcsListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
Context wrapped = new ContextThemeWrapper(context, defStyleRes);
mPopup = new PopupWindow(wrapped, attrs, defStyleAttr);
mPopup = new PopupWindowCompat(wrapped, attrs, defStyleAttr);
} else {
mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
mPopup = new PopupWindowCompat(context, attrs, defStyleAttr, defStyleRes);
}
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@

package com.actionbarsherlock.internal.widget;

import java.lang.reflect.Field;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.widget.PopupWindow;

/**
* Works around bugs in the handling of {@link ViewTreeObserver} by
* {@link PopupWindow}.
* <p>
* <code>PopupWindow</code> registers an {@link OnScrollChangedListener} with
* {@link ViewTreeObserver}, but does not keep a reference to the observer
* instance that it has registers on. This is problematic when the anchor view
* used by <code>PopupWindow</code> to access the observer is detached from the
* window, as it will revert from the shared <code>ViewTreeObserver</code> owned
* by the <code>ViewRoot</code> to a floating one, meaning
* <code>PopupWindow</code> cannot unregister it's listener anymore and has
* leaked it into the global observer.
* <p>
* This class works around this issue by
* <ul>
* <li>replacing <code>PopupWindow.mOnScrollChangedListener</code> with a no-op
* listener so that any registration or unregistration performed by
* <code>PopupWindow</code> itself has no effect and causes no leaks.
* <li>registering the real listener only with the shared
* <code>ViewTreeObserver</code> and keeping a reference to it to facilitate
* correct unregistration. The reason for not registering on a floating observer
* (before a view is attached) is that there is no safe way to get a reference
* to the shared observer that the floating one will be merged into. This would
* again cause the listener to leak.
* </ul>
*/
public class PopupWindowCompat extends PopupWindow {

private static final Field superListenerField;
static {
Field f = null;
try {
f = PopupWindow.class.getDeclaredField("mOnScrollChangedListener");
f.setAccessible(true);
} catch (NoSuchFieldException e) {
/* ignored */
}
superListenerField = f;
}

private static final OnScrollChangedListener NOP = new OnScrollChangedListener() {
@Override
public void onScrollChanged() {
/* do nothing */
}
};

private OnScrollChangedListener mSuperScrollListener;
private ViewTreeObserver mViewTreeObserver;

public PopupWindowCompat() {
super();
init();
}

public PopupWindowCompat(Context context) {
super(context);
init();
}

public PopupWindowCompat(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public PopupWindowCompat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

// @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public PopupWindowCompat(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}

public PopupWindowCompat(int width, int height) {
super(width, height);
init();
}

public PopupWindowCompat(View contentView) {
super(contentView);
init();
}

public PopupWindowCompat(View contentView, int width, int height, boolean focusable) {
super(contentView, width, height, focusable);
init();
}

public PopupWindowCompat(View contentView, int width, int height) {
super(contentView, width, height);
init();
}

private void init() {
if (superListenerField != null) {
try {
mSuperScrollListener = (OnScrollChangedListener) superListenerField.get(this);
superListenerField.set(this, NOP);
} catch (Exception e) {
mSuperScrollListener = null;
}
}
}

private void unregisterListener() {
// Don't do anything if we haven't managed to patch the super listener
if (mSuperScrollListener != null && mViewTreeObserver != null) {
if (mViewTreeObserver.isAlive()) {
mViewTreeObserver.removeOnScrollChangedListener(mSuperScrollListener);
}
mViewTreeObserver = null;
}
}

private void registerListener(View anchor) {
// Don't do anything if we haven't managed to patch the super listener.
// And don't bother attaching the listener if the anchor view isn't
// attached. This means we'll only have to deal with the real VTO owned
// by the ViewRoot.
if (mSuperScrollListener != null) {
ViewTreeObserver vto = (anchor.getWindowToken() != null) ? anchor.getViewTreeObserver()
: null;
if (vto != mViewTreeObserver) {
if (mViewTreeObserver != null && mViewTreeObserver.isAlive()) {
mViewTreeObserver.removeOnScrollChangedListener(mSuperScrollListener);
}
if ((mViewTreeObserver = vto) != null) {
vto.addOnScrollChangedListener(mSuperScrollListener);
}
}
}
}

@Override
public void showAsDropDown(View anchor, int xoff, int yoff) {
super.showAsDropDown(anchor, xoff, yoff);
registerListener(anchor);
}

@Override
public void update(View anchor, int xoff, int yoff, int width, int height) {
super.update(anchor, xoff, yoff, width, height);
registerListener(anchor);
}

@Override
public void update(View anchor, int width, int height) {
super.update(anchor, width, height);
registerListener(anchor);
}

@Override
public void showAtLocation(View parent, int gravity, int x, int y) {
super.showAtLocation(parent, gravity, x, y);
unregisterListener();
}

@Override
public void dismiss() {
super.dismiss();
unregisterListener();
}
}
Loading

0 comments on commit 7b55d63

Please sign in to comment.