diff --git a/README.md b/README.md
index ec9eec25..d676719c 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,7 @@ Tooltip style can be customized in your style object:
+
diff --git a/app/build.gradle b/app/build.gradle
index 7aa8daa4..797c534e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,18 +1,19 @@
apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion ANDROID_BUILD_SDK_VERSION as int
- buildToolsVersion ANDROID_BUILD_TOOLS_VERSION
defaultConfig {
- minSdkVersion 14
+ minSdkVersion 16
targetSdkVersion ANDROID_BUILD_TARGET_SDK_VERSION as int
versionCode 1
versionName VERSION_NAME
- jackOptions {
- enabled false
- }
+// jackOptions {
+// enabled false
+// }
}
buildTypes {
release {
@@ -22,8 +23,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
@@ -38,12 +39,15 @@ android {
}
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile project(':library')
- compile 'com.android.support:appcompat-v7:24.1.1'
- compile 'com.android.support:design:24.1.1'
- compile 'com.android.support:recyclerview-v7:24.1.1'
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation project(':neofecttooltip')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+
+ implementation "com.android.support:appcompat-v7:$ANDROID_BUILD_TOOLS_VERSION"
+ implementation "com.android.support:design:$ANDROID_BUILD_TOOLS_VERSION"
+ implementation "com.android.support:recyclerview-v7:$ANDROID_BUILD_TOOLS_VERSION"
+ implementation 'com.jakewharton.timber:timber:4.7.1'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c574e36e..59182bd5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,16 @@
android:label="@string/app_name"
android:theme="@style/AppTheme">
+
+
+
+
+
+
+
+
+ val gravity = XTooltip.Gravity.valueOf(spinner_gravities.selectedItem.toString())
+ val closePolicy = getClosePolicy()
+ val typeface = if (checkbox_font.isChecked) Typefaces[this, "fonts/at.ttc"] else null
+ val animation = if (checkbox_animation.isChecked) XTooltip.Animation.DEFAULT else null
+ val showDuration = if (text_duration.text.isNullOrEmpty()) 0 else text_duration.text.toString().toLong()
+ val fadeDuration = if (text_fade.text.isNullOrEmpty()) 0 else text_fade.text.toString().toLong()
+ val arrow = checkbox_arrow.isChecked
+ val overlay = checkbox_overlay.isChecked
+ val style = if (checkbox_style.isChecked) R.style.ToolTipAltStyle else null
+ val text =
+ if (text_tooltip.text.isNullOrEmpty()) "Lorem ipsum dolor" else text_tooltip.text!!.toString()
+
+ Timber.v("gravity: $gravity")
+ Timber.v("closePolicy: $closePolicy")
+
+ tooltip = XTooltip.Builder(this)
+ .anchor(button, 0, 0, false)
+ .text(text)
+ .styleId(style)
+ .typeface(typeface)
+ .maxWidth(metrics.widthPixels / 2)
+ .arrow(arrow)
+ .floatingAnimation(animation)
+ .closePolicy(closePolicy)
+ .showDuration(showDuration)
+ .fadeDuration(fadeDuration)
+ .overlay(overlay)
+ .create()
+
+ tooltip
+ ?.doOnHidden {
+ tooltip = null
+ }
+ ?.doOnFailure { }
+ ?.doOnShown {}
+ ?.show(button, gravity, true)
+ }
+
+ button2.setOnClickListener {
+ val fragment = TestDialogFragment.newInstance()
+ fragment.show(supportFragmentManager, "test_dialog_fragment")
+ }
+ }
+
+ private fun getClosePolicy(): ClosePolicy {
+ val builder = ClosePolicy.Builder()
+ builder.inside(switch1.isChecked)
+ builder.outside(switch3.isChecked)
+ builder.consume(switch2.isChecked)
+ return builder.build()
+ }
+
+ override fun onDestroy() {
+ Timber.i("onDestroy")
+ super.onDestroy()
+ tooltip?.dismiss()
+ }
+
+}
diff --git a/app/src/main/java/it/sephiroth/android/library/mymodule/app/MainActivity2.java b/app/src/main/java/it/sephiroth/android/library/mymodule/app/MainActivity2.java
index 4c5bf76b..a2e2a758 100644
--- a/app/src/main/java/it/sephiroth/android/library/mymodule/app/MainActivity2.java
+++ b/app/src/main/java/it/sephiroth/android/library/mymodule/app/MainActivity2.java
@@ -246,6 +246,7 @@ public void onClick(final View v) {
.activateDelay(2000)
.maxWidth(metrics.widthPixels / 2)
.withCallback(this)
+ .alignment(Tooltip.Alignment.BOTTOM)
.floatingAnimation(AnimationBuilder.DEFAULT)
.build()
).show();
@@ -262,6 +263,7 @@ public void onClick(final View v) {
.withArrow(true)
.maxWidth(metrics.widthPixels / 2)
.withCallback(this)
+ .alignment(Tooltip.Alignment.LEFT)
.withStyleId(R.style.ToolTipLayoutDefaultStyle_Custom1)
.build()
).show();
@@ -276,6 +278,7 @@ public void onClick(final View v) {
.withArrow(true)
.maxWidth((int) (metrics.widthPixels / 2.5))
.withCallback(this)
+ .alignment(Tooltip.Alignment.RIGHT)
.floatingAnimation(AnimationBuilder.DEFAULT)
.build()
).show();
@@ -289,6 +292,7 @@ public void onClick(final View v) {
.text("TOP. Touch Inside exclusive.")
.withArrow(true)
.withOverlay(false)
+ .alignment(Tooltip.Alignment.RIGHT)
.maxWidth(metrics.widthPixels / 3)
.withCallback(this)
.build()
@@ -308,6 +312,7 @@ public void onClick(final View v) {
.withOverlay(false)
.maxWidth(metrics.widthPixels / 3)
.showDelay(300)
+ .alignment(Tooltip.Alignment.TOP)
.withCallback(this)
.build()
);
diff --git a/app/src/main/java/it/sephiroth/android/library/mymodule/app/MyTextView.java b/app/src/main/java/it/sephiroth/android/library/mymodule/app/MyTextView.java
index 2452091e..af2eeca6 100644
--- a/app/src/main/java/it/sephiroth/android/library/mymodule/app/MyTextView.java
+++ b/app/src/main/java/it/sephiroth/android/library/mymodule/app/MyTextView.java
@@ -1,14 +1,14 @@
package it.sephiroth.android.library.mymodule.app;
import android.content.Context;
+import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.TextView;
/**
* Created by alessandro on 04/09/14.
*/
-public class MyTextView extends TextView {
+public class MyTextView extends AppCompatTextView {
public static interface OnAttachStatusListener {
void onAttachedtoWindow(View view);
diff --git a/app/src/main/java/it/sephiroth/android/library/mymodule/app/TestDialogFragment.kt b/app/src/main/java/it/sephiroth/android/library/mymodule/app/TestDialogFragment.kt
new file mode 100644
index 00000000..798e9bd9
--- /dev/null
+++ b/app/src/main/java/it/sephiroth/android/library/mymodule/app/TestDialogFragment.kt
@@ -0,0 +1,39 @@
+package it.sephiroth.android.library.mymodule.app;
+
+import android.os.Bundle
+import android.support.v4.app.DialogFragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import it.sephiroth.android.library.xtooltip.ClosePolicy
+import it.sephiroth.android.library.xtooltip.XTooltip
+import kotlinx.android.synthetic.main.dialog_fragment.*
+
+class TestDialogFragment : DialogFragment() {
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.dialog_fragment, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ button1.setOnClickListener { button ->
+ XTooltip.Builder(context!!)
+ .anchor(button, 0, 0, false)
+ .closePolicy(ClosePolicy.TOUCH_ANYWHERE_CONSUME)
+ .fadeDuration(200)
+ .showDuration(0)
+ .text("This is a dialog")
+ .create()
+ .show(button, XTooltip.Gravity.TOP, false)
+ }
+ }
+
+ companion object {
+ fun newInstance(): TestDialogFragment {
+ val frag = TestDialogFragment()
+ return frag
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..792e7ae6
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
new file mode 100644
index 00000000..bd773fdd
--- /dev/null
+++ b/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_fragment.xml b/app/src/main/res/layout/dialog_fragment.xml
new file mode 100644
index 00000000..853747ac
--- /dev/null
+++ b/app/src/main/res/layout/dialog_fragment.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bfb38c43..8c1c6767 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,4 +7,19 @@
Target Tooltip 2
Target Tooltip 3
+
+ - BOTTOM
+ - TOP
+ - LEFT
+ - RIGHT
+ - CENTER
+
+
+
+ - 0
+ - 1000
+ - 2000
+ - 5000
+ - 10000
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 7802c9dd..13f92f19 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -18,7 +18,7 @@
- #ffe5a000
- #ffe5c700
- 2dip
- - 8dip
+ - 5dip
- @style/ToolTipOverlayCustomStyle
- ?android:attr/textAppearanceInverse
@@ -33,4 +33,21 @@
- fonts/at.ttc
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index a641feb5..048a386c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,11 +1,13 @@
buildscript {
+ ext.kotlin_version = '1.3.11'
repositories {
+ google()
jcenter()
- mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.0-beta2'
+ classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -14,8 +16,8 @@ allprojects {
group = GROUP
repositories {
+ google()
jcenter()
- mavenCentral()
}
}
diff --git a/gradle.properties b/gradle.properties
index d615c99c..d4606fa6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,8 +16,8 @@ POM_DEVELOPER_EMAIL=alessandro.crugnola@gmail.com
POM_DEVELOPER_URL=http://blog.sephiroth.it
POM_DEVELOPER_ROLE=author
-ANDROID_BUILD_TARGET_SDK_VERSION=25
-ANDROID_BUILD_TOOLS_VERSION=25.0.2
-ANDROID_BUILD_SDK_VERSION=25
+ANDROID_BUILD_TARGET_SDK_VERSION=27
+ANDROID_BUILD_TOOLS_VERSION=27.0.2
+ANDROID_BUILD_SDK_VERSION=27
org.gradle.daemon=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0633896a..33ac8ad0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
\ No newline at end of file
diff --git a/library/.gitignore b/neofecttooltip/.gitignore
similarity index 100%
rename from library/.gitignore
rename to neofecttooltip/.gitignore
diff --git a/library/build.gradle b/neofecttooltip/build.gradle
similarity index 62%
rename from library/build.gradle
rename to neofecttooltip/build.gradle
index 91532538..9bb42a57 100644
--- a/library/build.gradle
+++ b/neofecttooltip/build.gradle
@@ -1,14 +1,15 @@
apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
group GROUP
version VERSION_NAME
android {
compileSdkVersion ANDROID_BUILD_SDK_VERSION as int
- buildToolsVersion ANDROID_BUILD_TOOLS_VERSION
defaultConfig {
- minSdkVersion 14
+ minSdkVersion 16
targetSdkVersion ANDROID_BUILD_TARGET_SDK_VERSION as int
versionCode 1
versionName VERSION_NAME
@@ -24,8 +25,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
@@ -42,9 +43,12 @@ android {
}
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- provided 'com.android.support:support-annotations:24.1.1'
- compile 'com.android.support:appcompat-v7:24.1.1'
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "com.android.support:support-annotations:$ANDROID_BUILD_TOOLS_VERSION"
+ implementation "com.android.support:appcompat-v7:$ANDROID_BUILD_TOOLS_VERSION"
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+ implementation 'com.jakewharton.timber:timber:4.7.1'
}
diff --git a/library/gradle.properties b/neofecttooltip/gradle.properties
similarity index 100%
rename from library/gradle.properties
rename to neofecttooltip/gradle.properties
diff --git a/library/proguard-rules.txt b/neofecttooltip/proguard-rules.txt
similarity index 100%
rename from library/proguard-rules.txt
rename to neofecttooltip/proguard-rules.txt
diff --git a/library/src/main/AndroidManifest.xml b/neofecttooltip/src/main/AndroidManifest.xml
similarity index 100%
rename from library/src/main/AndroidManifest.xml
rename to neofecttooltip/src/main/AndroidManifest.xml
diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java
similarity index 92%
rename from library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java
rename to neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java
index e28d644b..a633689b 100644
--- a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java
@@ -177,6 +177,10 @@ public enum Gravity {
LEFT, RIGHT, TOP, BOTTOM, CENTER
}
+ public enum Alignment {
+ LEFT, RIGHT, TOP, BOTTOM, CENTER
+ }
+
@SuppressWarnings ("unused")
public interface TooltipView {
void show();
@@ -259,9 +263,12 @@ static class TooltipViewImpl extends ViewGroup implements TooltipView {
private final Point mTmpPoint = new Point();
private final Rect mHitRect = new Rect();
private final float mTextViewElevation;
+ private final float mMargin;
private Callback mCallback;
private int[] mOldLocation;
private Gravity mGravity;
+ private Alignment mAlignment;
+ private int mAnchorShift;
private Animator mShowAnimation;
private boolean mShowing;
private WeakReference mViewAnchor;
@@ -311,6 +318,7 @@ public void run() {
}
};
private int mPadding;
+ private float mArrowHeight;
private CharSequence mText;
private Rect mViewRect;
private View mView;
@@ -402,11 +410,14 @@ public TooltipViewImpl(Context context, final Builder builder) {
context.getTheme()
.obtainStyledAttributes(null, R.styleable.TooltipLayout, builder.defStyleAttr, builder.defStyleRes);
this.mPadding = theme.getDimensionPixelSize(R.styleable.TooltipLayout_ttlm_padding, 30);
+ this.mArrowHeight = theme.getDimension(R.styleable.TooltipLayout_ttlm_arrowHeight, 0);
this.mTextAppearance = theme.getResourceId(R.styleable.TooltipLayout_android_textAppearance, 0);
this.mTextGravity = theme
.getInt(R.styleable.TooltipLayout_android_gravity, android.view.Gravity.TOP | android.view.Gravity.START);
this.mTextViewElevation = theme.getDimension(R.styleable.TooltipLayout_ttlm_elevation, 0);
int overlayStyle = theme.getResourceId(R.styleable.TooltipLayout_ttlm_overlayStyle, R.style.ToolTipOverlayDefaultStyle);
+ this.mMargin = theme.getDimension(R.styleable.TooltipLayout_ttlm_margin, 0);
+ this.mAnchorShift = theme.getDimensionPixelOffset(R.styleable.TooltipLayout_ttlm_anchorShift, -1);
String font = theme.getString(R.styleable.TooltipLayout_ttlm_font);
@@ -415,6 +426,7 @@ public TooltipViewImpl(Context context, final Builder builder) {
this.mToolTipId = builder.id;
this.mText = builder.text;
this.mGravity = builder.gravity;
+ this.mAlignment = builder.alignment;
this.mTextResId = builder.textResId;
this.mMaxWidth = builder.maxWidth;
this.mTopRule = builder.actionbarSize;
@@ -422,6 +434,13 @@ public TooltipViewImpl(Context context, final Builder builder) {
this.mShowDuration = builder.showDuration;
this.mShowDelay = builder.showDelay;
this.mHideArrow = builder.hideArrow;
+ if (mHideArrow) {
+ mArrowHeight = 0;
+ } else {
+ if (mArrowHeight == 0) {
+ mArrowHeight = mPadding;
+ }
+ }
this.mActivateDelay = builder.activateDelay;
this.mRestrict = builder.restrictToScreenEdges;
this.mFadeDuration = builder.fadeDuration;
@@ -798,6 +817,12 @@ private void initializeView() {
if (0 != mTextAppearance) {
mTextView.setTextAppearance(getContext(), mTextAppearance);
+
+ final TypedArray ta = getContext().obtainStyledAttributes(mTextAppearance, R.styleable.TooltipLayout);
+ final int lineSpacingAdd = ta.getDimensionPixelSize(R.styleable.TooltipLayout_android_lineSpacingExtra, 0);
+ final float lineSpacingMultiplier = ta.getFloat(R.styleable.TooltipLayout_android_lineSpacingMultiplier, 1);
+ ta.recycle();
+ mTextView.setLineSpacing(lineSpacingAdd, lineSpacingMultiplier);
}
mTextView.setGravity(mTextGravity);
@@ -808,11 +833,8 @@ private void initializeView() {
if (null != mDrawable) {
mTextView.setBackgroundDrawable(mDrawable);
- if (mHideArrow) {
- mTextView.setPadding(mPadding / 2, mPadding / 2, mPadding / 2, mPadding / 2);
- } else {
- mTextView.setPadding(mPadding, mPadding, mPadding, mPadding);
- }
+ mTextView.setPadding(mPadding + Math.round(mArrowHeight), mPadding + Math.round(mArrowHeight),
+ mPadding + Math.round(mArrowHeight), mPadding + Math.round(mArrowHeight));
}
this.addView(mView);
@@ -1029,6 +1051,49 @@ private void calculatePositions(List gravities, final boolean checkEdge
}
}
+ Point center = new Point(mViewRect.centerX(), mViewRect.centerY());
+
+ switch (mAlignment) {
+ case LEFT:
+ if (mGravity == TOP || mGravity == BOTTOM) {
+ int shift = mViewRect.left - mDrawRect.left - Math.round(mArrowHeight) + mAnchorShift;
+ mDrawRect.offset(shift, 0);
+ }
+ break;
+
+ case RIGHT:
+ if (mGravity == TOP || mGravity == BOTTOM) {
+ int shift = mViewRect.right - mDrawRect.right + Math.round(mArrowHeight) + mAnchorShift;
+ mDrawRect.offset(shift, 0);
+ }
+ break;
+
+ case TOP:
+ if (mGravity == LEFT || mGravity == RIGHT) {
+ int shift = mViewRect.top - mDrawRect.top + Math.round(mArrowHeight + mAnchorShift);
+ mDrawRect.offset(0, shift);
+ }
+ break;
+
+ case BOTTOM:
+ if (mGravity == LEFT || mGravity == RIGHT) {
+ int shift = mViewRect.bottom - mDrawRect.bottom + Math.round(mArrowHeight + mAnchorShift);
+ mDrawRect.offset(0, shift);
+ }
+ break;
+
+ case CENTER:
+ if (mGravity == TOP || mGravity == BOTTOM) {
+ mDrawRect.offset(-mAnchorShift, 0);
+ } else if (mGravity == LEFT || mGravity == RIGHT) {
+ mDrawRect.offset(0, mAnchorShift);
+ }
+ break;
+
+ default:
+ break;
+ }
+
if (null != mViewOverlay) {
mViewOverlay.setTranslationX(mViewRect.centerX() - mViewOverlay.getWidth() / 2);
mViewOverlay.setTranslationY(mViewRect.centerY() - mViewOverlay.getHeight() / 2);
@@ -1039,8 +1104,8 @@ private void calculatePositions(List gravities, final boolean checkEdge
mView.setTranslationY(mDrawRect.top);
if (null != mDrawable) {
- getAnchorPoint(gravity, mTmpPoint);
- mDrawable.setAnchor(gravity, mHideArrow ? 0 : mPadding / 2, mHideArrow ? null : mTmpPoint);
+ getAnchorPoint(gravity, mTmpPoint, center);
+ mDrawable.setAnchor(gravity, mHideArrow ? 0 : mPadding, mArrowHeight, mHideArrow ? null : mTmpPoint);
}
if (!mAlreadyCheck) {
@@ -1081,6 +1146,8 @@ private boolean calculatePositionLeft(
mViewRect.centerY() + height / 2
);
+ mDrawRect.offset(0, Math.round(-mMargin));
+
if ((mViewRect.width() / 2) < overlayWidth) {
mDrawRect.offset(-(overlayWidth - (mViewRect.width() / 2)), 0);
}
@@ -1111,6 +1178,8 @@ private boolean calculatePositionRight(
mViewRect.centerY() + height / 2
);
+ mDrawRect.offset(Math.round(mMargin), 0);
+
if ((mViewRect.width() / 2) < overlayWidth) {
mDrawRect.offset(overlayWidth - mViewRect.width() / 2, 0);
}
@@ -1141,6 +1210,8 @@ private boolean calculatePositionTop(
mViewRect.top
);
+ mDrawRect.offset(0, Math.round(-mMargin));
+
if ((mViewRect.height() / 2) < overlayHeight) {
mDrawRect.offset(0, -(overlayHeight - (mViewRect.height() / 2)));
}
@@ -1171,6 +1242,8 @@ private boolean calculatePositionBottom(
mViewRect.bottom + height
);
+ mDrawRect.offset(0, Math.round(mMargin));
+
if (mViewRect.height() / 2 < overlayHeight) {
mDrawRect.offset(0, overlayHeight - mViewRect.height() / 2);
}
@@ -1242,9 +1315,39 @@ void getAnchorPoint(final Gravity gravity, Point outPoint) {
if (!mHideArrow) {
if (gravity == LEFT || gravity == RIGHT) {
- outPoint.y -= mPadding / 2;
+ outPoint.y -= mArrowHeight;
} else if (gravity == TOP || gravity == BOTTOM) {
- outPoint.x -= mPadding / 2;
+ outPoint.x -= mArrowHeight;
+ }
+ }
+ }
+
+ void getAnchorPoint(final Gravity gravity, Point outPoint, Point center) {
+ if (gravity == BOTTOM) {
+ outPoint.x = center.x;
+ outPoint.y = mViewRect.bottom;
+ } else if (gravity == TOP) {
+ outPoint.x = center.x;
+ outPoint.y = mViewRect.top;
+ } else if (gravity == RIGHT) {
+ outPoint.x = mViewRect.right;
+ outPoint.y = center.y;
+ } else if (gravity == LEFT) {
+ outPoint.x = mViewRect.left;
+ outPoint.y = center.y;
+ } else if (this.mGravity == CENTER) {
+ outPoint.x = center.x;
+ outPoint.y = center.y;
+ }
+
+ outPoint.x -= mDrawRect.left;
+ outPoint.y -= mDrawRect.top;
+
+ if (!mHideArrow) {
+ if (gravity == LEFT || gravity == RIGHT) {
+ outPoint.y -= mArrowHeight;
+ } else if (gravity == TOP || gravity == BOTTOM) {
+ outPoint.x -= mArrowHeight;
}
}
}
@@ -1458,6 +1561,7 @@ public static final class Builder {
CharSequence text;
View view;
Gravity gravity;
+ Alignment alignment = Alignment.CENTER;
int actionbarSize = 0;
int textResId = R.layout.tooltip_textview;
int closePolicy = ClosePolicy.NONE;
@@ -1602,6 +1706,11 @@ public Builder anchor(final Point point, final Gravity gravity) {
return this;
}
+ public Builder alignment(Alignment alignment) {
+ this.alignment = alignment;
+ return this;
+ }
+
/**
* @deprecated use {#withArrow} instead
*/
diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlay.java b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlay.java
similarity index 92%
rename from library/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlay.java
rename to neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlay.java
index 98369243..67d2f14a 100644
--- a/library/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlay.java
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlay.java
@@ -2,10 +2,10 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
-import android.widget.ImageView;
-public class TooltipOverlay extends ImageView {
+public class TooltipOverlay extends AppCompatImageView {
private int mMargins;
public TooltipOverlay(Context context) {
diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlayDrawable.java b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlayDrawable.java
similarity index 100%
rename from library/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlayDrawable.java
rename to neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlayDrawable.java
diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/TooltipTextDrawable.java b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/TooltipTextDrawable.java
similarity index 95%
rename from library/src/main/java/it/sephiroth/android/library/tooltip/TooltipTextDrawable.java
rename to neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/TooltipTextDrawable.java
index 5fa7d1fa..6d108289 100644
--- a/library/src/main/java/it/sephiroth/android/library/tooltip/TooltipTextDrawable.java
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/TooltipTextDrawable.java
@@ -30,6 +30,7 @@ class TooltipTextDrawable extends Drawable {
private final float ellipseSize;
private Point point;
private int padding = 0;
+ private float arrowHeight;
private int arrowWeight = 0;
private Tooltip.Gravity gravity;
@@ -77,11 +78,12 @@ public void draw(final Canvas canvas) {
}
}
- public void setAnchor(final Tooltip.Gravity gravity, int padding, @Nullable Point point) {
+ public void setAnchor(final Tooltip.Gravity gravity, int padding, float arrowHeight, @Nullable Point point) {
if (gravity != this.gravity || padding != this.padding || !Utils.equals(this.point, point)) {
this.gravity = gravity;
this.padding = padding;
- this.arrowWeight = (int) ((float) padding / arrowRatio);
+ this.arrowHeight = arrowHeight;
+ this.arrowWeight = (int) (arrowHeight / arrowRatio);
if (null != point) {
this.point = new Point(point);
@@ -98,10 +100,10 @@ public void setAnchor(final Tooltip.Gravity gravity, int padding, @Nullable Poin
}
void calculatePath(Rect outBounds) {
- int left = outBounds.left + padding;
- int top = outBounds.top + padding;
- int right = outBounds.right - padding;
- int bottom = outBounds.bottom - padding;
+ int left = outBounds.left + Math.round(arrowHeight);
+ int top = outBounds.top + Math.round(arrowHeight);
+ int right = outBounds.right - Math.round(arrowHeight);
+ int bottom = outBounds.bottom - Math.round(arrowHeight);
final float maxY = bottom - ellipseSize;
final float maxX = right - ellipseSize;
diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/Typefaces.java b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/Typefaces.java
similarity index 100%
rename from library/src/main/java/it/sephiroth/android/library/tooltip/Typefaces.java
rename to neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/Typefaces.java
diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/Utils.java b/neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/Utils.java
similarity index 100%
rename from library/src/main/java/it/sephiroth/android/library/tooltip/Utils.java
rename to neofecttooltip/src/main/java/it/sephiroth/android/library/tooltip/Utils.java
diff --git a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt
new file mode 100644
index 00000000..4477769d
--- /dev/null
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt
@@ -0,0 +1,51 @@
+package it.sephiroth.android.library.xtooltip
+
+
+import android.content.Context
+import android.support.v7.widget.AppCompatImageView
+import android.util.AttributeSet
+import it.sephiroth.android.library.tooltip.R
+
+/**
+ * Created by alessandro crugnola on 12/12/15.
+ * alessandro.crugnola@gmail.com
+ *
+ *
+ * LICENSE
+ * Copyright 2015 Alessandro Crugnola
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ * is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+class TooltipOverlay : AppCompatImageView {
+ private var layoutMargins: Int = 0
+
+ @JvmOverloads
+ constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = R.style.ToolTipOverlayDefaultStyle
+ ) : super(context, attrs, defStyleAttr) {
+ init(context, R.style.ToolTipLayoutDefaultStyle)
+ }
+
+ private fun init(context: Context, defStyleResId: Int) {
+ val drawable = TooltipOverlayDrawable(context, defStyleResId)
+ setImageDrawable(drawable)
+
+ val array = context.theme.obtainStyledAttributes(defStyleResId, R.styleable.TooltipOverlay)
+ layoutMargins = array.getDimensionPixelSize(R.styleable.TooltipOverlay_android_layout_margin, 0)
+ array.recycle()
+
+ }
+
+ constructor(context: Context, defStyleAttr: Int, defStyleResId: Int) : super(context, null, defStyleAttr) {
+ init(context, defStyleResId)
+ }
+}
\ No newline at end of file
diff --git a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt
new file mode 100644
index 00000000..ae69aaf1
--- /dev/null
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt
@@ -0,0 +1,248 @@
+package it.sephiroth.android.library.xtooltip
+
+
+import android.animation.*
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.view.animation.AccelerateDecelerateInterpolator
+import it.sephiroth.android.library.tooltip.R
+import timber.log.Timber
+
+/**
+ * Created by alessandro crugnola on 12/12/15.
+ * alessandro.crugnola@gmail.com
+ *
+ *
+ * LICENSE
+ * Copyright 2015 Alessandro Crugnola
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ * is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+@Suppress("SpellCheckingInspection")
+class TooltipOverlayDrawable(context: Context, defStyleResId: Int) : Drawable() {
+ private val mOuterPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val mInnerPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private var mOuterRadius: Float = 0.toFloat()
+ private var innerRadius = 0f
+ set(rippleRadius) {
+ field = rippleRadius
+ invalidateSelf()
+ }
+ private val mFirstAnimatorSet: AnimatorSet
+ private val mSecondAnimatorSet: AnimatorSet
+ private val mFirstAnimator: ValueAnimator
+ private val mSecondAnimator: ValueAnimator
+ private var mRepeatIndex: Int = 0
+ private var mStarted: Boolean = false
+ private val mOuterAlpha: Int
+ private val mInnerAlpha: Int
+ private var mRepeatCount = 1
+ private var mDuration: Long = 400
+
+ private var outerAlpha: Int
+ get() = mOuterPaint.alpha
+ set(value) {
+ mOuterPaint.alpha = value
+ invalidateSelf()
+ }
+
+ private var innerAlpha: Int
+ get() = mInnerPaint.alpha
+ set(value) {
+ mInnerPaint.alpha = value
+ invalidateSelf()
+ }
+
+ private var outerRadius: Float
+ get() = mOuterRadius
+ set(value) {
+ mOuterRadius = value
+ invalidateSelf()
+ }
+
+ init {
+ mOuterPaint.style = Paint.Style.FILL
+ mInnerPaint.style = Paint.Style.FILL
+
+ val array = context.theme.obtainStyledAttributes(defStyleResId, R.styleable.TooltipOverlay)
+
+ for (i in 0 until array.indexCount) {
+ val index = array.getIndex(i)
+
+ when (index) {
+ R.styleable.TooltipOverlay_android_color -> {
+ val color = array.getColor(index, 0)
+ mOuterPaint.color = color
+ mInnerPaint.color = color
+
+ }
+ R.styleable.TooltipOverlay_ttlm_repeatCount -> mRepeatCount = array.getInt(index, 1)
+ R.styleable.TooltipOverlay_android_alpha -> {
+ val alpha = (array.getFloat(index, mInnerPaint.alpha / ALPHA_MAX) * 255).toInt()
+ mInnerPaint.alpha = alpha
+ mOuterPaint.alpha = alpha
+
+ }
+ R.styleable.TooltipOverlay_ttlm_duration -> mDuration = array.getInt(index, 400).toLong()
+ }
+ }
+
+ array.recycle()
+
+ mOuterAlpha = outerAlpha
+ mInnerAlpha = innerAlpha
+
+ // first
+ var fadeIn: Animator = ObjectAnimator.ofInt(this, "outerAlpha", 0, mOuterAlpha)
+ fadeIn.duration = (mDuration * FADEIN_DURATION).toLong()
+
+ var fadeOut: Animator = ObjectAnimator.ofInt(this, "outerAlpha", mOuterAlpha, 0, 0)
+ fadeOut.startDelay = (mDuration * FADEOUT_START_DELAY).toLong()
+ fadeOut.duration = (mDuration * (1.0 - FADEOUT_START_DELAY)).toLong()
+
+ mFirstAnimator = ObjectAnimator.ofFloat(this, "outerRadius", 0f, 1f)
+ mFirstAnimator.duration = mDuration
+
+ mFirstAnimatorSet = AnimatorSet()
+ mFirstAnimatorSet.playTogether(fadeIn, mFirstAnimator, fadeOut)
+ mFirstAnimatorSet.interpolator = AccelerateDecelerateInterpolator()
+ mFirstAnimatorSet.duration = mDuration
+
+ // second
+ fadeIn = ObjectAnimator.ofInt(this, "innerAlpha", 0, mInnerAlpha)
+ fadeIn.duration = (mDuration * FADEIN_DURATION).toLong()
+
+ fadeOut = ObjectAnimator.ofInt(this, "innerAlpha", mInnerAlpha, 0, 0)
+ fadeOut.setStartDelay((mDuration * FADEOUT_START_DELAY).toLong())
+ fadeOut.duration = (mDuration * (1.0 - FADEOUT_START_DELAY)).toLong()
+
+ mSecondAnimator = ObjectAnimator.ofFloat(this, "innerRadius", 0f, 1f)
+ mSecondAnimator.duration = mDuration
+
+ mSecondAnimatorSet = AnimatorSet()
+ mSecondAnimatorSet.playTogether(fadeIn, mSecondAnimator, fadeOut)
+ mSecondAnimatorSet.interpolator = AccelerateDecelerateInterpolator()
+ mSecondAnimatorSet.startDelay = (mDuration * SECOND_ANIM_START_DELAY).toLong()
+ mSecondAnimatorSet.duration = mDuration
+
+ mFirstAnimatorSet.addListener(object : AnimatorListenerAdapter() {
+ var cancelled: Boolean = false
+
+ override fun onAnimationCancel(animation: Animator) {
+ super.onAnimationCancel(animation)
+ cancelled = true
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ if (!cancelled && isVisible && ++mRepeatIndex < mRepeatCount) {
+ mFirstAnimatorSet.start()
+ }
+ }
+ })
+
+ mSecondAnimatorSet.addListener(object : AnimatorListenerAdapter() {
+ var cancelled: Boolean = false
+
+ override fun onAnimationCancel(animation: Animator) {
+ super.onAnimationCancel(animation)
+ cancelled = true
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ if (!cancelled && isVisible && mRepeatIndex < mRepeatCount) {
+ mSecondAnimatorSet.startDelay = 0
+ mSecondAnimatorSet.start()
+ }
+ }
+ })
+
+ }
+
+ override fun draw(canvas: Canvas) {
+ val bounds = bounds
+ val centerX = bounds.width() / 2
+ val centerY = bounds.height() / 2
+ canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), mOuterRadius, mOuterPaint)
+ canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), innerRadius, mInnerPaint)
+
+ }
+
+ override fun setAlpha(i: Int) {}
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+ override fun setVisible(visible: Boolean, restart: Boolean): Boolean {
+ val changed = isVisible != visible
+
+ if (visible) {
+ if (restart || !mStarted) {
+ replay()
+ }
+ } else {
+ stop()
+ }
+
+ return changed
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.TRANSLUCENT
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ Timber.i("onBoundsChange: $bounds")
+ super.onBoundsChange(bounds)
+ mOuterRadius = (Math.min(bounds.width(), bounds.height()) / 2).toFloat()
+ mFirstAnimator.setFloatValues(0f, mOuterRadius)
+ mSecondAnimator.setFloatValues(0f, mOuterRadius)
+ }
+
+ override fun getIntrinsicWidth(): Int {
+ return 96
+ }
+
+ override fun getIntrinsicHeight(): Int {
+ return 96
+ }
+
+ private fun play() {
+ mRepeatIndex = 0
+ mStarted = true
+ mFirstAnimatorSet.start()
+ mSecondAnimatorSet.startDelay = (mDuration * SECOND_ANIM_START_DELAY).toLong()
+ mSecondAnimatorSet.start()
+ }
+
+ private fun replay() {
+ stop()
+ play()
+ }
+
+ private fun stop() {
+ mFirstAnimatorSet.cancel()
+ mSecondAnimatorSet.cancel()
+
+ mRepeatIndex = 0
+ mStarted = false
+
+ innerRadius = 0f
+ outerRadius = 0f
+ }
+
+ @Suppress("SpellCheckingInspection")
+ companion object {
+ const val ALPHA_MAX = 255f
+ const val FADEOUT_START_DELAY = 0.55
+ const val FADEIN_DURATION = 0.3
+ const val SECOND_ANIM_START_DELAY = 0.25
+ }
+}
diff --git a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt
new file mode 100644
index 00000000..f6a17cd2
--- /dev/null
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt
@@ -0,0 +1,274 @@
+package it.sephiroth.android.library.xtooltip
+
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.support.v4.util.ObjectsCompat
+import it.sephiroth.android.library.tooltip.R
+import timber.log.Timber
+
+/**
+ * Created by alessandro crugnola on 12/12/15.
+ * alessandro.crugnola@gmail.com
+ *
+ *
+ * LICENSE
+ * Copyright 2015 Alessandro Crugnola
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ * is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+internal class TooltipTextDrawable(context: Context, builder: XTooltip.Builder) : Drawable() {
+ private val rectF: RectF
+ private val path: Path
+ private val tmpPoint = Point()
+ private val outlineRect = Rect()
+ private val bgPaint: Paint?
+ private val stPaint: Paint?
+ private val arrowRatio: Float
+ private val radius: Float
+ private var point: Point? = null
+ private var padding = 0
+ private var arrowWeight = 0
+ private var gravity: XTooltip.Gravity? = null
+
+ init {
+
+ val theme = context.theme.obtainStyledAttributes(
+ null,
+ R.styleable.TooltipLayout,
+ builder.defStyleAttr,
+ builder.defStyleRes
+ )
+ this.radius = theme.getDimensionPixelSize(R.styleable.TooltipLayout_ttlm_cornerRadius, 4).toFloat()
+ val strokeWidth = theme.getDimensionPixelSize(R.styleable.TooltipLayout_ttlm_strokeWeight, 2)
+ val backgroundColor = theme.getColor(R.styleable.TooltipLayout_ttlm_backgroundColor, 0)
+ val strokeColor = theme.getColor(R.styleable.TooltipLayout_ttlm_strokeColor, 0)
+ this.arrowRatio = theme.getFloat(R.styleable.TooltipLayout_ttlm_arrowRatio, ARROW_RATIO_DEFAULT)
+ theme.recycle()
+
+ this.rectF = RectF()
+
+ if (backgroundColor != 0) {
+ bgPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ bgPaint.color = backgroundColor
+ bgPaint.style = Paint.Style.FILL
+ } else {
+ bgPaint = null
+ }
+
+ if (strokeColor != 0) {
+ stPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ stPaint.color = strokeColor
+ stPaint.style = Paint.Style.STROKE
+ stPaint.strokeWidth = strokeWidth.toFloat()
+ } else {
+ stPaint = null
+ }
+
+ path = Path()
+ }
+
+ override fun draw(canvas: Canvas) {
+ bgPaint?.let {
+ canvas.drawPath(path, it)
+ }
+
+ stPaint?.let {
+ canvas.drawPath(path, it)
+ }
+ }
+
+ fun setAnchor(gravity: XTooltip.Gravity, padding: Int, point: Point?) {
+ if (gravity !== this.gravity || padding != this.padding || !ObjectsCompat.equals(this.point, point)) {
+ this.gravity = gravity
+ this.padding = padding
+ this.arrowWeight = (padding.toFloat() / arrowRatio).toInt()
+
+ point?.let {
+ this.point = Point(it)
+ } ?: run {
+ this.point = null
+ }
+
+ if (!bounds.isEmpty) {
+ calculatePath(bounds)
+ invalidateSelf()
+ }
+ }
+ }
+
+ private fun calculatePath(outBounds: Rect) {
+ val left = outBounds.left + padding
+ val top = outBounds.top + padding
+ val right = outBounds.right - padding
+ val bottom = outBounds.bottom - padding
+
+ val maxY = bottom - radius
+ val maxX = right - radius
+ val minY = top + radius
+ val minX = left + radius
+
+ if (null != point && null != gravity) {
+ calculatePathWithGravity(outBounds, left, top, right, bottom, maxY, maxX, minY, minX)
+ } else {
+ rectF.set(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
+ path.addRoundRect(rectF, radius, radius, Path.Direction.CW)
+ }
+ }
+
+ private fun calculatePathWithGravity(
+ outBounds: Rect,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ maxY: Float,
+ maxX: Float,
+ minY: Float,
+ minX: Float
+ ) {
+ val drawPoint =
+ isDrawPoint(left, top, right, bottom, maxY, maxX, minY, minX, tmpPoint, point!!, gravity, arrowWeight)
+
+ Timber.v("drawPoint: $drawPoint")
+
+ clampPoint(left, top, right, bottom, tmpPoint)
+
+ path.reset()
+
+ // top/left
+ path.moveTo(left + radius, top.toFloat())
+
+ if (drawPoint && gravity === XTooltip.Gravity.BOTTOM) {
+ path.lineTo((left + tmpPoint.x - arrowWeight).toFloat(), top.toFloat())
+ path.lineTo((left + tmpPoint.x).toFloat(), outBounds.top.toFloat())
+ path.lineTo((left + tmpPoint.x + arrowWeight).toFloat(), top.toFloat())
+ }
+
+ // top/right
+ path.lineTo(right - radius, top.toFloat())
+ path.quadTo(right.toFloat(), top.toFloat(), right.toFloat(), top + radius)
+
+ if (drawPoint && gravity === XTooltip.Gravity.LEFT) {
+ path.lineTo(right.toFloat(), (top + tmpPoint.y - arrowWeight).toFloat())
+ path.lineTo(outBounds.right.toFloat(), (top + tmpPoint.y).toFloat())
+ path.lineTo(right.toFloat(), (top + tmpPoint.y + arrowWeight).toFloat())
+ }
+
+ // bottom/right
+ path.lineTo(right.toFloat(), bottom - radius)
+ path.quadTo(right.toFloat(), bottom.toFloat(), right - radius, bottom.toFloat())
+
+ if (drawPoint && gravity === XTooltip.Gravity.TOP) {
+ path.lineTo((left + tmpPoint.x + arrowWeight).toFloat(), bottom.toFloat())
+ path.lineTo((left + tmpPoint.x).toFloat(), outBounds.bottom.toFloat())
+ path.lineTo((left + tmpPoint.x - arrowWeight).toFloat(), bottom.toFloat())
+ }
+
+ // bottom/left
+ path.lineTo(left + radius, bottom.toFloat())
+ path.quadTo(left.toFloat(), bottom.toFloat(), left.toFloat(), bottom - radius)
+
+ if (drawPoint && gravity === XTooltip.Gravity.RIGHT) {
+ path.lineTo(left.toFloat(), (top + tmpPoint.y + arrowWeight).toFloat())
+ path.lineTo(outBounds.left.toFloat(), (top + tmpPoint.y).toFloat())
+ path.lineTo(left.toFloat(), (top + tmpPoint.y - arrowWeight).toFloat())
+ }
+
+ // top/left
+ path.lineTo(left.toFloat(), top + radius)
+ path.quadTo(left.toFloat(), top.toFloat(), left + radius, top.toFloat())
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ calculatePath(bounds)
+ }
+
+ override fun getAlpha(): Int {
+ return bgPaint?.alpha ?: run { 0 }
+ }
+
+ override fun setAlpha(alpha: Int) {
+ bgPaint?.alpha = alpha
+ stPaint?.alpha = alpha
+ }
+
+ override fun setColorFilter(cf: ColorFilter?) {}
+
+ override fun getOpacity(): Int {
+ return PixelFormat.TRANSLUCENT
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ override fun getOutline(outline: Outline) {
+ copyBounds(outlineRect)
+ outlineRect.inset(padding, padding)
+ outline.setRoundRect(outlineRect, radius)
+ if (alpha < 255) {
+ outline.alpha = 0f
+ }
+ //outline.setAlpha(getAlpha() / ALPHA_MAX);
+ }
+
+ companion object {
+ const val ARROW_RATIO_DEFAULT = 1.4f
+
+ private fun isDrawPoint(
+ left: Int, top: Int, right: Int, bottom: Int, maxY: Float, maxX: Float, minY: Float,
+ minX: Float, tmpPoint: Point, point: Point, gravity: XTooltip.Gravity?,
+ arrowWeight: Int
+ ): Boolean {
+ Timber.i("isDrawPoint: $left, $top, $right, $bottom, $maxX, $maxY, $minX, $minY, $point, $arrowWeight")
+ var drawPoint = false
+ tmpPoint.set(point.x, point.y)
+
+ if (gravity === XTooltip.Gravity.RIGHT || gravity === XTooltip.Gravity.LEFT) {
+ if (tmpPoint.y in top..bottom) {
+ if (top + tmpPoint.y + arrowWeight > maxY) {
+ tmpPoint.y = (maxY - arrowWeight.toFloat() - top.toFloat()).toInt()
+ } else if (top + tmpPoint.y - arrowWeight < minY) {
+ tmpPoint.y = (minY + arrowWeight - top).toInt()
+ }
+ drawPoint = true
+ }
+ } else {
+ if (tmpPoint.x in left..right) {
+ if (tmpPoint.x in left..right) {
+ if (left + tmpPoint.x + arrowWeight > maxX) {
+ tmpPoint.x = (maxX - arrowWeight.toFloat() - left.toFloat()).toInt()
+ } else if (left + tmpPoint.x - arrowWeight < minX) {
+ tmpPoint.x = (minX + arrowWeight - left).toInt()
+ }
+ drawPoint = true
+ }
+ }
+ }
+ return drawPoint
+ }
+
+ private fun clampPoint(left: Int, top: Int, right: Int, bottom: Int, tmpPoint: Point) {
+ if (tmpPoint.y < top) {
+ tmpPoint.y = top
+ } else if (tmpPoint.y > bottom) {
+ tmpPoint.y = bottom
+ }
+ if (tmpPoint.x < left) {
+ tmpPoint.x = left
+ }
+ if (tmpPoint.x > right) {
+ tmpPoint.x = right
+ }
+ }
+ }
+}
diff --git a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt
new file mode 100644
index 00000000..c54e7705
--- /dev/null
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt
@@ -0,0 +1,43 @@
+package it.sephiroth.android.library.xtooltip
+
+import android.content.Context
+import android.graphics.Typeface
+import timber.log.Timber
+import java.util.*
+
+/**
+ * Created by alessandro crugnola on 12/12/15.
+ * alessandro.crugnola@gmail.com
+ *
+ *
+ * LICENSE
+ * Copyright 2015 Alessandro Crugnola
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ * is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+object Typefaces {
+ private val FONT_CACHE = Hashtable()
+
+ operator fun get(c: Context, assetPath: String): Typeface? {
+ synchronized(FONT_CACHE) {
+ if (!FONT_CACHE.containsKey(assetPath)) {
+ try {
+ val t = Typeface.createFromAsset(c.assets, assetPath)
+ FONT_CACHE[assetPath] = t
+ } catch (e: Exception) {
+ Timber.e("Could not get typeface '$assetPath' because ${e.message}")
+ return null
+ }
+
+ }
+ return FONT_CACHE[assetPath]
+ }
+ }
+}
\ No newline at end of file
diff --git a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt
new file mode 100644
index 00000000..7c1549a4
--- /dev/null
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt
@@ -0,0 +1,107 @@
+package it.sephiroth.android.library.xtooltip
+
+import android.animation.Animator
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewPropertyAnimator
+
+/**
+ * Created by alessandro crugnola on 12/12/15.
+ * alessandro.crugnola@gmail.com
+ *
+ *
+ * LICENSE
+ * Copyright 2015 Alessandro Crugnola
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ * is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+internal inline fun Rect.rectContainsWithTolerance(childRect: Rect, t: Int): Boolean {
+ return this.contains(childRect.left + t, childRect.top + t, childRect.right - t, childRect.bottom - t)
+}
+
+internal inline fun View.addOnAttachStateChangeListener(func: AttachStateChangeListener.() -> Unit): View {
+ val listener = AttachStateChangeListener()
+ listener.func()
+ addOnAttachStateChangeListener(listener)
+ return this
+}
+
+internal class AttachStateChangeListener : View.OnAttachStateChangeListener {
+
+ private var _onViewAttachedToWindow: ((view: View?, listener: View.OnAttachStateChangeListener) -> Unit)? = null
+ private var _onViewDetachedFromWindow: ((view: View?, listener: View.OnAttachStateChangeListener) -> Unit)? = null
+
+ fun onViewDetachedFromWindow(func: (view: View?, listener: View.OnAttachStateChangeListener) -> Unit) {
+ _onViewDetachedFromWindow = func
+ }
+
+ fun onViewAttachedToWindow(func: (view: View?, listener: View.OnAttachStateChangeListener) -> Unit) {
+ _onViewAttachedToWindow = func
+ }
+
+ override fun onViewDetachedFromWindow(v: View?) {
+ _onViewDetachedFromWindow?.invoke(v, this)
+ }
+
+ override fun onViewAttachedToWindow(v: View?) {
+ _onViewAttachedToWindow?.invoke(v, this)
+ }
+}
+
+internal inline fun ViewPropertyAnimator.setListener(
+ func: AnimationListener.() -> Unit
+ ): ViewPropertyAnimator {
+ val listener = AnimationListener()
+ listener.func()
+ setListener(listener)
+ return this
+}
+
+@Suppress("unused")
+internal class AnimationListener : Animator.AnimatorListener {
+ private var _onAnimationRepeat: ((animation: Animator) -> Unit)? = null
+ private var _onAnimationEnd: ((animation: Animator) -> Unit)? = null
+ private var _onAnimationStart: ((animation: Animator) -> Unit)? = null
+ private var _onAnimationCancel: ((animation: Animator) -> Unit)? = null
+
+ override fun onAnimationRepeat(animation: Animator) {
+ _onAnimationRepeat?.invoke(animation)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ _onAnimationCancel?.invoke(animation)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ _onAnimationEnd?.invoke(animation)
+ }
+
+ override fun onAnimationStart(animation: Animator) {
+ _onAnimationStart?.invoke(animation)
+ }
+
+ fun onAnimationRepeat(func: (animation: Animator) -> Unit) {
+ _onAnimationRepeat = func
+ }
+
+ fun onAnimationCancel(func: (animation: Animator) -> Unit) {
+ _onAnimationCancel = func
+ }
+
+ fun onAnimationEnd(func: (animation: Animator) -> Unit) {
+ _onAnimationEnd = func
+ }
+
+
+ fun onAnimationStart(func: (animation: Animator) -> Unit) {
+ _onAnimationStart = func
+ }
+
+}
\ No newline at end of file
diff --git a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt
new file mode 100644
index 00000000..09cf7cdf
--- /dev/null
+++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt
@@ -0,0 +1,933 @@
+package it.sephiroth.android.library.xtooltip
+
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.os.Build
+import android.os.Handler
+import android.os.IBinder
+import android.support.annotation.*
+import android.text.Html
+import android.text.Spannable
+import android.view.*
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.widget.FrameLayout
+import android.widget.PopupWindow.INPUT_METHOD_NOT_NEEDED
+import android.widget.TextView
+import it.sephiroth.android.library.tooltip.R
+import timber.log.Timber
+import java.lang.ref.WeakReference
+import java.util.*
+
+/**
+ * Created by alessandro crugnola on 12/12/15.
+ * alessandro.crugnola@gmail.com
+ *
+ *
+ * LICENSE
+ * Copyright 2015 Alessandro Crugnola
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ * is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+class XTooltip private constructor(private val context: Context, builder: Builder) {
+
+ private val windowManager: WindowManager =
+ context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ var isShowing = false
+ private set
+
+ private val mGravities = Gravity.values().filter { it != Gravity.CENTER }
+ private var isVisible = false
+ private val mSizeTolerance = context.resources.displayMetrics.density * 10
+
+ private val mLayoutInsetDecor = true
+ private val mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
+ private val mSoftInputMode = INPUT_METHOD_NOT_NEEDED
+ private val mHandler = Handler()
+
+ private var mPopupView: TooltipViewContainer? = null
+ private var mText: CharSequence?
+ private var mAnchorPoint: Point
+ private var mShowArrow: Boolean
+ private var mPadding: Int = 0
+ private var mActivateDelay: Long
+ private var mClosePolicy: ClosePolicy
+ private var mFadeDuration: Long
+ private var mShowDuration: Long
+ private var mMaxWidth: Int? = null
+ private var mTextAppearance: Int
+ private var mTextGravity: Int
+ private var mTextViewElevation: Float
+ private var mTypeface: Typeface? = null
+ private var mIsCustomView: Boolean = false
+ private var mTooltipLayoutIdRes = R.layout.textview
+ private var mTextViewIdRes = android.R.id.text1
+ private var mFloatingAnimation: Animation?
+ private var mAnimator: ValueAnimator? = null
+ private var mShowOverlay: Boolean
+ private var mOverlayStyle: Int
+ private var mActivated = false
+ private var mHasAnchorView = false
+ private var mFollowAnchor = false
+
+ private var mViewOverlay: TooltipOverlay? = null
+ private var mDrawable: TooltipTextDrawable? = null
+ private var mAnchorView: WeakReference? = null
+ private lateinit var mContentView: View
+ private lateinit var mTextView: TextView
+
+ private val hideRunnable = Runnable { hide() }
+ private val activateRunnable = Runnable { mActivated = true }
+
+ var contentView: View? = null
+ get() = mContentView
+ private set
+
+ private var predrawListener = ViewTreeObserver.OnPreDrawListener {
+ if (mHasAnchorView && null != mAnchorView?.get()) {
+ val view = mAnchorView?.get()!!
+ if (!view.viewTreeObserver.isAlive) {
+ removeListeners(view)
+ } else {
+ if (isShowing && null != mPopupView) {
+ view.getLocationOnScreen(mNewLocation)
+
+ if (mOldLocation == null) {
+ mOldLocation = intArrayOf(mNewLocation[0], mNewLocation[1])
+ }
+
+ if (mOldLocation!![0] != mNewLocation[1] || mOldLocation!![1] != mNewLocation[1]) {
+ offsetBy(
+ mNewLocation[0] - mOldLocation!![0],
+ mNewLocation[1] - mOldLocation!![1]
+ )
+ }
+ }
+ }
+ }
+ true
+ }
+
+ init {
+ val theme = context.theme
+ .obtainStyledAttributes(
+ null,
+ R.styleable.TooltipLayout,
+ builder.defStyleAttr,
+ builder.defStyleRes
+ )
+ this.mPadding = theme.getDimensionPixelSize(R.styleable.TooltipLayout_ttlm_padding, 30)
+ this.mTextAppearance =
+ theme.getResourceId(R.styleable.TooltipLayout_android_textAppearance, 0)
+ this.mTextGravity = theme
+ .getInt(
+ R.styleable.TooltipLayout_android_gravity,
+ android.view.Gravity.TOP or android.view.Gravity.START
+ )
+ this.mTextViewElevation = theme.getDimension(R.styleable.TooltipLayout_ttlm_elevation, 0f)
+ mOverlayStyle =
+ theme.getResourceId(
+ R.styleable.TooltipLayout_ttlm_overlayStyle,
+ R.style.ToolTipOverlayDefaultStyle
+ )
+ val font = theme.getString(R.styleable.TooltipLayout_ttlm_font)
+ theme.recycle()
+
+ this.mText = builder.text
+ this.mActivateDelay = builder.activateDelay
+ this.mAnchorPoint = builder.point!!
+ this.mClosePolicy = builder.closePolicy
+ this.mMaxWidth = builder.maxWidth
+ this.mFloatingAnimation = builder.floatingAnimation
+ this.mShowDuration = builder.showDuration
+ this.mFadeDuration = builder.fadeDuration
+ this.mShowOverlay = builder.overlay
+ this.mShowArrow = builder.showArrow && builder.layoutId == null
+ builder.anchorView?.let {
+ this.mAnchorView = WeakReference(it)
+ this.mHasAnchorView = true
+ this.mFollowAnchor = builder.followAnchor
+ }
+
+ builder.layoutId?.let {
+ mTextViewIdRes = builder.textId!!
+ mTooltipLayoutIdRes = builder.layoutId!!
+ mIsCustomView = true
+ } ?: run {
+ mDrawable = TooltipTextDrawable(context, builder)
+ }
+
+ builder.typeface?.let {
+ mTypeface = it
+ } ?: run {
+ font?.let { mTypeface = Typefaces[context, it] }
+ }
+ }
+
+ private var mFailureFunc: ((tooltip: XTooltip) -> Unit)? = null
+ private var mShownFunc: ((tooltip: XTooltip) -> Unit)? = null
+ private var mHiddenFunc: ((tooltip: XTooltip) -> Unit)? = null
+
+ @Suppress("UNUSED")
+ fun doOnFailure(func: ((tooltip: XTooltip) -> Unit)?): XTooltip {
+ mFailureFunc = func
+ return this
+ }
+
+ @Suppress("UNUSED")
+ fun doOnShown(func: ((tooltip: XTooltip) -> Unit)?): XTooltip {
+ mShownFunc = func
+ return this
+ }
+
+ @Suppress("UNUSED")
+ fun doOnHidden(func: ((tooltip: XTooltip) -> Unit)?): XTooltip {
+ mHiddenFunc = func
+ return this
+ }
+
+ @SuppressLint("RtlHardcoded")
+ private fun createPopupLayoutParams(token: IBinder): WindowManager.LayoutParams {
+ val p = WindowManager.LayoutParams()
+ p.gravity = android.view.Gravity.LEFT or android.view.Gravity.TOP
+ p.width = WindowManager.LayoutParams.MATCH_PARENT
+ p.height = WindowManager.LayoutParams.MATCH_PARENT
+ p.format = PixelFormat.TRANSLUCENT
+ p.flags = computeFlags(p.flags)
+ p.type = mWindowLayoutType
+ p.token = token
+ p.softInputMode = mSoftInputMode
+ p.title = "ToolTip:" + Integer.toHexString(hashCode())
+ return p
+ }
+
+
+ private fun computeFlags(curFlags: Int): Int {
+ var curFlags1 = curFlags
+ curFlags1 = curFlags1 or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+
+ curFlags1 = if (mClosePolicy.inside() || mClosePolicy.outside()) {
+ curFlags1 and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()
+ } else {
+ curFlags1 or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ }
+
+ if (!mClosePolicy.consume()) {
+ curFlags1 = curFlags1 or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ }
+ curFlags1 = curFlags1 or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ curFlags1 = curFlags1 or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ curFlags1 = curFlags1 or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ curFlags1 = curFlags1 or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ // curFlags1 = curFlags1 or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ return curFlags1
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ private fun preparePopup(params: WindowManager.LayoutParams, gravity: Gravity) {
+ mPopupView?.let {
+ if (mViewOverlay != null && gravity == Gravity.CENTER) {
+ it.removeView(mViewOverlay)
+ mViewOverlay = null
+ }
+ } ?: run {
+ val viewContainer = TooltipViewContainer(context)
+
+ if (mShowOverlay && mViewOverlay == null) {
+ mViewOverlay = TooltipOverlay(context, 0, mOverlayStyle)
+ with(mViewOverlay!!) {
+ adjustViewBounds = true
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+ }
+
+ val contentView =
+ LayoutInflater.from(context).inflate(mTooltipLayoutIdRes, viewContainer, false)
+
+ mFloatingAnimation?.let { contentView.setPadding(it.radius, it.radius, it.radius, it.radius) }
+
+ mTextView = contentView.findViewById(mTextViewIdRes)
+
+ with(mTextView) {
+ mDrawable?.let { background = it }
+
+ if (mShowArrow)
+ setPadding(mPadding, mPadding, mPadding, mPadding)
+ else
+ setPadding(mPadding / 2, mPadding / 2, mPadding / 2, mPadding / 2)
+
+ if (mTextAppearance != 0) {
+ @Suppress("DEPRECATION")
+ setTextAppearance(context, mTextAppearance)
+ }
+
+ if (!mIsCustomView && mTextViewElevation > 0 && Build.VERSION.SDK_INT >= 21) {
+ elevation = mTextViewElevation
+ translationZ = mTextViewElevation
+ outlineProvider = ViewOutlineProvider.BACKGROUND
+ }
+ this.gravity = mTextGravity
+
+ text = if (mText is Spannable) {
+ mText
+ } else {
+ @Suppress("DEPRECATION")
+ Html.fromHtml(this@XTooltip.mText as String)
+ }
+
+ mMaxWidth?.let { maxWidth = it }
+ mTypeface?.let { typeface = it }
+ }
+
+ if (null != mViewOverlay) {
+ viewContainer.addView(
+ mViewOverlay,
+ FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+ )
+ }
+
+ viewContainer.addView(contentView, FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT))
+ viewContainer.measureAllChildren = true
+ viewContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+
+ Timber.i("viewContainer size: ${viewContainer.measuredWidth}, ${viewContainer.measuredHeight}")
+ Timber.i("contentView size: ${contentView.measuredWidth}, ${contentView.measuredHeight}")
+
+ mTextView.addOnAttachStateChangeListener {
+ onViewAttachedToWindow { _: View?, _: View.OnAttachStateChangeListener ->
+ mAnimator?.start()
+
+ if (mShowDuration > 0) {
+ mHandler.removeCallbacks(hideRunnable)
+ mHandler.postDelayed(hideRunnable, mShowDuration)
+ }
+
+ mHandler.removeCallbacks(activateRunnable)
+ mHandler.postDelayed(activateRunnable, mActivateDelay)
+ }
+
+ onViewDetachedFromWindow { view: View?, listener: View.OnAttachStateChangeListener ->
+ view?.removeOnAttachStateChangeListener(listener)
+ mAnimator?.cancel()
+ removeCallbacks()
+ }
+ }
+
+ mContentView = contentView
+ mPopupView = viewContainer
+ }
+ }
+
+ private fun findPosition(
+ parent: View,
+ anchor: View?,
+ offset: Point,
+ gravities: ArrayList,
+ params: WindowManager.LayoutParams,
+ fitToScreen: Boolean = false
+ ): Positions? {
+
+ if (null == mPopupView) return null
+ if (gravities.isEmpty()) return null
+
+ val gravity = gravities.removeAt(0)
+
+ Timber.i("findPosition. $gravity, offset: $offset")
+
+ val displayFrame = Rect()
+ val anchorPosition = intArrayOf(0, 0)
+ val centerPosition = Point(offset)
+
+ parent.getWindowVisibleDisplayFrame(displayFrame)
+
+ anchor?.let {
+ anchor.getLocationOnScreen(anchorPosition)
+ centerPosition.x += anchorPosition[0] + anchor.width / 2
+ centerPosition.y += anchorPosition[1] + anchor.height / 2
+
+ when (gravity) {
+ Gravity.LEFT -> {
+ anchorPosition[1] += anchor.height / 2
+ }
+ Gravity.RIGHT -> {
+ anchorPosition[0] += anchor.width
+ anchorPosition[1] += anchor.height / 2
+ }
+ Gravity.TOP -> {
+ anchorPosition[0] += anchor.width / 2
+ }
+ Gravity.BOTTOM -> {
+ anchorPosition[0] += anchor.width / 2
+ anchorPosition[1] += anchor.height
+ }
+ Gravity.CENTER -> {
+ anchorPosition[0] += anchor.width / 2
+ anchorPosition[1] += anchor.height / 2
+ }
+ }
+ }
+
+ anchorPosition[0] += offset.x
+ anchorPosition[1] += offset.y
+
+ Timber.d("anchorPosition: ${anchorPosition[0]}, ${anchorPosition[1]}")
+ Timber.d("centerPosition: $centerPosition")
+ Timber.d("displayFrame: $displayFrame")
+
+ val w: Int = mContentView.measuredWidth
+ val h: Int = mContentView.measuredHeight
+
+ Timber.v("contentView size: $w, $h")
+
+ val contentPosition = Point()
+ val arrowPosition = Point()
+ val radius = (mFloatingAnimation?.radius ?: run { 0 })
+
+ when (gravity) {
+ Gravity.LEFT -> {
+ contentPosition.x = anchorPosition[0] - w
+ contentPosition.y = anchorPosition[1] - h / 2
+ arrowPosition.y = h / 2 - mPadding / 2 - radius
+ }
+ Gravity.TOP -> {
+ contentPosition.x = anchorPosition[0] - w / 2
+ contentPosition.y = anchorPosition[1] - h
+ arrowPosition.x = w / 2 - mPadding / 2 - radius
+ }
+ Gravity.RIGHT -> {
+ contentPosition.x = anchorPosition[0]
+ contentPosition.y = anchorPosition[1] - h / 2
+ arrowPosition.y = h / 2 - mPadding / 2 - radius
+ }
+ Gravity.BOTTOM -> {
+ contentPosition.x = anchorPosition[0] - w / 2
+ contentPosition.y = anchorPosition[1]
+ arrowPosition.x = w / 2 - mPadding / 2 - radius
+ }
+ Gravity.CENTER -> {
+ contentPosition.x = anchorPosition[0] - w / 2
+ contentPosition.y = anchorPosition[1] - h / 2
+ }
+ }
+
+ anchor?.let {
+ // pass
+ } ?: run {
+ mViewOverlay?.let {
+ when (gravity) {
+ Gravity.LEFT -> contentPosition.x -= it.measuredWidth / 2
+ Gravity.RIGHT -> contentPosition.x += it.measuredWidth / 2
+
+ Gravity.TOP -> contentPosition.y -= it.measuredHeight / 2
+ Gravity.BOTTOM -> contentPosition.y += it.measuredHeight / 2
+ Gravity.CENTER -> {
+ }
+ }
+ }
+ }
+
+ Timber.d("arrowPosition: $arrowPosition")
+ Timber.d("centerPosition: $centerPosition")
+ Timber.d("contentPosition: $contentPosition")
+
+ if (fitToScreen) {
+ val finalRect = Rect(
+ contentPosition.x,
+ contentPosition.y,
+ contentPosition.x + w,
+ contentPosition.y + h
+ )
+ if (!displayFrame.rectContainsWithTolerance(finalRect, mSizeTolerance.toInt())) {
+ Timber.e("content won't fit! $displayFrame, $finalRect")
+ return findPosition(parent, anchor, offset, gravities, params, fitToScreen)
+ }
+ }
+
+ return Positions(arrowPosition, centerPosition, contentPosition, gravity, params)
+ }
+
+ private var mCurrentPosition: Positions? = null
+ private var mOldLocation: IntArray? = null
+ private var mNewLocation: IntArray = intArrayOf(0, 0)
+
+ private fun invokePopup(positions: Positions?): XTooltip? {
+ positions?.let {
+ isShowing = true
+ mCurrentPosition = positions
+
+ setupAnimation(positions.gravity)
+
+ if (mHasAnchorView && mAnchorView?.get() != null) {
+ setupListeners(mAnchorView!!.get()!!)
+ }
+
+ mDrawable?.setAnchor(
+ it.gravity,
+ if (!mShowArrow) 0 else mPadding / 2,
+ if (!mShowArrow) null else it.arrowPoint
+ )
+
+ offsetBy(0, 0)
+
+ it.params.packageName = context.packageName
+ mPopupView?.fitsSystemWindows = mLayoutInsetDecor
+ windowManager.addView(mPopupView, it.params)
+ Timber.v("windowManager.addView: $mPopupView")
+ fadeIn(mFadeDuration)
+ return this
+ } ?: run {
+ mFailureFunc?.invoke(this)
+ return null
+ }
+ }
+
+ private fun offsetBy(xoff: Int, yoff: Int) {
+ if (isShowing && mPopupView != null && mCurrentPosition != null) {
+ Timber.i("offsetBy($xoff, $yoff)")
+ mContentView.translationX = mCurrentPosition!!.contentPoint.x.toFloat() + xoff
+ mContentView.translationY = mCurrentPosition!!.contentPoint.y.toFloat() + yoff
+
+ mViewOverlay?.let { viewOverlay ->
+ viewOverlay.translationX = mCurrentPosition!!.centerPoint.x.toFloat() - viewOverlay.measuredWidth /
+ 2 + xoff
+ viewOverlay.translationY = mCurrentPosition!!.centerPoint.y.toFloat() - viewOverlay.measuredHeight /
+ 2 + yoff
+ }
+ }
+ }
+
+ private fun setupListeners(anchorView: View) {
+ anchorView.addOnAttachStateChangeListener {
+ onViewDetachedFromWindow { view: View?, listener: View.OnAttachStateChangeListener ->
+ Timber.i("anchorView detached from parent")
+ view?.removeOnAttachStateChangeListener(listener)
+ dismiss()
+ }
+ }
+
+ if (mFollowAnchor) {
+ anchorView.viewTreeObserver.addOnPreDrawListener(predrawListener)
+ }
+ }
+
+ private fun removeListeners(anchorView: View?) {
+ if (mFollowAnchor) {
+ anchorView?.viewTreeObserver?.removeOnPreDrawListener(predrawListener)
+ }
+ }
+
+ private fun setupAnimation(gravity: Gravity) {
+ if (mTextView === mContentView || null == mFloatingAnimation) {
+ return
+ }
+
+ val endValue = mFloatingAnimation!!.radius
+ val duration = mFloatingAnimation!!.duration
+
+ val direction: Int = if (mFloatingAnimation!!.direction == 0) {
+ if (gravity === Gravity.TOP || gravity === Gravity.BOTTOM) 2 else 1
+ } else {
+ mFloatingAnimation!!.direction
+ }
+
+ val property = if (direction == 2) "translationY" else "translationX"
+ mAnimator =
+ ObjectAnimator.ofFloat(mTextView, property, -endValue.toFloat(), endValue.toFloat())
+ mAnimator!!.run {
+ setDuration(duration)
+ interpolator = AccelerateDecelerateInterpolator()
+ repeatCount = ValueAnimator.INFINITE
+ repeatMode = ValueAnimator.REVERSE
+ }
+ }
+
+ fun show(parent: View, gravity: Gravity, fitToScreen: Boolean = false) {
+ if (isShowing || (mHasAnchorView && mAnchorView?.get() == null)) return
+
+ isVisible = false
+
+ val params = createPopupLayoutParams(parent.windowToken)
+ preparePopup(params, gravity)
+
+ val gravities = mGravities.toCollection(ArrayList())
+ gravities.remove(gravity)
+ gravities.add(0, gravity)
+
+ invokePopup(
+ findPosition(
+ parent,
+ mAnchorView?.get(),
+ mAnchorPoint,
+ gravities,
+ params,
+ fitToScreen
+ )
+ )
+ }
+
+ fun hide() {
+ Timber.i("hide")
+ if (!isShowing) return
+ fadeOut(mFadeDuration)
+ }
+
+ fun dismiss() {
+ if (isShowing && mPopupView != null) {
+ removeListeners(mAnchorView?.get())
+ removeCallbacks()
+ windowManager.removeView(mPopupView)
+ Timber.v("dismiss: $mPopupView")
+ mPopupView = null
+ isShowing = false
+ isVisible = false
+
+ mHiddenFunc?.invoke(this)
+ }
+ }
+
+ private fun removeCallbacks() {
+ mHandler.removeCallbacks(hideRunnable)
+ mHandler.removeCallbacks(activateRunnable)
+ }
+
+ private fun fadeIn(fadeDuration: Long) {
+ if (!isShowing || isVisible) return
+
+ isVisible = true
+
+ if (fadeDuration > 0 && null != mPopupView) {
+ mPopupView!!.alpha = 0F
+ mPopupView!!.animate()
+ .setDuration(mFadeDuration)
+ .alpha(1f).start()
+ }
+ mShownFunc?.invoke(this)
+ }
+
+ private fun fadeOut(fadeDuration: Long) {
+ if (!isShowing || !isVisible) return
+
+ isVisible = false
+ removeCallbacks()
+
+ Timber.i("fadeOut($fadeDuration)")
+
+ if (fadeDuration > 0) {
+ mPopupView?.let { popupView ->
+ popupView.clearAnimation()
+ popupView.animate()
+ .alpha(0f)
+ .setDuration(fadeDuration)
+ .setListener {
+ onAnimationEnd {
+ popupView.visibility = View.INVISIBLE
+ dismiss()
+ }
+ }
+ .start()
+ }
+ } else {
+ dismiss()
+ }
+ }
+
+ inner class TooltipViewContainer(context: Context) : FrameLayout(context) {
+
+ init {
+ clipChildren = false
+ clipToPadding = false
+ }
+
+ private var sizeChange: ((w: Int, h: Int) -> Unit)? = null
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ sizeChange?.invoke(w, h)
+ }
+
+ override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ if (!isShowing || !isVisible || !mActivated) return super.dispatchKeyEvent(event)
+ Timber.i("dispatchKeyEvent: $event")
+
+ if (event.keyCode == KeyEvent.KEYCODE_BACK) {
+ if (keyDispatcherState == null) {
+ return super.dispatchKeyEvent(event)
+ }
+
+ if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) {
+ keyDispatcherState?.startTracking(event, this)
+ return true
+ } else if (event.action == KeyEvent.ACTION_UP) {
+ val state = keyDispatcherState
+ if (state != null && state.isTracking(event) && !event.isCanceled) {
+ Timber.v("Back pressed, close the tooltip")
+ hide()
+ return true
+ }
+ }
+ return super.dispatchKeyEvent(event)
+ } else {
+ return super.dispatchKeyEvent(event)
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (!isShowing || !isVisible || !mActivated) return false
+
+ Timber.i("onTouchEvent: $event")
+ Timber.d("event position: ${event.x}, ${event.y}")
+
+ val r1 = Rect()
+ mTextView.getGlobalVisibleRect(r1)
+ val containsTouch = r1.contains(event.x.toInt(), event.y.toInt())
+
+ if (mClosePolicy.anywhere()) {
+ hide()
+ } else if (mClosePolicy.inside() && containsTouch) {
+ hide()
+ } else if (mClosePolicy.outside() && !containsTouch) {
+ hide()
+ }
+
+ return mClosePolicy.consume()
+ }
+ }
+
+ private data class Positions(
+ val arrowPoint: Point,
+ val centerPoint: Point,
+ val contentPoint: Point,
+ val gravity: Gravity,
+ val params: WindowManager.LayoutParams
+ )
+
+ enum class Gravity {
+ LEFT, RIGHT, TOP, BOTTOM, CENTER
+ }
+
+ data class Animation(val radius: Int, val direction: Int, val duration: Long) {
+
+ @Suppress("unused")
+ companion object {
+ val DEFAULT = Animation(8, 0, 400)
+ val SLOW = Animation(4, 0, 600)
+ }
+ }
+
+ @Suppress("unused")
+ class Builder(private val context: Context) {
+ internal var point: Point? = null
+ internal var closePolicy = ClosePolicy.TOUCH_INSIDE_CONSUME
+ internal var text: CharSequence? = null
+ internal var anchorView: View? = null
+ internal var maxWidth: Int? = null
+ internal var defStyleRes = R.style.ToolTipLayoutDefaultStyle
+ internal var defStyleAttr = R.attr.ttlm_defaultStyle
+ internal var typeface: Typeface? = null
+ internal var overlay = true
+ internal var floatingAnimation: Animation? = null
+ internal var showDuration: Long = 0
+ internal var fadeDuration: Long = 100
+ internal var showArrow = true
+ internal var activateDelay = 0L
+ internal var followAnchor = false
+
+ @LayoutRes
+ internal var layoutId: Int? = null
+
+ @IdRes
+ internal var textId: Int? = null
+
+ fun typeface(value: Typeface?): Builder {
+ this.typeface = value
+ return this
+ }
+
+ fun styleId(@StyleRes styleId: Int?): Builder {
+ styleId?.let {
+ this.defStyleAttr = 0
+ this.defStyleRes = it
+ } ?: run {
+ this.defStyleRes = R.style.ToolTipLayoutDefaultStyle
+ this.defStyleAttr = R.attr.ttlm_defaultStyle
+ }
+ return this
+ }
+
+ fun customView(@LayoutRes layoutId: Int, @IdRes textId: Int): Builder {
+ this.layoutId = layoutId
+ this.textId = textId
+ return this
+ }
+
+ fun activateDelay(value: Long): Builder {
+ this.activateDelay = value
+ return this
+ }
+
+ fun arrow(value: Boolean): Builder {
+ this.showArrow = value
+ return this
+ }
+
+ fun fadeDuration(value: Long): Builder {
+ this.fadeDuration = value
+ return this
+ }
+
+ fun showDuration(value: Long): Builder {
+ this.showDuration = value
+ return this
+ }
+
+ fun floatingAnimation(value: Animation?): Builder {
+ this.floatingAnimation = value
+ return this
+ }
+
+ fun maxWidth(w: Int): Builder {
+ this.maxWidth = w
+ return this
+ }
+
+ fun maxWidth(res: Resources, @DimenRes dimension: Int): Builder {
+ return maxWidth(res.getDimensionPixelSize(dimension))
+ }
+
+ fun overlay(value: Boolean): Builder {
+ this.overlay = value
+ return this
+ }
+
+ fun anchor(x: Int, y: Int): Builder {
+ this.anchorView = null
+ this.point = Point(x, y)
+ return this
+ }
+
+ fun anchor(view: View, xoff: Int = 0, yoff: Int = 0, follow: Boolean = false): Builder {
+ this.anchorView = view
+ this.followAnchor = follow
+ this.point = Point(xoff, yoff)
+ return this
+ }
+
+ fun text(text: CharSequence): Builder {
+ this.text = text
+ return this
+ }
+
+ fun text(@StringRes text: Int): Builder {
+ this.text = context.getString(text)
+ return this
+ }
+
+ fun text(@StringRes text: Int, vararg args: Any): Builder {
+ this.text = context.getString(text, args)
+ return this
+ }
+
+ @JvmOverloads
+ fun closePolicy(policy: ClosePolicy, milliseconds:Long = 0): Builder {
+ this.closePolicy = policy
+ this.showDuration = milliseconds
+ Timber.v("closePolicy: $policy")
+ return this
+ }
+
+ fun create(): XTooltip {
+ if (null == anchorView && null == point) {
+ throw IllegalArgumentException("missing anchor point or anchor view")
+ }
+ return XTooltip(context, this)
+ }
+ }
+}
+
+class ClosePolicy internal constructor(private val policy: Int) {
+
+ fun consume() = policy and CONSUME == CONSUME
+
+ fun inside(): Boolean {
+ return policy and TOUCH_INSIDE == TOUCH_INSIDE
+ }
+
+ fun outside(): Boolean {
+ return policy and TOUCH_OUTSIDE == TOUCH_OUTSIDE
+ }
+
+ fun anywhere() = inside() and outside()
+
+ override fun toString(): String {
+ return "ClosePolicy{policy: $policy, inside:${inside()}, outside: ${outside()}, anywhere: ${anywhere()}, consume: ${consume()}}"
+ }
+
+ @Suppress("unused")
+ class Builder {
+ private var policy = NONE
+
+ fun consume(value: Boolean): Builder {
+ policy = if (value) policy or CONSUME else policy and CONSUME.inv()
+ return this
+ }
+
+ fun inside(value: Boolean): Builder {
+ policy = if (value) policy or TOUCH_INSIDE else policy and TOUCH_INSIDE.inv()
+ return this
+ }
+
+ fun outside(value: Boolean): Builder {
+ policy = if (value) policy or TOUCH_OUTSIDE else policy and TOUCH_OUTSIDE.inv()
+ return this
+ }
+
+ fun clear() {
+ policy = NONE
+ }
+
+ fun build() = ClosePolicy(policy)
+ }
+
+ @Suppress("unused")
+ companion object {
+ private const val NONE = 0
+ private const val TOUCH_INSIDE = 1 shl 1
+ private const val TOUCH_OUTSIDE = 1 shl 2
+ private const val CONSUME = 1 shl 3
+
+ @JvmField
+ val TOUCH_NONE = ClosePolicy(NONE)
+ @JvmField
+ val TOUCH_INSIDE_CONSUME = ClosePolicy(TOUCH_INSIDE or CONSUME)
+ @JvmField
+ val TOUCH_INSIDE_NO_CONSUME = ClosePolicy(TOUCH_INSIDE)
+ @JvmField
+ val TOUCH_OUTSIDE_CONSUME = ClosePolicy(TOUCH_OUTSIDE or CONSUME)
+ @JvmField
+ val TOUCH_OUTSIDE_NO_CONSUME = ClosePolicy(TOUCH_OUTSIDE)
+ @JvmField
+ val TOUCH_ANYWHERE_NO_CONSUME = ClosePolicy(TOUCH_INSIDE or TOUCH_OUTSIDE)
+ @JvmField
+ val TOUCH_ANYWHERE_CONSUME = ClosePolicy(TOUCH_INSIDE or TOUCH_OUTSIDE or CONSUME)
+ }
+
+}
\ No newline at end of file
diff --git a/neofecttooltip/src/main/res/layout/textview.xml b/neofecttooltip/src/main/res/layout/textview.xml
new file mode 100644
index 00000000..a682f61e
--- /dev/null
+++ b/neofecttooltip/src/main/res/layout/textview.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/library/src/main/res/layout/tooltip_textview.xml b/neofecttooltip/src/main/res/layout/tooltip_textview.xml
similarity index 100%
rename from library/src/main/res/layout/tooltip_textview.xml
rename to neofecttooltip/src/main/res/layout/tooltip_textview.xml
diff --git a/library/src/main/res/values/attrs.xml b/neofecttooltip/src/main/res/values/attrs.xml
similarity index 82%
rename from library/src/main/res/values/attrs.xml
rename to neofecttooltip/src/main/res/values/attrs.xml
index 5b20d3fa..bf65131d 100644
--- a/library/src/main/res/values/attrs.xml
+++ b/neofecttooltip/src/main/res/values/attrs.xml
@@ -11,10 +11,15 @@
+
+
+
+
+
diff --git a/library/src/main/res/values/dimens.xml b/neofecttooltip/src/main/res/values/dimens.xml
similarity index 67%
rename from library/src/main/res/values/dimens.xml
rename to neofecttooltip/src/main/res/values/dimens.xml
index 3b5e156e..d2407503 100644
--- a/library/src/main/res/values/dimens.xml
+++ b/neofecttooltip/src/main/res/values/dimens.xml
@@ -1,7 +1,8 @@
20dip
- 4dip
+ 20dip
0dip
2dp
+ 5dip
\ No newline at end of file
diff --git a/library/src/main/res/values/styles.xml b/neofecttooltip/src/main/res/values/styles.xml
similarity index 87%
rename from library/src/main/res/values/styles.xml
rename to neofecttooltip/src/main/res/values/styles.xml
index 9765a19c..a99ee40a 100644
--- a/library/src/main/res/values/styles.xml
+++ b/neofecttooltip/src/main/res/values/styles.xml
@@ -8,9 +8,12 @@
- @dimen/ttlm_default_stroke_weight
- @dimen/ttlm_default_corner_radius
- 1.4
+ - 0dp
+ - 0dp
- ?android:attr/textAppearanceSmall
- @style/ToolTipOverlayDefaultStyle
- @dimen/ttlm_default_elevation
+ - @dimen/ttlm_default_margin