/*
Copyright 2016 Tomer Goldstein
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.tomergoldst.tooltips;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.graphics.Outline;
import android.graphics.Point;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.TextView;
import java.util.HashMap;
import java.util.Map;
public class ToolTipsManager {
private static final String TAG = ToolTipsManager.class.getSimpleName();
private static final int DEFAULT_ANIM_DURATION = 400;
// Parameter for managing tip creation or reuse
private Map<Integer, View> mTipsMap = new HashMap<>();
private int mAnimationDuration;
private TipListener mListener;
public interface TipListener {
void onTipDismissed(View view, int anchorViewId, boolean byUser);
}
public ToolTipsManager(){
mAnimationDuration = DEFAULT_ANIM_DURATION;
}
public ToolTipsManager(TipListener listener){
mAnimationDuration = DEFAULT_ANIM_DURATION;
mListener = listener;
}
public View show(ToolTip toolTip) {
View tipView = create(toolTip);
if (tipView == null) {
return null;
}
// animate tip visibility
AnimationUtils.popup(tipView, mAnimationDuration).start();
return tipView;
}
private View create(ToolTip toolTip) {
if (toolTip.getAnchorView() == null) {
Log.e(TAG, "Unable to create a tip, anchor view is null");
return null;
}
if (toolTip.getRootView() == null) {
Log.e(TAG, "Unable to create a tip, root layout is null");
return null;
}
// only one tip is allowed near an anchor view at the same time, thus
// reuse tip if already exist
if (mTipsMap.containsKey(toolTip.getAnchorView().getId())) {
return mTipsMap.get(toolTip.getAnchorView().getId());
}
// init tip view parameters
TextView tipView = createTipView(toolTip);
// on RTL languages replace sides
if (UiUtils.isRtl()) {
switchToolTipSidePosition(toolTip);
}
// set tool tip background / shape
ToolTipBackgroundConstructor.setBackground(tipView, toolTip);
// add tip to root layout
toolTip.getRootView().addView(tipView);
// find where to position the tool tip
Point p = ToolTipCoordinatesFinder.getCoordinates(tipView, toolTip);
// move tip view to correct position
moveTipToCorrectPosition(tipView, p);
// set dismiss on click
tipView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dismiss(view, true);
}
});
// bind tipView with anchorView id
int anchorViewId = toolTip.getAnchorView().getId();
tipView.setTag(anchorViewId);
// enter tip to map by 'anchorView' id
mTipsMap.put(anchorViewId, tipView);
return tipView;
}
private void moveTipToCorrectPosition(TextView tipView, Point p) {
Coordinates tipViewCoordinates = new Coordinates(tipView);
int translationX = p.x - tipViewCoordinates.left;
int translationY = p.y - tipViewCoordinates.top;
tipView.setTranslationX(!UiUtils.isRtl() ? translationX : -translationX);
tipView.setTranslationY(translationY);
}
@NonNull
private TextView createTipView(ToolTip toolTip) {
TextView tipView = new TextView(toolTip.getContext());
tipView.setTextColor(toolTip.getTextColor());
tipView.setTextSize(toolTip.getTextSize());
tipView.setText(toolTip.getMessage() != null ? toolTip.getMessage() : toolTip.getSpannableMessage());
tipView.setVisibility(View.INVISIBLE);
tipView.setGravity(toolTip.getTextGravity());
setTipViewElevation(tipView, toolTip);
return tipView;
}
private void setTipViewElevation(TextView tipView, ToolTip toolTip) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (toolTip.getElevation() > 0) {
ViewOutlineProvider viewOutlineProvider = new ViewOutlineProvider() {
@SuppressLint("NewApi")
@Override
public void getOutline(View view, Outline outline) {
outline.setEmpty();
}
};
tipView.setOutlineProvider(viewOutlineProvider);
tipView.setElevation(toolTip.getElevation());
}
}
}
private void switchToolTipSidePosition(ToolTip toolTip) {
if (toolTip.positionedLeftTo()) {
toolTip.setPosition(ToolTip.POSITION_RIGHT_TO);
} else if (toolTip.positionedRightTo()) {
toolTip.setPosition(ToolTip.POSITION_LEFT_TO);
}
}
public void setAnimationDuration(int duration){
mAnimationDuration = duration;
}
public boolean dismiss(View tipView, boolean byUser) {
if (tipView != null && isVisible(tipView)) {
int key = (int) tipView.getTag();
mTipsMap.remove(key);
animateDismiss(tipView, byUser);
return true;
}
return false;
}
public boolean dismiss(Integer key) {
return mTipsMap.containsKey(key) && dismiss(mTipsMap.get(key), false);
}
public View find(Integer key) {
if (mTipsMap.containsKey(key)) {
return mTipsMap.get(key);
}
return null;
}
public boolean findAndDismiss(final View anchorView) {
View view = find(anchorView.getId());
return view != null && dismiss(view, false);
}
public void clear() {
if (!mTipsMap.isEmpty()) {
for (Map.Entry<Integer, View> entry : mTipsMap.entrySet()) {
dismiss(entry.getValue(), false);
}
}
mTipsMap.clear();
}
private void animateDismiss(final View view, final boolean byUser) {
AnimationUtils.popout(view, mAnimationDuration, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mListener != null){
mListener.onTipDismissed(view, (Integer) view.getTag(), byUser);
}
}
}).start();
}
public boolean isVisible(View tipView) {
return tipView.getVisibility() == View.VISIBLE;
}
}