From f30cb2abec8426808ceaf9b575a9ac85ca4c63a8 Mon Sep 17 00:00:00 2001 From: YooJaehong Date: Wed, 20 Sep 2017 12:47:25 +0900 Subject: [PATCH 01/12] android build tool version 2.3.3 --- app/build.gradle | 6 +++--- .../sephiroth/android/library/mymodule/app/MyTextView.java | 4 ++-- build.gradle | 2 +- library/build.gradle | 4 ++-- .../sephiroth/android/library/tooltip/TooltipOverlay.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7aa8daa4..deca560a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,9 +40,9 @@ 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' + compile 'com.android.support:appcompat-v7:25.3.1' + compile 'com.android.support:design:25.3.1' + compile 'com.android.support:recyclerview-v7:25.3.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/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/build.gradle b/build.gradle index a641feb5..092d4c87 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0-beta2' + classpath 'com.android.tools.build:gradle:2.3.3' } } diff --git a/library/build.gradle b/library/build.gradle index 91532538..f04894bb 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -43,8 +43,8 @@ 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' + provided 'com.android.support:support-annotations:25.3.1' + compile 'com.android.support:appcompat-v7:25.3.1' } diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlay.java b/library/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/library/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) { From e9d68a2d7c673cb93a73247bb4abe43ae90502ba Mon Sep 17 00:00:00 2001 From: YooJaehong Date: Thu, 21 Sep 2017 16:48:46 +0900 Subject: [PATCH 02/12] add margin between anchor view and tooltip --- README.md | 1 + .../it/sephiroth/android/library/tooltip/Tooltip.java | 10 ++++++++++ library/src/main/res/values/attrs.xml | 1 + library/src/main/res/values/dimens.xml | 1 + library/src/main/res/values/styles.xml | 1 + 5 files changed, 14 insertions(+) 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/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java index e28d644b..beb7b728 100644 --- a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java +++ b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java @@ -259,6 +259,7 @@ 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; @@ -407,6 +408,7 @@ public TooltipViewImpl(Context context, final Builder builder) { .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); String font = theme.getString(R.styleable.TooltipLayout_ttlm_font); @@ -1081,6 +1083,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 +1115,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 +1147,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 +1179,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); } diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 5b20d3fa..2fb1d724 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -15,6 +15,7 @@ + diff --git a/library/src/main/res/values/dimens.xml b/library/src/main/res/values/dimens.xml index 3b5e156e..3a8fb2e7 100644 --- a/library/src/main/res/values/dimens.xml +++ b/library/src/main/res/values/dimens.xml @@ -4,4 +4,5 @@ 4dip 0dip 2dp + 5dip \ No newline at end of file diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml index 9765a19c..bf4de0c7 100644 --- a/library/src/main/res/values/styles.xml +++ b/library/src/main/res/values/styles.xml @@ -11,6 +11,7 @@ ?android:attr/textAppearanceSmall @style/ToolTipOverlayDefaultStyle @dimen/ttlm_default_elevation + @dimen/ttlm_default_margin diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java index beb7b728..762011d7 100644 --- a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java +++ b/library/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(); @@ -263,6 +267,7 @@ static class TooltipViewImpl extends ViewGroup implements TooltipView { private Callback mCallback; private int[] mOldLocation; private Gravity mGravity; + private Alignment mAlignment; private Animator mShowAnimation; private boolean mShowing; private WeakReference mViewAnchor; @@ -417,6 +422,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; @@ -1031,6 +1037,42 @@ 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 - (mHideArrow ? 0 : mPadding / 2); + mDrawRect.offset(shift, 0); + } + break; + + case RIGHT: + if (mGravity == TOP || mGravity == BOTTOM) { + int shift = mViewRect.right - mDrawRect.right + (mHideArrow ? 0 : mPadding / 2); + mDrawRect.offset(shift, 0); + } + break; + + case TOP: + if (mGravity == LEFT || mGravity == RIGHT) { + int shift = mViewRect.top - mDrawRect.top + (mHideArrow ? 0 : mPadding / 2); + mDrawRect.offset(0, -shift); + } + break; + + case BOTTOM: + if (mGravity == LEFT || mGravity == RIGHT) { + int shift = mViewRect.bottom - mDrawRect.bottom + (mHideArrow ? 0 : mPadding / 2); + mDrawRect.offset(0, shift); + } + break; + + case CENTER: + default: + break; + } + if (null != mViewOverlay) { mViewOverlay.setTranslationX(mViewRect.centerX() - mViewOverlay.getWidth() / 2); mViewOverlay.setTranslationY(mViewRect.centerY() - mViewOverlay.getHeight() / 2); @@ -1041,7 +1083,7 @@ private void calculatePositions(List gravities, final boolean checkEdge mView.setTranslationY(mDrawRect.top); if (null != mDrawable) { - getAnchorPoint(gravity, mTmpPoint); + getAnchorPoint(gravity, mTmpPoint, center); mDrawable.setAnchor(gravity, mHideArrow ? 0 : mPadding / 2, mHideArrow ? null : mTmpPoint); } @@ -1259,6 +1301,36 @@ void getAnchorPoint(final Gravity gravity, Point outPoint) { } } + 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 -= mPadding / 2; + } else if (gravity == TOP || gravity == BOTTOM) { + outPoint.x -= mPadding / 2; + } + } + } + @Override public void setText(final CharSequence text) { this.mText = text; @@ -1468,6 +1540,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; @@ -1612,6 +1685,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/res/values/dimens.xml b/library/src/main/res/values/dimens.xml index 3a8fb2e7..d2407503 100644 --- a/library/src/main/res/values/dimens.xml +++ b/library/src/main/res/values/dimens.xml @@ -1,7 +1,7 @@ 20dip - 4dip + 20dip 0dip 2dp 5dip From 7aac470b64023bf1532470c7a6110b766a6eb0cb Mon Sep 17 00:00:00 2001 From: YooJaehong Date: Fri, 22 Sep 2017 13:16:31 +0900 Subject: [PATCH 04/12] disconnect relation between padding and arrow height --- .../android/library/tooltip/Tooltip.java | 34 +++++++++++-------- .../library/tooltip/TooltipTextDrawable.java | 14 ++++---- library/src/main/res/values/attrs.xml | 1 + library/src/main/res/values/styles.xml | 1 + 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java index 762011d7..d5f7b75f 100644 --- a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java +++ b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java @@ -317,6 +317,7 @@ public void run() { } }; private int mPadding; + private float mArrowHeight; private CharSequence mText; private Rect mViewRect; private View mView; @@ -408,6 +409,7 @@ 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); @@ -430,6 +432,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; @@ -816,11 +825,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); @@ -1042,28 +1048,28 @@ private void calculatePositions(List gravities, final boolean checkEdge switch (mAlignment) { case LEFT: if (mGravity == TOP || mGravity == BOTTOM) { - int shift = mViewRect.left - mDrawRect.left - (mHideArrow ? 0 : mPadding / 2); + int shift = mViewRect.left - mDrawRect.left - Math.round(mArrowHeight); mDrawRect.offset(shift, 0); } break; case RIGHT: if (mGravity == TOP || mGravity == BOTTOM) { - int shift = mViewRect.right - mDrawRect.right + (mHideArrow ? 0 : mPadding / 2); + int shift = mViewRect.right - mDrawRect.right + Math.round(mArrowHeight); mDrawRect.offset(shift, 0); } break; case TOP: if (mGravity == LEFT || mGravity == RIGHT) { - int shift = mViewRect.top - mDrawRect.top + (mHideArrow ? 0 : mPadding / 2); + int shift = mViewRect.top - mDrawRect.top + Math.round(mArrowHeight); mDrawRect.offset(0, -shift); } break; case BOTTOM: if (mGravity == LEFT || mGravity == RIGHT) { - int shift = mViewRect.bottom - mDrawRect.bottom + (mHideArrow ? 0 : mPadding / 2); + int shift = mViewRect.bottom - mDrawRect.bottom + Math.round(mArrowHeight); mDrawRect.offset(0, shift); } break; @@ -1084,7 +1090,7 @@ private void calculatePositions(List gravities, final boolean checkEdge if (null != mDrawable) { getAnchorPoint(gravity, mTmpPoint, center); - mDrawable.setAnchor(gravity, mHideArrow ? 0 : mPadding / 2, mHideArrow ? null : mTmpPoint); + mDrawable.setAnchor(gravity, mHideArrow ? 0 : mPadding, mArrowHeight, mHideArrow ? null : mTmpPoint); } if (!mAlreadyCheck) { @@ -1294,9 +1300,9 @@ 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; } } } @@ -1324,9 +1330,9 @@ void getAnchorPoint(final Gravity gravity, Point outPoint, Point center) { 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; } } } diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/TooltipTextDrawable.java b/library/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/library/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/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 2fb1d724..5c30db83 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -11,6 +11,7 @@ + diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml index bf4de0c7..b9a6e2d1 100644 --- a/library/src/main/res/values/styles.xml +++ b/library/src/main/res/values/styles.xml @@ -8,6 +8,7 @@ @dimen/ttlm_default_stroke_weight @dimen/ttlm_default_corner_radius 1.4 + 0dp ?android:attr/textAppearanceSmall @style/ToolTipOverlayDefaultStyle @dimen/ttlm_default_elevation From 64093e2aafed5d7a0c11afaee54e797b9847d220 Mon Sep 17 00:00:00 2001 From: YooJaehong Date: Fri, 22 Sep 2017 15:23:35 +0900 Subject: [PATCH 05/12] shift tooltip --- .../android/library/tooltip/Tooltip.java | 19 ++++++++++++++----- library/src/main/res/values/attrs.xml | 1 + library/src/main/res/values/styles.xml | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java index d5f7b75f..9c47b8d4 100644 --- a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java +++ b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java @@ -268,6 +268,7 @@ static class TooltipViewImpl extends ViewGroup implements TooltipView { private int[] mOldLocation; private Gravity mGravity; private Alignment mAlignment; + private int mAnchorShift; private Animator mShowAnimation; private boolean mShowing; private WeakReference mViewAnchor; @@ -416,6 +417,7 @@ public TooltipViewImpl(Context context, final Builder builder) { 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); @@ -1048,33 +1050,40 @@ private void calculatePositions(List gravities, final boolean checkEdge switch (mAlignment) { case LEFT: if (mGravity == TOP || mGravity == BOTTOM) { - int shift = mViewRect.left - mDrawRect.left - Math.round(mArrowHeight); + 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); + 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); - mDrawRect.offset(0, -shift); + 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); + 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; } diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 5c30db83..fee8d77f 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -12,6 +12,7 @@ + diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml index b9a6e2d1..a99ee40a 100644 --- a/library/src/main/res/values/styles.xml +++ b/library/src/main/res/values/styles.xml @@ -9,6 +9,7 @@ @dimen/ttlm_default_corner_radius 1.4 0dp + 0dp ?android:attr/textAppearanceSmall @style/ToolTipOverlayDefaultStyle @dimen/ttlm_default_elevation From ac8c70cb0b0f9e7c2d1fa02fd495d0ce9740d5af Mon Sep 17 00:00:00 2001 From: YooJaehong Date: Wed, 18 Oct 2017 18:19:12 +0900 Subject: [PATCH 06/12] make style lineSpacingExtra and lineSpacingMultiplier in TextAppearance work --- .../java/it/sephiroth/android/library/tooltip/Tooltip.java | 6 ++++++ library/src/main/res/values/attrs.xml | 2 ++ 2 files changed, 8 insertions(+) diff --git a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java index 9c47b8d4..a633689b 100644 --- a/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java +++ b/library/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java @@ -817,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); diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index fee8d77f..bf65131d 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -14,6 +14,8 @@ + + From fd8b4d44afcf82c50f8aacf7eaa0f1576c9400ef Mon Sep 17 00:00:00 2001 From: jhchoi Date: Wed, 25 Mar 2020 18:23:20 +0900 Subject: [PATCH 07/12] =?UTF-8?q?tooltip=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=202.0.1=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 14 +- build.gradle | 8 +- gradle.properties | 6 +- gradle/wrapper/gradle-wrapper.properties | 3 +- library/build.gradle | 13 +- .../android/library/xtooltip/Tooltip.kt | 927 ++++++++++++++++++ .../library/xtooltip/TooltipOverlay.kt | 53 + .../xtooltip/TooltipOverlayDrawable.kt | 248 +++++ .../library/xtooltip/TooltipTextDrawable.kt | 275 ++++++ .../android/library/xtooltip/Typefaces.kt | 43 + .../android/library/xtooltip/Utils.kt | 107 ++ library/src/main/res/layout/textview.xml | 21 + 12 files changed, 1700 insertions(+), 18 deletions(-) create mode 100644 library/src/main/java/it/sephiroth/android/library/xtooltip/Tooltip.kt create mode 100644 library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt create mode 100644 library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt create mode 100644 library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt create mode 100644 library/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt create mode 100644 library/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt create mode 100644 library/src/main/res/layout/textview.xml diff --git a/app/build.gradle b/app/build.gradle index deca560a..c07162d0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,14 +5,14 @@ android { 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 { @@ -40,9 +40,9 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':library') - compile 'com.android.support:appcompat-v7:25.3.1' - compile 'com.android.support:design:25.3.1' - compile 'com.android.support:recyclerview-v7:25.3.1' + compile 'com.android.support:appcompat-v7:27.0.2' + compile 'com.android.support:design:27.0.2' + compile 'com.android.support:recyclerview-v7:27.0.2' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' diff --git a/build.gradle b/build.gradle index 092d4c87..eef5d83a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,13 @@ buildscript { + ext.kotlin_version = '1.3.10' repositories { + google() jcenter() - mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.2.1' + 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..2e933b30 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=28 +ANDROID_BUILD_TOOLS_VERSION=28.0.3 +ANDROID_BUILD_SDK_VERSION=28 org.gradle.daemon=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0633896a..4777c576 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,5 @@ 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-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip \ No newline at end of file diff --git a/library/build.gradle b/library/build.gradle index f04894bb..27050fd6 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' group GROUP version VERSION_NAME @@ -8,7 +10,7 @@ android { buildToolsVersion ANDROID_BUILD_TOOLS_VERSION defaultConfig { - minSdkVersion 14 + minSdkVersion 16 targetSdkVersion ANDROID_BUILD_TARGET_SDK_VERSION as int versionCode 1 versionName VERSION_NAME @@ -42,9 +44,12 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - provided 'com.android.support:support-annotations:25.3.1' - compile 'com.android.support:appcompat-v7:25.3.1' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:support-annotations:27.0.2' + implementation 'com.android.support:appcompat-v7:27.0.2' + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'com.jakewharton.timber:timber:4.7.1' } diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/Tooltip.kt b/library/src/main/java/it/sephiroth/android/library/xtooltip/Tooltip.kt new file mode 100644 index 00000000..3f22380b --- /dev/null +++ b/library/src/main/java/it/sephiroth/android/library/xtooltip/Tooltip.kt @@ -0,0 +1,927 @@ +package it.sephiroth.android.library.xtooltip + +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.content.Context +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.IdRes +import android.support.annotation.LayoutRes +import android.support.annotation.StringRes +import android.support.annotation.StyleRes +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 androidx.annotation.IdRes +//import androidx.annotation.LayoutRes +//import androidx.annotation.StringRes +//import androidx.annotation.StyleRes +//import androidx.core.view.setPadding +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 Tooltip 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: Tooltip) -> Unit)? = null + private var mShownFunc: ((tooltip: Tooltip) -> Unit)? = null + private var mHiddenFunc: ((tooltip: Tooltip) -> Unit)? = null + + @Suppress("UNUSED") + fun doOnFailure(func: ((tooltip: Tooltip) -> Unit)?): Tooltip { + mFailureFunc = func + return this + } + + @Suppress("UNUSED") + fun doOnShown(func: ((tooltip: Tooltip) -> Unit)?): Tooltip { + mShownFunc = func + return this + } + + @Suppress("UNUSED") + fun doOnHidden(func: ((tooltip: Tooltip) -> Unit)?): Tooltip { + 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) as TextView + + 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@Tooltip.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?): Tooltip? { + 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 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 + } + + fun closePolicy(policy: ClosePolicy): Builder { + this.closePolicy = policy + Timber.v("closePolicy: $policy") + return this + } + + fun create(): Tooltip { + if (null == anchorView && null == point) { + throw IllegalArgumentException("missing anchor point or anchor view") + } + return Tooltip(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 + + val TOUCH_NONE = ClosePolicy(NONE) + val TOUCH_INSIDE_CONSUME = ClosePolicy(TOUCH_INSIDE or CONSUME) + val TOUCH_INSIDE_NO_CONSUME = ClosePolicy(TOUCH_INSIDE) + val TOUCH_OUTSIDE_CONSUME = ClosePolicy(TOUCH_OUTSIDE or CONSUME) + val TOUCH_OUTSIDE_NO_CONSUME = ClosePolicy(TOUCH_OUTSIDE) + val TOUCH_ANYWHERE_NO_CONSUME = ClosePolicy(TOUCH_INSIDE or TOUCH_OUTSIDE) + val TOUCH_ANYWHERE_CONSUME = ClosePolicy(TOUCH_INSIDE or TOUCH_OUTSIDE or CONSUME) + } + +} \ No newline at end of file diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt b/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt new file mode 100644 index 00000000..39e99da4 --- /dev/null +++ b/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt @@ -0,0 +1,53 @@ +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 + +//import androidx.appcompat.widget.AppCompatImageView + +/** + * 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/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt b/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt new file mode 100644 index 00000000..ae69aaf1 --- /dev/null +++ b/library/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/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt b/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt new file mode 100644 index 00000000..9ab4ed85 --- /dev/null +++ b/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt @@ -0,0 +1,275 @@ +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 androidx.core.util.ObjectsCompat +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: Tooltip.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: Tooltip.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: Tooltip.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 === Tooltip.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 === Tooltip.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 === Tooltip.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 === Tooltip.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: Tooltip.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 === Tooltip.Gravity.RIGHT || gravity === Tooltip.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/library/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt b/library/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt new file mode 100644 index 00000000..c54e7705 --- /dev/null +++ b/library/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/library/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt b/library/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt new file mode 100644 index 00000000..7c1549a4 --- /dev/null +++ b/library/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/library/src/main/res/layout/textview.xml b/library/src/main/res/layout/textview.xml new file mode 100644 index 00000000..81b92449 --- /dev/null +++ b/library/src/main/res/layout/textview.xml @@ -0,0 +1,21 @@ + + + + + From 428c011745258d398fdfedf5f79c85cdaff6caee Mon Sep 17 00:00:00 2001 From: jhchoi Date: Thu, 26 Mar 2020 12:00:03 +0900 Subject: [PATCH 08/12] Tooltip.kt -> XTooltip.kt --- library/build.gradle | 15 ++- .../library/xtooltip/TooltipTextDrawable.kt | 18 +-- .../xtooltip/{Tooltip.kt => XTooltip.kt} | 111 ++++++++++-------- library/src/main/res/layout/textview.xml | 2 +- 4 files changed, 76 insertions(+), 70 deletions(-) rename library/src/main/java/it/sephiroth/android/library/xtooltip/{Tooltip.kt => XTooltip.kt} (93%) diff --git a/library/build.gradle b/library/build.gradle index 27050fd6..bdbbbf6f 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -2,18 +2,17 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -group GROUP -version VERSION_NAME +//group GROUP +//version VERSION_NAME android { - compileSdkVersion ANDROID_BUILD_SDK_VERSION as int - buildToolsVersion ANDROID_BUILD_TOOLS_VERSION + compileSdkVersion 27 defaultConfig { minSdkVersion 16 - targetSdkVersion ANDROID_BUILD_TARGET_SDK_VERSION as int + targetSdkVersion 27 versionCode 1 - versionName VERSION_NAME + versionName "1.3.15" consumerProguardFiles 'proguard-rules.txt' } @@ -53,7 +52,7 @@ dependencies { } -apply from: rootProject.file('checkstyle.gradle') -apply from: 'https://raw.githubusercontent.com/sephiroth74/gradle-mvn-push/master/gradle-mvn-push.gradle' +//apply from: rootProject.file('checkstyle.gradle') +//apply from: 'https://raw.githubusercontent.com/sephiroth74/gradle-mvn-push/master/gradle-mvn-push.gradle' uploadArchives.dependsOn 'check' diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt b/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt index 9ab4ed85..f095f9bf 100644 --- a/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt +++ b/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt @@ -28,7 +28,7 @@ import timber.log.Timber * 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: Tooltip.Builder) : Drawable() { +internal class TooltipTextDrawable(context: Context, builder: XTooltip.Builder) : Drawable() { private val rectF: RectF private val path: Path private val tmpPoint = Point() @@ -40,7 +40,7 @@ internal class TooltipTextDrawable(context: Context, builder: Tooltip.Builder) : private var point: Point? = null private var padding = 0 private var arrowWeight = 0 - private var gravity: Tooltip.Gravity? = null + private var gravity: XTooltip.Gravity? = null init { @@ -89,7 +89,7 @@ internal class TooltipTextDrawable(context: Context, builder: Tooltip.Builder) : } } - fun setAnchor(gravity: Tooltip.Gravity, padding: Int, point: Point?) { + 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 @@ -150,7 +150,7 @@ internal class TooltipTextDrawable(context: Context, builder: Tooltip.Builder) : // top/left path.moveTo(left + radius, top.toFloat()) - if (drawPoint && gravity === Tooltip.Gravity.BOTTOM) { + 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()) @@ -160,7 +160,7 @@ internal class TooltipTextDrawable(context: Context, builder: Tooltip.Builder) : path.lineTo(right - radius, top.toFloat()) path.quadTo(right.toFloat(), top.toFloat(), right.toFloat(), top + radius) - if (drawPoint && gravity === Tooltip.Gravity.LEFT) { + 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()) @@ -170,7 +170,7 @@ internal class TooltipTextDrawable(context: Context, builder: Tooltip.Builder) : path.lineTo(right.toFloat(), bottom - radius) path.quadTo(right.toFloat(), bottom.toFloat(), right - radius, bottom.toFloat()) - if (drawPoint && gravity === Tooltip.Gravity.TOP) { + 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()) @@ -180,7 +180,7 @@ internal class TooltipTextDrawable(context: Context, builder: Tooltip.Builder) : path.lineTo(left + radius, bottom.toFloat()) path.quadTo(left.toFloat(), bottom.toFloat(), left.toFloat(), bottom - radius) - if (drawPoint && gravity === Tooltip.Gravity.RIGHT) { + 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()) @@ -227,14 +227,14 @@ internal class TooltipTextDrawable(context: Context, builder: Tooltip.Builder) : private fun isDrawPoint( left: Int, top: Int, right: Int, bottom: Int, maxY: Float, maxX: Float, minY: Float, - minX: Float, tmpPoint: Point, point: Point, gravity: Tooltip.Gravity?, + 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 === Tooltip.Gravity.RIGHT || gravity === Tooltip.Gravity.LEFT) { + 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() diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/Tooltip.kt b/library/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt similarity index 93% rename from library/src/main/java/it/sephiroth/android/library/xtooltip/Tooltip.kt rename to library/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt index 3f22380b..f82227aa 100644 --- a/library/src/main/java/it/sephiroth/android/library/xtooltip/Tooltip.kt +++ b/library/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt @@ -4,6 +4,7 @@ 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 @@ -11,10 +12,7 @@ import android.graphics.Typeface import android.os.Build import android.os.Handler import android.os.IBinder -import android.support.annotation.IdRes -import android.support.annotation.LayoutRes -import android.support.annotation.StringRes -import android.support.annotation.StyleRes +import android.support.annotation.* import android.text.Html import android.text.Spannable import android.view.* @@ -25,11 +23,7 @@ import android.widget.FrameLayout import android.widget.PopupWindow.INPUT_METHOD_NOT_NEEDED import android.widget.TextView import it.sephiroth.android.library.tooltip.R -//import androidx.annotation.IdRes -//import androidx.annotation.LayoutRes -//import androidx.annotation.StringRes -//import androidx.annotation.StyleRes -//import androidx.core.view.setPadding +import it.sephiroth.android.library.tooltip.Tooltip import timber.log.Timber import java.lang.ref.WeakReference import java.util.* @@ -51,7 +45,7 @@ import java.util.* * 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 Tooltip private constructor(private val context: Context, builder: Builder) { +class XTooltip private constructor(private val context: Context, builder: Builder) { private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager @@ -122,7 +116,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder offsetBy( mNewLocation[0] - mOldLocation!![0], mNewLocation[1] - mOldLocation!![1] - ) + ) } } } @@ -132,26 +126,26 @@ class Tooltip private constructor(private val context: Context, builder: Builder init { val theme = context.theme - .obtainStyledAttributes( - null, - R.styleable.TooltipLayout, - builder.defStyleAttr, - builder.defStyleRes - ) + .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 - ) + .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() @@ -186,24 +180,24 @@ class Tooltip private constructor(private val context: Context, builder: Builder } } - private var mFailureFunc: ((tooltip: Tooltip) -> Unit)? = null - private var mShownFunc: ((tooltip: Tooltip) -> Unit)? = null - private var mHiddenFunc: ((tooltip: Tooltip) -> Unit)? = null + 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: Tooltip) -> Unit)?): Tooltip { + fun doOnFailure(func: ((tooltip: XTooltip) -> Unit)?): XTooltip { mFailureFunc = func return this } @Suppress("UNUSED") - fun doOnShown(func: ((tooltip: Tooltip) -> Unit)?): Tooltip { + fun doOnShown(func: ((tooltip: XTooltip) -> Unit)?): XTooltip { mShownFunc = func return this } @Suppress("UNUSED") - fun doOnHidden(func: ((tooltip: Tooltip) -> Unit)?): Tooltip { + fun doOnHidden(func: ((tooltip: XTooltip) -> Unit)?): XTooltip { mHiddenFunc = func return this } @@ -262,7 +256,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT - ) + ) } } @@ -271,7 +265,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder mFloatingAnimation?.let { contentView.setPadding(it.radius, it.radius, it.radius, it.radius) } - mTextView = contentView.findViewById(mTextViewIdRes) as TextView + mTextView = contentView.findViewById(mTextViewIdRes) with(mTextView) { mDrawable?.let { background = it } @@ -297,7 +291,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder mText } else { @Suppress("DEPRECATION") - Html.fromHtml(this@Tooltip.mText as String) + Html.fromHtml(this@XTooltip.mText as String) } mMaxWidth?.let { maxWidth = it } @@ -308,7 +302,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder viewContainer.addView( mViewOverlay, FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT) - ) + ) } viewContainer.addView(contentView, FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)) @@ -350,7 +344,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder gravities: ArrayList, params: WindowManager.LayoutParams, fitToScreen: Boolean = false - ): Positions? { + ): Positions? { if (null == mPopupView) return null if (gravities.isEmpty()) return null @@ -461,7 +455,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder 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) @@ -475,7 +469,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder private var mOldLocation: IntArray? = null private var mNewLocation: IntArray = intArrayOf(0, 0) - private fun invokePopup(positions: Positions?): Tooltip? { + private fun invokePopup(positions: Positions?): XTooltip? { positions?.let { isShowing = true mCurrentPosition = positions @@ -490,7 +484,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder it.gravity, if (!mShowArrow) 0 else mPadding / 2, if (!mShowArrow) null else it.arrowPoint - ) + ) offsetBy(0, 0) @@ -586,8 +580,8 @@ class Tooltip private constructor(private val context: Context, builder: Builder gravities, params, fitToScreen - ) - ) + ) + ) } fun hide() { @@ -623,8 +617,8 @@ class Tooltip private constructor(private val context: Context, builder: Builder if (fadeDuration > 0 && null != mPopupView) { mPopupView!!.alpha = 0F mPopupView!!.animate() - .setDuration(mFadeDuration) - .alpha(1f).start() + .setDuration(mFadeDuration) + .alpha(1f).start() } mShownFunc?.invoke(this) } @@ -641,15 +635,15 @@ class Tooltip private constructor(private val context: Context, builder: Builder mPopupView?.let { popupView -> popupView.clearAnimation() popupView.animate() - .alpha(0f) - .setDuration(fadeDuration) - .setListener { - onAnimationEnd { - popupView.visibility = View.INVISIBLE - dismiss() + .alpha(0f) + .setDuration(fadeDuration) + .setListener { + onAnimationEnd { + popupView.visibility = View.INVISIBLE + dismiss() + } } - } - .start() + .start() } } else { dismiss() @@ -725,7 +719,7 @@ class Tooltip private constructor(private val context: Context, builder: Builder val contentPoint: Point, val gravity: Gravity, val params: WindowManager.LayoutParams - ) + ) enum class Gravity { LEFT, RIGHT, TOP, BOTTOM, CENTER @@ -816,6 +810,10 @@ class Tooltip private constructor(private val context: Context, builder: Builder 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 @@ -849,17 +847,19 @@ class Tooltip private constructor(private val context: Context, builder: Builder return this } - fun closePolicy(policy: ClosePolicy): Builder { + @JvmOverloads + fun closePolicy(policy: ClosePolicy, milliseconds:Long = 0): Builder { this.closePolicy = policy + this.showDuration = milliseconds Timber.v("closePolicy: $policy") return this } - fun create(): Tooltip { + fun create(): XTooltip { if (null == anchorView && null == point) { throw IllegalArgumentException("missing anchor point or anchor view") } - return Tooltip(context, this) + return XTooltip(context, this) } } } @@ -915,12 +915,19 @@ class ClosePolicy internal constructor(private val policy: Int) { 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) } diff --git a/library/src/main/res/layout/textview.xml b/library/src/main/res/layout/textview.xml index 81b92449..a682f61e 100644 --- a/library/src/main/res/layout/textview.xml +++ b/library/src/main/res/layout/textview.xml @@ -11,7 +11,7 @@ android:focusableInTouchMode="false" android:padding="0dp"> - Date: Thu, 26 Mar 2020 14:10:59 +0900 Subject: [PATCH 09/12] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 +- {library => neofecttooltip}/.gitignore | 0 {library => neofecttooltip}/build.gradle | 0 {library => neofecttooltip}/gradle.properties | 0 {library => neofecttooltip}/proguard-rules.txt | 0 {library => neofecttooltip}/src/main/AndroidManifest.xml | 0 .../main/java/it/sephiroth/android/library/tooltip/Tooltip.java | 0 .../it/sephiroth/android/library/tooltip/TooltipOverlay.java | 0 .../android/library/tooltip/TooltipOverlayDrawable.java | 0 .../sephiroth/android/library/tooltip/TooltipTextDrawable.java | 0 .../java/it/sephiroth/android/library/tooltip/Typefaces.java | 0 .../main/java/it/sephiroth/android/library/tooltip/Utils.java | 0 .../it/sephiroth/android/library/xtooltip/TooltipOverlay.kt | 0 .../android/library/xtooltip/TooltipOverlayDrawable.kt | 0 .../sephiroth/android/library/xtooltip/TooltipTextDrawable.kt | 0 .../java/it/sephiroth/android/library/xtooltip/Typefaces.kt | 0 .../main/java/it/sephiroth/android/library/xtooltip/Utils.kt | 0 .../main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt | 0 {library => neofecttooltip}/src/main/res/layout/textview.xml | 0 .../src/main/res/layout/tooltip_textview.xml | 0 {library => neofecttooltip}/src/main/res/values/attrs.xml | 0 {library => neofecttooltip}/src/main/res/values/dimens.xml | 0 {library => neofecttooltip}/src/main/res/values/styles.xml | 0 settings.gradle | 2 +- 24 files changed, 2 insertions(+), 2 deletions(-) rename {library => neofecttooltip}/.gitignore (100%) rename {library => neofecttooltip}/build.gradle (100%) rename {library => neofecttooltip}/gradle.properties (100%) rename {library => neofecttooltip}/proguard-rules.txt (100%) rename {library => neofecttooltip}/src/main/AndroidManifest.xml (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/tooltip/Tooltip.java (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlay.java (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/tooltip/TooltipOverlayDrawable.java (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/tooltip/TooltipTextDrawable.java (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/tooltip/Typefaces.java (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/tooltip/Utils.java (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt (100%) rename {library => neofecttooltip}/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt (100%) rename {library => neofecttooltip}/src/main/res/layout/textview.xml (100%) rename {library => neofecttooltip}/src/main/res/layout/tooltip_textview.xml (100%) rename {library => neofecttooltip}/src/main/res/values/attrs.xml (100%) rename {library => neofecttooltip}/src/main/res/values/dimens.xml (100%) rename {library => neofecttooltip}/src/main/res/values/styles.xml (100%) diff --git a/app/build.gradle b/app/build.gradle index c07162d0..3a3ecda1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,7 +39,7 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile project(':library') + compile project(':neofecttooltip') compile 'com.android.support:appcompat-v7:27.0.2' compile 'com.android.support:design:27.0.2' compile 'com.android.support:recyclerview-v7:27.0.2' 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 100% rename from library/build.gradle rename to neofecttooltip/build.gradle 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 100% 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 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 100% 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 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 100% 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 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/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt similarity index 100% rename from library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt rename to neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt similarity index 100% rename from library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt rename to neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlayDrawable.kt diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt similarity index 100% rename from library/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt rename to neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt similarity index 100% rename from library/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt rename to neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Typefaces.kt diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt similarity index 100% rename from library/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt rename to neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/Utils.kt diff --git a/library/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt similarity index 100% rename from library/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt rename to neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt diff --git a/library/src/main/res/layout/textview.xml b/neofecttooltip/src/main/res/layout/textview.xml similarity index 100% rename from library/src/main/res/layout/textview.xml rename to neofecttooltip/src/main/res/layout/textview.xml 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 100% rename from library/src/main/res/values/attrs.xml rename to neofecttooltip/src/main/res/values/attrs.xml diff --git a/library/src/main/res/values/dimens.xml b/neofecttooltip/src/main/res/values/dimens.xml similarity index 100% rename from library/src/main/res/values/dimens.xml rename to neofecttooltip/src/main/res/values/dimens.xml diff --git a/library/src/main/res/values/styles.xml b/neofecttooltip/src/main/res/values/styles.xml similarity index 100% rename from library/src/main/res/values/styles.xml rename to neofecttooltip/src/main/res/values/styles.xml diff --git a/settings.gradle b/settings.gradle index 6bcd2641..e5467dcd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include ':library' +include ':neofecttooltip' include ':app' From d412a5a4b1c931f965d9b70c42fa0e151a25ea04 Mon Sep 17 00:00:00 2001 From: jhchoi Date: Thu, 26 Mar 2020 15:21:08 +0900 Subject: [PATCH 10/12] =?UTF-8?q?build.gradle=EC=97=90=20ANDROID=5FBUILD?= =?UTF-8?q?=5FTARGET=5FSDK=5FVERSION=20=EC=83=81=EC=88=98=EC=82=AC?= =?UTF-8?q?=EC=9A=A9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 10 +++++----- gradle.properties | 6 +++--- neofecttooltip/build.gradle | 19 ++++++++++--------- .../android/library/xtooltip/XTooltip.kt | 1 - 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3a3ecda1..63bd9bff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,11 +38,11 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile project(':neofecttooltip') - compile 'com.android.support:appcompat-v7:27.0.2' - compile 'com.android.support:design:27.0.2' - compile 'com.android.support:recyclerview-v7:27.0.2' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':neofecttooltip') + 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" debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' diff --git a/gradle.properties b/gradle.properties index 2e933b30..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=28 -ANDROID_BUILD_TOOLS_VERSION=28.0.3 -ANDROID_BUILD_SDK_VERSION=28 +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/neofecttooltip/build.gradle b/neofecttooltip/build.gradle index bdbbbf6f..0ebbbde8 100644 --- a/neofecttooltip/build.gradle +++ b/neofecttooltip/build.gradle @@ -2,17 +2,18 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -//group GROUP -//version VERSION_NAME +group GROUP +version VERSION_NAME android { - compileSdkVersion 27 + compileSdkVersion ANDROID_BUILD_SDK_VERSION as int + buildToolsVersion ANDROID_BUILD_TOOLS_VERSION defaultConfig { minSdkVersion 16 - targetSdkVersion 27 + targetSdkVersion ANDROID_BUILD_TARGET_SDK_VERSION as int versionCode 1 - versionName "1.3.15" + versionName VERSION_NAME consumerProguardFiles 'proguard-rules.txt' } @@ -44,15 +45,15 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:support-annotations:27.0.2' - implementation 'com.android.support:appcompat-v7:27.0.2' + 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' } -//apply from: rootProject.file('checkstyle.gradle') -//apply from: 'https://raw.githubusercontent.com/sephiroth74/gradle-mvn-push/master/gradle-mvn-push.gradle' +apply from: rootProject.file('checkstyle.gradle') +apply from: 'https://raw.githubusercontent.com/sephiroth74/gradle-mvn-push/master/gradle-mvn-push.gradle' uploadArchives.dependsOn 'check' 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 index f82227aa..09cf7cdf 100644 --- a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt +++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/XTooltip.kt @@ -23,7 +23,6 @@ import android.widget.FrameLayout import android.widget.PopupWindow.INPUT_METHOD_NOT_NEEDED import android.widget.TextView import it.sephiroth.android.library.tooltip.R -import it.sephiroth.android.library.tooltip.Tooltip import timber.log.Timber import java.lang.ref.WeakReference import java.util.* From 06f473588598302ccf5913487b08de86067ed68b Mon Sep 17 00:00:00 2001 From: jhchoi Date: Thu, 26 Mar 2020 17:10:03 +0900 Subject: [PATCH 11/12] =?UTF-8?q?Xtooltip=20=EC=83=98=ED=94=8C=20=EC=95=A1?= =?UTF-8?q?=ED=8B=B0=EB=B9=84=ED=8B=B0=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 9 +- app/src/main/AndroidManifest.xml | 10 + .../library/mymodule/app/MainActivity.kt | 82 +++++++ .../mymodule/app/TestDialogFragment.kt | 39 ++++ app/src/main/res/layout/activity_main.xml | 25 +++ app/src/main/res/layout/content_main.xml | 201 ++++++++++++++++++ app/src/main/res/layout/dialog_fragment.xml | 27 +++ app/src/main/res/values/strings.xml | 15 ++ app/src/main/res/values/styles.xml | 17 ++ build.gradle | 2 +- neofecttooltip/build.gradle | 4 +- .../library/xtooltip/TooltipOverlay.kt | 2 - .../library/xtooltip/TooltipTextDrawable.kt | 1 - 13 files changed, 426 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/it/sephiroth/android/library/mymodule/app/MainActivity.kt create mode 100644 app/src/main/java/it/sephiroth/android/library/mymodule/app/TestDialogFragment.kt create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/content_main.xml create mode 100644 app/src/main/res/layout/dialog_fragment.xml diff --git a/app/build.gradle b/app/build.gradle index 63bd9bff..db4202f1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { compileSdkVersion ANDROID_BUILD_SDK_VERSION as int @@ -22,8 +24,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { @@ -40,10 +42,13 @@ android { dependencies { 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/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 7b7bd70d..13f92f19 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -33,4 +33,21 @@ fonts/at.ttc + + + + + diff --git a/build.gradle b/build.gradle index eef5d83a..ea32b5ce 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.10' + ext.kotlin_version = '1.3.11' repositories { google() jcenter() diff --git a/neofecttooltip/build.gradle b/neofecttooltip/build.gradle index 0ebbbde8..1cbc42a6 100644 --- a/neofecttooltip/build.gradle +++ b/neofecttooltip/build.gradle @@ -26,8 +26,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { 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 index 39e99da4..4477769d 100644 --- a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt +++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipOverlay.kt @@ -6,8 +6,6 @@ import android.support.v7.widget.AppCompatImageView import android.util.AttributeSet import it.sephiroth.android.library.tooltip.R -//import androidx.appcompat.widget.AppCompatImageView - /** * Created by alessandro crugnola on 12/12/15. * alessandro.crugnola@gmail.com 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 index f095f9bf..f6a17cd2 100644 --- a/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt +++ b/neofecttooltip/src/main/java/it/sephiroth/android/library/xtooltip/TooltipTextDrawable.kt @@ -8,7 +8,6 @@ import android.graphics.drawable.Drawable import android.os.Build import android.support.v4.util.ObjectsCompat import it.sephiroth.android.library.tooltip.R -//import androidx.core.util.ObjectsCompat import timber.log.Timber /** From 03e0e07913cc40c935f3071445c4e054c74bd2e2 Mon Sep 17 00:00:00 2001 From: jhchoi Date: Fri, 27 Mar 2020 10:46:30 +0900 Subject: [PATCH 12/12] =?UTF-8?q?gradle=20warning=20=EC=9B=90=EC=9D=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 1 - build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 3 +-- neofecttooltip/build.gradle | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index db4202f1..797c534e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,6 @@ apply plugin: 'kotlin-android-extensions' android { compileSdkVersion ANDROID_BUILD_SDK_VERSION as int - buildToolsVersion ANDROID_BUILD_TOOLS_VERSION defaultConfig { minSdkVersion 16 diff --git a/build.gradle b/build.gradle index ea32b5ce..048a386c 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4777c576..33ac8ad0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,5 +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-4.10.2-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip \ No newline at end of file diff --git a/neofecttooltip/build.gradle b/neofecttooltip/build.gradle index 1cbc42a6..9bb42a57 100644 --- a/neofecttooltip/build.gradle +++ b/neofecttooltip/build.gradle @@ -7,7 +7,6 @@ version VERSION_NAME android { compileSdkVersion ANDROID_BUILD_SDK_VERSION as int - buildToolsVersion ANDROID_BUILD_TOOLS_VERSION defaultConfig { minSdkVersion 16