Guest User

Untitled

a guest
Mar 22nd, 2018
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.79 KB | None | 0 0
  1. public class Tooltip {
  2.  
  3. public static final String TAG = Tooltip.class.getSimpleName();
  4.  
  5. @NonNull
  6. private final Context mContext;
  7. @NonNull
  8. private final CharSequence mText;
  9. private final int mOffset;
  10. private final boolean mCancelable;
  11. private final boolean mOutsideCancelable;
  12. private final boolean mAnchorCancelable;
  13. private final boolean mClipping;
  14. @NonNull
  15. private final View mContentView;
  16. @NonNull
  17. private final View mAnchorView;
  18. @NonNull
  19. private final PopupWindow mPopupWindow;
  20.  
  21. @NonNull
  22. private final PopupWindow mHelperPopupWindow;
  23. @NonNull
  24. private final View mHelperContentView;
  25.  
  26. @NonNull
  27. private PointF mCurrentLocation = new PointF();
  28. @NonNull
  29. private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = this::updateLocation;
  30. @NonNull
  31. private final View.OnAttachStateChangeListener mOnViewDetachListener = (OnViewDetachListener) this::dismiss;
  32. @NonNull
  33. private final OnTouchUpListener mOnTouchUpListener = () -> dismiss();
  34.  
  35. public Tooltip(@NonNull final Context context,
  36. @NonNull final View anchorView,
  37. @NonNull final CharSequence text,
  38. final int offset,
  39. final boolean cancelable,
  40. final boolean outsideCancelable,
  41. final boolean clipping,
  42. final boolean anchorCancelable) {
  43. mContext = context;
  44. mText = text;
  45. mOffset = offset;
  46. mCancelable = cancelable;
  47. mOutsideCancelable = outsideCancelable;
  48. mAnchorCancelable = anchorCancelable;
  49. mClipping = clipping;
  50.  
  51. mContentView = createContentView(mContext, mText, mCancelable, mOffset);
  52. mAnchorView = prepareAnchorView(anchorView);
  53. mPopupWindow = createPopupWindow(mContext, mContentView, mCancelable, mOutsideCancelable);
  54.  
  55. mHelperContentView = createHelperView(mContext);
  56. mHelperPopupWindow = createHelperPopupWindow(mContext, mHelperContentView);
  57. }
  58.  
  59. @NonNull
  60. private View createHelperView(final @NonNull Context context) {
  61. final View view = new View(context);
  62. view.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
  63. return view;
  64. }
  65.  
  66. @NonNull
  67. private PopupWindow createHelperPopupWindow(@NonNull final Context context, @NonNull final View contentView) {
  68. final PopupWindow popupWindow = new PopupWindow(context);
  69. popupWindow.setContentView(contentView);
  70. popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
  71. popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
  72. popupWindow.setWidth(0);
  73. popupWindow.setHeight(WindowManager.LayoutParams.MATCH_PARENT);
  74.  
  75. return popupWindow;
  76. }
  77.  
  78. @NonNull
  79. private View prepareAnchorView(@NonNull final View anchorView) {
  80. anchorView.addOnAttachStateChangeListener(mOnViewDetachListener);
  81. if (mAnchorCancelable) {
  82. anchorView.setOnTouchListener(mOnTouchUpListener);
  83. }
  84. return anchorView;
  85. }
  86.  
  87. @NonNull
  88. private PopupWindow createPopupWindow(@NonNull final Context context,
  89. @NonNull final View contentView,
  90. final boolean cancelable,
  91. final boolean outsideCancelable) {
  92. final PopupWindow popupWindow = new PopupWindow(context);
  93. popupWindow.setClippingEnabled(false);
  94. popupWindow.setFocusable(false);
  95. popupWindow.setOutsideTouchable(outsideCancelable);
  96. popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
  97. popupWindow.setContentView(contentView);
  98. popupWindow.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
  99. popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
  100. popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
  101. return popupWindow;
  102. }
  103.  
  104. @NonNull
  105. private View createContentView(@NonNull final Context context,
  106. @NonNull final CharSequence text,
  107. final boolean cancelable,
  108. final int offset) {
  109. final TextView textView = new TextView(context);
  110. textView.setBackgroundResource(R.drawable.pop_up_b_l); /* set any background to get a correct size */
  111. textView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener); /* to listen when content will measure */
  112. textView.setText(text);
  113. textView.setTextColor(Color.WHITE);
  114. textView.setTextSize(13);
  115. if (cancelable) {
  116. textView.setOnClickListener(v -> dismiss());
  117. }
  118. return textView;
  119. }
  120.  
  121. private void updateLocation() {
  122. debug("layout changed, calculating new position.");
  123. final PointF location = calculateLocation();
  124. if (!location.equals(mCurrentLocation)) {
  125. debug("updateLocation");
  126. mPopupWindow.setClippingEnabled(mClipping);
  127. mPopupWindow.update((int) location.x, (int) location.y, mPopupWindow.getWidth(), mPopupWindow.getHeight());
  128. }
  129. mCurrentLocation = location;
  130. }
  131.  
  132. @NonNull
  133. private PointF calculateLocation() {
  134. final PointF location = new PointF();
  135.  
  136. final RectF anchorRect = calculateRectOnScreen(mAnchorView);
  137. final RectF contentRect = calculateRectOnScreen(mContentView);
  138. final RectF screenRect = getScreenRect();
  139.  
  140. final boolean leftBias = anchorRect.centerX() < screenRect.centerX();
  141. final boolean rightBias = !leftBias;
  142. final boolean topBias = anchorRect.centerY() < screenRect.centerY();
  143. final boolean bottomBias = !topBias;
  144.  
  145. if (leftBias && topBias) {
  146. mContentView.setBackgroundResource(R.drawable.pop_up_u_l);
  147. location.x = (int) anchorRect.left;
  148. location.y = (int) anchorRect.top + anchorRect.height() + mOffset;
  149. } else if (leftBias && bottomBias) {
  150. mContentView.setBackgroundResource(R.drawable.pop_up_b_l);
  151.  
  152. location.x = (int) anchorRect.left;
  153. location.y = (int) anchorRect.top - contentRect.height() - mOffset;
  154. } else if (rightBias && topBias) {
  155. mContentView.setBackgroundResource(R.drawable.pop_up_u_r);
  156.  
  157. location.x = (int) (anchorRect.left - (contentRect.width() - anchorRect.width()));
  158. location.y = (int) anchorRect.bottom + mOffset;
  159. } else if (rightBias && bottomBias) {
  160. mContentView.setBackgroundResource(R.drawable.pop_up_b_r);
  161.  
  162. location.x = (int) (anchorRect.left - (contentRect.width() - anchorRect.width()));
  163. location.y = (int) anchorRect.top - contentRect.height() - mOffset;
  164. }
  165.  
  166. debug("leftBias && topBias: " + leftBias + " && " + topBias);
  167. debug("anchorRect: " + anchorRect + ", width: " + anchorRect.width() + ", height: " + anchorRect.height());
  168. debug("contentRect: " + contentRect + ", width: " + contentRect.width() + ", height: " + contentRect.height());
  169. debug("screenRect: " + screenRect + ", width: " + screenRect.width() + ", height: " + screenRect.height());
  170. debug("location: " + location);
  171.  
  172. return location;
  173. }
  174.  
  175. public boolean isShowing() {
  176. return mPopupWindow.isShowing();
  177. }
  178.  
  179. public void show() {
  180. if (!isShowing()) {
  181. debug("show");
  182. mHelperPopupWindow.showAtLocation(mAnchorView, Gravity.NO_GRAVITY, 0, 0);
  183.  
  184. if (mClipping) {
  185. mAnchorView.post(() -> mPopupWindow.showAsDropDown(mAnchorView));
  186. } else {
  187. mAnchorView.post(() -> mPopupWindow.showAtLocation(mAnchorView, Gravity.NO_GRAVITY, 0, 0));
  188. }
  189. }
  190. }
  191.  
  192. public void dismiss() {
  193. debug("dismiss");
  194. mAnchorView.setOnTouchListener(null);
  195. mAnchorView.removeOnAttachStateChangeListener(mOnViewDetachListener);
  196.  
  197. removeOnGlobalLayoutListener(mContentView, mOnGlobalLayoutListener);
  198. removeOnGlobalLayoutListener(mHelperContentView , mOnGlobalLayoutListener);
  199.  
  200. mPopupWindow.dismiss();
  201. mHelperPopupWindow.dismiss();
  202. }
  203.  
  204. private interface OnTouchUpListener extends View.OnTouchListener {
  205.  
  206. @Override
  207. default boolean onTouch(final View v, final MotionEvent event) {
  208. if (event.getAction() == MotionEvent.ACTION_UP) {
  209. onTouchUp();
  210. }
  211. return false;
  212. }
  213.  
  214. void onTouchUp();
  215. }
  216.  
  217. private interface OnViewDetachListener extends View.OnAttachStateChangeListener {
  218.  
  219. @Override
  220. default void onViewAttachedToWindow(final View v) {}
  221.  
  222. @Override
  223. default void onViewDetachedFromWindow(final View v) {
  224. onViewDetached();
  225. }
  226.  
  227. void onViewDetached();
  228. }
  229.  
  230. private void debug(@NonNull final String message) {
  231. PalLog.d(TAG, message);
  232. }
  233.  
  234. @NonNull
  235. public RectF getScreenRect() {
  236. final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
  237. return new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
  238. }
  239.  
  240. public static class Builder {
  241.  
  242. @NonNull
  243. private final Context mContext;
  244. @NonNull
  245. private final View mAnchor;
  246.  
  247. private CharSequence mText = "";
  248. private boolean mCancelable = true;
  249. private boolean mOutsideCancelable = false;
  250. private int mOffset = dpToPx(-6);
  251. private boolean mClipping = false;
  252. private boolean mAnchorCancelable = true;
  253.  
  254. public Builder(@NonNull final View anchorView) {
  255. mAnchor = anchorView;
  256. mContext = anchorView.getContext();
  257. }
  258.  
  259. @NonNull
  260. public Builder setOutsideCancelable(final boolean outsideCancelable) {
  261. mOutsideCancelable = outsideCancelable;
  262. return this;
  263. }
  264.  
  265. @NonNull
  266. public Builder setCancelable(final boolean cancelable) {
  267. mCancelable = cancelable;
  268. return this;
  269. }
  270.  
  271. @NonNull
  272. public Builder setAnchorCancelable(final boolean anchorCancelable) {
  273. mAnchorCancelable = anchorCancelable;
  274. return this;
  275. }
  276.  
  277. @NonNull
  278. public Builder setClipping(final boolean clipping) {
  279. mClipping = clipping;
  280. return this;
  281. }
  282.  
  283. @NonNull
  284. public Builder setOffset(final int offset) {
  285. mOffset = offset;
  286. return this;
  287. }
  288.  
  289. @NonNull
  290. public Builder setText(@NonNull final CharSequence text) {
  291. mText = text;
  292. return this;
  293. }
  294.  
  295. @NonNull
  296. public Builder setText(@StringRes final int text) {
  297. setText(mContext.getString(text));
  298. return this;
  299. }
  300.  
  301. @NonNull
  302. public Tooltip build() {
  303. return new Tooltip(mContext, mAnchor, mText, mOffset, mCancelable, mOutsideCancelable, mClipping, mAnchorCancelable);
  304. }
  305. }
  306.  
  307. protected static class Utils {
  308.  
  309. @SuppressWarnings("deprecation")
  310. @SuppressLint("ObsoleteSdkInt")
  311. public static void removeOnGlobalLayoutListener(@NonNull final View view,
  312. @NonNull final ViewTreeObserver.OnGlobalLayoutListener listener) {
  313. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  314. view.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
  315. } else {
  316. view.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
  317. }
  318. }
  319.  
  320. public static int dpToPx(final int dp) {
  321. return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
  322. }
  323.  
  324. @NonNull
  325. public static RectF calculateRectOnScreen(@NonNull final View view) {
  326. final int[] location = new int[2];
  327. view.getLocationOnScreen(location);
  328. return new RectF(location[0], location[1], location[0] + view.getMeasuredWidth(), location[1] + view.getMeasuredHeight());
  329. }
  330.  
  331. @NonNull
  332. public static RectF calculateRectInWindow(@NonNull final View view) {
  333. final int[] location = new int[2];
  334. view.getLocationInWindow(location);
  335. return new RectF(location[0], location[1], location[0] + view.getMeasuredWidth(), location[1] + view.getMeasuredHeight());
  336. }
  337. }
  338. }
Add Comment
Please, Sign In to add comment