1 // Copyright 2018 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.ui.widget; 6 7 import android.graphics.Rect; 8 import android.view.View; 9 import android.view.ViewTreeObserver; 10 11 import androidx.core.view.ViewCompat; 12 13 /** 14 * Provides a {@Rect} for the location of a {@View} in its window, see 15 * {@link View#getLocationOnScreen(int[])}. 16 */ 17 public class ViewRectProvider extends RectProvider 18 implements ViewTreeObserver.OnGlobalLayoutListener, View.OnAttachStateChangeListener, 19 ViewTreeObserver.OnPreDrawListener { 20 private final int[] mCachedWindowCoordinates = new int[2]; 21 private final Rect mInsetRect = new Rect(); 22 private final View mView; 23 24 /** If not {@code null}, the {@link ViewTreeObserver} that we are registered to. */ 25 private ViewTreeObserver mViewTreeObserver; 26 27 private boolean mIncludePadding; 28 29 /** 30 * Creates an instance of a {@link ViewRectProvider}. 31 * @param view The {@link View} used to generate a {@link Rect}. 32 */ ViewRectProvider(View view)33 public ViewRectProvider(View view) { 34 mView = view; 35 mCachedWindowCoordinates[0] = -1; 36 mCachedWindowCoordinates[1] = -1; 37 } 38 39 /** 40 * Specifies the inset values in pixels that determine how to shrink the {@link View} bounds 41 * when creating the {@link Rect}. 42 */ setInsetPx(int left, int top, int right, int bottom)43 public void setInsetPx(int left, int top, int right, int bottom) { 44 mInsetRect.set(left, top, right, bottom); 45 refreshRectBounds(); 46 } 47 48 /** 49 * Specifies the inset values in pixels that determine how to shrink the {@link View} bounds 50 * when creating the {@link Rect}. 51 */ setInsetPx(Rect insetRect)52 public void setInsetPx(Rect insetRect) { 53 mInsetRect.set(insetRect); 54 refreshRectBounds(); 55 } 56 57 /** 58 * Whether padding should be included in the {@link Rect} for the {@link View}. 59 * @param includePadding Whether padding should be included. Defaults to false. 60 */ setIncludePadding(boolean includePadding)61 public void setIncludePadding(boolean includePadding) { 62 mIncludePadding = includePadding; 63 } 64 65 @Override startObserving(Observer observer)66 public void startObserving(Observer observer) { 67 mView.addOnAttachStateChangeListener(this); 68 mViewTreeObserver = mView.getViewTreeObserver(); 69 mViewTreeObserver.addOnGlobalLayoutListener(this); 70 mViewTreeObserver.addOnPreDrawListener(this); 71 72 refreshRectBounds(); 73 74 super.startObserving(observer); 75 } 76 77 @Override stopObserving()78 public void stopObserving() { 79 mView.removeOnAttachStateChangeListener(this); 80 81 if (mViewTreeObserver != null && mViewTreeObserver.isAlive()) { 82 mViewTreeObserver.removeOnGlobalLayoutListener(this); 83 mViewTreeObserver.removeOnPreDrawListener(this); 84 } 85 mViewTreeObserver = null; 86 87 super.stopObserving(); 88 } 89 90 // ViewTreeObserver.OnGlobalLayoutListener implementation. 91 @Override onGlobalLayout()92 public void onGlobalLayout() { 93 if (!mView.isShown()) notifyRectHidden(); 94 } 95 96 // ViewTreeObserver.OnPreDrawListener implementation. 97 @Override onPreDraw()98 public boolean onPreDraw() { 99 if (!mView.isShown()) { 100 notifyRectHidden(); 101 } else { 102 refreshRectBounds(); 103 } 104 105 return true; 106 } 107 108 // View.OnAttachStateChangedObserver implementation. 109 @Override onViewAttachedToWindow(View v)110 public void onViewAttachedToWindow(View v) {} 111 112 @Override onViewDetachedFromWindow(View v)113 public void onViewDetachedFromWindow(View v) { 114 notifyRectHidden(); 115 } 116 refreshRectBounds()117 private void refreshRectBounds() { 118 int previousPositionX = mCachedWindowCoordinates[0]; 119 int previousPositionY = mCachedWindowCoordinates[1]; 120 mView.getLocationInWindow(mCachedWindowCoordinates); 121 122 mCachedWindowCoordinates[0] = Math.max(mCachedWindowCoordinates[0], 0); 123 mCachedWindowCoordinates[1] = Math.max(mCachedWindowCoordinates[1], 0); 124 125 // Return if the window coordinates haven't changed. 126 if (mCachedWindowCoordinates[0] == previousPositionX 127 && mCachedWindowCoordinates[1] == previousPositionY) { 128 return; 129 } 130 131 mRect.left = mCachedWindowCoordinates[0]; 132 mRect.top = mCachedWindowCoordinates[1]; 133 mRect.right = mRect.left + mView.getWidth(); 134 mRect.bottom = mRect.top + mView.getHeight(); 135 136 mRect.left += mInsetRect.left; 137 mRect.top += mInsetRect.top; 138 mRect.right -= mInsetRect.right; 139 mRect.bottom -= mInsetRect.bottom; 140 141 // Account for the padding. 142 if (!mIncludePadding) { 143 boolean isRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 144 mRect.left += 145 isRtl ? ViewCompat.getPaddingEnd(mView) : ViewCompat.getPaddingStart(mView); 146 mRect.right -= 147 isRtl ? ViewCompat.getPaddingStart(mView) : ViewCompat.getPaddingEnd(mView); 148 mRect.top += mView.getPaddingTop(); 149 mRect.bottom -= mView.getPaddingBottom(); 150 } 151 152 // Make sure we still have a valid Rect after applying the inset. 153 mRect.right = Math.max(mRect.left, mRect.right); 154 mRect.bottom = Math.max(mRect.top, mRect.bottom); 155 156 mRect.right = Math.min(mRect.right, mView.getRootView().getWidth()); 157 mRect.bottom = Math.min(mRect.bottom, mView.getRootView().getHeight()); 158 159 notifyRectChanged(); 160 } 161 } 162