diff --git a/.gitignore b/.gitignore index ea90b38..4743a68 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,3 @@ .gradle/* .idea/* .idea -gradle -gradle/ -gradlew -gradlew.bat diff --git a/build.gradle b/build.gradle index a8f9585..c93718a 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,10 @@ buildscript { repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' @@ -17,6 +18,7 @@ buildscript { allprojects { repositories { jcenter() + google() } } diff --git a/gradle.properties b/gradle.properties index 1d3591c..211fb54 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,6 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true + +android.useAndroidX=true diff --git a/gradle/buildCacheConfig.gradle b/gradle/buildCacheConfig.gradle new file mode 100644 index 0000000..4139017 --- /dev/null +++ b/gradle/buildCacheConfig.gradle @@ -0,0 +1,10 @@ +buildCache { + def isCi = System.getenv().containsKey("JENKINS_URL") + + local { enabled = !isCi } + remote(HttpBuildCache) { + push = isCi + enabled = true + url = 'https://gradle-enterprise-poc.vinted.net/cache/' + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..cc4fdc2 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bf86cbf --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +#list: https://services.gradle.org/distributions/ diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..2fe81a7 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/sample/build.gradle b/sample/build.gradle index 1fac549..96ea0fb 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 25 - buildToolsVersion "25.0.3" + compileSdkVersion 29 defaultConfig { applicationId "com.fenchtose.tooltip" - minSdkVersion 11 - targetSdkVersion 25 + minSdkVersion 14 + targetSdkVersion 26 versionCode 1 versionName "1.0" } @@ -24,11 +23,12 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:25.3.1' - compile 'com.android.support:design:25.3.1' + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' + implementation 'com.google.android.material:material:1.2.1' // compile project(":tooltip") - compile 'com.fenchtose:tooltip:0.1.6' + implementation 'com.fenchtose:tooltip:0.1.6' } diff --git a/sample/src/main/java/com/fenchtose/tooltip_demo/MainActivity.java b/sample/src/main/java/com/fenchtose/tooltip_demo/MainActivity.java index 3587961..373ff8b 100644 --- a/sample/src/main/java/com/fenchtose/tooltip_demo/MainActivity.java +++ b/sample/src/main/java/com/fenchtose/tooltip_demo/MainActivity.java @@ -4,8 +4,6 @@ import android.content.res.Resources; import android.support.annotation.NonNull; import android.support.annotation.StringRes; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; @@ -14,6 +12,8 @@ import android.widget.FrameLayout; import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; import com.fenchtose.tooltip.Tooltip; import com.fenchtose.tooltip.TooltipAnimation; diff --git a/sample/src/main/java/com/fenchtose/tooltip_demo/SecondActivity.java b/sample/src/main/java/com/fenchtose/tooltip_demo/SecondActivity.java index f58681d..88da32c 100644 --- a/sample/src/main/java/com/fenchtose/tooltip_demo/SecondActivity.java +++ b/sample/src/main/java/com/fenchtose/tooltip_demo/SecondActivity.java @@ -5,15 +5,15 @@ import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; +import androidx.appcompat.app.AppCompatActivity; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; import com.fenchtose.tooltip.Tooltip; import com.fenchtose.tooltip.TooltipAnimation; +import com.google.android.material.floatingactionbutton.FloatingActionButton; /** * Created by Jay Rambhia on 5/27/16. diff --git a/sample/src/main/res/layout/activity_second.xml b/sample/src/main/res/layout/activity_second.xml index 53d0665..d17737a 100644 --- a/sample/src/main/res/layout/activity_second.xml +++ b/sample/src/main/res/layout/activity_second.xml @@ -1,4 +1,4 @@ - - - - + - - + - --> - + - --> - \ No newline at end of file + \ No newline at end of file diff --git a/tooltip/build.gradle b/tooltip/build.gradle index 09a0912..801a9d0 100644 --- a/tooltip/build.gradle +++ b/tooltip/build.gradle @@ -9,12 +9,11 @@ def siteUrl = "https://github.com/jayrambhia/Tooltip" def gitUrl = "https://github.com/jayrambhia/Tooltip.git" android { - compileSdkVersion 23 - buildToolsVersion "25.0.0" + compileSdkVersion 29 defaultConfig { minSdkVersion 11 - targetSdkVersion 23 + targetSdkVersion 26 versionCode 12 versionName version } @@ -77,8 +76,8 @@ configurations { } dependencies { - compile 'com.android.support:support-annotations:23.3.0' - javadocDeps 'com.android.support:support-annotations:23.3.0' + api 'androidx.annotation:annotation:1.0.2' + javadocDeps 'androidx.annotation:annotation:1.0.2' } task sourcesJar(type: Jar) { @@ -101,6 +100,8 @@ artifacts { archives sourcesJar } -task findConventions << { - println project.getConvention() -} \ No newline at end of file +task findConventions { + doLast { + println project.getConvention() + } +} diff --git a/tooltip/src/main/java/com/fenchtose/tooltip/AnimationUtils.java b/tooltip/src/main/java/com/fenchtose/tooltip/AnimationUtils.java index 115c790..c58bc6c 100644 --- a/tooltip/src/main/java/com/fenchtose/tooltip/AnimationUtils.java +++ b/tooltip/src/main/java/com/fenchtose/tooltip/AnimationUtils.java @@ -4,9 +4,9 @@ import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.os.Build; -import android.support.annotation.NonNull; import android.view.View; import android.view.ViewAnimationUtils; +import androidx.annotation.NonNull; /** * Helper class to create Animator Objects diff --git a/tooltip/src/main/java/com/fenchtose/tooltip/Tooltip.java b/tooltip/src/main/java/com/fenchtose/tooltip/Tooltip.java index 84944fc..3e4163c 100644 --- a/tooltip/src/main/java/com/fenchtose/tooltip/Tooltip.java +++ b/tooltip/src/main/java/com/fenchtose/tooltip/Tooltip.java @@ -10,15 +10,15 @@ import android.graphics.Point; import android.os.Build; import android.os.Handler; -import android.support.annotation.ColorInt; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import androidx.annotation.ColorInt; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -51,7 +51,10 @@ public class Tooltip extends ViewGroup { private boolean isCancelable = true; private boolean autoAdjust = true; - private int padding; + private int leftPadding = 0; + private int rightPadding = 0; + private int topPadding = 0; + private int bottomPadding = 0; private Listener builderListener; private Listener listener; @@ -68,7 +71,12 @@ public class Tooltip extends ViewGroup { public static final int TOP = 1; public static final int RIGHT = 2; public static final int BOTTOM = 3; - @IntDef({LEFT, TOP, RIGHT, BOTTOM}) + public static final int TOP_LEFT = 4; + public static final int TOP_RIGHT = 5; + public static final int BOTTOM_LEFT = 6; + public static final int BOTTOM_RIGHT = 7; + + @IntDef({LEFT, TOP, RIGHT, BOTTOM, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT}) @Retention(RetentionPolicy.SOURCE) public @interface Position {} @@ -111,7 +119,12 @@ private void init(@NonNull Builder builder) { this.autoAdjust = builder.autoAdjust; this.position = builder.position; - this.padding = builder.padding; + + this.leftPadding = builder.leftPadding; + this.topPadding = builder.topPadding; + this.rightPadding = builder.rightPadding; + this.bottomPadding = builder.bottomPadding; + this.checkForPreDraw = builder.checkForPreDraw; this.debug = builder.debug; @@ -167,6 +180,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (debug) { Log.i(TAG, "child measured width: " + child.getMeasuredWidth()); + Log.i(TAG, "child measured height: " + child.getMeasuredHeight()); } } @@ -236,8 +250,8 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { if (debug) { Log.d(TAG, "anchor location: " + anchorLocation[0] + ", " + anchorLocation[1]); Log.d(TAG, "holder location: " + holderLocation[0] + ", " + holderLocation[1]); + Log.d(TAG, "left (dx): " + left + ", top (dy): " + top); Log.d(TAG, "child w: " + w + " h: " + h); - Log.d(TAG, "left: " + left + ", top: " + top); } tipPath.reset(); @@ -252,16 +266,16 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { int diff = (anchorView.getHeight() - h) / 2; // We should pad right side - left -= (w + padding + (showTip ? tip.getHeight() : 0)); + left -= (w + rightPadding + (showTip ? tip.getHeight() : 0)); // Top and bottom padding is not required top += diff; if (showTip) { px = left + w + tip.getHeight(); - py = top + h/2; + py = top + h / 2; tipPath.moveTo(px, py); - tipPath.lineTo(px - tip.getHeight(), py + tip.getWidth()/2); - tipPath.lineTo(px - tip.getHeight(), py - tip.getWidth()/2); + tipPath.lineTo(px - tip.getHeight(), py + tip.getWidth() / 2f); + tipPath.lineTo(px - tip.getHeight(), py - tip.getWidth() / 2f); tipPath.lineTo(px, py); } @@ -273,16 +287,16 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { // align with horizontal axis int diff = (anchorView.getHeight() - h) / 2; // We should pad left side - left += (anchorView.getWidth() + padding + (showTip ? tip.getHeight() : 0)); + left += (anchorView.getWidth() + leftPadding + (showTip ? tip.getHeight() : 0)); // Top and bottom padding is not required top += diff; if (showTip) { px = left - tip.getHeight(); - py = top + h/2; + py = top + h / 2; tipPath.moveTo(px, py); - tipPath.lineTo(px + tip.getHeight(), py + tip.getWidth()/2); - tipPath.lineTo(px + tip.getHeight(), py - tip.getWidth()/2); + tipPath.lineTo(px + tip.getHeight(), py + tip.getWidth() / 2f); + tipPath.lineTo(px + tip.getHeight(), py - tip.getWidth() / 2f); tipPath.lineTo(px, py); } @@ -298,14 +312,14 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { left += diff; // We should only pad bottom - top -= (h + padding + (showTip ? tip.getHeight() : 0)); + top -= (h + bottomPadding + (showTip ? tip.getHeight() : 0)); if (showTip) { px = left + w / 2; py = top + h + tip.getHeight(); tipPath.moveTo(px, py); - tipPath.lineTo(px - tip.getWidth() / 2, py - tip.getHeight()); - tipPath.lineTo(px + tip.getWidth() / 2, py - tip.getHeight()); + tipPath.lineTo(px - tip.getWidth() / 2f, py - tip.getHeight()); + tipPath.lineTo(px + tip.getWidth() / 2f, py - tip.getHeight()); tipPath.lineTo(px, py); } @@ -313,7 +327,7 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { } case BOTTOM: { - // to top of anchor view + // to bottom of anchor view // align with vertical axis int diff = (anchorView.getWidth() - w) / 2; @@ -321,7 +335,7 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { left += diff; // We should only pad top - top += anchorView.getHeight() + padding + (showTip ? tip.getHeight() : 0); + top += anchorView.getHeight() + topPadding + (showTip ? tip.getHeight() : 0); if (debug) { Log.d(TAG, "tip top: " + top); @@ -331,29 +345,105 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { px = left + w / 2; py = top - tip.getHeight(); tipPath.moveTo(px, py); - tipPath.lineTo(px - tip.getWidth() / 2, py + tip.getHeight()); - tipPath.lineTo(px + tip.getWidth() / 2, py + tip.getHeight()); + tipPath.lineTo(px - tip.getWidth() / 2f, py + tip.getHeight()); + tipPath.lineTo(px + tip.getWidth() / 2f, py + tip.getHeight()); tipPath.lineTo(px, py); + } + + break; + } + + case TOP_LEFT: { + // to top left corner of anchor view + int diff = anchorView.getWidth() - w; + left -= rightPadding - diff; + // We should only pad bottom + top -= (h + bottomPadding + (showTip ? tip.getHeight() : 0)); + if (showTip) { + px = left + w - anchorView.getWidth() / 2; + py = top + h + tip.getHeight(); + tipPath.moveTo(px, py); + tipPath.lineTo(px - tip.getWidth() / 2f, py - tip.getHeight()); + tipPath.lineTo(px + tip.getWidth() / 2f, py - tip.getHeight()); + tipPath.lineTo(px, py); } break; } + case TOP_RIGHT: { + // to top right corner of anchor view + left += leftPadding; + // We should only pad bottom + top -= (h + bottomPadding + (showTip ? tip.getHeight() : 0)); + + if (showTip) { + px = left + anchorView.getWidth() / 2; + py = top + h + tip.getHeight(); + tipPath.moveTo(px, py); + tipPath.lineTo(px - tip.getWidth() / 2f, py - tip.getHeight()); + tipPath.lineTo(px + tip.getWidth() / 2f, py - tip.getHeight()); + tipPath.lineTo(px, py); + } + + break; + } + + case BOTTOM_LEFT: { + // to bottom left corner of anchor view + int diff = anchorView.getWidth() - w; + left -= rightPadding - diff; + // We should only pad top + top += anchorView.getHeight() + topPadding + (showTip ? tip.getHeight() : 0); + + if (showTip) { + px = left + w - anchorView.getWidth() / 2; + py = top - tip.getHeight(); + tipPath.moveTo(px, py); + tipPath.lineTo(px - tip.getWidth() / 2f, py + tip.getHeight()); + tipPath.lineTo(px + tip.getWidth() / 2f, py + tip.getHeight()); + tipPath.lineTo(px, py); + } + + break; + } + + case BOTTOM_RIGHT: { + // to bottom right corner of anchor view + left += leftPadding; + // We should only pad top + top += anchorView.getHeight() + topPadding + (showTip ? tip.getHeight() : 0); + + if (showTip) { + px = left + anchorView.getWidth() / 2; + py = top - tip.getHeight(); + tipPath.moveTo(px, py); + tipPath.lineTo(px - tip.getWidth() / 2f, py + tip.getHeight()); + tipPath.lineTo(px + tip.getWidth() / 2f, py + tip.getHeight()); + tipPath.lineTo(px, py); + } + + break; + } } if (autoAdjust) { switch (position) { case TOP: case BOTTOM: + case TOP_LEFT: + case TOP_RIGHT: + case BOTTOM_LEFT: + case BOTTOM_RIGHT: if (left + w > r) { // View is going out on the right side // Add padding to the right - left = r - w - padding; + left = r - w - rightPadding; } else if (left < l) { // View is going out on the left side // Add padding to the left - left = l + padding; + left = l + leftPadding; } break; @@ -362,11 +452,11 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { if (top + h > b) { // View is going out on the bottom side // Add padding to bottom - top = b - h - padding; + top = b - h - bottomPadding; } else if (top < t) { // View is going out on the top side // Add padding to top - top = t + padding; + top = t + topPadding; } break; } @@ -389,20 +479,36 @@ private void doLayout(boolean changed, int l, int t, int r, int b) { switch (position) { case TOP: - px = left + child.getMeasuredWidth()/2; + px = left + child.getMeasuredWidth() / 2; py = top + child.getMeasuredHeight(); break; case BOTTOM: - px = left + child.getMeasuredWidth()/2; + px = left + child.getMeasuredWidth() / 2; py = top; break; case LEFT: px = left + child.getMeasuredWidth(); - py = top + child.getMeasuredHeight(); + py = top + child.getMeasuredHeight() / 2; break; case RIGHT: px = left; - py = top + child.getMeasuredHeight()/2; + py = top + child.getMeasuredHeight() / 2; + break; + case TOP_LEFT: + px = left + child.getMeasuredWidth(); + py = top + child.getMeasuredHeight(); + break; + case TOP_RIGHT: + px = left; + py = top + child.getMeasuredHeight(); + break; + case BOTTOM_LEFT: + px = left + child.getMeasuredWidth(); + py = top; + break; + case BOTTOM_RIGHT: + px = left; + py = top; break; } } @@ -640,19 +746,51 @@ private Animator getScaleAnimator(@NonNull TooltipAnimation animation, @NonNull float startScale, float endScale) { switch (position) { + case BOTTOM_LEFT: case BOTTOM: - return AnimationUtils.scaleY(contentView, size[0]/2, 0 , startScale, endScale, animation.getDuration()); + case BOTTOM_RIGHT: + return AnimationUtils.scaleY(contentView, size[0] / 2, 0, startScale, endScale, animation.getDuration()); + case TOP_LEFT: case TOP: - return AnimationUtils.scaleY(contentView, size[0]/2, size[1] , startScale, endScale, animation.getDuration()); + case TOP_RIGHT: + return AnimationUtils.scaleY(contentView, size[0] / 2, size[1], startScale, endScale, animation.getDuration()); case RIGHT: - return AnimationUtils.scaleX(contentView, 0, size[1]/2, startScale, endScale, animation.getDuration()); + return AnimationUtils.scaleX(contentView, 0, size[1] / 2, startScale, endScale, animation.getDuration()); case LEFT: - return AnimationUtils.scaleX(contentView, size[0], size[1]/2, startScale, endScale, animation.getDuration()); + return AnimationUtils.scaleX(contentView, size[0], size[1] / 2, startScale, endScale, animation.getDuration()); default: return null; } } + /** + * Gets tooltip horizontal position + **/ + public float getContentX() { + return contentView.getX(); + } + + /** + * Sets tooltip horizontal position + **/ + public void setContentX(float x) { + contentView.setX(x); + } + + /** + * Gets tooltip vertical position + **/ + public float getContentY() { + return contentView.getY(); + } + + /** + * Sets tooltip vertical position + **/ + public void setContentY(float y) { + contentView.setY(y); + } + /** * Builder class for {@link Tooltip}. Builder has the responsibility of creating the Tooltip * and adding/displaying it in the {@link #rootView}. @@ -702,9 +840,12 @@ public static class Builder { private Tip tip; /** - * Margin from the anchor and screen boundaries + * Margins from the anchor and screen boundaries */ - private int padding = 0; + private int leftPadding = 0; + private int topPadding = 0; + private int rightPadding = 0; + private int bottomPadding = 0; /** * If you want the tooltip to dismiss automatically after a certain amount of time, @@ -745,6 +886,16 @@ public static class Builder { */ private boolean debug = false; + /** + * Bound from top and bottom, if tooltip is outside bounds then it wil be clipped + */ + private int topBottomBound = 0; + + /** + * Should tooltip be clipped beyond top bottom bounds + */ + private boolean clipOutsideBounds = true; + public Builder(@NonNull Context context) { this.context = context; handler = new Handler(); @@ -835,11 +986,38 @@ public Builder autoAdjust(boolean autoAdjust) { /** * Margin from the anchor and screen boundaries - * @param padding - margin from the screen edge (in pixels). + * @param padding all (left, top, right, bottom) margins from the screen edge (in pixels). * @return Builder */ public Builder withPadding(int padding) { - this.padding = padding; + return withPadding(padding, padding, padding, padding); + } + + /** + * Margins from the anchor and screen boundaries + * + * @param topBottom top and bottom margins from the screen edge (in pixels). + * @param leftRight left and right margins from the screen edge (in pixels). + * @return Builder + */ + public Builder withPadding(int topBottom, int leftRight) { + return withPadding(leftRight, topBottom, leftRight, topBottom); + } + + /** + * Margins from the anchor and screen boundaries, + * + * @param left left margin from the screen edge (in pixels). + * @param top top margin from the screen edge (in pixels). + * @param right right margin from the screen edge (in pixels). + * @param bottom bottom margin from the screen edge (in pixels). + * @return Builder + */ + public Builder withPadding(int left, int top, int right, int bottom) { + this.leftPadding = left; + this.topPadding = top; + this.rightPadding = right; + this.bottomPadding = bottom; return this; } @@ -912,6 +1090,30 @@ public Builder debug(boolean debug) { return this; } + /** + * Sets constraint from top and bottom in pixels + * Tooltip moved with setContentY will be not drawn beyond boundaries + * with a condition that clipOutsideBounds is true + * + * @param boundary additional top and bottom boundaries for tooltip + * @return Builder + */ + public Builder topBottomBounds(int boundary) { + this.topBottomBound = boundary; + return this; + } + + /** + * Sets that tooltip should be clipped outside bounds, default is true + * + * @param clipOutsideBounds if true tooltip will be clipped outside top and bottom bounds + * @return Builder + */ + public Builder clipOutsideBounds(boolean clipOutsideBounds) { + this.clipOutsideBounds = clipOutsideBounds; + return this; + } + /** * Create a new instance of Tooltip. This method will throw {@link NullPointerException} * if {@link #anchorView} or {@link #rootView} or {@link #contentView} is not assigned. @@ -954,6 +1156,9 @@ public Tooltip show() { rootView.addView(tooltip, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + tooltip.setPadding(0, topBottomBound, 0, topBottomBound); + tooltip.setClipToPadding(clipOutsideBounds); + anchorView.getLocationInWindow(anchorLocation); if (debug) { Log.i(TAG, "anchor location after adding: " + anchorLocation[0] + ", " + anchorLocation[1]); diff --git a/tooltip/src/main/java/com/fenchtose/tooltip/TooltipAnimation.java b/tooltip/src/main/java/com/fenchtose/tooltip/TooltipAnimation.java index 91d5d44..071f596 100644 --- a/tooltip/src/main/java/com/fenchtose/tooltip/TooltipAnimation.java +++ b/tooltip/src/main/java/com/fenchtose/tooltip/TooltipAnimation.java @@ -1,10 +1,10 @@ package com.fenchtose.tooltip; import android.animation.Animator; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy;