diff --git a/taptargetview/src/main/AndroidManifest.xml b/taptargetview/src/main/AndroidManifest.xml
index 05033b8..90e9fee 100644
--- a/taptargetview/src/main/AndroidManifest.xml
+++ b/taptargetview/src/main/AndroidManifest.xml
@@ -1 +1 @@
-
+
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/FloatValueAnimatorBuilder.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/FloatValueAnimatorBuilder.java
index 6389158..ddf6f31 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/FloatValueAnimatorBuilder.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/FloatValueAnimatorBuilder.java
@@ -28,14 +28,6 @@ class FloatValueAnimatorBuilder {
EndListener endListener;
- interface UpdateListener {
- void onUpdate(float lerpTime);
- }
-
- interface EndListener {
- void onEnd();
- }
-
protected FloatValueAnimatorBuilder() {
this(false);
}
@@ -95,4 +87,12 @@ public void onAnimationEnd(Animator animation) {
return animator;
}
+
+ interface UpdateListener {
+ void onUpdate(float lerpTime);
+ }
+
+ interface EndListener {
+ void onEnd();
+ }
}
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ReflectUtil.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ReflectUtil.java
index c9d84af..193e5bd 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ReflectUtil.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ReflectUtil.java
@@ -1,31 +1,33 @@
-/**
- * Copyright 2016 Keepsafe Software, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.getkeepsafe.taptargetview;
-
-import java.lang.reflect.Field;
-
-class ReflectUtil {
- ReflectUtil() {
- }
-
- /** Returns the value of the given private field from the source object **/
- static Object getPrivateField(Object source, String fieldName)
- throws NoSuchFieldException, IllegalAccessException {
- final Field objectField = source.getClass().getDeclaredField(fieldName);
- objectField.setAccessible(true);
- return objectField.get(source);
- }
-}
+/**
+ * Copyright 2016 Keepsafe Software, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.getkeepsafe.taptargetview;
+
+import java.lang.reflect.Field;
+
+class ReflectUtil {
+ ReflectUtil() {
+ }
+
+ /**
+ * Returns the value of the given private field from the source object
+ **/
+ static Object getPrivateField(Object source, String fieldName)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field objectField = source.getClass().getDeclaredField(fieldName);
+ objectField.setAccessible(true);
+ return objectField.get(source);
+ }
+}
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTarget.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTarget.java
index aeb0ab9..ec4efa3 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTarget.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTarget.java
@@ -19,14 +19,15 @@
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.view.View;
+
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import androidx.appcompat.widget.Toolbar;
-import android.view.View;
+import androidx.core.content.ContextCompat;
/**
* Describes the properties and options for a {@link TapTargetView}.
@@ -40,463 +41,588 @@
* @see ViewTapTarget ViewTapTarget for targeting standard Android views
*/
public class TapTarget {
- final CharSequence title;
- @Nullable
- final CharSequence description;
-
- float outerCircleAlpha = 0.96f;
- int targetRadius = 44;
-
- Rect bounds;
- Drawable icon;
- Typeface titleTypeface;
- Typeface descriptionTypeface;
-
- @ColorRes
- private int outerCircleColorRes = -1;
- @ColorRes
- private int targetCircleColorRes = -1;
- @ColorRes
- private int dimColorRes = -1;
- @ColorRes
- private int titleTextColorRes = -1;
- @ColorRes
- private int descriptionTextColorRes = -1;
-
- private Integer outerCircleColor = null;
- private Integer targetCircleColor = null;
- private Integer dimColor = null;
- private Integer titleTextColor = null;
- private Integer descriptionTextColor = null;
-
- @DimenRes
- private int titleTextDimen = -1;
- @DimenRes
- private int descriptionTextDimen = -1;
-
- private int titleTextSize = 20;
- private int descriptionTextSize = 18;
- int id = -1;
-
- boolean drawShadow = false;
- boolean cancelable = true;
- boolean tintTarget = true;
- boolean transparentTarget = false;
- float descriptionTextAlpha = 0.54f;
-
- /**
- * Return a tap target for the overflow button from the given toolbar
- *
- * Note: This is currently experimental, use at your own risk
- */
- public static TapTarget forToolbarOverflow(Toolbar toolbar, CharSequence title) {
- return forToolbarOverflow(toolbar, title, null);
- }
-
- /** Return a tap target for the overflow button from the given toolbar
- *
- * Note: This is currently experimental, use at your own risk
- */
- public static TapTarget forToolbarOverflow(Toolbar toolbar, CharSequence title,
- @Nullable CharSequence description) {
- return new ToolbarTapTarget(toolbar, false, title, description);
- }
-
- /** Return a tap target for the overflow button from the given toolbar
- *
- * Note: This is currently experimental, use at your own risk
- */
- public static TapTarget forToolbarOverflow(android.widget.Toolbar toolbar, CharSequence title) {
- return forToolbarOverflow(toolbar, title, null);
- }
-
- /** Return a tap target for the overflow button from the given toolbar
- *
- * Note: This is currently experimental, use at your own risk
- */
- public static TapTarget forToolbarOverflow(android.widget.Toolbar toolbar, CharSequence title,
- @Nullable CharSequence description) {
- return new ToolbarTapTarget(toolbar, false, title, description);
- }
-
- /** Return a tap target for the navigation button (back, up, etc) from the given toolbar **/
- public static TapTarget forToolbarNavigationIcon(Toolbar toolbar, CharSequence title) {
- return forToolbarNavigationIcon(toolbar, title, null);
- }
-
- /** Return a tap target for the navigation button (back, up, etc) from the given toolbar **/
- public static TapTarget forToolbarNavigationIcon(Toolbar toolbar, CharSequence title,
- @Nullable CharSequence description) {
- return new ToolbarTapTarget(toolbar, true, title, description);
- }
-
- /** Return a tap target for the navigation button (back, up, etc) from the given toolbar **/
- public static TapTarget forToolbarNavigationIcon(android.widget.Toolbar toolbar, CharSequence title) {
- return forToolbarNavigationIcon(toolbar, title, null);
- }
-
- /** Return a tap target for the navigation button (back, up, etc) from the given toolbar **/
- public static TapTarget forToolbarNavigationIcon(android.widget.Toolbar toolbar, CharSequence title,
- @Nullable CharSequence description) {
- return new ToolbarTapTarget(toolbar, true, title, description);
- }
-
- /** Return a tap target for the menu item from the given toolbar **/
- public static TapTarget forToolbarMenuItem(Toolbar toolbar, @IdRes int menuItemId,
- CharSequence title) {
- return forToolbarMenuItem(toolbar, menuItemId, title, null);
- }
-
- /** Return a tap target for the menu item from the given toolbar **/
- public static TapTarget forToolbarMenuItem(Toolbar toolbar, @IdRes int menuItemId,
- CharSequence title, @Nullable CharSequence description) {
- return new ToolbarTapTarget(toolbar, menuItemId, title, description);
- }
-
- /** Return a tap target for the menu item from the given toolbar **/
- public static TapTarget forToolbarMenuItem(android.widget.Toolbar toolbar, @IdRes int menuItemId,
- CharSequence title) {
- return forToolbarMenuItem(toolbar, menuItemId, title, null);
- }
-
- /** Return a tap target for the menu item from the given toolbar **/
- public static TapTarget forToolbarMenuItem(android.widget.Toolbar toolbar, @IdRes int menuItemId,
- CharSequence title, @Nullable CharSequence description) {
- return new ToolbarTapTarget(toolbar, menuItemId, title, description);
- }
-
- /** Return a tap target for the specified view **/
- public static TapTarget forView(View view, CharSequence title) {
- return forView(view, title, null);
- }
-
- /** Return a tap target for the specified view **/
- public static TapTarget forView(View view, CharSequence title, @Nullable CharSequence description) {
- return new ViewTapTarget(view, title, description);
- }
-
- /** Return a tap target for the specified bounds **/
- public static TapTarget forBounds(Rect bounds, CharSequence title) {
- return forBounds(bounds, title, null);
- }
-
- /** Return a tap target for the specified bounds **/
- public static TapTarget forBounds(Rect bounds, CharSequence title, @Nullable CharSequence description) {
- return new TapTarget(bounds, title, description);
- }
-
- protected TapTarget(Rect bounds, CharSequence title, @Nullable CharSequence description) {
- this(title, description);
- if (bounds == null) {
- throw new IllegalArgumentException("Cannot pass null bounds or title");
- }
-
- this.bounds = bounds;
- }
-
- protected TapTarget(CharSequence title, @Nullable CharSequence description) {
- if (title == null) {
- throw new IllegalArgumentException("Cannot pass null title");
- }
-
- this.title = title;
- this.description = description;
- }
-
- /** Specify whether the target should be transparent **/
- public TapTarget transparentTarget(boolean transparent) {
- this.transparentTarget = transparent;
- return this;
- }
-
- /** Specify the color resource for the outer circle **/
- public TapTarget outerCircleColor(@ColorRes int color) {
- this.outerCircleColorRes = color;
- return this;
- }
-
- /** Specify the color value for the outer circle **/
- // TODO(Hilal): In v2, this API should be cleaned up / torched
- public TapTarget outerCircleColorInt(@ColorInt int color) {
- this.outerCircleColor = color;
- return this;
- }
-
- /** Specify the alpha value [0.0, 1.0] of the outer circle **/
- public TapTarget outerCircleAlpha(float alpha) {
- if (alpha < 0.0f || alpha > 1.0f) {
- throw new IllegalArgumentException("Given an invalid alpha value: " + alpha);
- }
- this.outerCircleAlpha = alpha;
- return this;
- }
-
- /** Specify the color resource for the target circle **/
- public TapTarget targetCircleColor(@ColorRes int color) {
- this.targetCircleColorRes = color;
- return this;
- }
-
- /** Specify the color value for the target circle **/
- // TODO(Hilal): In v2, this API should be cleaned up / torched
- public TapTarget targetCircleColorInt(@ColorInt int color) {
- this.targetCircleColor = color;
- return this;
- }
-
- /** Specify the color resource for all text **/
- public TapTarget textColor(@ColorRes int color) {
- this.titleTextColorRes = color;
- this.descriptionTextColorRes = color;
- return this;
- }
-
- /** Specify the color value for all text **/
- // TODO(Hilal): In v2, this API should be cleaned up / torched
- public TapTarget textColorInt(@ColorInt int color) {
- this.titleTextColor = color;
- this.descriptionTextColor = color;
- return this;
- }
-
- /** Specify the color resource for the title text **/
- public TapTarget titleTextColor(@ColorRes int color) {
- this.titleTextColorRes = color;
- return this;
- }
-
- /** Specify the color value for the title text **/
- // TODO(Hilal): In v2, this API should be cleaned up / torched
- public TapTarget titleTextColorInt(@ColorInt int color) {
- this.titleTextColor = color;
- return this;
- }
-
- /** Specify the color resource for the description text **/
- public TapTarget descriptionTextColor(@ColorRes int color) {
- this.descriptionTextColorRes = color;
- return this;
- }
-
- /** Specify the color value for the description text **/
- // TODO(Hilal): In v2, this API should be cleaned up / torched
- public TapTarget descriptionTextColorInt(@ColorInt int color) {
- this.descriptionTextColor = color;
- return this;
- }
-
- /** Specify the typeface for all text **/
- public TapTarget textTypeface(Typeface typeface) {
- if (typeface == null) throw new IllegalArgumentException("Cannot use a null typeface");
- titleTypeface = typeface;
- descriptionTypeface = typeface;
- return this;
- }
-
- /** Specify the typeface for title text **/
- public TapTarget titleTypeface(Typeface titleTypeface) {
- if (titleTypeface == null) throw new IllegalArgumentException("Cannot use a null typeface");
- this.titleTypeface = titleTypeface;
- return this;
- }
-
- /** Specify the typeface for description text **/
- public TapTarget descriptionTypeface(Typeface descriptionTypeface) {
- if (descriptionTypeface == null) throw new IllegalArgumentException("Cannot use a null typeface");
- this.descriptionTypeface = descriptionTypeface;
- return this;
- }
-
- /** Specify the text size for the title in SP **/
- public TapTarget titleTextSize(int sp) {
- if (sp < 0) throw new IllegalArgumentException("Given negative text size");
- this.titleTextSize = sp;
- return this;
- }
-
- /** Specify the text size for the description in SP **/
- public TapTarget descriptionTextSize(int sp) {
- if (sp < 0) throw new IllegalArgumentException("Given negative text size");
- this.descriptionTextSize = sp;
- return this;
- }
-
- /**
- * Specify the text size for the title via a dimen resource
- *
- * Note: If set, this value will take precedence over the specified sp size
- */
- public TapTarget titleTextDimen(@DimenRes int dimen) {
- this.titleTextDimen = dimen;
- return this;
- }
-
- /** Specify the alpha value [0.0, 1.0] of the description text **/
- public TapTarget descriptionTextAlpha(float descriptionTextAlpha) {
- if (descriptionTextAlpha < 0 || descriptionTextAlpha > 1f) {
- throw new IllegalArgumentException("Given an invalid alpha value: " + descriptionTextAlpha);
- }
- this.descriptionTextAlpha = descriptionTextAlpha;
- return this;
- }
-
- /**
- * Specify the text size for the description via a dimen resource
- *
- * Note: If set, this value will take precedence over the specified sp size
- */
- public TapTarget descriptionTextDimen(@DimenRes int dimen) {
- this.descriptionTextDimen = dimen;
- return this;
- }
-
- /**
- * Specify the color resource to use as a dim effect
- *
- * Note: The given color will have its opacity modified to 30% automatically
- */
- public TapTarget dimColor(@ColorRes int color) {
- this.dimColorRes = color;
- return this;
- }
-
- /**
- * Specify the color value to use as a dim effect
- *
- * Note: The given color will have its opacity modified to 30% automatically
- */
- // TODO(Hilal): In v2, this API should be cleaned up / torched
- public TapTarget dimColorInt(@ColorInt int color) {
- this.dimColor = color;
- return this;
- }
-
- /** Specify whether or not to draw a drop shadow around the outer circle **/
- public TapTarget drawShadow(boolean draw) {
- this.drawShadow = draw;
- return this;
- }
-
- /** Specify whether or not the target should be cancelable **/
- public TapTarget cancelable(boolean status) {
- this.cancelable = status;
- return this;
- }
-
- /** Specify whether to tint the target's icon with the outer circle's color **/
- public TapTarget tintTarget(boolean tint) {
- this.tintTarget = tint;
- return this;
- }
-
- /** Specify the icon that will be drawn in the center of the target bounds **/
- public TapTarget icon(Drawable icon) {
- return icon(icon, false);
- }
-
- /**
- * Specify the icon that will be drawn in the center of the target bounds
- * @param hasSetBounds Whether the drawable already has its bounds correctly set. If the
- * drawable does not have its bounds set, then the following bounds will
- * be applied:
- * (0, 0, intrinsic-width, intrinsic-height)
- */
- public TapTarget icon(Drawable icon, boolean hasSetBounds) {
- if (icon == null) throw new IllegalArgumentException("Cannot use null drawable");
- this.icon = icon;
-
- if (!hasSetBounds) {
- this.icon.setBounds(new Rect(0, 0, this.icon.getIntrinsicWidth(), this.icon.getIntrinsicHeight()));
- }
-
- return this;
- }
-
- /** Specify a unique identifier for this target. **/
- public TapTarget id(int id) {
- this.id = id;
- return this;
- }
-
- /** Specify the target radius in dp. **/
- public TapTarget targetRadius(int targetRadius) {
- this.targetRadius = targetRadius;
- return this;
- }
-
- /** Return the id associated with this tap target **/
- public int id() {
- return id;
- }
-
- /**
- * In case your target needs time to be ready (laid out in your view, not created, etc), the
- * runnable passed here will be invoked when the target is ready.
- */
- public void onReady(Runnable runnable) {
- runnable.run();
- }
-
- /**
- * Returns the target bounds. Throws an exception if they are not set
- * (target may not be ready)
- *
- * This will only be called internally when {@link #onReady(Runnable)} invokes its runnable
- */
- public Rect bounds() {
- if (bounds == null) {
- throw new IllegalStateException("Requesting bounds that are not set! Make sure your target is ready");
- }
- return bounds;
- }
-
- @Nullable
- Integer outerCircleColorInt(Context context) {
- return colorResOrInt(context, outerCircleColor, outerCircleColorRes);
- }
-
- @Nullable
- Integer targetCircleColorInt(Context context) {
- return colorResOrInt(context, targetCircleColor, targetCircleColorRes);
- }
-
- @Nullable
- Integer dimColorInt(Context context) {
- return colorResOrInt(context, dimColor, dimColorRes);
- }
-
- @Nullable
- Integer titleTextColorInt(Context context) {
- return colorResOrInt(context, titleTextColor, titleTextColorRes);
- }
-
- @Nullable
- Integer descriptionTextColorInt(Context context) {
- return colorResOrInt(context, descriptionTextColor, descriptionTextColorRes);
- }
-
- int titleTextSizePx(Context context) {
- return dimenOrSize(context, titleTextSize, titleTextDimen);
- }
-
- int descriptionTextSizePx(Context context) {
- return dimenOrSize(context, descriptionTextSize, descriptionTextDimen);
- }
-
- @Nullable
- private Integer colorResOrInt(Context context, @Nullable Integer value, @ColorRes int resource) {
- if (resource != -1) {
- return ContextCompat.getColor(context, resource);
- }
-
- return value;
- }
-
- private int dimenOrSize(Context context, int size, @DimenRes int dimen) {
- if (dimen != -1) {
- return context.getResources().getDimensionPixelSize(dimen);
- }
-
- return UiUtil.sp(context, size);
- }
+ final CharSequence title;
+ @Nullable
+ final CharSequence description;
+
+ float outerCircleAlpha = 0.96f;
+ int targetRadius = 44;
+ int targetPadding = 20;
+
+ Rect bounds;
+ Drawable icon;
+ Typeface titleTypeface;
+ Typeface descriptionTypeface;
+ int id = -1;
+ boolean drawShadow = false;
+ boolean cancelable = true;
+ boolean tintTarget = true;
+ boolean drawOval = false;
+ boolean transparentTarget = false;
+ float descriptionTextAlpha = 0.54f;
+ @ColorRes
+ private int outerCircleColorRes = -1;
+ @ColorRes
+ private int targetTintColorRes = -1;
+ @ColorRes
+ private int targetCircleColorRes = -1;
+ @ColorRes
+ private int dimColorRes = -1;
+ @ColorRes
+ private int titleTextColorRes = -1;
+ @ColorRes
+ private int descriptionTextColorRes = -1;
+ private Integer outerCircleColor = null;
+ private Integer targetTintColor = null;
+ private Integer targetCircleColor = null;
+ private Integer dimColor = null;
+ private Integer titleTextColor = null;
+ private Integer descriptionTextColor = null;
+ @DimenRes
+ private int titleTextDimen = -1;
+ @DimenRes
+ private int descriptionTextDimen = -1;
+ private int titleTextSize = 20;
+ private int descriptionTextSize = 18;
+
+ protected TapTarget(Rect bounds, CharSequence title, @Nullable CharSequence description) {
+ this(title, description);
+ if (bounds == null) {
+ throw new IllegalArgumentException("Cannot pass null bounds or title");
+ }
+
+ this.bounds = bounds;
+ }
+
+ protected TapTarget(CharSequence title, @Nullable CharSequence description) {
+ if (title == null) {
+ throw new IllegalArgumentException("Cannot pass null title");
+ }
+
+ this.title = title;
+ this.description = description;
+ }
+
+ /**
+ * Return a tap target for the overflow button from the given toolbar
+ *
+ * Note: This is currently experimental, use at your own risk
+ */
+ public static TapTarget forToolbarOverflow(Toolbar toolbar, CharSequence title) {
+ return forToolbarOverflow(toolbar, title, null);
+ }
+
+ /**
+ * Return a tap target for the overflow button from the given toolbar
+ *
+ * Note: This is currently experimental, use at your own risk
+ */
+ public static TapTarget forToolbarOverflow(Toolbar toolbar, CharSequence title,
+ @Nullable CharSequence description) {
+ return new ToolbarTapTarget(toolbar, false, title, description);
+ }
+
+ /**
+ * Return a tap target for the overflow button from the given toolbar
+ *
+ * Note: This is currently experimental, use at your own risk
+ */
+ public static TapTarget forToolbarOverflow(android.widget.Toolbar toolbar, CharSequence title) {
+ return forToolbarOverflow(toolbar, title, null);
+ }
+
+ /**
+ * Return a tap target for the overflow button from the given toolbar
+ *
+ * Note: This is currently experimental, use at your own risk
+ */
+ public static TapTarget forToolbarOverflow(android.widget.Toolbar toolbar, CharSequence title,
+ @Nullable CharSequence description) {
+ return new ToolbarTapTarget(toolbar, false, title, description);
+ }
+
+ /**
+ * Return a tap target for the navigation button (back, up, etc) from the given toolbar
+ **/
+ public static TapTarget forToolbarNavigationIcon(Toolbar toolbar, CharSequence title) {
+ return forToolbarNavigationIcon(toolbar, title, null);
+ }
+
+ /**
+ * Return a tap target for the navigation button (back, up, etc) from the given toolbar
+ **/
+ public static TapTarget forToolbarNavigationIcon(Toolbar toolbar, CharSequence title,
+ @Nullable CharSequence description) {
+ return new ToolbarTapTarget(toolbar, true, title, description);
+ }
+
+ /**
+ * Return a tap target for the navigation button (back, up, etc) from the given toolbar
+ **/
+ public static TapTarget forToolbarNavigationIcon(android.widget.Toolbar toolbar, CharSequence title) {
+ return forToolbarNavigationIcon(toolbar, title, null);
+ }
+
+ /**
+ * Return a tap target for the navigation button (back, up, etc) from the given toolbar
+ **/
+ public static TapTarget forToolbarNavigationIcon(android.widget.Toolbar toolbar, CharSequence title,
+ @Nullable CharSequence description) {
+ return new ToolbarTapTarget(toolbar, true, title, description);
+ }
+
+ /**
+ * Return a tap target for the menu item from the given toolbar
+ **/
+ public static TapTarget forToolbarMenuItem(Toolbar toolbar, @IdRes int menuItemId,
+ CharSequence title) {
+ return forToolbarMenuItem(toolbar, menuItemId, title, null);
+ }
+
+ /**
+ * Return a tap target for the menu item from the given toolbar
+ **/
+ public static TapTarget forToolbarMenuItem(Toolbar toolbar, @IdRes int menuItemId,
+ CharSequence title, @Nullable CharSequence description) {
+ return new ToolbarTapTarget(toolbar, menuItemId, title, description);
+ }
+
+ /**
+ * Return a tap target for the menu item from the given toolbar
+ **/
+ public static TapTarget forToolbarMenuItem(android.widget.Toolbar toolbar, @IdRes int menuItemId,
+ CharSequence title) {
+ return forToolbarMenuItem(toolbar, menuItemId, title, null);
+ }
+
+ /**
+ * Return a tap target for the menu item from the given toolbar
+ **/
+ public static TapTarget forToolbarMenuItem(android.widget.Toolbar toolbar, @IdRes int menuItemId,
+ CharSequence title, @Nullable CharSequence description) {
+ return new ToolbarTapTarget(toolbar, menuItemId, title, description);
+ }
+
+ /**
+ * Return a tap target for the specified view
+ **/
+ public static TapTarget forView(View view, CharSequence title) {
+ return forView(view, title, null);
+ }
+
+ /**
+ * Return a tap target for the specified view
+ **/
+ public static TapTarget forView(View view, CharSequence title, @Nullable CharSequence description) {
+ return new ViewTapTarget(view, title, description);
+ }
+
+ /**
+ * Return a tap target for the specified bounds
+ **/
+ public static TapTarget forBounds(Rect bounds, CharSequence title) {
+ return forBounds(bounds, title, null);
+ }
+
+ /**
+ * Return a tap target for the specified bounds
+ **/
+ public static TapTarget forBounds(Rect bounds, CharSequence title, @Nullable CharSequence description) {
+ return new TapTarget(bounds, title, description);
+ }
+
+ /**
+ * Specify whether the target should be transparent
+ **/
+ public TapTarget transparentTarget(boolean transparent) {
+ this.transparentTarget = transparent;
+ return this;
+ }
+
+ /**
+ * Specify the color resource for the outer circle
+ **/
+ public TapTarget outerCircleColor(@ColorRes int color) {
+ this.outerCircleColorRes = color;
+ return this;
+ }
+
+ /**
+ * Specify the color value for the outer circle
+ **/
+ // TODO(Hilal): In v2, this API should be cleaned up / torched
+ public TapTarget outerCircleColorInt(@ColorInt int color) {
+ this.outerCircleColor = color;
+ return this;
+ }
+
+ /**
+ * Specify the color resource for the target
+ **/
+ public TapTarget targetTintColor(@ColorRes int color) {
+ this.targetTintColorRes = color;
+ return this;
+ }
+
+ /**
+ * Specify the color value for the target
+ **/
+ public TapTarget targetTintColorInt(@ColorInt int color) {
+ this.targetTintColor = color;
+ return this;
+ }
+
+ /**
+ * Specify the alpha value [0.0, 1.0] of the outer circle
+ **/
+ public TapTarget outerCircleAlpha(float alpha) {
+ if (alpha < 0.0f || alpha > 1.0f) {
+ throw new IllegalArgumentException("Given an invalid alpha value: " + alpha);
+ }
+ this.outerCircleAlpha = alpha;
+ return this;
+ }
+
+ /**
+ * Specify the color resource for the target circle
+ **/
+ public TapTarget targetCircleColor(@ColorRes int color) {
+ this.targetCircleColorRes = color;
+ return this;
+ }
+
+ /**
+ * Specify the color value for the target circle
+ **/
+ // TODO(Hilal): In v2, this API should be cleaned up / torched
+ public TapTarget targetCircleColorInt(@ColorInt int color) {
+ this.targetCircleColor = color;
+ return this;
+ }
+
+ /**
+ * Specify the color resource for all text
+ **/
+ public TapTarget textColor(@ColorRes int color) {
+ this.titleTextColorRes = color;
+ this.descriptionTextColorRes = color;
+ return this;
+ }
+
+ /**
+ * Specify the color value for all text
+ **/
+ // TODO(Hilal): In v2, this API should be cleaned up / torched
+ public TapTarget textColorInt(@ColorInt int color) {
+ this.titleTextColor = color;
+ this.descriptionTextColor = color;
+ return this;
+ }
+
+ /**
+ * Specify the color resource for the title text
+ **/
+ public TapTarget titleTextColor(@ColorRes int color) {
+ this.titleTextColorRes = color;
+ return this;
+ }
+
+ /**
+ * Specify the color value for the title text
+ **/
+ // TODO(Hilal): In v2, this API should be cleaned up / torched
+ public TapTarget titleTextColorInt(@ColorInt int color) {
+ this.titleTextColor = color;
+ return this;
+ }
+
+ /**
+ * Specify the color resource for the description text
+ **/
+ public TapTarget descriptionTextColor(@ColorRes int color) {
+ this.descriptionTextColorRes = color;
+ return this;
+ }
+
+ /**
+ * Specify the color value for the description text
+ **/
+ // TODO(Hilal): In v2, this API should be cleaned up / torched
+ public TapTarget descriptionTextColorInt(@ColorInt int color) {
+ this.descriptionTextColor = color;
+ return this;
+ }
+
+ /**
+ * Specify the typeface for all text
+ **/
+ public TapTarget textTypeface(Typeface typeface) {
+ if (typeface == null) throw new IllegalArgumentException("Cannot use a null typeface");
+ titleTypeface = typeface;
+ descriptionTypeface = typeface;
+ return this;
+ }
+
+ /**
+ * Specify the typeface for title text
+ **/
+ public TapTarget titleTypeface(Typeface titleTypeface) {
+ if (titleTypeface == null) throw new IllegalArgumentException("Cannot use a null typeface");
+ this.titleTypeface = titleTypeface;
+ return this;
+ }
+
+ /**
+ * Specify the typeface for description text
+ **/
+ public TapTarget descriptionTypeface(Typeface descriptionTypeface) {
+ if (descriptionTypeface == null)
+ throw new IllegalArgumentException("Cannot use a null typeface");
+ this.descriptionTypeface = descriptionTypeface;
+ return this;
+ }
+
+ /**
+ * Specify the text size for the title in SP
+ **/
+ public TapTarget titleTextSize(int sp) {
+ if (sp < 0) throw new IllegalArgumentException("Given negative text size");
+ this.titleTextSize = sp;
+ return this;
+ }
+
+ /**
+ * Specify the text size for the description in SP
+ **/
+ public TapTarget descriptionTextSize(int sp) {
+ if (sp < 0) throw new IllegalArgumentException("Given negative text size");
+ this.descriptionTextSize = sp;
+ return this;
+ }
+
+ /**
+ * Specify the text size for the title via a dimen resource
+ *
+ * Note: If set, this value will take precedence over the specified sp size
+ */
+ public TapTarget titleTextDimen(@DimenRes int dimen) {
+ this.titleTextDimen = dimen;
+ return this;
+ }
+
+ /**
+ * Specify the alpha value [0.0, 1.0] of the description text
+ **/
+ public TapTarget descriptionTextAlpha(float descriptionTextAlpha) {
+ if (descriptionTextAlpha < 0 || descriptionTextAlpha > 1f) {
+ throw new IllegalArgumentException("Given an invalid alpha value: " + descriptionTextAlpha);
+ }
+ this.descriptionTextAlpha = descriptionTextAlpha;
+ return this;
+ }
+
+ /**
+ * Specify the text size for the description via a dimen resource
+ *
+ * Note: If set, this value will take precedence over the specified sp size
+ */
+ public TapTarget descriptionTextDimen(@DimenRes int dimen) {
+ this.descriptionTextDimen = dimen;
+ return this;
+ }
+
+ /**
+ * Specify the color resource to use as a dim effect
+ *
+ * Note: The given color will have its opacity modified to 30% automatically
+ */
+ public TapTarget dimColor(@ColorRes int color) {
+ this.dimColorRes = color;
+ return this;
+ }
+
+ /**
+ * Specify the color value to use as a dim effect
+ *
+ * Note: The given color will have its opacity modified to 30% automatically
+ */
+ // TODO(Hilal): In v2, this API should be cleaned up / torched
+ public TapTarget dimColorInt(@ColorInt int color) {
+ this.dimColor = color;
+ return this;
+ }
+
+ /**
+ * Specify whether or not to draw a drop shadow around the outer circle
+ **/
+ public TapTarget drawShadow(boolean draw) {
+ this.drawShadow = draw;
+ return this;
+ }
+
+ /**
+ * Specify whether or not the target should be cancelable
+ **/
+ public TapTarget cancelable(boolean status) {
+ this.cancelable = status;
+ return this;
+ }
+
+ /**
+ * Specify whether to tint the target's icon with the outer circle's color
+ **/
+ public TapTarget tintTarget(boolean tint) {
+ this.tintTarget = tint;
+ return this;
+ }
+
+ /**
+ * Specify whether to draw the target as oval
+ * This config invalidate targetRadius
+ **/
+ public TapTarget drawOval(boolean drawOval) {
+
+ if (targetRadius != 44)
+ throw new IllegalStateException("drawOval can't be used combined with targetRadius");
+
+ this.drawOval = drawOval;
+ return this;
+ }
+
+ /**
+ * Specify the icon that will be drawn in the center of the target bounds
+ **/
+ public TapTarget icon(Drawable icon) {
+ return icon(icon, false);
+ }
+
+ /**
+ * Specify the icon that will be drawn in the center of the target bounds
+ *
+ * @param hasSetBounds Whether the drawable already has its bounds correctly set. If the
+ * drawable does not have its bounds set, then the following bounds will
+ * be applied:
+ * (0, 0, intrinsic-width, intrinsic-height)
+ */
+ public TapTarget icon(Drawable icon, boolean hasSetBounds) {
+ if (icon == null) throw new IllegalArgumentException("Cannot use null drawable");
+ this.icon = icon;
+
+ if (!hasSetBounds) {
+ this.icon.setBounds(new Rect(0, 0, this.icon.getIntrinsicWidth(), this.icon.getIntrinsicHeight()));
+ }
+
+ return this;
+ }
+
+ /**
+ * Specify a unique identifier for this target.
+ **/
+ public TapTarget id(int id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * Specify the target radius in dp.
+ **/
+ public TapTarget targetRadius(int targetRadius) {
+
+ if (drawOval)
+ throw new IllegalStateException("targetRadius can't be used combined with drawOval");
+
+ this.targetRadius = targetRadius;
+ return this;
+ }
+
+ /**
+ * Specify the target radius in dp.
+ **/
+ public TapTarget targetPadding(int targetPadding) {
+ this.targetPadding = targetPadding;
+ return this;
+ }
+
+ /**
+ * Return the id associated with this tap target
+ **/
+ public int id() {
+ return id;
+ }
+
+ /**
+ * In case your target needs time to be ready (laid out in your view, not created, etc), the
+ * runnable passed here will be invoked when the target is ready.
+ */
+ public void onReady(Runnable runnable) {
+ runnable.run();
+ }
+
+ /**
+ * Returns the target bounds. Throws an exception if they are not set
+ * (target may not be ready)
+ *
+ * This will only be called internally when {@link #onReady(Runnable)} invokes its runnable
+ */
+ public Rect bounds() {
+ if (bounds == null) {
+ throw new IllegalStateException("Requesting bounds that are not set! Make sure your target is ready");
+ }
+ return bounds;
+ }
+
+ @Nullable
+ Integer outerCircleColorInt(Context context) {
+ return colorResOrInt(context, outerCircleColor, outerCircleColorRes);
+ }
+
+ @Nullable
+ Integer targetTintColorInt(Context context) {
+ return colorResOrInt(context, targetTintColor, targetTintColorRes);
+ }
+
+ @Nullable
+ Integer targetCircleColorInt(Context context) {
+ return colorResOrInt(context, targetCircleColor, targetCircleColorRes);
+ }
+
+ @Nullable
+ Integer dimColorInt(Context context) {
+ return colorResOrInt(context, dimColor, dimColorRes);
+ }
+
+ @Nullable
+ Integer titleTextColorInt(Context context) {
+ return colorResOrInt(context, titleTextColor, titleTextColorRes);
+ }
+
+ @Nullable
+ Integer descriptionTextColorInt(Context context) {
+ return colorResOrInt(context, descriptionTextColor, descriptionTextColorRes);
+ }
+
+ int titleTextSizePx(Context context) {
+ return dimenOrSize(context, titleTextSize, titleTextDimen);
+ }
+
+ int descriptionTextSizePx(Context context) {
+ return dimenOrSize(context, descriptionTextSize, descriptionTextDimen);
+ }
+
+ @Nullable
+ private Integer colorResOrInt(Context context, @Nullable Integer value, @ColorRes int resource) {
+ if (resource != -1) {
+ return ContextCompat.getColor(context, resource);
+ }
+
+ return value;
+ }
+
+ private int dimenOrSize(Context context, int size, @DimenRes int dimen) {
+ if (dimen != -1) {
+ return context.getResources().getDimensionPixelSize(dimen);
+ }
+
+ return UiUtil.sp(context, size);
+ }
}
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTargetSequence.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTargetSequence.java
index 23a33b2..eb6ff09 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTargetSequence.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTargetSequence.java
@@ -17,6 +17,7 @@
import android.app.Activity;
import android.app.Dialog;
+
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -32,38 +33,49 @@
* Internally, a FIFO queue is held to dictate which {@link TapTarget} will be shown.
*/
public class TapTargetSequence {
- private final @Nullable Activity activity;
- private final @Nullable Dialog dialog;
+ private final @Nullable
+ Activity activity;
+ private final @Nullable
+ Dialog dialog;
private final Queue targets;
- private boolean active;
-
- @Nullable
- private TapTargetView currentView;
-
Listener listener;
boolean considerOuterCircleCanceled;
boolean continueOnCancel;
+ private boolean active;
+ @Nullable
+ private TapTargetView currentView;
+ private final TapTargetView.Listener tapTargetListener = new TapTargetView.Listener() {
+ @Override
+ public void onTargetClick(TapTargetView view) {
+ super.onTargetClick(view);
+ if (listener != null) {
+ listener.onSequenceStep(view.target, true);
+ }
+ showNext();
+ }
- public interface Listener {
- /** Called when there are no more tap targets to display */
- void onSequenceFinish();
-
- /**
- * Called when moving onto the next tap target.
- * @param lastTarget The last displayed target
- * @param targetClicked Whether the last displayed target was clicked (this will always be true
- * unless you have set {@link #continueOnCancel(boolean)} and the user
- * clicks outside of the target
- */
- void onSequenceStep(TapTarget lastTarget, boolean targetClicked);
+ @Override
+ public void onOuterCircleClick(TapTargetView view) {
+ if (considerOuterCircleCanceled) {
+ onTargetCancel(view);
+ }
+ }
- /**
- * Called when the user taps outside of the current target, the target is cancelable, and
- * {@link #continueOnCancel(boolean)} is not set.
- * @param lastTarget The last displayed target
- */
- void onSequenceCanceled(TapTarget lastTarget);
- }
+ @Override
+ public void onTargetCancel(TapTargetView view) {
+ super.onTargetCancel(view);
+ if (continueOnCancel) {
+ if (listener != null) {
+ listener.onSequenceStep(view.target, false);
+ }
+ showNext();
+ } else {
+ if (listener != null) {
+ listener.onSequenceCanceled(view.target);
+ }
+ }
+ }
+ };
public TapTargetSequence(Activity activity) {
if (activity == null) throw new IllegalArgumentException("Activity is null");
@@ -79,43 +91,57 @@ public TapTargetSequence(Dialog dialog) {
this.targets = new LinkedList<>();
}
- /** Adds the given targets, in order, to the pending queue of {@link TapTarget}s */
+ /**
+ * Adds the given targets, in order, to the pending queue of {@link TapTarget}s
+ */
public TapTargetSequence targets(List targets) {
this.targets.addAll(targets);
return this;
}
- /** Adds the given targets, in order, to the pending queue of {@link TapTarget}s */
+ /**
+ * Adds the given targets, in order, to the pending queue of {@link TapTarget}s
+ */
public TapTargetSequence targets(TapTarget... targets) {
Collections.addAll(this.targets, targets);
return this;
}
- /** Adds the given target to the pending queue of {@link TapTarget}s */
+ /**
+ * Adds the given target to the pending queue of {@link TapTarget}s
+ */
public TapTargetSequence target(TapTarget target) {
this.targets.add(target);
return this;
}
- /** Whether or not to continue the sequence when a {@link TapTarget} is canceled **/
+ /**
+ * Whether or not to continue the sequence when a {@link TapTarget} is canceled
+ **/
public TapTargetSequence continueOnCancel(boolean status) {
this.continueOnCancel = status;
return this;
}
- /** Whether or not to consider taps on the outer circle as a cancellation **/
+ /**
+ * Whether or not to consider taps on the outer circle as a cancellation
+ **/
public TapTargetSequence considerOuterCircleCanceled(boolean status) {
this.considerOuterCircleCanceled = status;
return this;
}
- /** Specify the listener for this sequence **/
+ /**
+ * Specify the listener for this sequence
+ **/
public TapTargetSequence listener(Listener listener) {
this.listener = listener;
return this;
}
- /** Immediately starts the sequence and displays the first target from the queue **/
+ /**
+ * Immediately starts the sequence and displays the first target from the queue
+ **/
@UiThread
public void start() {
if (targets.isEmpty() || active) {
@@ -126,7 +152,9 @@ public void start() {
showNext();
}
- /** Immediately starts the sequence from the given targetId's position in the queue */
+ /**
+ * Immediately starts the sequence from the given targetId's position in the queue
+ */
public void startWith(int targetId) {
if (active) {
return;
@@ -144,7 +172,9 @@ public void startWith(int targetId) {
start();
}
- /** Immediately starts the sequence at the specified zero-based index in the queue */
+ /**
+ * Immediately starts the sequence at the specified zero-based index in the queue
+ */
public void startAt(int index) {
if (active) {
return;
@@ -170,6 +200,7 @@ public void startAt(int index) {
* Cancels the sequence, if the current target is cancelable.
* When the sequence is canceled, the current target is dismissed and the remaining targets are
* removed from the sequence.
+ *
* @return whether the sequence was canceled or not
*/
@UiThread
@@ -203,36 +234,28 @@ void showNext() {
}
}
- private final TapTargetView.Listener tapTargetListener = new TapTargetView.Listener() {
- @Override
- public void onTargetClick(TapTargetView view) {
- super.onTargetClick(view);
- if (listener != null) {
- listener.onSequenceStep(view.target, true);
- }
- showNext();
- }
+ public interface Listener {
+ /**
+ * Called when there are no more tap targets to display
+ */
+ void onSequenceFinish();
- @Override
- public void onOuterCircleClick(TapTargetView view) {
- if (considerOuterCircleCanceled) {
- onTargetCancel(view);
- }
- }
+ /**
+ * Called when moving onto the next tap target.
+ *
+ * @param lastTarget The last displayed target
+ * @param targetClicked Whether the last displayed target was clicked (this will always be true
+ * unless you have set {@link #continueOnCancel(boolean)} and the user
+ * clicks outside of the target
+ */
+ void onSequenceStep(TapTarget lastTarget, boolean targetClicked);
- @Override
- public void onTargetCancel(TapTargetView view) {
- super.onTargetCancel(view);
- if (continueOnCancel) {
- if (listener != null) {
- listener.onSequenceStep(view.target, false);
- }
- showNext();
- } else {
- if (listener != null) {
- listener.onSequenceCanceled(view.target);
- }
- }
- }
- };
+ /**
+ * Called when the user taps outside of the current target, the target is cancelable, and
+ * {@link #continueOnCancel(boolean)} is not set.
+ *
+ * @param lastTarget The last displayed target
+ */
+ void onSequenceCanceled(TapTarget lastTarget);
+ }
}
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTargetView.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTargetView.java
index c21735d..48f4eb6 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTargetView.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTargetView.java
@@ -33,11 +33,11 @@
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
-import androidx.annotation.Nullable;
import android.text.DynamicLayout;
import android.text.Layout;
import android.text.SpannableStringBuilder;
@@ -55,6 +55,8 @@
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
+import androidx.annotation.Nullable;
+
/**
* TapTargetView implements a feature discovery paradigm following Google's Material Design
* guidelines.
@@ -67,981 +69,1107 @@
*/
@SuppressLint("ViewConstructor")
public class TapTargetView extends View {
- private boolean isDismissed = false;
- private boolean isDismissing = false;
- private boolean isInteractable = true;
-
- final int TARGET_PADDING;
- final int TARGET_RADIUS;
- final int TARGET_PULSE_RADIUS;
- final int TEXT_PADDING;
- final int TEXT_SPACING;
- final int TEXT_MAX_WIDTH;
- final int TEXT_POSITIONING_BIAS;
- final int CIRCLE_PADDING;
- final int GUTTER_DIM;
- final int SHADOW_DIM;
- final int SHADOW_JITTER_DIM;
-
- @Nullable
- final ViewGroup boundingParent;
- final ViewManager parent;
- final TapTarget target;
- final Rect targetBounds;
-
- final TextPaint titlePaint;
- final TextPaint descriptionPaint;
- final Paint outerCirclePaint;
- final Paint outerCircleShadowPaint;
- final Paint targetCirclePaint;
- final Paint targetCirclePulsePaint;
-
- CharSequence title;
- @Nullable
- StaticLayout titleLayout;
- @Nullable
- CharSequence description;
- @Nullable
- StaticLayout descriptionLayout;
- boolean isDark;
- boolean debug;
- boolean shouldTintTarget;
- boolean shouldDrawShadow;
- boolean cancelable;
- boolean visible;
-
- // Debug related variables
- @Nullable
- SpannableStringBuilder debugStringBuilder;
- @Nullable
- DynamicLayout debugLayout;
- @Nullable
- TextPaint debugTextPaint;
- @Nullable
- Paint debugPaint;
-
- // Drawing properties
- Rect drawingBounds;
- Rect textBounds;
-
- Path outerCirclePath;
- float outerCircleRadius;
- int calculatedOuterCircleRadius;
- int[] outerCircleCenter;
- int outerCircleAlpha;
-
- float targetCirclePulseRadius;
- int targetCirclePulseAlpha;
-
- float targetCircleRadius;
- int targetCircleAlpha;
-
- int textAlpha;
- int dimColor;
-
- float lastTouchX;
- float lastTouchY;
-
- int topBoundary;
- int bottomBoundary;
-
- Bitmap tintedTarget;
-
- Listener listener;
-
- @Nullable
- ViewOutlineProvider outlineProvider;
-
- public static TapTargetView showFor(Activity activity, TapTarget target) {
- return showFor(activity, target, null);
- }
-
- public static TapTargetView showFor(Activity activity, TapTarget target, Listener listener) {
- if (activity == null) throw new IllegalArgumentException("Activity is null");
-
- final ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
- final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- final ViewGroup content = (ViewGroup) decor.findViewById(android.R.id.content);
- final TapTargetView tapTargetView = new TapTargetView(activity, decor, content, target, listener);
- decor.addView(tapTargetView, layoutParams);
-
- return tapTargetView;
- }
-
- public static TapTargetView showFor(Dialog dialog, TapTarget target) {
- return showFor(dialog, target, null);
- }
-
- public static TapTargetView showFor(Dialog dialog, TapTarget target, Listener listener) {
- if (dialog == null) throw new IllegalArgumentException("Dialog is null");
-
- final Context context = dialog.getContext();
- final WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
- params.format = PixelFormat.RGBA_8888;
- params.flags = 0;
- params.gravity = Gravity.START | Gravity.TOP;
- params.x = 0;
- params.y = 0;
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.MATCH_PARENT;
-
- final TapTargetView tapTargetView = new TapTargetView(context, windowManager, null, target, listener);
- windowManager.addView(tapTargetView, params);
-
- return tapTargetView;
- }
-
- public static class Listener {
- /** Signals that the user has clicked inside of the target **/
- public void onTargetClick(TapTargetView view) {
- view.dismiss(true);
- }
- /** Signals that the user has long clicked inside of the target **/
- public void onTargetLongClick(TapTargetView view) {
- onTargetClick(view);
+ private final float ovalPercentFactor = 1.5F;
+
+ private boolean isDismissed = false;
+ private boolean isDismissing = false;
+ private boolean isInteractable = true;
+
+ int TARGET_RADIUS;
+ final int TARGET_PADDING;
+ final int TARGET_PULSE_RADIUS;
+ final int TEXT_PADDING;
+ final int TEXT_SPACING;
+ final int TEXT_MAX_WIDTH;
+ final int TEXT_POSITIONING_BIAS;
+ final int CIRCLE_PADDING;
+ final int GUTTER_DIM;
+ final int SHADOW_DIM;
+ final int SHADOW_JITTER_DIM;
+
+ @Nullable
+ final ViewGroup boundingParent;
+ final ViewManager parent;
+ final TapTarget target;
+ final Rect targetBounds;
+
+ final TextPaint titlePaint;
+ final TextPaint descriptionPaint;
+ final Paint outerCirclePaint;
+ final Paint outerCircleShadowPaint;
+ final Paint targetCirclePaint;
+ final Paint targetCirclePulsePaint;
+
+ final RectF targetOvalRect;
+ final RectF targetOvalPulseRect;
+
+ CharSequence title;
+ @Nullable
+ StaticLayout titleLayout;
+ @Nullable
+ CharSequence description;
+ @Nullable
+ StaticLayout descriptionLayout;
+ boolean isDark;
+ boolean debug;
+ boolean shouldTintTarget;
+ boolean shouldDrawShadow;
+ boolean cancelable;
+ boolean visible;
+
+ // Debug related variables
+ @Nullable
+ SpannableStringBuilder debugStringBuilder;
+ @Nullable
+ DynamicLayout debugLayout;
+ @Nullable
+ TextPaint debugTextPaint;
+ @Nullable
+ Paint debugPaint;
+
+ // Drawing properties
+ Rect drawingBounds;
+ Rect textBounds;
+
+ Path outerCirclePath;
+ float outerCircleRadius;
+ int calculatedOuterCircleRadius;
+ int[] outerCircleCenter;
+ int outerCircleAlpha;
+
+ float targetOvalPulseScaleFactor;
+ float targetCirclePulseRadius;
+ int targetCirclePulseAlpha;
+
+ float targetOvalScaleFactor;
+ float targetCircleRadius;
+ int targetCircleAlpha;
+
+ int textAlpha;
+ int dimColor;
+
+ float lastTouchX;
+ float lastTouchY;
+
+ int topBoundary;
+ int bottomBoundary;
+
+ Bitmap tintedTarget;
+
+ Listener listener;
+
+ @Nullable
+ ViewOutlineProvider outlineProvider;
+
+ public static TapTargetView showFor(Activity activity, TapTarget target) {
+ return showFor(activity, target, null);
}
- /** If cancelable, signals that the user has clicked outside of the outer circle **/
- public void onTargetCancel(TapTargetView view) {
- view.dismiss(false);
- }
+ public static TapTargetView showFor(Activity activity, TapTarget target, Listener listener) {
+ if (activity == null) throw new IllegalArgumentException("Activity is null");
- /** Signals that the user clicked on the outer circle portion of the tap target **/
- public void onOuterCircleClick(TapTargetView view) {
- // no-op as default
+ final ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
+ final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ final ViewGroup content = (ViewGroup) decor.findViewById(android.R.id.content);
+ final TapTargetView tapTargetView = new TapTargetView(activity, decor, content, target, listener);
+ decor.addView(tapTargetView, layoutParams);
+
+ return tapTargetView;
}
- /**
- * Signals that the tap target has been dismissed
- * @param userInitiated Whether the user caused this action
- */
- public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
+ public static TapTargetView showFor(Dialog dialog, TapTarget target) {
+ return showFor(dialog, target, null);
}
- }
- final FloatValueAnimatorBuilder.UpdateListener expandContractUpdateListener = new FloatValueAnimatorBuilder.UpdateListener() {
- @Override
- public void onUpdate(float lerpTime) {
- final float newOuterCircleRadius = calculatedOuterCircleRadius * lerpTime;
- final boolean expanding = newOuterCircleRadius > outerCircleRadius;
- if (!expanding) {
- // When contracting we need to invalidate the old drawing bounds. Otherwise
- // you will see artifacts as the circle gets smaller
- calculateDrawingBounds();
- }
-
- final float targetAlpha = target.outerCircleAlpha * 255;
- outerCircleRadius = newOuterCircleRadius;
- outerCircleAlpha = (int) Math.min(targetAlpha, (lerpTime * 1.5f * targetAlpha));
- outerCirclePath.reset();
- outerCirclePath.addCircle(outerCircleCenter[0], outerCircleCenter[1], outerCircleRadius, Path.Direction.CW);
-
- targetCircleAlpha = (int) Math.min(255.0f, (lerpTime * 1.5f * 255.0f));
-
- if (expanding) {
- targetCircleRadius = TARGET_RADIUS * Math.min(1.0f, lerpTime * 1.5f);
- } else {
- targetCircleRadius = TARGET_RADIUS * lerpTime;
- targetCirclePulseRadius *= lerpTime;
- }
-
- textAlpha = (int) (delayedLerp(lerpTime, 0.7f) * 255);
-
- if (expanding) {
- calculateDrawingBounds();
- }
-
- invalidateViewAndOutline(drawingBounds);
+ public static TapTargetView showFor(Dialog dialog, TapTarget target, Listener listener) {
+ if (dialog == null) throw new IllegalArgumentException("Dialog is null");
+
+ final Context context = dialog.getContext();
+ final WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+ params.format = PixelFormat.RGBA_8888;
+ params.flags = 0;
+ params.gravity = Gravity.START | Gravity.TOP;
+ params.x = 0;
+ params.y = 0;
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+
+ final TapTargetView tapTargetView = new TapTargetView(context, windowManager, null, target, listener);
+ windowManager.addView(tapTargetView, params);
+
+ return tapTargetView;
}
- };
- final ValueAnimator expandAnimation = new FloatValueAnimatorBuilder()
- .duration(250)
- .delayBy(250)
- .interpolator(new AccelerateDecelerateInterpolator())
- .onUpdate(new FloatValueAnimatorBuilder.UpdateListener() {
- @Override
- public void onUpdate(float lerpTime) {
- expandContractUpdateListener.onUpdate(lerpTime);
- }
- })
- .onEnd(new FloatValueAnimatorBuilder.EndListener() {
- @Override
- public void onEnd() {
- pulseAnimation.start();
- isInteractable = true;
+ public static class Listener {
+ /**
+ * Signals that the user has clicked inside of the target
+ **/
+ public void onTargetClick(TapTargetView view) {
+ view.dismiss(true);
}
- })
- .build();
-
- final ValueAnimator pulseAnimation = new FloatValueAnimatorBuilder()
- .duration(1000)
- .repeat(ValueAnimator.INFINITE)
- .interpolator(new AccelerateDecelerateInterpolator())
- .onUpdate(new FloatValueAnimatorBuilder.UpdateListener() {
- @Override
- public void onUpdate(float lerpTime) {
- final float pulseLerp = delayedLerp(lerpTime, 0.5f);
- targetCirclePulseRadius = (1.0f + pulseLerp) * TARGET_RADIUS;
- targetCirclePulseAlpha = (int) ((1.0f - pulseLerp) * 255);
- targetCircleRadius = TARGET_RADIUS + halfwayLerp(lerpTime) * TARGET_PULSE_RADIUS;
- if (outerCircleRadius != calculatedOuterCircleRadius) {
- outerCircleRadius = calculatedOuterCircleRadius;
- }
+ /**
+ * Signals that the user has long clicked inside of the target
+ **/
+ public void onTargetLongClick(TapTargetView view) {
+ onTargetClick(view);
+ }
- calculateDrawingBounds();
- invalidateViewAndOutline(drawingBounds);
+ /**
+ * If cancelable, signals that the user has clicked outside of the outer circle
+ **/
+ public void onTargetCancel(TapTargetView view) {
+ view.dismiss(false);
}
- })
- .build();
- final ValueAnimator dismissAnimation = new FloatValueAnimatorBuilder(true)
- .duration(250)
- .interpolator(new AccelerateDecelerateInterpolator())
- .onUpdate(new FloatValueAnimatorBuilder.UpdateListener() {
- @Override
- public void onUpdate(float lerpTime) {
- expandContractUpdateListener.onUpdate(lerpTime);
+ /**
+ * Signals that the user clicked on the outer circle portion of the tap target
+ **/
+ public void onOuterCircleClick(TapTargetView view) {
+ // no-op as default
}
- })
- .onEnd(new FloatValueAnimatorBuilder.EndListener() {
- @Override
- public void onEnd() {
- finishDismiss(true);
+
+ /**
+ * Signals that the tap target has been dismissed
+ *
+ * @param userInitiated Whether the user caused this action
+ */
+ public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
}
- })
- .build();
+ }
- private final ValueAnimator dismissConfirmAnimation = new FloatValueAnimatorBuilder()
- .duration(250)
- .interpolator(new AccelerateDecelerateInterpolator())
- .onUpdate(new FloatValueAnimatorBuilder.UpdateListener() {
+ final FloatValueAnimatorBuilder.UpdateListener expandContractUpdateListener = new FloatValueAnimatorBuilder.UpdateListener() {
@Override
public void onUpdate(float lerpTime) {
- final float spedUpLerp = Math.min(1.0f, lerpTime * 2.0f);
- outerCircleRadius = calculatedOuterCircleRadius * (1.0f + (spedUpLerp * 0.2f));
- outerCircleAlpha = (int) ((1.0f - spedUpLerp) * target.outerCircleAlpha * 255.0f);
- outerCirclePath.reset();
- outerCirclePath.addCircle(outerCircleCenter[0], outerCircleCenter[1], outerCircleRadius, Path.Direction.CW);
- targetCircleRadius = (1.0f - lerpTime) * TARGET_RADIUS;
- targetCircleAlpha = (int) ((1.0f - lerpTime) * 255.0f);
- targetCirclePulseRadius = (1.0f + lerpTime) * TARGET_RADIUS;
- targetCirclePulseAlpha = (int) ((1.0f - lerpTime) * targetCirclePulseAlpha);
- textAlpha = (int) ((1.0f - spedUpLerp) * 255.0f);
- calculateDrawingBounds();
- invalidateViewAndOutline(drawingBounds);
- }
- })
- .onEnd(new FloatValueAnimatorBuilder.EndListener() {
- @Override
- public void onEnd() {
- finishDismiss(true);
- }
- })
- .build();
-
- private ValueAnimator[] animators = new ValueAnimator[]
- {expandAnimation, pulseAnimation, dismissConfirmAnimation, dismissAnimation};
-
- private final ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener;
-
- /**
- * This constructor should only be used directly for very specific use cases not covered by
- * the static factory methods.
- *
- * @param context The host context
- * @param parent The parent that this TapTargetView will become a child of. This parent should
- * allow the largest possible area for this view to utilize
- * @param boundingParent Optional. Will be used to calculate boundaries if needed. For example,
- * if your view is added to the decor view of your Window, then you want
- * to adjust for system ui like the navigation bar or status bar, and so
- * you would pass in the content view (which doesn't include system ui)
- * here.
- * @param target The {@link TapTarget} to target
- * @param userListener Optional. The {@link Listener} instance for this view
- */
- public TapTargetView(final Context context,
- final ViewManager parent,
- @Nullable final ViewGroup boundingParent,
- final TapTarget target,
- @Nullable final Listener userListener) {
- super(context);
- if (target == null) throw new IllegalArgumentException("Target cannot be null");
-
- this.target = target;
- this.parent = parent;
- this.boundingParent = boundingParent;
- this.listener = userListener != null ? userListener : new Listener();
- this.title = target.title;
- this.description = target.description;
-
- TARGET_PADDING = UiUtil.dp(context, 20);
- CIRCLE_PADDING = UiUtil.dp(context, 40);
- TARGET_RADIUS = UiUtil.dp(context, target.targetRadius);
- TEXT_PADDING = UiUtil.dp(context, 40);
- TEXT_SPACING = UiUtil.dp(context, 8);
- TEXT_MAX_WIDTH = UiUtil.dp(context, 360);
- TEXT_POSITIONING_BIAS = UiUtil.dp(context, 20);
- GUTTER_DIM = UiUtil.dp(context, 88);
- SHADOW_DIM = UiUtil.dp(context, 8);
- SHADOW_JITTER_DIM = UiUtil.dp(context, 1);
- TARGET_PULSE_RADIUS = (int) (0.1f * TARGET_RADIUS);
-
- outerCirclePath = new Path();
- targetBounds = new Rect();
- drawingBounds = new Rect();
-
- titlePaint = new TextPaint();
- titlePaint.setTextSize(target.titleTextSizePx(context));
- titlePaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
- titlePaint.setAntiAlias(true);
-
- descriptionPaint = new TextPaint();
- descriptionPaint.setTextSize(target.descriptionTextSizePx(context));
- descriptionPaint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
- descriptionPaint.setAntiAlias(true);
- descriptionPaint.setAlpha((int) (0.54f * 255.0f));
-
- outerCirclePaint = new Paint();
- outerCirclePaint.setAntiAlias(true);
- outerCirclePaint.setAlpha((int) (target.outerCircleAlpha * 255.0f));
-
- outerCircleShadowPaint = new Paint();
- outerCircleShadowPaint.setAntiAlias(true);
- outerCircleShadowPaint.setAlpha(50);
- outerCircleShadowPaint.setStyle(Paint.Style.STROKE);
- outerCircleShadowPaint.setStrokeWidth(SHADOW_JITTER_DIM);
- outerCircleShadowPaint.setColor(Color.BLACK);
-
- targetCirclePaint = new Paint();
- targetCirclePaint.setAntiAlias(true);
-
- targetCirclePulsePaint = new Paint();
- targetCirclePulsePaint.setAntiAlias(true);
-
- applyTargetOptions(context);
-
- final boolean hasKitkat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
- final boolean translucentStatusBar;
- final boolean translucentNavigationBar;
- final boolean layoutNoLimits;
-
- if (context instanceof Activity) {
- Activity activity = (Activity) context;
- final int flags = activity.getWindow().getAttributes().flags;
- translucentStatusBar = hasKitkat && (flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0;
- translucentNavigationBar = hasKitkat && (flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0;
- layoutNoLimits = (flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0;
- } else {
- translucentStatusBar = false;
- translucentNavigationBar = false;
- layoutNoLimits = false;
- }
+ final float newOuterCircleRadius = calculatedOuterCircleRadius * lerpTime;
+ final boolean expanding = newOuterCircleRadius > outerCircleRadius;
+ if (!expanding) {
+ // When contracting we need to invalidate the old drawing bounds. Otherwise
+ // you will see artifacts as the circle gets smaller
+ calculateDrawingBounds();
+ }
- globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (isDismissing) {
- return;
- }
- updateTextLayouts();
- target.onReady(new Runnable() {
- @Override
- public void run() {
- final int[] offset = new int[2];
-
- targetBounds.set(target.bounds());
-
- getLocationOnScreen(offset);
- targetBounds.offset(-offset[0], -offset[1]);
-
- if (boundingParent != null) {
- final WindowManager windowManager
- = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- final DisplayMetrics displayMetrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getMetrics(displayMetrics);
-
- final Rect rect = new Rect();
- boundingParent.getWindowVisibleDisplayFrame(rect);
- int[] parentLocation = new int[2];
- boundingParent.getLocationInWindow(parentLocation);
-
- if (translucentStatusBar) {
- rect.top = parentLocation[1];
- }
- if (translucentNavigationBar) {
- rect.bottom = parentLocation[1] + boundingParent.getHeight();
- }
-
- // We bound the boundaries to be within the screen's coordinates to
- // handle the case where the flag FLAG_LAYOUT_NO_LIMITS is set
- if (layoutNoLimits) {
- topBoundary = Math.max(0, rect.top);
- bottomBoundary = Math.min(rect.bottom, displayMetrics.heightPixels);
- } else {
- topBoundary = rect.top;
- bottomBoundary = rect.bottom;
- }
+ final float targetAlpha = target.outerCircleAlpha * 255;
+ outerCircleRadius = newOuterCircleRadius;
+ outerCircleAlpha = (int) Math.min(targetAlpha, (lerpTime * 1.5f * targetAlpha));
+ outerCirclePath.reset();
+ outerCirclePath.addCircle(outerCircleCenter[0], outerCircleCenter[1], outerCircleRadius, Path.Direction.CW);
+
+ targetCircleAlpha = (int) Math.min(255.0f, (lerpTime * 1.5f * 255.0f));
+
+ targetOvalScaleFactor = lerpTime;
+
+ if (expanding) {
+ targetCircleRadius = TARGET_RADIUS * Math.min(1.0f, lerpTime * 1.5f);
+ } else {
+ targetCircleRadius = TARGET_RADIUS * lerpTime;
+ targetCirclePulseRadius *= lerpTime;
+ targetOvalPulseScaleFactor = targetOvalScaleFactor * lerpTime;
}
- drawTintedTarget();
- requestFocus();
- calculateDimensions();
+ textAlpha = (int) (delayedLerp(lerpTime, 0.7f) * 255);
- startExpandAnimation();
- }
- });
- }
- };
+ if (expanding) {
+ calculateDrawingBounds();
+ }
- getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
-
- setFocusableInTouchMode(true);
- setClickable(true);
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (listener == null || outerCircleCenter == null || !isInteractable) return;
-
- final boolean clickedInTarget =
- distance(targetBounds.centerX(), targetBounds.centerY(), (int) lastTouchX, (int) lastTouchY) <= targetCircleRadius;
- final double distanceToOuterCircleCenter = distance(outerCircleCenter[0], outerCircleCenter[1],
- (int) lastTouchX, (int) lastTouchY);
- final boolean clickedInsideOfOuterCircle = distanceToOuterCircleCenter <= outerCircleRadius;
-
- if (clickedInTarget) {
- isInteractable = false;
- listener.onTargetClick(TapTargetView.this);
- } else if (clickedInsideOfOuterCircle) {
- listener.onOuterCircleClick(TapTargetView.this);
- } else if (cancelable) {
- isInteractable = false;
- listener.onTargetCancel(TapTargetView.this);
+ invalidateViewAndOutline(drawingBounds);
}
- }
- });
+ };
- setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (listener == null) return false;
+ final ValueAnimator expandAnimation = new FloatValueAnimatorBuilder()
+ .duration(250)
+ .delayBy(250)
+ .interpolator(new AccelerateDecelerateInterpolator())
+ .onUpdate(new FloatValueAnimatorBuilder.UpdateListener() {
+ @Override
+ public void onUpdate(float lerpTime) {
+ expandContractUpdateListener.onUpdate(lerpTime);
+ }
+ })
+ .onEnd(new FloatValueAnimatorBuilder.EndListener() {
+ @Override
+ public void onEnd() {
+ pulseAnimation.start();
+ isInteractable = true;
+ }
+ })
+ .build();
+
+ final ValueAnimator pulseAnimation = new FloatValueAnimatorBuilder()
+ .duration(1000)
+ .repeat(ValueAnimator.INFINITE)
+ .interpolator(new AccelerateDecelerateInterpolator())
+ .onUpdate(new FloatValueAnimatorBuilder.UpdateListener() {
+ @Override
+ public void onUpdate(float lerpTime) {
+ final float pulseLerp = delayedLerp(lerpTime, 0.5f);
+ targetCirclePulseRadius = (1.0f + pulseLerp) * TARGET_RADIUS;
+ targetOvalPulseScaleFactor = lerpTime;
+ targetCirclePulseAlpha = (int) ((1.0f - pulseLerp) * 255);
+ targetCircleRadius = TARGET_RADIUS + halfwayLerp(lerpTime) * TARGET_PULSE_RADIUS;
+ targetOvalScaleFactor = halfwayLerp(lerpTime);
+
+ if (outerCircleRadius != calculatedOuterCircleRadius) {
+ outerCircleRadius = calculatedOuterCircleRadius;
+ }
+
+ calculateDrawingBounds();
+ invalidateViewAndOutline(drawingBounds);
+ }
+ })
+ .build();
+
+ final ValueAnimator dismissAnimation = new FloatValueAnimatorBuilder(true)
+ .duration(250)
+ .interpolator(new AccelerateDecelerateInterpolator())
+ .onUpdate(new FloatValueAnimatorBuilder.UpdateListener() {
+ @Override
+ public void onUpdate(float lerpTime) {
+ expandContractUpdateListener.onUpdate(lerpTime);
+ }
+ })
+ .onEnd(new FloatValueAnimatorBuilder.EndListener() {
+ @Override
+ public void onEnd() {
+ finishDismiss(true);
+ }
+ })
+ .build();
+
+ private final ValueAnimator dismissConfirmAnimation = new FloatValueAnimatorBuilder()
+ .duration(250)
+ .interpolator(new AccelerateDecelerateInterpolator())
+ .onUpdate(new FloatValueAnimatorBuilder.UpdateListener() {
+ @Override
+ public void onUpdate(float lerpTime) {
+ final float spedUpLerp = Math.min(1.0f, lerpTime * 2.0f);
+ outerCircleRadius = calculatedOuterCircleRadius * (1.0f + (spedUpLerp * 0.2f));
+ targetOvalScaleFactor = spedUpLerp * 0.2f;
+ outerCircleAlpha = (int) ((1.0f - spedUpLerp) * target.outerCircleAlpha * 255.0f);
+ outerCirclePath.reset();
+ outerCirclePath.addCircle(outerCircleCenter[0], outerCircleCenter[1], outerCircleRadius, Path.Direction.CW);
+ targetCircleRadius = (1.0f - lerpTime) * TARGET_RADIUS;
+ targetOvalPulseScaleFactor = lerpTime;
+ targetCircleAlpha = (int) ((1.0f - lerpTime) * 255.0f);
+ targetCirclePulseRadius = (1.0f + lerpTime) * TARGET_RADIUS;
+ targetCirclePulseAlpha = (int) ((1.0f - lerpTime) * targetCirclePulseAlpha);
+ textAlpha = (int) ((1.0f - spedUpLerp) * 255.0f);
+ calculateDrawingBounds();
+ invalidateViewAndOutline(drawingBounds);
+ }
+ })
+ .onEnd(new FloatValueAnimatorBuilder.EndListener() {
+ @Override
+ public void onEnd() {
+ finishDismiss(true);
+ }
+ })
+ .build();
+
+ private ValueAnimator[] animators = new ValueAnimator[]
+ {expandAnimation, pulseAnimation, dismissConfirmAnimation, dismissAnimation};
+
+ private final ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener;
- if (targetBounds.contains((int) lastTouchX, (int) lastTouchY)) {
- listener.onTargetLongClick(TapTargetView.this);
- return true;
+ /**
+ * This constructor should only be used directly for very specific use cases not covered by
+ * the static factory methods.
+ *
+ * @param context The host context
+ * @param parent The parent that this TapTargetView will become a child of. This parent should
+ * allow the largest possible area for this view to utilize
+ * @param boundingParent Optional. Will be used to calculate boundaries if needed. For example,
+ * if your view is added to the decor view of your Window, then you want
+ * to adjust for system ui like the navigation bar or status bar, and so
+ * you would pass in the content view (which doesn't include system ui)
+ * here.
+ * @param target The {@link TapTarget} to target
+ * @param userListener Optional. The {@link Listener} instance for this view
+ */
+ public TapTargetView(final Context context,
+ final ViewManager parent,
+ @Nullable final ViewGroup boundingParent,
+ final TapTarget target,
+ @Nullable final Listener userListener) {
+ super(context);
+ if (target == null) throw new IllegalArgumentException("Target cannot be null");
+
+ this.target = target;
+ this.parent = parent;
+ this.boundingParent = boundingParent;
+ this.listener = userListener != null ? userListener : new Listener();
+ this.title = target.title;
+ this.description = target.description;
+
+ TARGET_PADDING = UiUtil.dp(context, target.targetPadding);
+ CIRCLE_PADDING = UiUtil.dp(context, 40);
+ TARGET_RADIUS = UiUtil.dp(context, target.targetRadius);
+ TEXT_PADDING = UiUtil.dp(context, 40);
+ TEXT_SPACING = UiUtil.dp(context, 8);
+ TEXT_MAX_WIDTH = UiUtil.dp(context, 360);
+ TEXT_POSITIONING_BIAS = UiUtil.dp(context, 20);
+ GUTTER_DIM = UiUtil.dp(context, 88);
+ SHADOW_DIM = UiUtil.dp(context, 8);
+ SHADOW_JITTER_DIM = UiUtil.dp(context, 1);
+ TARGET_PULSE_RADIUS = (int) (0.1f * TARGET_RADIUS);
+
+ outerCirclePath = new Path();
+ targetBounds = new Rect();
+ drawingBounds = new Rect();
+
+ titlePaint = new TextPaint();
+ titlePaint.setTextSize(target.titleTextSizePx(context));
+ titlePaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
+ titlePaint.setAntiAlias(true);
+
+ descriptionPaint = new TextPaint();
+ descriptionPaint.setTextSize(target.descriptionTextSizePx(context));
+ descriptionPaint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
+ descriptionPaint.setAntiAlias(true);
+ descriptionPaint.setAlpha((int) (0.54f * 255.0f));
+
+ outerCirclePaint = new Paint();
+ outerCirclePaint.setAntiAlias(true);
+ outerCirclePaint.setAlpha((int) (target.outerCircleAlpha * 255.0f));
+
+ outerCircleShadowPaint = new Paint();
+ outerCircleShadowPaint.setAntiAlias(true);
+ outerCircleShadowPaint.setAlpha(50);
+ outerCircleShadowPaint.setStyle(Paint.Style.STROKE);
+ outerCircleShadowPaint.setStrokeWidth(SHADOW_JITTER_DIM);
+ outerCircleShadowPaint.setColor(Color.BLACK);
+
+ targetCirclePaint = new Paint();
+ targetCirclePaint.setAntiAlias(true);
+
+ targetCirclePulsePaint = new Paint();
+ targetCirclePulsePaint.setAntiAlias(true);
+
+ targetOvalRect = new RectF(
+ targetBounds.left * ovalPercentFactor,
+ targetBounds.top * ovalPercentFactor,
+ targetBounds.right * ovalPercentFactor,
+ targetBounds.bottom * ovalPercentFactor
+ );
+ targetOvalPulseRect = new RectF(
+ targetBounds.left * ovalPercentFactor,
+ targetBounds.top * ovalPercentFactor,
+ targetBounds.right * ovalPercentFactor,
+ targetBounds.bottom * ovalPercentFactor
+ );
+
+ applyTargetOptions(context);
+
+ final boolean hasKitkat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ final boolean translucentStatusBar;
+ final boolean translucentNavigationBar;
+ final boolean layoutNoLimits;
+
+ if (context instanceof Activity) {
+ Activity activity = (Activity) context;
+ final int flags = activity.getWindow().getAttributes().flags;
+ translucentStatusBar = hasKitkat && (flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0;
+ translucentNavigationBar = hasKitkat && (flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0;
+ layoutNoLimits = (flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0;
+ } else {
+ translucentStatusBar = false;
+ translucentNavigationBar = false;
+ layoutNoLimits = false;
}
- return false;
- }
- });
- }
-
- private void startExpandAnimation() {
- if (!visible) {
- isInteractable = false;
- expandAnimation.start();
- visible = true;
- }
- }
-
- protected void applyTargetOptions(Context context) {
- shouldTintTarget = !target.transparentTarget && target.tintTarget;
- shouldDrawShadow = target.drawShadow;
- cancelable = target.cancelable;
-
- // We can't clip out portions of a view outline, so if the user specified a transparent
- // target, we need to fallback to drawing a jittered shadow approximation
- if (shouldDrawShadow && Build.VERSION.SDK_INT >= 21 && !target.transparentTarget) {
- outlineProvider = new ViewOutlineProvider() {
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- @Override
- public void getOutline(View view, Outline outline) {
- if (outerCircleCenter == null) return;
- outline.setOval(
- (int) (outerCircleCenter[0] - outerCircleRadius), (int) (outerCircleCenter[1] - outerCircleRadius),
- (int) (outerCircleCenter[0] + outerCircleRadius), (int) (outerCircleCenter[1] + outerCircleRadius));
- outline.setAlpha(outerCircleAlpha / 255.0f);
- if (Build.VERSION.SDK_INT >= 22) {
- outline.offset(0, SHADOW_DIM);
- }
- }
- };
+ globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (isDismissing) {
+ return;
+ }
+ updateTextLayouts();
+ target.onReady(new Runnable() {
+ @Override
+ public void run() {
+ final int[] offset = new int[2];
+
+ targetBounds.set(target.bounds());
+ if (target.drawOval) {
+ float targetSize = Math.max(
+ targetWidth(), targetHeight()
+ ) + 2 * TARGET_PADDING;
+ TARGET_RADIUS = (int) targetSize / 5;
+ }
+
+ getLocationOnScreen(offset);
+ targetBounds.offset(-offset[0], -offset[1]);
+
+ if (boundingParent != null) {
+ final WindowManager windowManager
+ = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ final DisplayMetrics displayMetrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+
+ final Rect rect = new Rect();
+ boundingParent.getWindowVisibleDisplayFrame(rect);
+ int[] parentLocation = new int[2];
+ boundingParent.getLocationInWindow(parentLocation);
+
+ if (translucentStatusBar) {
+ rect.top = parentLocation[1];
+ }
+ if (translucentNavigationBar) {
+ rect.bottom = parentLocation[1] + boundingParent.getHeight();
+ }
+
+ // We bound the boundaries to be within the screen's coordinates to
+ // handle the case where the flag FLAG_LAYOUT_NO_LIMITS is set
+ if (layoutNoLimits) {
+ topBoundary = Math.max(0, rect.top);
+ bottomBoundary = Math.min(rect.bottom, displayMetrics.heightPixels);
+ } else {
+ topBoundary = rect.top;
+ bottomBoundary = rect.bottom;
+ }
+ }
+
+ drawTintedTarget();
+ requestFocus();
+ calculateDimensions();
+
+ startExpandAnimation();
+ }
+ });
+ }
+ };
+
+ getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
+
+ setFocusableInTouchMode(true);
+ setClickable(true);
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (listener == null || outerCircleCenter == null || !isInteractable) return;
+
+ boolean clickedInTarget;
+
+ if (target.drawOval) {
+ clickedInTarget = inOval(
+ (int) lastTouchX,
+ (int) lastTouchY,
+ targetBounds.centerX(),
+ targetBounds.centerY(),
+ (int) (targetOvalRect.bottom - targetOvalRect.top),
+ (int) (targetOvalRect.right - targetOvalRect.left)
+ );
+ } else {
+ clickedInTarget = distance(targetBounds.centerX(),
+ targetBounds.centerY(),
+ (int) lastTouchX,
+ (int) lastTouchY
+ ) <= targetCircleRadius;
+ }
+
+ final double distanceToOuterCircleCenter = distance(outerCircleCenter[0], outerCircleCenter[1],
+ (int) lastTouchX, (int) lastTouchY);
+ final boolean clickedInsideOfOuterCircle = distanceToOuterCircleCenter <= outerCircleRadius;
+
+ if (clickedInTarget) {
+ isInteractable = false;
+ listener.onTargetClick(TapTargetView.this);
+ } else if (clickedInsideOfOuterCircle) {
+ listener.onOuterCircleClick(TapTargetView.this);
+ } else if (cancelable) {
+ isInteractable = false;
+ listener.onTargetCancel(TapTargetView.this);
+ }
+ }
+ });
- setOutlineProvider(outlineProvider);
- setElevation(SHADOW_DIM);
- }
+ setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (listener == null) return false;
+
+ if (targetBounds.contains((int) lastTouchX, (int) lastTouchY)) {
+ listener.onTargetLongClick(TapTargetView.this);
+ return true;
+ }
- if (shouldDrawShadow && outlineProvider == null && Build.VERSION.SDK_INT < 18) {
- setLayerType(LAYER_TYPE_SOFTWARE, null);
- } else {
- setLayerType(LAYER_TYPE_HARDWARE, null);
+ return false;
+ }
+ });
}
- final Resources.Theme theme = context.getTheme();
- isDark = UiUtil.themeIntAttr(context, "isLightTheme") == 0;
+ private Boolean inOval(int x, int y, int h, int k, int b, int a) {
- final Integer outerCircleColor = target.outerCircleColorInt(context);
- if (outerCircleColor != null) {
- outerCirclePaint.setColor(outerCircleColor);
- } else if (theme != null) {
- outerCirclePaint.setColor(UiUtil.themeIntAttr(context, "colorPrimary"));
- } else {
- outerCirclePaint.setColor(Color.WHITE);
- }
+ float checkpoint = checkpoint(h, k, x, y, a / 2f, b / 2f);
- final Integer targetCircleColor = target.targetCircleColorInt(context);
- if (targetCircleColor != null) {
- targetCirclePaint.setColor(targetCircleColor);
- } else {
- targetCirclePaint.setColor(isDark ? Color.BLACK : Color.WHITE);
- }
+ if (checkpoint > 1)
+ return false;
+ else if (checkpoint == 1)
+ return true;
+ else
+ return true;
- if (target.transparentTarget) {
- targetCirclePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
- targetCirclePulsePaint.setColor(targetCirclePaint.getColor());
+ private float checkpoint(float h, float k, float x, float y, float a, float b) {
- final Integer targetDimColor = target.dimColorInt(context);
- if (targetDimColor != null) {
- dimColor = UiUtil.setAlpha(targetDimColor, 0.3f);
- } else {
- dimColor = -1;
- }
+ // checking the equation of
+ // ellipse with the given point
+ double p = (Math.pow((x - h), 2.0) / Math.pow(a, 2.0))
+ + (Math.pow((y - k), 2.0) / Math.pow(b, 2.0));
- final Integer titleTextColor = target.titleTextColorInt(context);
- if (titleTextColor != null) {
- titlePaint.setColor(titleTextColor);
- } else {
- titlePaint.setColor(isDark ? Color.BLACK : Color.WHITE);
- }
+ return (float) p;
- final Integer descriptionTextColor = target.descriptionTextColorInt(context);
- if (descriptionTextColor != null) {
- descriptionPaint.setColor(descriptionTextColor);
- } else {
- descriptionPaint.setColor(titlePaint.getColor());
}
- if (target.titleTypeface != null) {
- titlePaint.setTypeface(target.titleTypeface);
+ private void startExpandAnimation() {
+ if (!visible) {
+ isInteractable = false;
+ expandAnimation.start();
+ visible = true;
+ }
}
- if (target.descriptionTypeface != null) {
- descriptionPaint.setTypeface(target.descriptionTypeface);
- }
- }
+ protected void applyTargetOptions(Context context) {
+ shouldTintTarget = !target.transparentTarget && target.tintTarget;
+ shouldDrawShadow = target.drawShadow;
+ cancelable = target.cancelable;
+
+ // We can't clip out portions of a view outline, so if the user specified a transparent
+ // target, we need to fallback to drawing a jittered shadow approximation
+ if (shouldDrawShadow && Build.VERSION.SDK_INT >= 21 && !target.transparentTarget) {
+ outlineProvider = new ViewOutlineProvider() {
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void getOutline(View view, Outline outline) {
+ if (outerCircleCenter == null) return;
+ outline.setOval(
+ (int) (outerCircleCenter[0] - outerCircleRadius), (int) (outerCircleCenter[1] - outerCircleRadius),
+ (int) (outerCircleCenter[0] + outerCircleRadius), (int) (outerCircleCenter[1] + outerCircleRadius));
+ outline.setAlpha(outerCircleAlpha / 255.0f);
+ if (Build.VERSION.SDK_INT >= 22) {
+ outline.offset(0, SHADOW_DIM);
+ }
+ }
+ };
+
+ setOutlineProvider(outlineProvider);
+ setElevation(SHADOW_DIM);
+ }
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- onDismiss(false);
- }
+ if (shouldDrawShadow && outlineProvider == null && Build.VERSION.SDK_INT < 18) {
+ setLayerType(LAYER_TYPE_SOFTWARE, null);
+ } else {
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
- void onDismiss(boolean userInitiated) {
- if (isDismissed) return;
+ final Resources.Theme theme = context.getTheme();
+ isDark = UiUtil.themeIntAttr(context, "isLightTheme") == 0;
- isDismissing = false;
- isDismissed = true;
+ final Integer outerCircleColor = target.outerCircleColorInt(context);
+ if (outerCircleColor != null) {
+ outerCirclePaint.setColor(outerCircleColor);
+ } else if (theme != null) {
+ outerCirclePaint.setColor(UiUtil.themeIntAttr(context, "colorPrimary"));
+ } else {
+ outerCirclePaint.setColor(Color.WHITE);
+ }
- for (final ValueAnimator animator : animators) {
- animator.cancel();
- animator.removeAllUpdateListeners();
- }
+ final Integer targetCircleColor = target.targetCircleColorInt(context);
+ if (targetCircleColor != null) {
+ targetCirclePaint.setColor(targetCircleColor);
+ } else {
+ targetCirclePaint.setColor(isDark ? Color.BLACK : Color.WHITE);
+ }
- ViewUtil.removeOnGlobalLayoutListener(getViewTreeObserver(), globalLayoutListener);
- visible = false;
+ if (target.transparentTarget) {
+ targetCirclePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ }
- if (listener != null) {
- listener.onTargetDismissed(this, userInitiated);
- }
- }
+ targetCirclePulsePaint.setColor(targetCirclePaint.getColor());
- @Override
- protected void onDraw(Canvas c) {
- if (isDismissed || outerCircleCenter == null) return;
+ final Integer targetDimColor = target.dimColorInt(context);
+ if (targetDimColor != null) {
+ dimColor = UiUtil.setAlpha(targetDimColor, 0.3f);
+ } else {
+ dimColor = -1;
+ }
- if (topBoundary > 0 && bottomBoundary > 0) {
- c.clipRect(0, topBoundary, getWidth(), bottomBoundary);
- }
+ final Integer titleTextColor = target.titleTextColorInt(context);
+ if (titleTextColor != null) {
+ titlePaint.setColor(titleTextColor);
+ } else {
+ titlePaint.setColor(isDark ? Color.BLACK : Color.WHITE);
+ }
- if (dimColor != -1) {
- c.drawColor(dimColor);
- }
+ final Integer descriptionTextColor = target.descriptionTextColorInt(context);
+ if (descriptionTextColor != null) {
+ descriptionPaint.setColor(descriptionTextColor);
+ } else {
+ descriptionPaint.setColor(titlePaint.getColor());
+ }
- int saveCount;
- outerCirclePaint.setAlpha(outerCircleAlpha);
- if (shouldDrawShadow && outlineProvider == null) {
- saveCount = c.save();
- {
- c.clipPath(outerCirclePath, Region.Op.DIFFERENCE);
- drawJitteredShadow(c);
- }
- c.restoreToCount(saveCount);
- }
- c.drawCircle(outerCircleCenter[0], outerCircleCenter[1], outerCircleRadius, outerCirclePaint);
+ if (target.titleTypeface != null) {
+ titlePaint.setTypeface(target.titleTypeface);
+ }
- targetCirclePaint.setAlpha(targetCircleAlpha);
- if (targetCirclePulseAlpha > 0) {
- targetCirclePulsePaint.setAlpha(targetCirclePulseAlpha);
- c.drawCircle(targetBounds.centerX(), targetBounds.centerY(),
- targetCirclePulseRadius, targetCirclePulsePaint);
- }
- c.drawCircle(targetBounds.centerX(), targetBounds.centerY(),
- targetCircleRadius, targetCirclePaint);
-
- saveCount = c.save();
- {
- c.translate(textBounds.left, textBounds.top);
- titlePaint.setAlpha(textAlpha);
- if (titleLayout != null) {
- titleLayout.draw(c);
- }
-
- if (descriptionLayout != null && titleLayout != null) {
- c.translate(0, titleLayout.getHeight() + TEXT_SPACING);
- descriptionPaint.setAlpha((int) (target.descriptionTextAlpha * textAlpha));
- descriptionLayout.draw(c);
- }
- }
- c.restoreToCount(saveCount);
-
- saveCount = c.save();
- {
- if (tintedTarget != null) {
- c.translate(targetBounds.centerX() - tintedTarget.getWidth() / 2,
- targetBounds.centerY() - tintedTarget.getHeight() / 2);
- c.drawBitmap(tintedTarget, 0, 0, targetCirclePaint);
- } else if (target.icon != null) {
- c.translate(targetBounds.centerX() - target.icon.getBounds().width() / 2,
- targetBounds.centerY() - target.icon.getBounds().height() / 2);
- target.icon.setAlpha(targetCirclePaint.getAlpha());
- target.icon.draw(c);
- }
+ if (target.descriptionTypeface != null) {
+ descriptionPaint.setTypeface(target.descriptionTypeface);
+ }
}
- c.restoreToCount(saveCount);
- if (debug) {
- drawDebugInformation(c);
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ onDismiss(false);
}
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent e) {
- lastTouchX = e.getX();
- lastTouchY = e.getY();
- return super.onTouchEvent(e);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (isVisible() && cancelable && keyCode == KeyEvent.KEYCODE_BACK) {
- event.startTracking();
- return true;
+
+ void onDismiss(boolean userInitiated) {
+ if (isDismissed) return;
+
+ isDismissing = false;
+ isDismissed = true;
+
+ for (final ValueAnimator animator : animators) {
+ animator.cancel();
+ animator.removeAllUpdateListeners();
+ }
+
+ ViewUtil.removeOnGlobalLayoutListener(getViewTreeObserver(), globalLayoutListener);
+ visible = false;
+
+ if (listener != null) {
+ listener.onTargetDismissed(this, userInitiated);
+ }
}
- return false;
- }
+ @Override
+ protected void onDraw(Canvas c) {
+ if (isDismissed || outerCircleCenter == null) return;
+
+ if (topBoundary > 0 && bottomBoundary > 0) {
+ c.clipRect(0, topBoundary, getWidth(), bottomBoundary);
+ }
+
+ if (dimColor != -1) {
+ c.drawColor(dimColor);
+ }
+
+ int saveCount;
+ outerCirclePaint.setAlpha(outerCircleAlpha);
+ if (shouldDrawShadow && outlineProvider == null) {
+ saveCount = c.save();
+ {
+ c.clipPath(outerCirclePath, Region.Op.DIFFERENCE);
+ drawJitteredShadow(c);
+ }
+ c.restoreToCount(saveCount);
+ }
+ c.drawCircle(outerCircleCenter[0], outerCircleCenter[1], outerCircleRadius, outerCirclePaint);
+
+ targetCirclePaint.setAlpha(targetCircleAlpha);
+ if (targetCirclePulseAlpha > 0 && !target.drawOval) {
+ targetCirclePulsePaint.setAlpha(targetCirclePulseAlpha);
+ c.drawCircle(targetBounds.centerX(), targetBounds.centerY(),
+ targetCirclePulseRadius, targetCirclePulsePaint);
+ } else if (targetCirclePulseAlpha > 0 && target.drawOval) {
+ targetCirclePulsePaint.setAlpha(targetCirclePulseAlpha);
+ targetCirclePulsePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ targetOvalPulseRect.set(
+ targetBounds.left - TARGET_PADDING - (TARGET_PADDING * targetOvalPulseScaleFactor * 2),
+ targetBounds.top - TARGET_PADDING - (TARGET_PADDING * targetOvalPulseScaleFactor * 2),
+ targetBounds.right + TARGET_PADDING + (TARGET_PADDING * targetOvalPulseScaleFactor * 2),
+ targetBounds.bottom + TARGET_PADDING + (TARGET_PADDING * targetOvalPulseScaleFactor * 2)
+ );
+
+ c.drawOval(targetOvalPulseRect, targetCirclePulsePaint);
+ }
+
+ if (!target.drawOval) {
+ c.drawCircle(targetBounds.centerX(), targetBounds.centerY(),
+ targetCircleRadius, targetCirclePaint);
+ } else {
+ targetCirclePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ targetOvalRect.set(
+ targetBounds.left - TARGET_PADDING - (TARGET_PADDING * targetOvalScaleFactor),
+ targetBounds.top - TARGET_PADDING - (TARGET_PADDING * targetOvalScaleFactor),
+ targetBounds.right + TARGET_PADDING + (TARGET_PADDING * targetOvalScaleFactor),
+ targetBounds.bottom + TARGET_PADDING + (TARGET_PADDING * targetOvalScaleFactor)
+ );
+
+ c.drawOval(targetOvalRect, targetCirclePaint);
+ }
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (isVisible() && isInteractable && cancelable
- && keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
- isInteractable = false;
+ saveCount = c.save();
+ {
+ c.translate(textBounds.left, textBounds.top);
+ titlePaint.setAlpha(textAlpha);
+ if (titleLayout != null) {
+ titleLayout.draw(c);
+ }
- if (listener != null) {
- listener.onTargetCancel(this);
- } else {
- new Listener().onTargetCancel(this);
- }
+ if (descriptionLayout != null && titleLayout != null) {
+ c.translate(0, titleLayout.getHeight() + TEXT_SPACING);
+ descriptionPaint.setAlpha((int) (target.descriptionTextAlpha * textAlpha));
+ descriptionLayout.draw(c);
+ }
+ }
+ c.restoreToCount(saveCount);
+
+ saveCount = c.save();
+ {
+ if (tintedTarget != null) {
+ c.translate(targetBounds.centerX() - tintedTarget.getWidth() / 2,
+ targetBounds.centerY() - tintedTarget.getHeight() / 2);
+ c.drawBitmap(tintedTarget, 0, 0, targetCirclePaint);
+ } else if (target.icon != null) {
+ c.translate(targetBounds.centerX() - target.icon.getBounds().width() / 2,
+ targetBounds.centerY() - target.icon.getBounds().height() / 2);
+ target.icon.setAlpha(targetCirclePaint.getAlpha());
+ target.icon.draw(c);
+ }
+ }
+ c.restoreToCount(saveCount);
- return true;
+ if (debug) {
+ drawDebugInformation(c);
+ }
}
- return false;
- }
-
- /**
- * Dismiss this view
- * @param tappedTarget If the user tapped the target or not
- * (results in different dismiss animations)
- */
- public void dismiss(boolean tappedTarget) {
- isDismissing = true;
- pulseAnimation.cancel();
- expandAnimation.cancel();
- if (!visible || outerCircleCenter == null) {
- finishDismiss(tappedTarget);
- return;
+ private float targetWidth() {
+ return targetBounds.right - targetBounds.left;
}
- if (tappedTarget) {
- dismissConfirmAnimation.start();
- } else {
- dismissAnimation.start();
+
+ private float targetHeight() {
+ return targetBounds.bottom - targetBounds.top;
}
- }
-
- private void finishDismiss(boolean userInitiated) {
- onDismiss(userInitiated);
- ViewUtil.removeView(parent, TapTargetView.this);
- }
-
- /** Specify whether to draw a wireframe around the view, useful for debugging **/
- public void setDrawDebug(boolean status) {
- if (debug != status) {
- debug = status;
- postInvalidate();
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ lastTouchX = e.getX();
+ lastTouchY = e.getY();
+ return super.onTouchEvent(e);
}
- }
-
- /** Returns whether this view is visible or not **/
- public boolean isVisible() {
- return !isDismissed && visible;
- }
-
- void drawJitteredShadow(Canvas c) {
- final float baseAlpha = 0.20f * outerCircleAlpha;
- outerCircleShadowPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- outerCircleShadowPaint.setAlpha((int) baseAlpha);
- c.drawCircle(outerCircleCenter[0], outerCircleCenter[1] + SHADOW_DIM, outerCircleRadius, outerCircleShadowPaint);
- outerCircleShadowPaint.setStyle(Paint.Style.STROKE);
- final int numJitters = 7;
- for (int i = numJitters - 1; i > 0; --i) {
- outerCircleShadowPaint.setAlpha((int) ((i / (float) numJitters) * baseAlpha));
- c.drawCircle(outerCircleCenter[0], outerCircleCenter[1] + SHADOW_DIM ,
- outerCircleRadius + (numJitters - i) * SHADOW_JITTER_DIM , outerCircleShadowPaint);
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (isVisible() && cancelable && keyCode == KeyEvent.KEYCODE_BACK) {
+ event.startTracking();
+ return true;
+ }
+
+ return false;
}
- }
-
- void drawDebugInformation(Canvas c) {
- if (debugPaint == null) {
- debugPaint = new Paint();
- debugPaint.setARGB(255, 255, 0, 0);
- debugPaint.setStyle(Paint.Style.STROKE);
- debugPaint.setStrokeWidth(UiUtil.dp(getContext(), 1));
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (isVisible() && isInteractable && cancelable
+ && keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
+ isInteractable = false;
+
+ if (listener != null) {
+ listener.onTargetCancel(this);
+ } else {
+ new Listener().onTargetCancel(this);
+ }
+
+ return true;
+ }
+
+ return false;
}
- if (debugTextPaint == null) {
- debugTextPaint = new TextPaint();
- debugTextPaint.setColor(0xFFFF0000);
- debugTextPaint.setTextSize(UiUtil.sp(getContext(), 16));
+ /**
+ * Dismiss this view
+ *
+ * @param tappedTarget If the user tapped the target or not
+ * (results in different dismiss animations)
+ */
+ public void dismiss(boolean tappedTarget) {
+ isDismissing = true;
+ pulseAnimation.cancel();
+ expandAnimation.cancel();
+ if (!visible || outerCircleCenter == null) {
+ finishDismiss(tappedTarget);
+ return;
+ }
+ if (tappedTarget) {
+ dismissConfirmAnimation.start();
+ } else {
+ dismissAnimation.start();
+ }
}
- // Draw wireframe
- debugPaint.setStyle(Paint.Style.STROKE);
- c.drawRect(textBounds, debugPaint);
- c.drawRect(targetBounds, debugPaint);
- c.drawCircle(outerCircleCenter[0], outerCircleCenter[1], 10, debugPaint);
- c.drawCircle(outerCircleCenter[0], outerCircleCenter[1], calculatedOuterCircleRadius - CIRCLE_PADDING, debugPaint);
- c.drawCircle(targetBounds.centerX(), targetBounds.centerY(), TARGET_RADIUS + TARGET_PADDING, debugPaint);
-
- // Draw positions and dimensions
- debugPaint.setStyle(Paint.Style.FILL);
- final String debugText =
- "Text bounds: " + textBounds.toShortString() + "\n" +
- "Target bounds: " + targetBounds.toShortString() + "\n" +
- "Center: " + outerCircleCenter[0] + " " + outerCircleCenter[1] + "\n" +
- "View size: " + getWidth() + " " + getHeight() + "\n" +
- "Target bounds: " + targetBounds.toShortString();
-
- if (debugStringBuilder == null) {
- debugStringBuilder = new SpannableStringBuilder(debugText);
- } else {
- debugStringBuilder.clear();
- debugStringBuilder.append(debugText);
+ private void finishDismiss(boolean userInitiated) {
+ onDismiss(userInitiated);
+ ViewUtil.removeView(parent, TapTargetView.this);
}
- if (debugLayout == null) {
- debugLayout = new DynamicLayout(debugText, debugTextPaint, getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
+ /**
+ * Specify whether to draw a wireframe around the view, useful for debugging
+ **/
+ public void setDrawDebug(boolean status) {
+ if (debug != status) {
+ debug = status;
+ postInvalidate();
+ }
}
- final int saveCount = c.save();
- {
- debugPaint.setARGB(220, 0, 0, 0);
- c.translate(0.0f, topBoundary);
- c.drawRect(0.0f, 0.0f, debugLayout.getWidth(), debugLayout.getHeight(), debugPaint);
- debugPaint.setARGB(255, 255, 0, 0);
- debugLayout.draw(c);
+ /**
+ * Returns whether this view is visible or not
+ **/
+ public boolean isVisible() {
+ return !isDismissed && visible;
}
- c.restoreToCount(saveCount);
- }
-
- void drawTintedTarget() {
- final Drawable icon = target.icon;
- if (!shouldTintTarget || icon == null) {
- tintedTarget = null;
- return;
+
+ void drawJitteredShadow(Canvas c) {
+ final float baseAlpha = 0.20f * outerCircleAlpha;
+ outerCircleShadowPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ outerCircleShadowPaint.setAlpha((int) baseAlpha);
+ c.drawCircle(outerCircleCenter[0], outerCircleCenter[1] + SHADOW_DIM, outerCircleRadius, outerCircleShadowPaint);
+ outerCircleShadowPaint.setStyle(Paint.Style.STROKE);
+ final int numJitters = 7;
+ for (int i = numJitters - 1; i > 0; --i) {
+ outerCircleShadowPaint.setAlpha((int) ((i / (float) numJitters) * baseAlpha));
+ c.drawCircle(outerCircleCenter[0], outerCircleCenter[1] + SHADOW_DIM,
+ outerCircleRadius + (numJitters - i) * SHADOW_JITTER_DIM, outerCircleShadowPaint);
+ }
}
- if (tintedTarget != null) return;
-
- tintedTarget = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(tintedTarget);
- icon.setColorFilter(new PorterDuffColorFilter(
- outerCirclePaint.getColor(), PorterDuff.Mode.SRC_ATOP));
- icon.draw(canvas);
- icon.setColorFilter(null);
- }
-
- void updateTextLayouts() {
- final int textWidth = Math.min(getWidth(), TEXT_MAX_WIDTH) - TEXT_PADDING * 2;
- if (textWidth <= 0) {
- return;
+ void drawDebugInformation(Canvas c) {
+ if (debugPaint == null) {
+ debugPaint = new Paint();
+ debugPaint.setARGB(255, 255, 0, 0);
+ debugPaint.setStyle(Paint.Style.STROKE);
+ debugPaint.setStrokeWidth(UiUtil.dp(getContext(), 1));
+ }
+
+ if (debugTextPaint == null) {
+ debugTextPaint = new TextPaint();
+ debugTextPaint.setColor(0xFFFF0000);
+ debugTextPaint.setTextSize(UiUtil.sp(getContext(), 16));
+ }
+
+ // Draw wireframe
+ debugPaint.setStyle(Paint.Style.STROKE);
+ c.drawRect(textBounds, debugPaint);
+ c.drawRect(targetBounds, debugPaint);
+ c.drawCircle(outerCircleCenter[0], outerCircleCenter[1], 10, debugPaint);
+ c.drawCircle(outerCircleCenter[0], outerCircleCenter[1], calculatedOuterCircleRadius - CIRCLE_PADDING, debugPaint);
+ c.drawCircle(targetBounds.centerX(), targetBounds.centerY(), TARGET_RADIUS + TARGET_PADDING, debugPaint);
+
+ // Draw positions and dimensions
+ debugPaint.setStyle(Paint.Style.FILL);
+ final String debugText =
+ "Text bounds: " + textBounds.toShortString() + "\n" +
+ "Target bounds: " + targetBounds.toShortString() + "\n" +
+ "Center: " + outerCircleCenter[0] + " " + outerCircleCenter[1] + "\n" +
+ "View size: " + getWidth() + " " + getHeight() + "\n" +
+ "Target bounds: " + targetBounds.toShortString();
+
+ if (debugStringBuilder == null) {
+ debugStringBuilder = new SpannableStringBuilder(debugText);
+ } else {
+ debugStringBuilder.clear();
+ debugStringBuilder.append(debugText);
+ }
+
+ if (debugLayout == null) {
+ debugLayout = new DynamicLayout(debugText, debugTextPaint, getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
+ }
+
+ final int saveCount = c.save();
+ {
+ debugPaint.setARGB(220, 0, 0, 0);
+ c.translate(0.0f, topBoundary);
+ c.drawRect(0.0f, 0.0f, debugLayout.getWidth(), debugLayout.getHeight(), debugPaint);
+ debugPaint.setARGB(255, 255, 0, 0);
+ debugLayout.draw(c);
+ }
+ c.restoreToCount(saveCount);
}
- titleLayout = new StaticLayout(title, titlePaint, textWidth,
- Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
+ void drawTintedTarget() {
+ final Drawable icon = target.icon;
+ if (!shouldTintTarget || icon == null) {
+ tintedTarget = null;
+ return;
+ }
+
+ if (tintedTarget != null) return;
+
+ tintedTarget = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(tintedTarget);
+
+ Integer targetTintColor = target.targetTintColorInt(getContext());
+ if (targetTintColor == null)
+ targetTintColor = outerCirclePaint.getColor();
- if (description != null) {
- descriptionLayout = new StaticLayout(description, descriptionPaint, textWidth,
- Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
- } else {
- descriptionLayout = null;
+ icon.setColorFilter(new PorterDuffColorFilter(
+ targetTintColor, PorterDuff.Mode.SRC_ATOP));
+ icon.draw(canvas);
+ icon.setColorFilter(null);
}
- }
- float halfwayLerp(float lerp) {
- if (lerp < 0.5f) {
- return lerp / 0.5f;
+ void updateTextLayouts() {
+ final int textWidth = Math.min(getWidth(), TEXT_MAX_WIDTH) - TEXT_PADDING * 2;
+ if (textWidth <= 0) {
+ return;
+ }
+
+ titleLayout = new StaticLayout(title, titlePaint, textWidth,
+ Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
+
+ if (description != null) {
+ descriptionLayout = new StaticLayout(description, descriptionPaint, textWidth,
+ Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
+ } else {
+ descriptionLayout = null;
+ }
}
- return (1.0f - lerp) / 0.5f;
- }
+ float halfwayLerp(float lerp) {
+ if (lerp < 0.5f) {
+ return lerp / 0.5f;
+ }
- float delayedLerp(float lerp, float threshold) {
- if (lerp < threshold) {
- return 0.0f;
+ return (1.0f - lerp) / 0.5f;
}
- return (lerp - threshold) / (1.0f - threshold);
- }
+ float delayedLerp(float lerp, float threshold) {
+ if (lerp < threshold) {
+ return 0.0f;
+ }
- void calculateDimensions() {
- textBounds = getTextBounds();
- outerCircleCenter = getOuterCircleCenterPoint();
- calculatedOuterCircleRadius = getOuterCircleRadius(outerCircleCenter[0], outerCircleCenter[1], textBounds, targetBounds);
- }
+ return (lerp - threshold) / (1.0f - threshold);
+ }
- void calculateDrawingBounds() {
- if (outerCircleCenter == null) {
- // Called dismiss before we got a chance to display the tap target
- // So we have no center -> cant determine the drawing bounds
- return;
+ void calculateDimensions() {
+ textBounds = getTextBounds();
+ outerCircleCenter = getOuterCircleCenterPoint();
+ calculatedOuterCircleRadius = getOuterCircleRadius(outerCircleCenter[0], outerCircleCenter[1], textBounds, targetBounds);
}
- drawingBounds.left = (int) Math.max(0, outerCircleCenter[0] - outerCircleRadius);
- drawingBounds.top = (int) Math.min(0, outerCircleCenter[1] - outerCircleRadius);
- drawingBounds.right = (int) Math.min(getWidth(),
- outerCircleCenter[0] + outerCircleRadius + CIRCLE_PADDING);
- drawingBounds.bottom = (int) Math.min(getHeight(),
- outerCircleCenter[1] + outerCircleRadius + CIRCLE_PADDING);
- }
-
- int getOuterCircleRadius(int centerX, int centerY, Rect textBounds, Rect targetBounds) {
- final int targetCenterX = targetBounds.centerX();
- final int targetCenterY = targetBounds.centerY();
- final int expandedRadius = (int) (1.1f * TARGET_RADIUS);
- final Rect expandedBounds = new Rect(targetCenterX, targetCenterY, targetCenterX, targetCenterY);
- expandedBounds.inset(-expandedRadius, -expandedRadius);
-
- final int textRadius = maxDistanceToPoints(centerX, centerY, textBounds);
- final int targetRadius = maxDistanceToPoints(centerX, centerY, expandedBounds);
- return Math.max(textRadius, targetRadius) + CIRCLE_PADDING;
- }
-
- Rect getTextBounds() {
- final int totalTextHeight = getTotalTextHeight();
- final int totalTextWidth = getTotalTextWidth();
-
- final int possibleTop = targetBounds.centerY() - TARGET_RADIUS - TARGET_PADDING - totalTextHeight;
- final int top;
- if (possibleTop > topBoundary) {
- top = possibleTop;
- } else {
- top = targetBounds.centerY() + TARGET_RADIUS + TARGET_PADDING;
+
+ void calculateDrawingBounds() {
+ if (outerCircleCenter == null) {
+ // Called dismiss before we got a chance to display the tap target
+ // So we have no center -> cant determine the drawing bounds
+ return;
+ }
+ drawingBounds.left = (int) Math.max(0, outerCircleCenter[0] - outerCircleRadius);
+ drawingBounds.top = (int) Math.min(0, outerCircleCenter[1] - outerCircleRadius);
+ drawingBounds.right = (int) Math.min(getWidth(),
+ outerCircleCenter[0] + outerCircleRadius + CIRCLE_PADDING);
+ drawingBounds.bottom = (int) Math.min(getHeight(),
+ outerCircleCenter[1] + outerCircleRadius + CIRCLE_PADDING);
+ }
+
+ int getOuterCircleRadius(int centerX, int centerY, Rect textBounds, Rect targetBounds) {
+ final int targetCenterX = targetBounds.centerX();
+ final int targetCenterY = targetBounds.centerY();
+ final int expandedRadius = (int) (1.1f * TARGET_RADIUS);
+ final Rect expandedBounds = new Rect(targetCenterX, targetCenterY, targetCenterX, targetCenterY);
+ expandedBounds.inset(-expandedRadius, -expandedRadius);
+
+ final int textRadius = maxDistanceToPoints(centerX, centerY, textBounds);
+ final int targetRadius = maxDistanceToPoints(centerX, centerY, expandedBounds);
+ return Math.max(textRadius, targetRadius) + CIRCLE_PADDING;
}
- final int relativeCenterDistance = (getWidth() / 2) - targetBounds.centerX();
- final int bias = relativeCenterDistance < 0 ? -TEXT_POSITIONING_BIAS : TEXT_POSITIONING_BIAS;
- final int left = Math.max(TEXT_PADDING, targetBounds.centerX() - bias - totalTextWidth);
- final int right = Math.min(getWidth() - TEXT_PADDING, left + totalTextWidth);
- return new Rect(left, top, right, top + totalTextHeight);
- }
+ Rect getTextBounds() {
+ final int totalTextHeight = getTotalTextHeight();
+ final int totalTextWidth = getTotalTextWidth();
- int[] getOuterCircleCenterPoint() {
- if (inGutter(targetBounds.centerY())) {
- return new int[]{targetBounds.centerX(), targetBounds.centerY()};
+ final int possibleTop = targetBounds.centerY() - TARGET_RADIUS - TARGET_PADDING - totalTextHeight;
+ final int top;
+ if (possibleTop > topBoundary) {
+ top = possibleTop;
+ } else {
+ top = targetBounds.centerY() + TARGET_RADIUS + TARGET_PADDING;
+ }
+
+ final int relativeCenterDistance = (getWidth() / 2) - targetBounds.centerX();
+ final int bias = relativeCenterDistance < 0 ? -TEXT_POSITIONING_BIAS : TEXT_POSITIONING_BIAS;
+ final int left = Math.max(TEXT_PADDING, targetBounds.centerX() - bias - totalTextWidth);
+ final int right = Math.min(getWidth() - TEXT_PADDING, left + totalTextWidth);
+ return new Rect(left, top, right, top + totalTextHeight);
}
- final int targetRadius = Math.max(targetBounds.width(), targetBounds.height()) / 2 + TARGET_PADDING;
- final int totalTextHeight = getTotalTextHeight();
+ int[] getOuterCircleCenterPoint() {
+ if (inGutter(targetBounds.centerY())) {
+ return new int[]{targetBounds.centerX(), targetBounds.centerY()};
+ }
- final boolean onTop = targetBounds.centerY() - TARGET_RADIUS - TARGET_PADDING - totalTextHeight > 0;
+ final int targetRadius = Math.max(targetBounds.width(), targetBounds.height()) / 2 + TARGET_PADDING;
+ final int totalTextHeight = getTotalTextHeight();
- final int left = Math.min(textBounds.left, targetBounds.left - targetRadius);
- final int right = Math.max(textBounds.right, targetBounds.right + targetRadius);
- final int titleHeight = titleLayout == null ? 0 : titleLayout.getHeight();
- final int centerY = onTop ?
- targetBounds.centerY() - TARGET_RADIUS - TARGET_PADDING - totalTextHeight + titleHeight
- :
- targetBounds.centerY() + TARGET_RADIUS + TARGET_PADDING + titleHeight;
+ final boolean onTop = targetBounds.centerY() - TARGET_RADIUS - TARGET_PADDING - totalTextHeight > 0;
- return new int[] { (left + right) / 2, centerY };
- }
+ final int left = Math.min(textBounds.left, targetBounds.left - targetRadius);
+ final int right = Math.max(textBounds.right, targetBounds.right + targetRadius);
+ final int titleHeight = titleLayout == null ? 0 : titleLayout.getHeight();
+ final int centerY = onTop ?
+ targetBounds.centerY() - TARGET_RADIUS - TARGET_PADDING - totalTextHeight + titleHeight
+ :
+ targetBounds.centerY() + TARGET_RADIUS + TARGET_PADDING + titleHeight;
- int getTotalTextHeight() {
- if (titleLayout == null) {
- return 0;
+ return new int[]{(left + right) / 2, centerY};
}
- if (descriptionLayout == null) {
- return titleLayout.getHeight() + TEXT_SPACING;
+ int getTotalTextHeight() {
+ if (titleLayout == null) {
+ return 0;
+ }
+
+ if (descriptionLayout == null) {
+ return titleLayout.getHeight() + TEXT_SPACING;
+ }
+
+ return titleLayout.getHeight() + descriptionLayout.getHeight() + TEXT_SPACING;
}
- return titleLayout.getHeight() + descriptionLayout.getHeight() + TEXT_SPACING;
- }
+ int getTotalTextWidth() {
+ if (titleLayout == null) {
+ return 0;
+ }
+
+ if (descriptionLayout == null) {
+ return titleLayout.getWidth();
+ }
- int getTotalTextWidth() {
- if (titleLayout == null) {
- return 0;
+ return Math.max(titleLayout.getWidth(), descriptionLayout.getWidth());
}
- if (descriptionLayout == null) {
- return titleLayout.getWidth();
+ boolean inGutter(int y) {
+ if (bottomBoundary > 0) {
+ return y < GUTTER_DIM || y > bottomBoundary - GUTTER_DIM;
+ } else {
+ return y < GUTTER_DIM || y > getHeight() - GUTTER_DIM;
+ }
}
- return Math.max(titleLayout.getWidth(), descriptionLayout.getWidth());
- }
+ int maxDistanceToPoints(int x1, int y1, Rect bounds) {
+ final double tl = distance(x1, y1, bounds.left, bounds.top);
+ final double tr = distance(x1, y1, bounds.right, bounds.top);
+ final double bl = distance(x1, y1, bounds.left, bounds.bottom);
+ final double br = distance(x1, y1, bounds.right, bounds.bottom);
+ return (int) Math.max(tl, Math.max(tr, Math.max(bl, br)));
+ }
- boolean inGutter(int y) {
- if (bottomBoundary > 0) {
- return y < GUTTER_DIM || y > bottomBoundary - GUTTER_DIM;
- } else {
- return y < GUTTER_DIM || y > getHeight() - GUTTER_DIM;
+ double distance(int x1, int y1, int x2, int y2) {
+ return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
- }
-
- int maxDistanceToPoints(int x1, int y1, Rect bounds) {
- final double tl = distance(x1, y1, bounds.left, bounds.top);
- final double tr = distance(x1, y1, bounds.right, bounds.top);
- final double bl = distance(x1, y1, bounds.left, bounds.bottom);
- final double br = distance(x1, y1, bounds.right, bounds.bottom);
- return (int) Math.max(tl, Math.max(tr, Math.max(bl, br)));
- }
-
- double distance(int x1, int y1, int x2, int y2) {
- return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
- }
-
- void invalidateViewAndOutline(Rect bounds) {
- invalidate(bounds);
- if (outlineProvider != null && Build.VERSION.SDK_INT >= 21) {
- invalidateOutline();
+
+ void invalidateViewAndOutline(Rect bounds) {
+ invalidate(bounds);
+ if (outlineProvider != null && Build.VERSION.SDK_INT >= 21) {
+ invalidateOutline();
+ }
}
- }
}
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ToolbarTapTarget.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ToolbarTapTarget.java
index 266d7ac..e11e7f3 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ToolbarTapTarget.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ToolbarTapTarget.java
@@ -1,270 +1,271 @@
-/**
- * Copyright 2016 Keepsafe Software, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.getkeepsafe.taptargetview;
-
-import android.annotation.TargetApi;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import androidx.annotation.IdRes;
-import androidx.annotation.Nullable;
-import androidx.appcompat.widget.Toolbar;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-
-import java.util.ArrayList;
-import java.util.Stack;
-
-class ToolbarTapTarget extends ViewTapTarget {
- ToolbarTapTarget(Toolbar toolbar, @IdRes int menuItemId,
- CharSequence title, @Nullable CharSequence description) {
- super(toolbar.findViewById(menuItemId), title, description);
- }
-
- ToolbarTapTarget(android.widget.Toolbar toolbar, @IdRes int menuItemId,
- CharSequence title, @Nullable CharSequence description) {
- super(toolbar.findViewById(menuItemId), title, description);
- }
-
- ToolbarTapTarget(Toolbar toolbar, boolean findNavView,
- CharSequence title, @Nullable CharSequence description) {
- super(findNavView ? findNavView(toolbar) : findOverflowView(toolbar), title, description);
- }
-
- ToolbarTapTarget(android.widget.Toolbar toolbar, boolean findNavView,
- CharSequence title, @Nullable CharSequence description) {
- super(findNavView ? findNavView(toolbar) : findOverflowView(toolbar), title, description);
- }
-
- private static ToolbarProxy proxyOf(Object instance) {
- if (instance == null) {
- throw new IllegalArgumentException("Given null instance");
- }
-
- if (instance instanceof Toolbar) {
- return new SupportToolbarProxy((Toolbar) instance);
- } else if (instance instanceof android.widget.Toolbar) {
- return new StandardToolbarProxy((android.widget.Toolbar) instance);
- }
-
- throw new IllegalStateException("Couldn't provide proper toolbar proxy instance");
- }
-
- private static View findNavView(Object instance) {
- final ToolbarProxy toolbar = proxyOf(instance);
-
- // First we try to find the view via its content description
- final CharSequence currentDescription = toolbar.getNavigationContentDescription();
- final boolean hadContentDescription = !TextUtils.isEmpty(currentDescription);
- final CharSequence sentinel = hadContentDescription ? currentDescription : "taptarget-findme";
- toolbar.setNavigationContentDescription(sentinel);
-
- final ArrayList possibleViews = new ArrayList<>(1);
- toolbar.findViewsWithText(possibleViews, sentinel, View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
-
- if (!hadContentDescription) {
- toolbar.setNavigationContentDescription(null);
- }
-
- if (possibleViews.size() > 0) {
- return possibleViews.get(0);
- }
-
- // If that doesn't work, we try to grab it via matching its drawable
- final Drawable navigationIcon = toolbar.getNavigationIcon();
- if (navigationIcon == null) {
- throw new IllegalStateException("Toolbar does not have a navigation view set!");
- }
-
- final int size = toolbar.getChildCount();
- for (int i = 0; i < size; ++i) {
- final View child = toolbar.getChildAt(i);
- if (child instanceof ImageButton) {
- final Drawable childDrawable = ((ImageButton) child).getDrawable();
- if (childDrawable == navigationIcon) {
- return child;
- }
- }
- }
-
- throw new IllegalStateException("Could not find navigation view for Toolbar!");
- }
-
- private static View findOverflowView(Object instance) {
- final ToolbarProxy toolbar = proxyOf(instance);
-
- // First we try to find the overflow menu view via drawable matching
- final Drawable overflowDrawable = toolbar.getOverflowIcon();
- if (overflowDrawable != null) {
- final Stack parents = new Stack<>();
- parents.push((ViewGroup) toolbar.internalToolbar());
- while (!parents.empty()) {
- ViewGroup parent = parents.pop();
- final int size = parent.getChildCount();
- for (int i = 0; i < size; ++i) {
- final View child = parent.getChildAt(i);
- if (child instanceof ViewGroup) {
- parents.push((ViewGroup) child);
- continue;
- }
- if (child instanceof ImageView) {
- final Drawable childDrawable = ((ImageView) child).getDrawable();
- if (childDrawable == overflowDrawable) {
- return child;
- }
- }
- }
- }
- }
-
- // If that doesn't work, we fall-back to our last resort solution: Reflection
- // Toolbars contain an "ActionMenuView" which in turn contains an "ActionMenuPresenter".
- // The "ActionMenuPresenter" then holds a reference to an "OverflowMenuButton" which is the
- // desired target
- try {
- final Object actionMenuView = ReflectUtil.getPrivateField(toolbar.internalToolbar(), "mMenuView");
- final Object actionMenuPresenter = ReflectUtil.getPrivateField(actionMenuView, "mPresenter");
- return (View) ReflectUtil.getPrivateField(actionMenuPresenter, "mOverflowButton");
- } catch (NoSuchFieldException e) {
- throw new IllegalStateException("Could not find overflow view for Toolbar!", e);
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Unable to access overflow view for Toolbar!", e);
- }
- }
-
- private interface ToolbarProxy {
- CharSequence getNavigationContentDescription();
-
- void setNavigationContentDescription(CharSequence description);
-
- void findViewsWithText(ArrayList out, CharSequence toFind, int flags);
-
- Drawable getNavigationIcon();
-
- @Nullable
- Drawable getOverflowIcon();
-
- int getChildCount();
-
- View getChildAt(int position);
-
- Object internalToolbar();
- }
-
- private static class SupportToolbarProxy implements ToolbarProxy {
- private final Toolbar toolbar;
-
- SupportToolbarProxy(Toolbar toolbar) {
- this.toolbar = toolbar;
- }
-
- @Override
- public CharSequence getNavigationContentDescription() {
- return toolbar.getNavigationContentDescription();
- }
-
- @Override
- public void setNavigationContentDescription(CharSequence description) {
- toolbar.setNavigationContentDescription(description);
- }
-
- @Override
- public void findViewsWithText(ArrayList out, CharSequence toFind, int flags) {
- toolbar.findViewsWithText(out, toFind, flags);
- }
-
- @Override
- public Drawable getNavigationIcon() {
- return toolbar.getNavigationIcon();
- }
-
- @Override
- public Drawable getOverflowIcon() {
- return toolbar.getOverflowIcon();
- }
-
- @Override
- public int getChildCount() {
- return toolbar.getChildCount();
- }
-
- @Override
- public View getChildAt(int position) {
- return toolbar.getChildAt(position);
- }
-
- @Override
- public Object internalToolbar() {
- return toolbar;
- }
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private static class StandardToolbarProxy implements ToolbarProxy {
- private final android.widget.Toolbar toolbar;
-
- StandardToolbarProxy(android.widget.Toolbar toolbar) {
- this.toolbar = toolbar;
- }
-
- @Override
- public CharSequence getNavigationContentDescription() {
- return toolbar.getNavigationContentDescription();
- }
-
- @Override
- public void setNavigationContentDescription(CharSequence description) {
- toolbar.setNavigationContentDescription(description);
- }
-
- @Override
- public void findViewsWithText(ArrayList out, CharSequence toFind, int flags) {
- toolbar.findViewsWithText(out, toFind, flags);
- }
-
- @Override
- public Drawable getNavigationIcon() {
- return toolbar.getNavigationIcon();
- }
-
- @Nullable
- @Override
- public Drawable getOverflowIcon() {
- if (Build.VERSION.SDK_INT >= 23) {
- return toolbar.getOverflowIcon();
- }
-
- return null;
- }
-
- @Override
- public int getChildCount() {
- return toolbar.getChildCount();
- }
-
- @Override
- public View getChildAt(int position) {
- return toolbar.getChildAt(position);
- }
-
- @Override
- public Object internalToolbar() {
- return toolbar;
- }
- }
-}
+/**
+ * Copyright 2016 Keepsafe Software, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.getkeepsafe.taptargetview;
+
+import android.annotation.TargetApi;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.Toolbar;
+
+import java.util.ArrayList;
+import java.util.Stack;
+
+class ToolbarTapTarget extends ViewTapTarget {
+ ToolbarTapTarget(Toolbar toolbar, @IdRes int menuItemId,
+ CharSequence title, @Nullable CharSequence description) {
+ super(toolbar.findViewById(menuItemId), title, description);
+ }
+
+ ToolbarTapTarget(android.widget.Toolbar toolbar, @IdRes int menuItemId,
+ CharSequence title, @Nullable CharSequence description) {
+ super(toolbar.findViewById(menuItemId), title, description);
+ }
+
+ ToolbarTapTarget(Toolbar toolbar, boolean findNavView,
+ CharSequence title, @Nullable CharSequence description) {
+ super(findNavView ? findNavView(toolbar) : findOverflowView(toolbar), title, description);
+ }
+
+ ToolbarTapTarget(android.widget.Toolbar toolbar, boolean findNavView,
+ CharSequence title, @Nullable CharSequence description) {
+ super(findNavView ? findNavView(toolbar) : findOverflowView(toolbar), title, description);
+ }
+
+ private static ToolbarProxy proxyOf(Object instance) {
+ if (instance == null) {
+ throw new IllegalArgumentException("Given null instance");
+ }
+
+ if (instance instanceof Toolbar) {
+ return new SupportToolbarProxy((Toolbar) instance);
+ } else if (instance instanceof android.widget.Toolbar) {
+ return new StandardToolbarProxy((android.widget.Toolbar) instance);
+ }
+
+ throw new IllegalStateException("Couldn't provide proper toolbar proxy instance");
+ }
+
+ private static View findNavView(Object instance) {
+ final ToolbarProxy toolbar = proxyOf(instance);
+
+ // First we try to find the view via its content description
+ final CharSequence currentDescription = toolbar.getNavigationContentDescription();
+ final boolean hadContentDescription = !TextUtils.isEmpty(currentDescription);
+ final CharSequence sentinel = hadContentDescription ? currentDescription : "taptarget-findme";
+ toolbar.setNavigationContentDescription(sentinel);
+
+ final ArrayList possibleViews = new ArrayList<>(1);
+ toolbar.findViewsWithText(possibleViews, sentinel, View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
+
+ if (!hadContentDescription) {
+ toolbar.setNavigationContentDescription(null);
+ }
+
+ if (possibleViews.size() > 0) {
+ return possibleViews.get(0);
+ }
+
+ // If that doesn't work, we try to grab it via matching its drawable
+ final Drawable navigationIcon = toolbar.getNavigationIcon();
+ if (navigationIcon == null) {
+ throw new IllegalStateException("Toolbar does not have a navigation view set!");
+ }
+
+ final int size = toolbar.getChildCount();
+ for (int i = 0; i < size; ++i) {
+ final View child = toolbar.getChildAt(i);
+ if (child instanceof ImageButton) {
+ final Drawable childDrawable = ((ImageButton) child).getDrawable();
+ if (childDrawable == navigationIcon) {
+ return child;
+ }
+ }
+ }
+
+ throw new IllegalStateException("Could not find navigation view for Toolbar!");
+ }
+
+ private static View findOverflowView(Object instance) {
+ final ToolbarProxy toolbar = proxyOf(instance);
+
+ // First we try to find the overflow menu view via drawable matching
+ final Drawable overflowDrawable = toolbar.getOverflowIcon();
+ if (overflowDrawable != null) {
+ final Stack parents = new Stack<>();
+ parents.push((ViewGroup) toolbar.internalToolbar());
+ while (!parents.empty()) {
+ ViewGroup parent = parents.pop();
+ final int size = parent.getChildCount();
+ for (int i = 0; i < size; ++i) {
+ final View child = parent.getChildAt(i);
+ if (child instanceof ViewGroup) {
+ parents.push((ViewGroup) child);
+ continue;
+ }
+ if (child instanceof ImageView) {
+ final Drawable childDrawable = ((ImageView) child).getDrawable();
+ if (childDrawable == overflowDrawable) {
+ return child;
+ }
+ }
+ }
+ }
+ }
+
+ // If that doesn't work, we fall-back to our last resort solution: Reflection
+ // Toolbars contain an "ActionMenuView" which in turn contains an "ActionMenuPresenter".
+ // The "ActionMenuPresenter" then holds a reference to an "OverflowMenuButton" which is the
+ // desired target
+ try {
+ final Object actionMenuView = ReflectUtil.getPrivateField(toolbar.internalToolbar(), "mMenuView");
+ final Object actionMenuPresenter = ReflectUtil.getPrivateField(actionMenuView, "mPresenter");
+ return (View) ReflectUtil.getPrivateField(actionMenuPresenter, "mOverflowButton");
+ } catch (NoSuchFieldException e) {
+ throw new IllegalStateException("Could not find overflow view for Toolbar!", e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Unable to access overflow view for Toolbar!", e);
+ }
+ }
+
+ private interface ToolbarProxy {
+ CharSequence getNavigationContentDescription();
+
+ void setNavigationContentDescription(CharSequence description);
+
+ void findViewsWithText(ArrayList out, CharSequence toFind, int flags);
+
+ Drawable getNavigationIcon();
+
+ @Nullable
+ Drawable getOverflowIcon();
+
+ int getChildCount();
+
+ View getChildAt(int position);
+
+ Object internalToolbar();
+ }
+
+ private static class SupportToolbarProxy implements ToolbarProxy {
+ private final Toolbar toolbar;
+
+ SupportToolbarProxy(Toolbar toolbar) {
+ this.toolbar = toolbar;
+ }
+
+ @Override
+ public CharSequence getNavigationContentDescription() {
+ return toolbar.getNavigationContentDescription();
+ }
+
+ @Override
+ public void setNavigationContentDescription(CharSequence description) {
+ toolbar.setNavigationContentDescription(description);
+ }
+
+ @Override
+ public void findViewsWithText(ArrayList out, CharSequence toFind, int flags) {
+ toolbar.findViewsWithText(out, toFind, flags);
+ }
+
+ @Override
+ public Drawable getNavigationIcon() {
+ return toolbar.getNavigationIcon();
+ }
+
+ @Override
+ public Drawable getOverflowIcon() {
+ return toolbar.getOverflowIcon();
+ }
+
+ @Override
+ public int getChildCount() {
+ return toolbar.getChildCount();
+ }
+
+ @Override
+ public View getChildAt(int position) {
+ return toolbar.getChildAt(position);
+ }
+
+ @Override
+ public Object internalToolbar() {
+ return toolbar;
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static class StandardToolbarProxy implements ToolbarProxy {
+ private final android.widget.Toolbar toolbar;
+
+ StandardToolbarProxy(android.widget.Toolbar toolbar) {
+ this.toolbar = toolbar;
+ }
+
+ @Override
+ public CharSequence getNavigationContentDescription() {
+ return toolbar.getNavigationContentDescription();
+ }
+
+ @Override
+ public void setNavigationContentDescription(CharSequence description) {
+ toolbar.setNavigationContentDescription(description);
+ }
+
+ @Override
+ public void findViewsWithText(ArrayList out, CharSequence toFind, int flags) {
+ toolbar.findViewsWithText(out, toFind, flags);
+ }
+
+ @Override
+ public Drawable getNavigationIcon() {
+ return toolbar.getNavigationIcon();
+ }
+
+ @Nullable
+ @Override
+ public Drawable getOverflowIcon() {
+ if (Build.VERSION.SDK_INT >= 23) {
+ return toolbar.getOverflowIcon();
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getChildCount() {
+ return toolbar.getChildCount();
+ }
+
+ @Override
+ public View getChildAt(int position) {
+ return toolbar.getChildAt(position);
+ }
+
+ @Override
+ public Object internalToolbar() {
+ return toolbar;
+ }
+ }
+}
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/UiUtil.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/UiUtil.java
index b34ec09..3be1b32 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/UiUtil.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/UiUtil.java
@@ -17,28 +17,31 @@
import android.content.Context;
import android.content.res.Resources;
-
-import androidx.annotation.ColorRes;
-import androidx.annotation.DimenRes;
import android.util.TypedValue;
class UiUtil {
UiUtil() {
}
- /** Returns the given pixel value in dp **/
+ /**
+ * Returns the given pixel value in dp
+ **/
static int dp(Context context, int val) {
return (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, val, context.getResources().getDisplayMetrics());
+ TypedValue.COMPLEX_UNIT_DIP, val, context.getResources().getDisplayMetrics());
}
- /** Returns the given pixel value in sp **/
+ /**
+ * Returns the given pixel value in sp
+ **/
static int sp(Context context, int val) {
return (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP, val, context.getResources().getDisplayMetrics());
+ TypedValue.COMPLEX_UNIT_SP, val, context.getResources().getDisplayMetrics());
}
- /** Returns the value of the desired theme integer attribute, or -1 if not found **/
+ /**
+ * Returns the value of the desired theme integer attribute, or -1 if not found
+ **/
static int themeIntAttr(Context context, String attr) {
final Resources.Theme theme = context.getTheme();
if (theme == null) {
@@ -57,7 +60,9 @@ static int themeIntAttr(Context context, String attr) {
return value.data;
}
- /** Modifies the alpha value of the given ARGB color **/
+ /**
+ * Modifies the alpha value of the given ARGB color
+ **/
static int setAlpha(int argb, float alpha) {
if (alpha > 1.0f) {
alpha = 1.0f;
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ViewTapTarget.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ViewTapTarget.java
index 045ae58..0031f45 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ViewTapTarget.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ViewTapTarget.java
@@ -19,9 +19,10 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
-import androidx.annotation.Nullable;
import android.view.View;
+import androidx.annotation.Nullable;
+
class ViewTapTarget extends TapTarget {
final View view;
@@ -42,7 +43,7 @@ public void run() {
final int[] location = new int[2];
view.getLocationOnScreen(location);
bounds = new Rect(location[0], location[1],
- location[0] + view.getWidth(), location[1] + view.getHeight());
+ location[0] + view.getWidth(), location[1] + view.getHeight());
if (icon == null && view.getWidth() > 0 && view.getHeight() > 0) {
final Bitmap viewBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
diff --git a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ViewUtil.java b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ViewUtil.java
index 44c9a9a..03ce33d 100644
--- a/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ViewUtil.java
+++ b/taptargetview/src/main/java/com/getkeepsafe/taptargetview/ViewUtil.java
@@ -16,66 +16,71 @@
package com.getkeepsafe.taptargetview;
import android.os.Build;
-import androidx.core.view.ViewCompat;
import android.view.View;
import android.view.ViewManager;
import android.view.ViewTreeObserver;
-class ViewUtil {
- ViewUtil() {
- }
-
- /** Returns whether or not the view has been laid out **/
- private static boolean isLaidOut(View view) {
- return ViewCompat.isLaidOut(view) && view.getWidth() > 0 && view.getHeight() > 0;
- }
+import androidx.core.view.ViewCompat;
- /** Executes the given {@link java.lang.Runnable} when the view is laid out **/
- static void onLaidOut(final View view, final Runnable runnable) {
- if (isLaidOut(view)) {
- runnable.run();
- return;
+class ViewUtil {
+ ViewUtil() {
}
- final ViewTreeObserver observer = view.getViewTreeObserver();
- observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- final ViewTreeObserver trueObserver;
+ /**
+ * Returns whether or not the view has been laid out
+ **/
+ private static boolean isLaidOut(View view) {
+ return ViewCompat.isLaidOut(view) && view.getWidth() > 0 && view.getHeight() > 0;
+ }
- if (observer.isAlive()) {
- trueObserver = observer;
- } else {
- trueObserver = view.getViewTreeObserver();
+ /**
+ * Executes the given {@link java.lang.Runnable} when the view is laid out
+ **/
+ static void onLaidOut(final View view, final Runnable runnable) {
+ if (isLaidOut(view)) {
+ runnable.run();
+ return;
}
- removeOnGlobalLayoutListener(trueObserver, this);
+ final ViewTreeObserver observer = view.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ final ViewTreeObserver trueObserver;
- runnable.run();
- }
- });
- }
+ if (observer.isAlive()) {
+ trueObserver = observer;
+ } else {
+ trueObserver = view.getViewTreeObserver();
+ }
- @SuppressWarnings("deprecation")
- static void removeOnGlobalLayoutListener(ViewTreeObserver observer,
- ViewTreeObserver.OnGlobalLayoutListener listener) {
- if (Build.VERSION.SDK_INT >= 16) {
- observer.removeOnGlobalLayoutListener(listener);
- } else {
- observer.removeGlobalOnLayoutListener(listener);
+ removeOnGlobalLayoutListener(trueObserver, this);
+
+ runnable.run();
+ }
+ });
}
- }
- static void removeView(ViewManager parent, View child) {
- if (parent == null || child == null) {
- return;
+ @SuppressWarnings("deprecation")
+ static void removeOnGlobalLayoutListener(ViewTreeObserver observer,
+ ViewTreeObserver.OnGlobalLayoutListener listener) {
+ if (Build.VERSION.SDK_INT >= 16) {
+ observer.removeOnGlobalLayoutListener(listener);
+ } else {
+ observer.removeGlobalOnLayoutListener(listener);
+ }
}
- try {
- parent.removeView(child);
- } catch (Exception ignored) {
- // This catch exists for modified versions of Android that have a buggy ViewGroup
- // implementation. See b.android.com/77639, #121 and #49
+ static void removeView(ViewManager parent, View child) {
+ if (parent == null || child == null) {
+ return;
+ }
+
+ try {
+ parent.removeView(child);
+ } catch (Exception ignored) {
+ // This catch exists for modified versions of Android that have a buggy ViewGroup
+ // implementation. See b.android.com/77639, #121 and #49
+ }
}
- }
}