Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- public class Tooltip {
- public static final String TAG = Tooltip.class.getSimpleName();
- @NonNull
- private final Context mContext;
- @NonNull
- private final CharSequence mText;
- private final int mOffset;
- private final boolean mCancelable;
- private final boolean mOutsideCancelable;
- private final boolean mAnchorCancelable;
- private final boolean mClipping;
- @NonNull
- private final View mContentView;
- @NonNull
- private final View mAnchorView;
- @NonNull
- private final PopupWindow mPopupWindow;
- @NonNull
- private final PopupWindow mHelperPopupWindow;
- @NonNull
- private final View mHelperContentView;
- @NonNull
- private PointF mCurrentLocation = new PointF();
- @NonNull
- private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = this::updateLocation;
- @NonNull
- private final View.OnAttachStateChangeListener mOnViewDetachListener = (OnViewDetachListener) this::dismiss;
- @NonNull
- private final OnTouchUpListener mOnTouchUpListener = () -> dismiss();
- public Tooltip(@NonNull final Context context,
- @NonNull final View anchorView,
- @NonNull final CharSequence text,
- final int offset,
- final boolean cancelable,
- final boolean outsideCancelable,
- final boolean clipping,
- final boolean anchorCancelable) {
- mContext = context;
- mText = text;
- mOffset = offset;
- mCancelable = cancelable;
- mOutsideCancelable = outsideCancelable;
- mAnchorCancelable = anchorCancelable;
- mClipping = clipping;
- mContentView = createContentView(mContext, mText, mCancelable, mOffset);
- mAnchorView = prepareAnchorView(anchorView);
- mPopupWindow = createPopupWindow(mContext, mContentView, mCancelable, mOutsideCancelable);
- mHelperContentView = createHelperView(mContext);
- mHelperPopupWindow = createHelperPopupWindow(mContext, mHelperContentView);
- }
- @NonNull
- private View createHelperView(final @NonNull Context context) {
- final View view = new View(context);
- view.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
- return view;
- }
- @NonNull
- private PopupWindow createHelperPopupWindow(@NonNull final Context context, @NonNull final View contentView) {
- final PopupWindow popupWindow = new PopupWindow(context);
- popupWindow.setContentView(contentView);
- popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
- popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
- popupWindow.setWidth(0);
- popupWindow.setHeight(WindowManager.LayoutParams.MATCH_PARENT);
- return popupWindow;
- }
- @NonNull
- private View prepareAnchorView(@NonNull final View anchorView) {
- anchorView.addOnAttachStateChangeListener(mOnViewDetachListener);
- if (mAnchorCancelable) {
- anchorView.setOnTouchListener(mOnTouchUpListener);
- }
- return anchorView;
- }
- @NonNull
- private PopupWindow createPopupWindow(@NonNull final Context context,
- @NonNull final View contentView,
- final boolean cancelable,
- final boolean outsideCancelable) {
- final PopupWindow popupWindow = new PopupWindow(context);
- popupWindow.setClippingEnabled(false);
- popupWindow.setFocusable(false);
- popupWindow.setOutsideTouchable(outsideCancelable);
- popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- popupWindow.setContentView(contentView);
- popupWindow.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
- popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
- return popupWindow;
- }
- @NonNull
- private View createContentView(@NonNull final Context context,
- @NonNull final CharSequence text,
- final boolean cancelable,
- final int offset) {
- final TextView textView = new TextView(context);
- textView.setBackgroundResource(R.drawable.pop_up_b_l); /* set any background to get a correct size */
- textView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener); /* to listen when content will measure */
- textView.setText(text);
- textView.setTextColor(Color.WHITE);
- textView.setTextSize(13);
- if (cancelable) {
- textView.setOnClickListener(v -> dismiss());
- }
- return textView;
- }
- private void updateLocation() {
- debug("layout changed, calculating new position.");
- final PointF location = calculateLocation();
- if (!location.equals(mCurrentLocation)) {
- debug("updateLocation");
- mPopupWindow.setClippingEnabled(mClipping);
- mPopupWindow.update((int) location.x, (int) location.y, mPopupWindow.getWidth(), mPopupWindow.getHeight());
- }
- mCurrentLocation = location;
- }
- @NonNull
- private PointF calculateLocation() {
- final PointF location = new PointF();
- final RectF anchorRect = calculateRectOnScreen(mAnchorView);
- final RectF contentRect = calculateRectOnScreen(mContentView);
- final RectF screenRect = getScreenRect();
- final boolean leftBias = anchorRect.centerX() < screenRect.centerX();
- final boolean rightBias = !leftBias;
- final boolean topBias = anchorRect.centerY() < screenRect.centerY();
- final boolean bottomBias = !topBias;
- if (leftBias && topBias) {
- mContentView.setBackgroundResource(R.drawable.pop_up_u_l);
- location.x = (int) anchorRect.left;
- location.y = (int) anchorRect.top + anchorRect.height() + mOffset;
- } else if (leftBias && bottomBias) {
- mContentView.setBackgroundResource(R.drawable.pop_up_b_l);
- location.x = (int) anchorRect.left;
- location.y = (int) anchorRect.top - contentRect.height() - mOffset;
- } else if (rightBias && topBias) {
- mContentView.setBackgroundResource(R.drawable.pop_up_u_r);
- location.x = (int) (anchorRect.left - (contentRect.width() - anchorRect.width()));
- location.y = (int) anchorRect.bottom + mOffset;
- } else if (rightBias && bottomBias) {
- mContentView.setBackgroundResource(R.drawable.pop_up_b_r);
- location.x = (int) (anchorRect.left - (contentRect.width() - anchorRect.width()));
- location.y = (int) anchorRect.top - contentRect.height() - mOffset;
- }
- debug("leftBias && topBias: " + leftBias + " && " + topBias);
- debug("anchorRect: " + anchorRect + ", width: " + anchorRect.width() + ", height: " + anchorRect.height());
- debug("contentRect: " + contentRect + ", width: " + contentRect.width() + ", height: " + contentRect.height());
- debug("screenRect: " + screenRect + ", width: " + screenRect.width() + ", height: " + screenRect.height());
- debug("location: " + location);
- return location;
- }
- public boolean isShowing() {
- return mPopupWindow.isShowing();
- }
- public void show() {
- if (!isShowing()) {
- debug("show");
- mHelperPopupWindow.showAtLocation(mAnchorView, Gravity.NO_GRAVITY, 0, 0);
- if (mClipping) {
- mAnchorView.post(() -> mPopupWindow.showAsDropDown(mAnchorView));
- } else {
- mAnchorView.post(() -> mPopupWindow.showAtLocation(mAnchorView, Gravity.NO_GRAVITY, 0, 0));
- }
- }
- }
- public void dismiss() {
- debug("dismiss");
- mAnchorView.setOnTouchListener(null);
- mAnchorView.removeOnAttachStateChangeListener(mOnViewDetachListener);
- removeOnGlobalLayoutListener(mContentView, mOnGlobalLayoutListener);
- removeOnGlobalLayoutListener(mHelperContentView , mOnGlobalLayoutListener);
- mPopupWindow.dismiss();
- mHelperPopupWindow.dismiss();
- }
- private interface OnTouchUpListener extends View.OnTouchListener {
- @Override
- default boolean onTouch(final View v, final MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- onTouchUp();
- }
- return false;
- }
- void onTouchUp();
- }
- private interface OnViewDetachListener extends View.OnAttachStateChangeListener {
- @Override
- default void onViewAttachedToWindow(final View v) {}
- @Override
- default void onViewDetachedFromWindow(final View v) {
- onViewDetached();
- }
- void onViewDetached();
- }
- private void debug(@NonNull final String message) {
- PalLog.d(TAG, message);
- }
- @NonNull
- public RectF getScreenRect() {
- final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
- return new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
- }
- public static class Builder {
- @NonNull
- private final Context mContext;
- @NonNull
- private final View mAnchor;
- private CharSequence mText = "";
- private boolean mCancelable = true;
- private boolean mOutsideCancelable = false;
- private int mOffset = dpToPx(-6);
- private boolean mClipping = false;
- private boolean mAnchorCancelable = true;
- public Builder(@NonNull final View anchorView) {
- mAnchor = anchorView;
- mContext = anchorView.getContext();
- }
- @NonNull
- public Builder setOutsideCancelable(final boolean outsideCancelable) {
- mOutsideCancelable = outsideCancelable;
- return this;
- }
- @NonNull
- public Builder setCancelable(final boolean cancelable) {
- mCancelable = cancelable;
- return this;
- }
- @NonNull
- public Builder setAnchorCancelable(final boolean anchorCancelable) {
- mAnchorCancelable = anchorCancelable;
- return this;
- }
- @NonNull
- public Builder setClipping(final boolean clipping) {
- mClipping = clipping;
- return this;
- }
- @NonNull
- public Builder setOffset(final int offset) {
- mOffset = offset;
- return this;
- }
- @NonNull
- public Builder setText(@NonNull final CharSequence text) {
- mText = text;
- return this;
- }
- @NonNull
- public Builder setText(@StringRes final int text) {
- setText(mContext.getString(text));
- return this;
- }
- @NonNull
- public Tooltip build() {
- return new Tooltip(mContext, mAnchor, mText, mOffset, mCancelable, mOutsideCancelable, mClipping, mAnchorCancelable);
- }
- }
- protected static class Utils {
- @SuppressWarnings("deprecation")
- @SuppressLint("ObsoleteSdkInt")
- public static void removeOnGlobalLayoutListener(@NonNull final View view,
- @NonNull final ViewTreeObserver.OnGlobalLayoutListener listener) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- view.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
- } else {
- view.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
- }
- }
- public static int dpToPx(final int dp) {
- return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
- }
- @NonNull
- public static RectF calculateRectOnScreen(@NonNull final View view) {
- final int[] location = new int[2];
- view.getLocationOnScreen(location);
- return new RectF(location[0], location[1], location[0] + view.getMeasuredWidth(), location[1] + view.getMeasuredHeight());
- }
- @NonNull
- public static RectF calculateRectInWindow(@NonNull final View view) {
- final int[] location = new int[2];
- view.getLocationInWindow(location);
- return new RectF(location[0], location[1], location[0] + view.getMeasuredWidth(), location[1] + view.getMeasuredHeight());
- }
- }
- }
Add Comment
Please, Sign In to add comment