15ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes/* 25ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * Copyright (C) 2014 The Android Open Source Project 35ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * 45ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 55ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * you may not use this file except in compliance with the License. 65ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * You may obtain a copy of the License at 75ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * 85ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * http://www.apache.org/licenses/LICENSE-2.0 95ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * 105ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * Unless required by applicable law or agreed to in writing, software 115ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * distributed under the License is distributed on an "AS IS" BASIS, 125ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * See the License for the specific language governing permissions and 145ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * limitations under the License. 155ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes */ 165ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 1766698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banespackage android.support.v7.widget; 185ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 198e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikasimport static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 208e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas 215ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.content.Context; 225ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.os.Build; 23b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesimport android.support.annotation.AttrRes; 24b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesimport android.support.annotation.NonNull; 25b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesimport android.support.annotation.Nullable; 26c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport android.support.annotation.RestrictTo; 27b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesimport android.support.annotation.StyleRes; 2844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banesimport android.support.v4.widget.PopupWindowCompat; 295ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.support.v7.appcompat.R; 305ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.util.AttributeSet; 3145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banesimport android.util.Log; 325ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.view.View; 3345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banesimport android.view.ViewTreeObserver.OnScrollChangedListener; 345ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.widget.PopupWindow; 355ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 3645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banesimport java.lang.ref.WeakReference; 3745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banesimport java.lang.reflect.Field; 3845266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes 39b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesclass AppCompatPopupWindow extends PopupWindow { 405ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 4145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes private static final String TAG = "AppCompatPopupWindow"; 4244918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes private static final boolean COMPAT_OVERLAP_ANCHOR = Build.VERSION.SDK_INT < 21; 4345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes 4444918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes private boolean mOverlapAnchor; 455ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 46b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes public AppCompatPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs, 47b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes @AttrRes int defStyleAttr) { 485ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes super(context, attrs, defStyleAttr); 49b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes init(context, attrs, defStyleAttr, 0); 50b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes } 51b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes 52b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes public AppCompatPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs, 53b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { 54b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes super(context, attrs, defStyleAttr, defStyleRes); 55b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes init(context, attrs, defStyleAttr, defStyleRes); 56b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes } 575ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 58b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 59469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, 60b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes R.styleable.PopupWindow, defStyleAttr, defStyleRes); 6144918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes if (a.hasValue(R.styleable.PopupWindow_overlapAnchor)) { 6244918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes setSupportOverlapAnchor(a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false)); 6344918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes } 64469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes // We re-set this for tinting purposes 65469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes setBackgroundDrawable(a.getDrawable(R.styleable.PopupWindow_android_popupBackground)); 66b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes 67b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes final int sdk = Build.VERSION.SDK_INT; 68b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes if (defStyleRes != 0 && sdk < 11) { 69b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes // If we have a defStyleRes, but we're on < API 11, we need to manually set attributes 70b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes // from the style 7164dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov // android:popupAnimationStyle was added in API 9 7264dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov if (a.hasValue(R.styleable.PopupWindow_android_popupAnimationStyle)) { 7364dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov setAnimationStyle(a.getResourceId( 7464dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov R.styleable.PopupWindow_android_popupAnimationStyle, -1)); 75b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes } 76b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes } 77b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes 785ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes a.recycle(); 7945266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes 8045266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 8145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes // For devices pre-ICS, we need to wrap the internal OnScrollChangedListener 8245266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes // due to NPEs. 8345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes wrapOnScrollChangedListener(this); 8445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } 855ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes } 865ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 875ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes @Override 885ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes public void showAsDropDown(View anchor, int xoff, int yoff) { 8944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) { 905ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes // If we're pre-L, emulate overlapAnchor by modifying the yOff 915ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes yoff -= anchor.getHeight(); 925ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes } 935ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes super.showAsDropDown(anchor, xoff, yoff); 945ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes } 955ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 965ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes @Override 975ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { 9844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) { 995ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes // If we're pre-L, emulate overlapAnchor by modifying the yOff 1005ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes yoff -= anchor.getHeight(); 1015ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes } 1025ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes super.showAsDropDown(anchor, xoff, yoff, gravity); 1035ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes } 1045ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes 1055ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes @Override 1065ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes public void update(View anchor, int xoff, int yoff, int width, int height) { 10744918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) { 1085ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes // If we're pre-L, emulate overlapAnchor by modifying the yOff 1095ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes yoff -= anchor.getHeight(); 1105ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes } 1115ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes super.update(anchor, xoff, yoff, width, height); 1125ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes } 11345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes 11445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes private static void wrapOnScrollChangedListener(final PopupWindow popup) { 11545266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes try { 11645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes final Field fieldAnchor = PopupWindow.class.getDeclaredField("mAnchor"); 11745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes fieldAnchor.setAccessible(true); 11845266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes 11945266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes final Field fieldListener = PopupWindow.class 12045266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes .getDeclaredField("mOnScrollChangedListener"); 12145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes fieldListener.setAccessible(true); 12245266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes 12345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes final OnScrollChangedListener originalListener = 12445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes (OnScrollChangedListener) fieldListener.get(popup); 12545266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes 12645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes // Now set a new listener, wrapping the original and only proxying the call when 12745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes // we have an anchor view. 12845266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes fieldListener.set(popup, new OnScrollChangedListener() { 12945266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes @Override 13045266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes public void onScrollChanged() { 13145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes try { 13245266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes WeakReference<View> mAnchor = (WeakReference<View>) fieldAnchor.get(popup); 13345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes if (mAnchor == null || mAnchor.get() == null) { 13445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes return; 13545266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } else { 13645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes originalListener.onScrollChanged(); 13745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } 13845266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } catch (IllegalAccessException e) { 13945266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes // Oh well... 14045266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } 14145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } 14245266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes }); 14345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } catch (Exception e) { 14445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes Log.d(TAG, "Exception while installing workaround OnScrollChangedListener", e); 14545266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } 14645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes } 14745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes 14844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes /** 14944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes * @hide 15044918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes */ 1518e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas @RestrictTo(LIBRARY_GROUP) 15244918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes public void setSupportOverlapAnchor(boolean overlapAnchor) { 15344918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes if (COMPAT_OVERLAP_ANCHOR) { 15444918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes mOverlapAnchor = overlapAnchor; 15544918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes } else { 15644918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes PopupWindowCompat.setOverlapAnchor(this, overlapAnchor); 15744918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes } 15844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes } 15944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes 16044918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes /** 16144918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes * @hide 16244918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes */ 1638e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas @RestrictTo(LIBRARY_GROUP) 16444918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes public boolean getSupportOverlapAnchor() { 16544918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes if (COMPAT_OVERLAP_ANCHOR) { 16644918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes return mOverlapAnchor; 16744918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes } else { 16844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes return PopupWindowCompat.getOverlapAnchor(this); 16944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes } 17044918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes } 17144918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes 1725ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes} 173