DroidFish: Use the TourGuide library to display usage hints the first

time the program is started.
This commit is contained in:
Peter Osterlund 2016-01-10 14:08:54 +01:00
parent 88074f5327
commit cbba46a6d5
31 changed files with 2136 additions and 39 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -26,6 +26,7 @@
layout="@layout/title">
</include>
<LinearLayout
android:id="@+id/buttons"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"

View File

@ -25,49 +25,55 @@
android:paddingTop="1dp">
<TextView
android:id="@+id/status"
android:layout_width="0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:includeFontPadding="true"
android:fontFamily="monospace"
android:typeface="monospace"
android:textSize="12sp"/>
<ImageButton
android:id="@+id/custom3Button"
android:contentDescription="@string/buttonDesc_custom3"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/custom2Button"
android:contentDescription="@string/buttonDesc_custom2"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/custom1Button"
android:contentDescription="@string/buttonDesc_custom1"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/modeButton"
android:contentDescription="@string/buttonDesc_mode"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/undoButton"
android:contentDescription="@string/buttonDesc_back"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/redoButton"
android:contentDescription="@string/buttonDesc_forward"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<LinearLayout
android:id="@+id/buttons"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/custom3Button"
android:contentDescription="@string/buttonDesc_custom3"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/custom2Button"
android:contentDescription="@string/buttonDesc_custom2"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/custom1Button"
android:contentDescription="@string/buttonDesc_custom1"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/modeButton"
android:contentDescription="@string/buttonDesc_mode"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/undoButton"
android:contentDescription="@string/buttonDesc_back"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
<ImageButton
android:id="@+id/redoButton"
android:contentDescription="@string/buttonDesc_forward"
android:layout_width="36dp"
android:layout_height="32dp">
</ImageButton>
</LinearLayout>
</LinearLayout>
<view
class="org.petero.droidfish.MyRelativeLayout"

View File

@ -21,6 +21,7 @@
layout="@layout/title">
</include>
<LinearLayout
android:id="@+id/buttons"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--<View-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="5dp"-->
<!--android:background="@drawable/shadow_upward" />-->
<LinearLayout
android:id="@+id/toolTip_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#e74c3c"
android:orientation="vertical"
android:gravity="center"
android:padding="10dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Default Title"
android:textColor="@color/White"
android:textSize="20sp" />
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Default Description"
android:textColor="@color/White" />
</LinearLayout>
</LinearLayout>

View File

@ -240,6 +240,12 @@
<li>
Syzygy tablebases probing code, Copyright © 2011-2013 Ronald de Man.
</li>
<li>
<a href="https://github.com/worker8/TourGuide">TourGuide</a> library, Copyright © 2015 Tan Jun Rong.
</li>
<li>
<a href="https://github.com/Tesla-Redux/android-floating-action-button">Floating action button</a> library, Copyright © 2014 str4d and Jerzy Chalupski.
</li>
</ul>
<h3>Translations</h3>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="default_normal">@android:color/holo_blue_dark</color>
<color name="default_pressed">@android:color/holo_blue_light</color>
<color name="default_disabled">@android:color/darker_gray</color>
<color name="White">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FloatingActionButton">
<attr name="fab_colorPressed" format="color"/>
<attr name="fab_colorDisabled" format="color"/>
<attr name="fab_colorNormal" format="color"/>
<attr name="fab_icon" format="reference"/>
<attr name="fab_size" format="enum">
<enum name="normal" value="0"/>
<enum name="mini" value="1"/>
</attr>
<attr name="fab_title" format="string"/>
<attr name="fab_stroke_visible" format="boolean"/>
</declare-styleable>
<declare-styleable name="AddFloatingActionButton">
<attr name="fab_plusIconColor" format="color"/>
</declare-styleable>
<declare-styleable name="FloatingActionsMenu">
<attr name="fab_addButtonColorPressed" format="color"/>
<attr name="fab_addButtonColorNormal" format="color"/>
<attr name="fab_addButtonSize" format="enum">
<enum name="normal" value="0"/>
<enum name="mini" value="1"/>
</attr>
<attr name="fab_addButtonPlusIconColor" format="color"/>
<attr name="fab_addButtonStrokeVisible" format="boolean"/>
<attr name="fab_labelStyle" format="reference"/>
<attr name="fab_labelsPosition" format="enum">
<enum name="left" value="0"/>
<enum name="right" value="1"/>
</attr>
<attr name="fab_expandDirection" format="enum">
<enum name="up" value="0"/>
<enum name="down" value="1"/>
<enum name="left" value="2"/>
<enum name="right" value="3"/>
</attr>
</declare-styleable>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="default_normal">#ff0099cc</color><!-- android.R.color.holo_blue_dark -->
<color name="default_pressed">#ff33b5e5</color><!-- android.R.color.holo_blue_light -->
<color name="default_disabled">#aaa</color><!-- android.R.color.darker_gray -->
<color name="White">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,17 @@
<resources>
<dimen name="fab_size_normal">56dp</dimen>
<dimen name="fab_size_mini">40dp</dimen>
<dimen name="fab_icon_size">24dp</dimen>
<dimen name="fab_plus_icon_size">14dp</dimen>
<dimen name="fab_plus_icon_stroke">2dp</dimen>
<dimen name="fab_shadow_offset">3dp</dimen>
<dimen name="fab_shadow_radius">9dp</dimen>
<dimen name="fab_stroke_width">1dp</dimen>
<dimen name="fab_actions_spacing">16dp</dimen>
<dimen name="fab_labels_margin">8dp</dimen>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="fab_expand_menu_button" type="id"/>
<item name="fab_label" type="id"/>
</resources>

View File

@ -413,6 +413,20 @@ you are not actively using the program.\
<string name="prefs_rtbPath_summary">Directory where Syzygy tablebases are installed. Leave blank to use default directory</string>
<string name="prefs_rtbPathNet_title">Syzygy Network Directory</string>
<string name="prefs_rtbPathNet_summary">Directory for network engines where Syzygy tablebases are installed.</string>
<string name="prefs_guideShowOnStart_title">Startup Guide</string>
<string name="prefs_guideShowOnStart_summary">Show the startup guide the next time the program is started.</string>
<string name="tour_leftMenu_title">Left Menu</string>
<string name="tour_leftMenu_desc">To open the left menu, tap on the left side of the title bar or swipe from the left side of the screen towards the right side.</string>
<string name="tour_rightMenu_title">Right Menu</string>
<string name="tour_rightMenu_desc">To open the right menu, tap on the right side of the title bar or swipe from the right side of the screen towards the left side.</string>
<string name="tour_chessBoard_title">Chess Board</string>
<string name="tour_chessBoard_desc">Touch and hold the chess board to open the tools menu.</string>
<string name="tour_buttons_title">Buttons</string>
<string name="tour_buttons_desc">Tap a button to invoke its action. Touch and hold a button to open a menu containing secondary actions. To configure button actions go to Left Menu &gt; Settings &gt; Behavior &gt; Configure Buttons.</string>
<string name="tour_moveList_title">Move List</string>
<string name="tour_moveList_desc">Tap a move in the move list to set the chess board to the corresponding position. Touch and hold the move list to open the Edit Game menu.</string>
<string name="tour_analysis_title">Analysis information</string>
<string name="tour_analysis_desc">When the engine is thinking, touch and hold the analysis information to open the Analysis menu.</string>
<string name="buttonDesc_custom1">@string/prefs_custom_button_1</string>
<string name="buttonDesc_custom2">@string/prefs_custom_button_2</string>
<string name="buttonDesc_custom3">@string/prefs_custom_button_3</string>

View File

@ -518,6 +518,12 @@
</PreferenceCategory>
<PreferenceCategory
android:title="@string/prefs_other">
<CheckBoxPreference
android:key="guideShowOnStart"
android:title="@string/prefs_guideShowOnStart_title"
android:summary="@string/prefs_guideShowOnStart_summary"
android:defaultValue="true">
</CheckBoxPreference>
<PreferenceScreen
android:key="bookSettings"
android:title="@string/prefs_bookSettings_title"

View File

@ -0,0 +1,431 @@
package net.i2p.android.ext.floatingactionbutton;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.ShapeDrawable.ShaderFactory;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.widget.ImageButton;
import android.widget.TextView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.petero.droidfish.R;
public class FloatingActionButton extends ImageButton {
public static final int SIZE_NORMAL = 0;
public static final int SIZE_MINI = 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ SIZE_NORMAL, SIZE_MINI })
public @interface FAB_SIZE {
}
int mColorNormal;
int mColorPressed;
int mColorDisabled;
String mTitle;
@DrawableRes
private int mIcon;
private Drawable mIconDrawable;
private int mSize;
private float mCircleSize;
private float mShadowRadius;
private float mShadowOffset;
private int mDrawableSize;
boolean mStrokeVisible;
public FloatingActionButton(Context context) {
this(context, null);
}
public FloatingActionButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
void init(Context context, AttributeSet attributeSet) {
TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionButton, 0, 0);
mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal, getColor(R.color.default_normal));
mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, getColor(R.color.default_pressed));
mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, getColor(R.color.default_disabled));
mSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL);
mIcon = attr.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0);
mTitle = attr.getString(R.styleable.FloatingActionButton_fab_title);
mStrokeVisible = attr.getBoolean(R.styleable.FloatingActionButton_fab_stroke_visible, true);
attr.recycle();
updateCircleSize();
mShadowRadius = getDimension(R.dimen.fab_shadow_radius);
mShadowOffset = getDimension(R.dimen.fab_shadow_offset);
updateDrawableSize();
updateBackground();
}
private void updateDrawableSize() {
mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius);
}
private void updateCircleSize() {
mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini);
}
public void setSize(@FAB_SIZE int size) {
if (size != SIZE_MINI && size != SIZE_NORMAL) {
throw new IllegalArgumentException("Use @FAB_SIZE constants only!");
}
if (mSize != size) {
mSize = size;
updateCircleSize();
updateDrawableSize();
updateBackground();
}
}
@FAB_SIZE
public int getSize() {
return mSize;
}
public void setIcon(@DrawableRes int icon) {
if (mIcon != icon) {
mIcon = icon;
mIconDrawable = null;
updateBackground();
}
}
public void setIconDrawable(@NonNull Drawable iconDrawable) {
if (mIconDrawable != iconDrawable) {
mIcon = 0;
mIconDrawable = iconDrawable;
updateBackground();
}
}
/**
* @return the current Color for normal state.
*/
public int getColorNormal() {
return mColorNormal;
}
public void setColorNormalResId(@ColorRes int colorNormal) {
setColorNormal(getColor(colorNormal));
}
public void setColorNormal(int color) {
if (mColorNormal != color) {
mColorNormal = color;
updateBackground();
}
}
/**
* @return the current color for pressed state.
*/
public int getColorPressed() {
return mColorPressed;
}
public void setColorPressedResId(@ColorRes int colorPressed) {
setColorPressed(getColor(colorPressed));
}
public void setColorPressed(int color) {
if (mColorPressed != color) {
mColorPressed = color;
updateBackground();
}
}
/**
* @return the current color for disabled state.
*/
public int getColorDisabled() {
return mColorDisabled;
}
public void setColorDisabledResId(@ColorRes int colorDisabled) {
setColorDisabled(getColor(colorDisabled));
}
public void setColorDisabled(int color) {
if (mColorDisabled != color) {
mColorDisabled = color;
updateBackground();
}
}
public void setStrokeVisible(boolean visible) {
if (mStrokeVisible != visible) {
mStrokeVisible = visible;
updateBackground();
}
}
public boolean isStrokeVisible() {
return mStrokeVisible;
}
int getColor(@ColorRes int id) {
return getResources().getColor(id);
}
float getDimension(@DimenRes int id) {
return getResources().getDimension(id);
}
public void setTitle(String title) {
mTitle = title;
TextView label = getLabelView();
if (label != null) {
label.setText(title);
}
}
TextView getLabelView() {
return (TextView) getTag(R.id.fab_label);
}
public String getTitle() {
return mTitle;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mDrawableSize, mDrawableSize);
}
void updateBackground() {
final float strokeWidth = getDimension(R.dimen.fab_stroke_width);
final float halfStrokeWidth = strokeWidth / 2f;
LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[] {
getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.fab_bg_normal : R.drawable.fab_bg_mini),
createFillDrawable(strokeWidth),
createOuterStrokeDrawable(strokeWidth),
getIconDrawable()
});
int iconOffset = (int) (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2;
int circleInsetHorizontal = (int) (mShadowRadius);
int circleInsetTop = (int) (mShadowRadius - mShadowOffset);
int circleInsetBottom = (int) (mShadowRadius + mShadowOffset);
layerDrawable.setLayerInset(1,
circleInsetHorizontal,
circleInsetTop,
circleInsetHorizontal,
circleInsetBottom);
layerDrawable.setLayerInset(2,
(int) (circleInsetHorizontal - halfStrokeWidth),
(int) (circleInsetTop - halfStrokeWidth),
(int) (circleInsetHorizontal - halfStrokeWidth),
(int) (circleInsetBottom - halfStrokeWidth));
layerDrawable.setLayerInset(3,
circleInsetHorizontal + iconOffset,
circleInsetTop + iconOffset,
circleInsetHorizontal + iconOffset,
circleInsetBottom + iconOffset);
setBackgroundCompat(layerDrawable);
}
Drawable getIconDrawable() {
if (mIconDrawable != null) {
return mIconDrawable;
} else if (mIcon != 0) {
return getResources().getDrawable(mIcon);
} else {
return new ColorDrawable(Color.TRANSPARENT);
}
}
private StateListDrawable createFillDrawable(float strokeWidth) {
StateListDrawable drawable = new StateListDrawable();
drawable.addState(new int[] { -android.R.attr.state_enabled }, createCircleDrawable(mColorDisabled, strokeWidth));
drawable.addState(new int[] { android.R.attr.state_pressed }, createCircleDrawable(mColorPressed, strokeWidth));
drawable.addState(new int[] { }, createCircleDrawable(mColorNormal, strokeWidth));
return drawable;
}
private Drawable createCircleDrawable(int color, float strokeWidth) {
int alpha = Color.alpha(color);
int opaqueColor = opaque(color);
ShapeDrawable fillDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = fillDrawable.getPaint();
paint.setAntiAlias(true);
paint.setColor(opaqueColor);
Drawable[] layers = {
fillDrawable,
createInnerStrokesDrawable(opaqueColor, strokeWidth)
};
LayerDrawable drawable = alpha == 255 || !mStrokeVisible
? new LayerDrawable(layers)
: new TranslucentLayerDrawable(alpha, layers);
int halfStrokeWidth = (int) (strokeWidth / 2f);
drawable.setLayerInset(1, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth);
return drawable;
}
private static class TranslucentLayerDrawable extends LayerDrawable {
private final int mAlpha;
public TranslucentLayerDrawable(int alpha, Drawable... layers) {
super(layers);
mAlpha = alpha;
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, mAlpha, Canvas.ALL_SAVE_FLAG);
super.draw(canvas);
canvas.restore();
}
}
private Drawable createOuterStrokeDrawable(float strokeWidth) {
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
paint.setColor(Color.BLACK);
paint.setAlpha(opacityToAlpha(0.02f));
return shapeDrawable;
}
private int opacityToAlpha(float opacity) {
return (int) (255f * opacity);
}
private int darkenColor(int argb) {
return adjustColorBrightness(argb, 0.9f);
}
private int lightenColor(int argb) {
return adjustColorBrightness(argb, 1.1f);
}
private int adjustColorBrightness(int argb, float factor) {
float[] hsv = new float[3];
Color.colorToHSV(argb, hsv);
hsv[2] = Math.min(hsv[2] * factor, 1f);
return Color.HSVToColor(Color.alpha(argb), hsv);
}
private int halfTransparent(int argb) {
return Color.argb(
Color.alpha(argb) / 2,
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private int opaque(int argb) {
return Color.rgb(
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private Drawable createInnerStrokesDrawable(final int color, float strokeWidth) {
if (!mStrokeVisible) {
return new ColorDrawable(Color.TRANSPARENT);
}
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final int bottomStrokeColor = darkenColor(color);
final int bottomStrokeColorHalfTransparent = halfTransparent(bottomStrokeColor);
final int topStrokeColor = lightenColor(color);
final int topStrokeColorHalfTransparent = halfTransparent(topStrokeColor);
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
shapeDrawable.setShaderFactory(new ShaderFactory() {
@Override
public Shader resize(int width, int height) {
return new LinearGradient(width / 2, 0, width / 2, height,
new int[] { topStrokeColor, topStrokeColorHalfTransparent, color, bottomStrokeColorHalfTransparent, bottomStrokeColor },
new float[] { 0f, 0.2f, 0.5f, 0.8f, 1f },
TileMode.CLAMP
);
}
});
return shapeDrawable;
}
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
private void setBackgroundCompat(Drawable drawable) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
setBackground(drawable);
} else {
setBackgroundDrawable(drawable);
}
}
@Override
public void setVisibility(int visibility) {
TextView label = getLabelView();
if (label != null) {
label.setVisibility(visibility);
}
super.setVisibility(visibility);
}
}

View File

@ -60,6 +60,12 @@ import org.petero.droidfish.gamelogic.TimeControlData;
import org.petero.droidfish.tb.Probe;
import org.petero.droidfish.tb.ProbeResult;
import tourguide.tourguide.Overlay;
import tourguide.tourguide.Pointer;
import tourguide.tourguide.Sequence;
import tourguide.tourguide.ToolTip;
import tourguide.tourguide.TourGuide;
import com.kalab.chess.enginesupport.ChessEngine;
import com.kalab.chess.enginesupport.ChessEngineResolver;
import com.larvalabs.svgandroid.SVG;
@ -191,7 +197,9 @@ public class DroidFish extends Activity
private TextView status;
private ScrollView moveListScroll;
private MoveListView moveList;
private View thinkingScroll;
private TextView thinking;
private View buttons;
private ImageButton custom1Button, custom2Button, custom3Button;
private ImageButton modeButton, undoButton, redoButton;
private ButtonActions custom1ButtonActions, custom2ButtonActions, custom3ButtonActions;
@ -254,6 +262,9 @@ public class DroidFish extends Activity
private Typeface figNotation;
private Typeface defaultThinkingListTypeFace;
private boolean guideShowOnStart;
private TourGuide tourGuide;
/** Defines all configurable button actions. */
private ActionFactory actionFactory = new ActionFactory() {
@ -486,6 +497,94 @@ public class DroidFish extends Activity
else
loadPGNFromFile(intentFilename);
}
startTourGuide();
}
private void startTourGuide(){
if (!guideShowOnStart)
return;
tourGuide = TourGuide.init(this);
ArrayList<TourGuide> guides = new ArrayList<TourGuide>();
TourGuide tg = TourGuide.init(this);
tg.setToolTip(new ToolTip()
.setTitle(getString(R.string.tour_leftMenu_title))
.setDescription(getString(R.string.tour_leftMenu_desc))
.setGravity(Gravity.BOTTOM | Gravity.RIGHT));
tg.playLater(whiteTitleText);
guides.add(tg);
tg = TourGuide.init(this);
tg.setToolTip(new ToolTip()
.setTitle(getString(R.string.tour_rightMenu_title))
.setDescription(getString(R.string.tour_rightMenu_desc))
.setGravity(Gravity.BOTTOM | Gravity.LEFT));
tg.playLater(blackTitleText);
guides.add(tg);
tg = TourGuide.init(this);
int gravity = !landScapeView() ? Gravity.BOTTOM : leftHandedView() ? Gravity.LEFT : Gravity.RIGHT;
tg.setToolTip(new ToolTip()
.setTitle(getString(R.string.tour_chessBoard_title))
.setDescription(getString(R.string.tour_chessBoard_desc))
.setGravity(gravity));
tg.playLater(cb);
guides.add(tg);
tg = TourGuide.init(this);
gravity = !landScapeView() ? Gravity.TOP : Gravity.BOTTOM;
tg.setToolTip(new ToolTip()
.setTitle(getString(R.string.tour_buttons_title))
.setDescription(getString(R.string.tour_buttons_desc))
.setGravity(gravity));
tg.playLater(buttons);
guides.add(tg);
tg = TourGuide.init(this);
gravity = !landScapeView() ? Gravity.TOP : leftHandedView() ? Gravity.RIGHT : Gravity.LEFT;
tg.setToolTip(new ToolTip()
.setTitle(getString(R.string.tour_moveList_title))
.setDescription(getString(R.string.tour_moveList_desc))
.setGravity(gravity));
tg.playLater(moveListScroll);
guides.add(tg);
tg = TourGuide.init(this);
tg.setToolTip(new ToolTip()
.setTitle(getString(R.string.tour_analysis_title))
.setDescription(getString(R.string.tour_analysis_desc))
.setGravity(Gravity.TOP));
tg.playLater(thinkingScroll);
guides.add(tg);
tg.setOverlay(new Overlay()
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
guideShowOnStart = false;
Editor editor = settings.edit();
editor.putBoolean("guideShowOnStart", false);
editor.commit();
tourGuide.next();
tourGuide = null;
}
}));
Sequence sequence = new Sequence.SequenceBuilder()
.add(guides.toArray(new TourGuide[guides.size()]))
.setDefaultOverlay(new Overlay()
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tourGuide.next();
}
}))
.setDefaultPointer(new Pointer())
.setContinueMethod(Sequence.ContinueMethod.OverlayListener)
.build();
tourGuide.playInSequence(sequence);
}
// Unicode code points for chess pieces
@ -664,12 +763,20 @@ public class DroidFish extends Activity
updateThinkingInfo();
ctrl.updateRemainingTime();
ctrl.updateMaterialDiffList();
if (tourGuide != null) {
tourGuide.cleanUp();
tourGuide = null;
}
}
/** Return true if the current orientation is landscape. */
private final boolean landScapeView() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}
/** Return true if left-handed layout should be used. */
private final boolean leftHandedView() {
return settings.getBoolean("leftHanded", false) &&
(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
return settings.getBoolean("leftHanded", false) && landScapeView();
}
/** Re-read preferences settings. */
@ -708,6 +815,7 @@ public class DroidFish extends Activity
status = (TextView)findViewById(R.id.status);
moveListScroll = (ScrollView)findViewById(R.id.scrollView);
moveList = (MoveListView)findViewById(R.id.moveList);
thinkingScroll = (View)findViewById(R.id.scrollViewBot);
thinking = (TextView)findViewById(R.id.thinking);
defaultThinkingListTypeFace = thinking.getTypeface();
status.setFocusable(false);
@ -909,6 +1017,7 @@ public class DroidFish extends Activity
}
});
buttons = (View)findViewById(R.id.buttons);
custom1Button = (ImageButton)findViewById(R.id.custom1Button);
custom1ButtonActions.setImageButton(custom1Button, this);
custom2Button = (ImageButton)findViewById(R.id.custom2Button);
@ -1078,6 +1187,8 @@ public class DroidFish extends Activity
custom3ButtonActions.readPrefs(settings, actionFactory);
updateButtons();
guideShowOnStart = settings.getBoolean("guideShowOnStart", true);
bookOptions.filename = settings.getString("bookFile", "");
bookOptions.maxLength = getIntSetting("bookMaxLength", 1000000);
bookOptions.preferMainLines = settings.getBoolean("bookPreferMainLines", false);

View File

@ -0,0 +1,311 @@
package tourguide.tourguide;
import android.animation.AnimatorSet;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.support.v4.view.MotionEventCompat;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.FrameLayout;
import java.util.ArrayList;
/**
* TODO: document your custom view class.
*/
public class FrameLayoutWithHole extends FrameLayout {
private TextPaint mTextPaint;
private Activity mActivity;
private TourGuide.MotionType mMotionType;
private Paint mEraser;
Bitmap mEraserBitmap;
private Canvas mEraserCanvas;
private Paint mPaint;
private Paint transparentPaint;
private View mViewHole; // This is the targeted view to be highlighted, where the hole should be placed
private int mRadius;
private int [] mPos;
private float mDensity;
private Overlay mOverlay;
private ArrayList<AnimatorSet> mAnimatorSetArrayList;
public void setViewHole(View viewHole) {
this.mViewHole = viewHole;
enforceMotionType();
}
public void addAnimatorSet(AnimatorSet animatorSet){
if (mAnimatorSetArrayList==null){
mAnimatorSetArrayList = new ArrayList<AnimatorSet>();
}
mAnimatorSetArrayList.add(animatorSet);
}
private void enforceMotionType(){
Log.d("tourguide", "enforceMotionType 1");
if (mViewHole!=null) {Log.d("tourguide","enforceMotionType 2");
if (mMotionType!=null && mMotionType == TourGuide.MotionType.ClickOnly) {
Log.d("tourguide","enforceMotionType 3");
Log.d("tourguide","only Clicking");
mViewHole.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mViewHole.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
} else if (mMotionType!=null && mMotionType == TourGuide.MotionType.SwipeOnly) {
Log.d("tourguide","enforceMotionType 4");
Log.d("tourguide","only Swiping");
mViewHole.setClickable(false);
}
}
}
public FrameLayoutWithHole(Activity context, View view) {
this(context, view, TourGuide.MotionType.AllowAll);
}
public FrameLayoutWithHole(Activity context, View view, TourGuide.MotionType motionType) {
this(context, view, motionType, new Overlay());
}
public FrameLayoutWithHole(Activity context, View view, TourGuide.MotionType motionType, Overlay overlay) {
super(context);
mActivity = context;
mViewHole = view;
init(null, 0);
enforceMotionType();
mOverlay = overlay;
int [] pos = new int[2];
mViewHole.getLocationOnScreen(pos);
mPos = pos;
mDensity = context.getResources().getDisplayMetrics().density;
int padding = (int)(20 * mDensity);
if (mViewHole.getHeight() > mViewHole.getWidth()) {
mRadius = mViewHole.getHeight()/2 + padding;
} else {
mRadius = mViewHole.getWidth()/2 + padding;
}
mMotionType = motionType;
}
private void init(AttributeSet attrs, int defStyle) {
// Load attributes
// final TypedArray a = getContext().obtainStyledAttributes(
// attrs, FrameLayoutWithHole, defStyle, 0);
//
//
// a.recycle();
setWillNotDraw(false);
// Set up a default TextPaint object
mTextPaint = new TextPaint();
mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextAlign(Paint.Align.LEFT);
Point size = new Point();
size.x = mActivity.getResources().getDisplayMetrics().widthPixels;
size.y = mActivity.getResources().getDisplayMetrics().heightPixels;
mEraserBitmap = Bitmap.createBitmap(size.x, size.y, Bitmap.Config.ARGB_8888);
mEraserCanvas = new Canvas(mEraserBitmap);
mPaint = new Paint();
mPaint.setColor(0xcc000000);
transparentPaint = new Paint();
transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mEraser = new Paint();
mEraser.setColor(0xFFFFFFFF);
mEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mEraser.setFlags(Paint.ANTI_ALIAS_FLAG);
Log.d("tourguide","getHeight: "+ size.y);
Log.d("tourguide","getWidth: " + size.x);
}
private boolean mCleanUpLock = false;
protected void cleanUp(){
if (getParent() != null) {
if (mOverlay!=null && mOverlay.mExitAnimation!=null) {
performOverlayExitAnimation();
} else {
((ViewGroup) this.getParent()).removeView(this);
}
}
}
private void performOverlayExitAnimation(){
if (!mCleanUpLock) {
final FrameLayout _pointerToFrameLayout = this;
mCleanUpLock = true;
Log.d("tourguide","Overlay exit animation listener is overwritten...");
mOverlay.mExitAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override public void onAnimationStart(Animation animation) {}
@Override public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
((ViewGroup) _pointerToFrameLayout.getParent()).removeView(_pointerToFrameLayout);
}
});
this.startAnimation(mOverlay.mExitAnimation);
}
}
/* comment this whole method to cause a memory leak */
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
/* cleanup reference to prevent memory leak */
mEraserCanvas.setBitmap(null);
mEraserBitmap = null;
if (mAnimatorSetArrayList != null && mAnimatorSetArrayList.size() > 0){
for(int i=0;i<mAnimatorSetArrayList.size();i++){
mAnimatorSetArrayList.get(i).end();
mAnimatorSetArrayList.get(i).removeAllListeners();
}
}
}
/** Show an event in the LogCat view, for debugging */
private void dumpEvent(MotionEvent event) {
String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" ,
"POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" };
StringBuilder sb = new StringBuilder();
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_" ).append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN
|| actionCode == MotionEvent.ACTION_POINTER_UP) {
sb.append("(pid " ).append(
action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")" );
}
sb.append("[" );
for (int i = 0; i < event.getPointerCount(); i++) {
sb.append("#" ).append(i);
sb.append("(pid " ).append(event.getPointerId(i));
sb.append(")=" ).append((int) event.getX(i));
sb.append("," ).append((int) event.getY(i));
if (i + 1 < event.getPointerCount())
sb.append(";" );
}
sb.append("]" );
Log.d("tourguide", sb.toString());
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//first check if the location button should handle the touch event
dumpEvent(ev);
int action = MotionEventCompat.getActionMasked(ev);
if(mViewHole != null) {
int[] pos = new int[2];
mViewHole.getLocationOnScreen(pos);
Log.d("tourguide", "[dispatchTouchEvent] mViewHole.getHeight(): "+mViewHole.getHeight());
Log.d("tourguide", "[dispatchTouchEvent] mViewHole.getWidth(): "+mViewHole.getWidth());
Log.d("tourguide", "[dispatchTouchEvent] Touch X(): "+ev.getRawX());
Log.d("tourguide", "[dispatchTouchEvent] Touch Y(): "+ev.getRawY());
// Log.d("tourguide", "[dispatchTouchEvent] X of image: "+pos[0]);
// Log.d("tourguide", "[dispatchTouchEvent] Y of image: "+pos[1]);
Log.d("tourguide", "[dispatchTouchEvent] X lower bound: "+ pos[0]);
Log.d("tourguide", "[dispatchTouchEvent] X higher bound: "+(pos[0] +mViewHole.getWidth()));
Log.d("tourguide", "[dispatchTouchEvent] Y lower bound: "+ pos[1]);
Log.d("tourguide", "[dispatchTouchEvent] Y higher bound: "+(pos[1] +mViewHole.getHeight()));
if(ev.getRawY() >= pos[1] && ev.getRawY() <= (pos[1] + mViewHole.getHeight()) && ev.getRawX() >= pos[0] && ev.getRawX() <= (pos[0] + mViewHole.getWidth())) { //location button event
Log.d("tourguide","to the BOTTOM!");
Log.d("tourguide",""+ev.getAction());
// switch(action) {
// case (MotionEvent.ACTION_DOWN) :
// Log.d("tourguide","Action was DOWN");
// return false;
// case (MotionEvent.ACTION_MOVE) :
// Log.d("tourguide","Action was MOVE");
// return true;
// case (MotionEvent.ACTION_UP) :
// Log.d("tourguide","Action was UP");
//// ev.setAction(MotionEvent.ACTION_DOWN|MotionEvent.ACTION_UP);
//// return super.dispatchTouchEvent(ev);
// return false;
// case (MotionEvent.ACTION_CANCEL) :
// Log.d("tourguide","Action was CANCEL");
// return true;
// case (MotionEvent.ACTION_OUTSIDE) :
// Log.d("tourguide","Movement occurred outside bounds " +
// "of current screen element");
// return true;
// default :
// return super.dispatchTouchEvent(ev);
// }
// return mViewHole.onTouchEvent(ev);
return false;
}
}
return super.dispatchTouchEvent(ev);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mEraserBitmap.eraseColor(Color.TRANSPARENT);
if (mOverlay!=null) {
mEraserCanvas.drawColor(mOverlay.mBackgroundColor);
int padding = (int) (10 * mDensity);
if (mOverlay.mStyle == Overlay.Style.Rectangle) {
mEraserCanvas.drawRect(mPos[0] - padding, mPos[1] - padding, mPos[0] + mViewHole.getWidth() + padding, mPos[1] + mViewHole.getHeight() + padding, mEraser);
} else {
mEraserCanvas.drawCircle(mPos[0] + mViewHole.getWidth() / 2, mPos[1] + mViewHole.getHeight() / 2, mRadius, mEraser);
}
}
canvas.drawBitmap(mEraserBitmap, 0, 0, null);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mOverlay!=null && mOverlay.mEnterAnimation!=null) {
this.startAnimation(mOverlay.mEnterAnimation);
}
}
/**
*
* Convenient method to obtain screen width in pixel
*
* @param activity
* @return screen width in pixel
*/
public int getScreenWidth(Activity activity){
return activity.getResources().getDisplayMetrics().widthPixels;
}
/**
*
* Convenient method to obtain screen height in pixel
*
* @param activity
* @return screen width in pixel
*/
public int getScreenHeight(Activity activity){
return activity.getResources().getDisplayMetrics().heightPixels;
}
}

View File

@ -0,0 +1,83 @@
package tourguide.tourguide;
import android.graphics.Color;
import android.view.View;
import android.view.animation.Animation;
/**
* Created by tanjunrong on 6/20/15.
*/
public class Overlay {
public int mBackgroundColor;
public boolean mDisableClick;
public Style mStyle;
public Animation mEnterAnimation, mExitAnimation;
public View.OnClickListener mOnClickListener;
public enum Style {
Circle, Rectangle
}
public Overlay() {
this(true, Color.parseColor("#55000000"), Style.Circle);
}
public Overlay(boolean disableClick, int backgroundColor, Style style) {
mDisableClick = disableClick;
mBackgroundColor = backgroundColor;
mStyle = style;
}
/**
* Set background color
* @param backgroundColor
* @return return ToolTip instance for chaining purpose
*/
public Overlay setBackgroundColor(int backgroundColor){
mBackgroundColor = backgroundColor;
return this;
}
/**
* Set to true if you want to block all user input to pass through this overlay, set to false if you want to allow user input under the overlay
* @param yes_no
* @return return Overlay instance for chaining purpose
*/
public Overlay disableClick(boolean yes_no){
mDisableClick = yes_no;
return this;
}
public Overlay setStyle(Style style){
mStyle = style;
return this;
}
/**
* Set enter animation
* @param enterAnimation
* @return return Overlay instance for chaining purpose
*/
public Overlay setEnterAnimation(Animation enterAnimation){
mEnterAnimation = enterAnimation;
return this;
}
/**
* Set exit animation
* @param exitAnimation
* @return return Overlay instance for chaining purpose
*/
public Overlay setExitAnimation(Animation exitAnimation){
mExitAnimation = exitAnimation;
return this;
}
/**
* Set onClickListener for the Overlay
* @param onClickListener
* @return return Overlay instance for chaining purpose
*/
public Overlay setOnClickListener(View.OnClickListener onClickListener){
mOnClickListener=onClickListener;
return this;
}
}

View File

@ -0,0 +1,41 @@
package tourguide.tourguide;
import android.graphics.Color;
import android.view.Gravity;
/**
* Created by tanjunrong on 6/20/15.
*/
public class Pointer {
public int mGravity = Gravity.CENTER;
public int mColor = Color.WHITE;
public Pointer() {
this(Gravity.CENTER, Color.parseColor("#FFFFFF"));
}
public Pointer(int gravity, int color) {
this.mGravity = gravity;
this.mColor = color;
}
/**
* Set color
* @param color
* @return return Pointer instance for chaining purpose
*/
public Pointer setColor(int color){
mColor = color;
return this;
}
/**
* Set gravity
* @param gravity
* @return return Pointer instance for chaining purpose
*/
public Pointer setGravity(int gravity){
mGravity = gravity;
return this;
}
}

View File

@ -0,0 +1,219 @@
package tourguide.tourguide;
import android.view.View;
/**
* Created by aaronliew on 8/7/15.
*/
public class Sequence {
TourGuide [] mTourGuideArray;
Overlay mDefaultOverlay;
ToolTip mDefaultToolTip;
Pointer mDefaultPointer;
ContinueMethod mContinueMethod;
boolean mDisableTargetButton;
public int mCurrentSequence;
TourGuide mParentTourGuide;
public enum ContinueMethod {
Overlay, OverlayListener
}
private Sequence(SequenceBuilder builder){
this.mTourGuideArray = builder.mTourGuideArray;
this.mDefaultOverlay = builder.mDefaultOverlay;
this.mDefaultToolTip = builder.mDefaultToolTip;
this.mDefaultPointer = builder.mDefaultPointer;
this.mContinueMethod = builder.mContinueMethod;
this.mCurrentSequence = builder.mCurrentSequence;
// TODO: to be implemented
this.mDisableTargetButton = builder.mDisableTargetButton;
}
/**
* sets the parent TourGuide that will run this Sequence
*/
protected void setParentTourGuide(TourGuide parentTourGuide){
mParentTourGuide = parentTourGuide;
if(mContinueMethod == ContinueMethod.Overlay) {
for (final TourGuide tourGuide : mTourGuideArray) {
tourGuide.mOverlay.mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
mParentTourGuide.next();
}
};
}
}
}
public TourGuide getNextTourGuide() {
return mTourGuideArray[mCurrentSequence];
}
public ContinueMethod getContinueMethod() {
return mContinueMethod;
}
public TourGuide[] getTourGuideArray() {
return mTourGuideArray;
}
public Overlay getDefaultOverlay() {
return mDefaultOverlay;
}
public ToolTip getDefaultToolTip() {
return mDefaultToolTip;
}
public ToolTip getToolTip() {
// individual tour guide has higher priority
if (mTourGuideArray[mCurrentSequence].mToolTip != null){
return mTourGuideArray[mCurrentSequence].mToolTip;
} else {
return mDefaultToolTip;
}
}
public Overlay getOverlay() {
// Overlay is used as a method to proceed to next TourGuide, so the default overlay is already assigned appropriately if needed
return mTourGuideArray[mCurrentSequence].mOverlay;
}
public Pointer getPointer() {
// individual tour guide has higher priority
if (mTourGuideArray[mCurrentSequence].mPointer != null){
return mTourGuideArray[mCurrentSequence].mPointer;
} else {
return mDefaultPointer;
}
}
public static class SequenceBuilder {
TourGuide [] mTourGuideArray;
Overlay mDefaultOverlay;
ToolTip mDefaultToolTip;
Pointer mDefaultPointer;
ContinueMethod mContinueMethod;
int mCurrentSequence;
boolean mDisableTargetButton;
public SequenceBuilder add(TourGuide... tourGuideArray){
mTourGuideArray = tourGuideArray;
return this;
}
public SequenceBuilder setDefaultOverlay(Overlay defaultOverlay){
mDefaultOverlay = defaultOverlay;
return this;
}
// This might not be useful, but who knows.. maybe someone needs it
public SequenceBuilder setDefaultToolTip(ToolTip defaultToolTip){
mDefaultToolTip = defaultToolTip;
return this;
}
public SequenceBuilder setDefaultPointer(Pointer defaultPointer){
mDefaultPointer = defaultPointer;
return this;
}
// TODO: this is an uncompleted feature, make it private first
// This is intended to be used to disable the button, so people cannot click on in during a Tour, instead, people can only click on Next button or Overlay to proceed
private SequenceBuilder setDisableButton(boolean disableTargetButton){
mDisableTargetButton = disableTargetButton;
return this;
}
/**
* @param continueMethod ContinueMethod.Overlay or ContinueMethod.OverlayListener
* ContnueMethod.Overlay - clicking on Overlay will make TourGuide proceed to the next one.
* ContinueMethod.OverlayListener - you need to provide OverlayListeners, and call tourGuideHandler.next() in the listener to proceed to the next one.
*/
public SequenceBuilder setContinueMethod(ContinueMethod continueMethod){
mContinueMethod = continueMethod;
return this;
}
public Sequence build(){
mCurrentSequence = 0;
checkIfContinueMethodNull();
checkAtLeastTwoTourGuideSupplied();
checkOverlayListener(mContinueMethod);
return new Sequence(this);
}
private void checkIfContinueMethodNull(){
if (mContinueMethod == null){
throw new IllegalArgumentException("Continue Method is not set. Please provide ContinueMethod in setContinueMethod");
}
}
private void checkAtLeastTwoTourGuideSupplied() {
if (mTourGuideArray == null || mTourGuideArray.length <= 1){
throw new IllegalArgumentException("In order to run a sequence, you must at least supply 2 TourGuide into Sequence using add()");
}
}
private void checkOverlayListener(ContinueMethod continueMethod) {
if(continueMethod == ContinueMethod.OverlayListener){
boolean pass = true;
if (mDefaultOverlay != null && mDefaultOverlay.mOnClickListener != null) {
pass = true;
// when default listener is available, we loop through individual tour guide, and
// assign default listener to individual tour guide
for (TourGuide tourGuide : mTourGuideArray) {
if (tourGuide.mOverlay == null) {
tourGuide.mOverlay = mDefaultOverlay;
}
if (tourGuide.mOverlay != null && tourGuide.mOverlay.mOnClickListener == null) {
tourGuide.mOverlay.mOnClickListener = mDefaultOverlay.mOnClickListener;
}
}
} else { // case where: default listener is not available
for (TourGuide tourGuide : mTourGuideArray) {
//Both of the overlay and default listener is not null, throw the error
if (tourGuide.mOverlay != null && tourGuide.mOverlay.mOnClickListener == null) {
pass = false;
break;
} else if (tourGuide.mOverlay == null){
pass = false;
break;
}
}
}
if (!pass){
throw new IllegalArgumentException("ContinueMethod.OverlayListener is chosen as the ContinueMethod, but no Default Overlay Listener is set, or not all Overlay.mListener is set for all the TourGuide passed in.");
}
} else if(continueMethod == ContinueMethod.Overlay){
// when Overlay ContinueMethod is used, listener must not be supplied (to avoid unexpected result)
boolean pass = true;
if (mDefaultOverlay != null && mDefaultOverlay.mOnClickListener != null) {
pass = false;
} else {
for (TourGuide tourGuide : mTourGuideArray) {
if (tourGuide.mOverlay != null && tourGuide.mOverlay.mOnClickListener != null ) {
pass = false;
break;
}
}
}
if (mDefaultOverlay != null) {
for (TourGuide tourGuide : mTourGuideArray) {
if (tourGuide.mOverlay == null) {
tourGuide.mOverlay = mDefaultOverlay;
}
}
}
if (!pass) {
throw new IllegalArgumentException("ContinueMethod.Overlay is chosen as the ContinueMethod, but either default overlay listener is still set OR individual overlay listener is still set, make sure to clear all Overlay listener");
}
}
}
}
}

View File

@ -0,0 +1,119 @@
package tourguide.tourguide;
import android.graphics.Color;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.BounceInterpolator;
/**
* Created by tanjunrong on 6/17/15.
*/
public class ToolTip {
public String mTitle, mDescription;
public int mBackgroundColor, mTextColor;
public Animation mEnterAnimation, mExitAnimation;
public boolean mShadow;
public int mGravity;
public View.OnClickListener mOnClickListener;
public ToolTip(){
/* default values */
mTitle = "";
mDescription = "";
mBackgroundColor = Color.parseColor("#3498db");
mTextColor = Color.parseColor("#FFFFFF");
mEnterAnimation = new AlphaAnimation(0f, 1f);
mEnterAnimation.setDuration(1000);
mEnterAnimation.setFillAfter(true);
mEnterAnimation.setInterpolator(new BounceInterpolator());
mShadow = true;
// TODO: exit animation
mGravity = Gravity.CENTER;
}
/**
* Set title text
* @param title
* @return return ToolTip instance for chaining purpose
*/
public ToolTip setTitle(String title){
mTitle = title;
return this;
}
/**
* Set description text
* @param description
* @return return ToolTip instance for chaining purpose
*/
public ToolTip setDescription(String description){
mDescription = description;
return this;
}
/**
* Set background color
* @param backgroundColor
* @return return ToolTip instance for chaining purpose
*/
public ToolTip setBackgroundColor(int backgroundColor){
mBackgroundColor = backgroundColor;
return this;
}
/**
* Set text color
* @param textColor
* @return return ToolTip instance for chaining purpose
*/
public ToolTip setTextColor(int textColor){
mTextColor = textColor;
return this;
}
/**
* Set enter animation
* @param enterAnimation
* @return return ToolTip instance for chaining purpose
*/
public ToolTip setEnterAnimation(Animation enterAnimation){
mEnterAnimation = enterAnimation;
return this;
}
/**
* Set exit animation
* @param exitAnimation
* @return return ToolTip instance for chaining purpose
*/
// TODO:
// public ToolTip setExitAnimation(Animation exitAnimation){
// mExitAnimation = exitAnimation;
// return this;
// }
/**
* Set the gravity, the setGravity is centered relative to the targeted button
* @param gravity Gravity.CENTER, Gravity.TOP, Gravity.BOTTOM, etc
* @return return ToolTip instance for chaining purpose
*/
public ToolTip setGravity(int gravity){
mGravity = gravity;
return this;
}
/**
* Set if you want to have setShadow
* @param shadow
* @return return ToolTip instance for chaining purpose
*/
public ToolTip setShadow(boolean shadow){
mShadow = shadow;
return this;
}
public ToolTip setOnClickListener(View.OnClickListener onClickListener){
mOnClickListener = onClickListener;
return this;
}
}

View File

@ -0,0 +1,633 @@
package tourguide.tourguide;
import org.petero.droidfish.R;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Point;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.i2p.android.ext.floatingactionbutton.FloatingActionButton;
/**
* Created by tanjunrong on 2/10/15.
*/
public class TourGuide {
/**
* This describes the animation techniques
* */
public enum Technique {
Click, HorizontalLeft, HorizontalRight, VerticalUpward, VerticalDownward
}
/**
* This describes the allowable motion, for example if you want the users to learn about clicking, but want to stop them from swiping, then use ClickOnly
*/
public enum MotionType {
AllowAll, ClickOnly, SwipeOnly
}
private Technique mTechnique;
private View mHighlightedView;
private Activity mActivity;
private MotionType mMotionType;
private FrameLayoutWithHole mFrameLayout;
private View mToolTipViewGroup;
public ToolTip mToolTip;
public Pointer mPointer;
public Overlay mOverlay;
private Sequence mSequence;
/*************
*
* Public API
*
*************/
/* Static builder */
public static TourGuide init(Activity activity){
return new TourGuide(activity);
}
/* Constructor */
public TourGuide(Activity activity){
mActivity = activity;
}
/**
* Setter for the animation to be used
* @param technique Animation to be used
* @return return TourGuide instance for chaining purpose
*/
public TourGuide with(Technique technique) {
mTechnique = technique;
return this;
}
/**
* Sets which motion type is motionType
* @param motionType
* @return return TourGuide instance for chaining purpose
*/
public TourGuide motionType(MotionType motionType){
mMotionType = motionType;
return this;
}
/**
* Sets the duration
* @param view the view in which the tutorial button will be placed on top of
* @return return TourGuide instance for chaining purpose
*/
public TourGuide playOn(View view){
mHighlightedView = view;
setupView();
return this;
}
/**
* Sets the overlay
* @param overlay this overlay object should contain the attributes of the overlay, such as background color, animation, Style, etc
* @return return TourGuide instance for chaining purpose
*/
public TourGuide setOverlay(Overlay overlay){
mOverlay = overlay;
return this;
}
/**
* Set the toolTip
* @param toolTip this toolTip object should contain the attributes of the ToolTip, such as, the title text, and the description text, background color, etc
* @return return TourGuide instance for chaining purpose
*/
public TourGuide setToolTip(ToolTip toolTip){
mToolTip = toolTip;
return this;
}
/**
* Set the Pointer
* @param pointer this pointer object should contain the attributes of the Pointer, such as the pointer color, pointer gravity, etc, refer to @Link{pointer}
* @return return TourGuide instance for chaining purpose
*/
public TourGuide setPointer(Pointer pointer){
mPointer = pointer;
return this;
}
/**
* Clean up the tutorial that is added to the activity
*/
public void cleanUp(){
mFrameLayout.cleanUp();
if (mToolTipViewGroup!=null) {
((ViewGroup) mActivity.getWindow().getDecorView()).removeView(mToolTipViewGroup);
}
}
public TourGuide playLater(View view){
mHighlightedView = view;
return this;
}
/**************************
* Sequence related method
**************************/
public TourGuide playInSequence(Sequence sequence){
setSequence(sequence);
next();
return this;
}
public TourGuide setSequence(Sequence sequence){
mSequence = sequence;
mSequence.setParentTourGuide(this);
for (TourGuide tourGuide : sequence.mTourGuideArray){
if (tourGuide.mHighlightedView == null) {
throw new NullPointerException("Please specify the view using 'playLater' method");
}
}
return this;
}
public TourGuide next(){
if (mFrameLayout!=null) {
cleanUp();
}
if (mSequence.mCurrentSequence < mSequence.mTourGuideArray.length) {
setToolTip(mSequence.getToolTip());
setPointer(mSequence.getPointer());
setOverlay(mSequence.getOverlay());
mHighlightedView = mSequence.getNextTourGuide().mHighlightedView;
setupView();
mSequence.mCurrentSequence++;
}
return this;
}
/**
*
* @return FrameLayoutWithHole that is used as overlay
*/
public FrameLayoutWithHole getOverlay(){
return mFrameLayout;
}
/**
*
* @return the ToolTip container View
*/
public View getToolTip(){
return mToolTipViewGroup;
}
/******
*
* Private methods
*
*******/
//TODO: move into Pointer
private int getXBasedOnGravity(int width){
int [] pos = new int[2];
mHighlightedView.getLocationOnScreen(pos);
int x = pos[0];
if((mPointer.mGravity & Gravity.RIGHT) == Gravity.RIGHT){
return x+mHighlightedView.getWidth()-width;
} else if ((mPointer.mGravity & Gravity.LEFT) == Gravity.LEFT) {
return x;
} else { // this is center
return x+mHighlightedView.getWidth()/2-width/2;
}
}
//TODO: move into Pointer
private int getYBasedOnGravity(int height){
int [] pos = new int[2];
mHighlightedView.getLocationInWindow(pos);
int y = pos[1];
if((mPointer.mGravity & Gravity.BOTTOM) == Gravity.BOTTOM){
return y+mHighlightedView.getHeight()-height;
} else if ((mPointer.mGravity & Gravity.TOP) == Gravity.TOP) {
return y;
}else { // this is center
return y+mHighlightedView.getHeight()/2-height/2;
}
}
private void setupView(){
// TODO: throw exception if either mActivity, mDuration, mHighlightedView is null
checking();
final ViewTreeObserver viewTreeObserver = mHighlightedView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// make sure this only run once
mHighlightedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
/* Initialize a frame layout with a hole */
mFrameLayout = new FrameLayoutWithHole(mActivity, mHighlightedView, mMotionType, mOverlay);
/* handle click disable */
handleDisableClicking(mFrameLayout);
/* setup floating action button */
if (mPointer != null) {
FloatingActionButton fab = setupAndAddFABToFrameLayout(mFrameLayout);
performAnimationOn(fab);
}
setupFrameLayout();
/* setup tooltip view */
setupToolTip();
}
});
}
private void checking(){
// There is not check for tooltip because tooltip can be null, it means there no tooltip will be shown
}
private void handleDisableClicking(FrameLayoutWithHole frameLayoutWithHole){
// 1. if user provides an overlay listener, use that as 1st priority
if (mOverlay != null && mOverlay.mOnClickListener!=null) {
frameLayoutWithHole.setClickable(true);
frameLayoutWithHole.setOnClickListener(mOverlay.mOnClickListener);
}
// 2. if overlay listener is not provided, check if it's disabled
else if (mOverlay != null && mOverlay.mDisableClick) {
Log.w("tourguide", "Overlay's default OnClickListener is null, it will proceed to next tourguide when it is clicked");
frameLayoutWithHole.setViewHole(mHighlightedView);
frameLayoutWithHole.setSoundEffectsEnabled(false);
frameLayoutWithHole.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {} // do nothing, disabled.
});
}
}
private void setupToolTip(){
final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
if (mToolTip != null) {
/* inflate and get views */
ViewGroup parent = (ViewGroup) mActivity.getWindow().getDecorView();
LayoutInflater layoutInflater = mActivity.getLayoutInflater();
mToolTipViewGroup = layoutInflater.inflate(R.layout.tooltip, null);
View toolTipContainer = mToolTipViewGroup.findViewById(R.id.toolTip_container);
TextView toolTipTitleTV = (TextView) mToolTipViewGroup.findViewById(R.id.title);
TextView toolTipDescriptionTV = (TextView) mToolTipViewGroup.findViewById(R.id.description);
/* set tooltip attributes */
toolTipContainer.setBackgroundColor(mToolTip.mBackgroundColor);
if (mToolTip.mTitle == null){
toolTipTitleTV.setVisibility(View.GONE);
} else {
toolTipTitleTV.setText(mToolTip.mTitle);
}
if (mToolTip.mDescription == null){
toolTipDescriptionTV.setVisibility(View.GONE);
} else {
toolTipDescriptionTV.setText(mToolTip.mDescription);
}
mToolTipViewGroup.startAnimation(mToolTip.mEnterAnimation);
/* add setShadow if it's turned on */
if (mToolTip.mShadow) {
mToolTipViewGroup.setBackgroundDrawable(mActivity.getResources().getDrawable(R.drawable.drop_shadow));
}
/* position and size calculation */
int [] pos = new int[2];
mHighlightedView.getLocationOnScreen(pos);
int targetViewX = pos[0];
final int targetViewY = pos[1];
// get measured size of tooltip
mToolTipViewGroup.measure(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
int toolTipMeasuredWidth = mToolTipViewGroup.getMeasuredWidth();
int toolTipMeasuredHeight = mToolTipViewGroup.getMeasuredHeight();
Point resultPoint = new Point(); // this holds the final position of tooltip
float density = mActivity.getResources().getDisplayMetrics().density;
final float adjustment = 10 * density; //adjustment is that little overlapping area of tooltip and targeted button
// calculate x position, based on gravity, tooltipMeasuredWidth, parent max width, x position of target view, adjustment
if (toolTipMeasuredWidth > parent.getWidth()){
resultPoint.x = getXForTooTip(mToolTip.mGravity, parent.getWidth(), targetViewX, adjustment);
} else {
resultPoint.x = getXForTooTip(mToolTip.mGravity, toolTipMeasuredWidth, targetViewX, adjustment);
}
resultPoint.y = getYForTooTip(mToolTip.mGravity, toolTipMeasuredHeight, targetViewY, adjustment);
// add view to parent
// ((ViewGroup) mActivity.getWindow().getDecorView().findViewById(android.R.id.content)).addView(mToolTipViewGroup, layoutParams);
parent.addView(mToolTipViewGroup, layoutParams);
// 1. width < screen check
if (toolTipMeasuredWidth > parent.getWidth()){
mToolTipViewGroup.getLayoutParams().width = parent.getWidth();
toolTipMeasuredWidth = parent.getWidth();
}
// 2. x left boundary check
if (resultPoint.x < 0){
mToolTipViewGroup.getLayoutParams().width = toolTipMeasuredWidth + resultPoint.x; //since point.x is negative, use plus
resultPoint.x = 0;
}
// 3. x right boundary check
int tempRightX = resultPoint.x + toolTipMeasuredWidth;
if ( tempRightX > parent.getWidth()){
mToolTipViewGroup.getLayoutParams().width = parent.getWidth() - resultPoint.x; //since point.x is negative, use plus
}
// pass toolTip onClickListener into toolTipViewGroup
if (mToolTip.mOnClickListener!=null) {
mToolTipViewGroup.setOnClickListener(mToolTip.mOnClickListener);
}
// TODO: no boundary check for height yet, this is a unlikely case though
// height boundary can be fixed by user changing the gravity to the other size, since there are plenty of space vertically compared to horizontally
// this needs an viewTreeObserver, that's because TextView measurement of it's vertical height is not accurate (didn't take into account of multiple lines yet) before it's rendered
// re-calculate height again once it's rendered
final ViewTreeObserver viewTreeObserver = mToolTipViewGroup.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mToolTipViewGroup.getViewTreeObserver().removeGlobalOnLayoutListener(this);// make sure this only run once
int fixedY;
int toolTipHeightAfterLayouted = mToolTipViewGroup.getHeight();
fixedY = getYForTooTip(mToolTip.mGravity, toolTipHeightAfterLayouted, targetViewY, adjustment);
layoutParams.setMargins((int)mToolTipViewGroup.getX(),fixedY,0,0);
}
});
// set the position using setMargins on the left and top
layoutParams.setMargins(resultPoint.x, resultPoint.y, 0, 0);
}
}
private int getXForTooTip(int gravity, int toolTipMeasuredWidth, int targetViewX, float adjustment){
int x;
if ((gravity & Gravity.LEFT) == Gravity.LEFT){
x = targetViewX - toolTipMeasuredWidth + (int)adjustment;
} else if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
x = targetViewX + mHighlightedView.getWidth() - (int)adjustment;
} else {
x = targetViewX + mHighlightedView.getWidth() / 2 - toolTipMeasuredWidth / 2;
}
return x;
}
private int getYForTooTip(int gravity, int toolTipMeasuredHeight, int targetViewY, float adjustment){
int y;
if ((gravity & Gravity.TOP) == Gravity.TOP) {
if (((gravity & Gravity.LEFT) == Gravity.LEFT) || ((gravity & Gravity.RIGHT) == Gravity.RIGHT)) {
y = targetViewY - toolTipMeasuredHeight + (int)adjustment;
} else {
y = targetViewY - toolTipMeasuredHeight - (int)adjustment;
}
} else if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
if (((gravity & Gravity.LEFT) == Gravity.LEFT) || ((gravity & Gravity.RIGHT) == Gravity.RIGHT)) {
y = targetViewY + mHighlightedView.getHeight() - (int) adjustment;
} else {
y = targetViewY + mHighlightedView.getHeight() + (int) adjustment;
}
} else { // this is center
if (((gravity & Gravity.LEFT) == Gravity.LEFT) || ((gravity & Gravity.RIGHT) == Gravity.RIGHT)) {
y = targetViewY + mHighlightedView.getHeight() / 2 - (int) adjustment;
} else {
y = targetViewY + mHighlightedView.getHeight() / 2 + (int) adjustment;
}
}
return y;
}
private FloatingActionButton setupAndAddFABToFrameLayout(final FrameLayoutWithHole frameLayoutWithHole){
// invisFab is invisible, and it's only used for getting the width and height
final FloatingActionButton invisFab = new FloatingActionButton(mActivity);
invisFab.setSize(FloatingActionButton.SIZE_MINI);
invisFab.setVisibility(View.INVISIBLE);
((ViewGroup)mActivity.getWindow().getDecorView()).addView(invisFab);
// fab is the real fab that is going to be added
final FloatingActionButton fab = new FloatingActionButton(mActivity);
fab.setBackgroundColor(Color.BLUE);
fab.setSize(FloatingActionButton.SIZE_MINI);
fab.setColorNormal(mPointer.mColor);
fab.setStrokeVisible(false);
fab.setClickable(false);
// When invisFab is layouted, it's width and height can be used to calculate the correct position of fab
final ViewTreeObserver viewTreeObserver = invisFab.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// make sure this only run once
invisFab.getViewTreeObserver().removeGlobalOnLayoutListener(this);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
frameLayoutWithHole.addView(fab, params);
// measure size of image to be placed
params.setMargins(getXBasedOnGravity(invisFab.getWidth()), getYBasedOnGravity(invisFab.getHeight()), 0, 0);
}
});
return fab;
}
private void setupFrameLayout(){
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
ViewGroup contentArea = (ViewGroup) mActivity.getWindow().getDecorView().findViewById(android.R.id.content);
int [] pos = new int[2];
contentArea.getLocationOnScreen(pos);
// frameLayoutWithHole's coordinates are calculated taking full screen height into account
// but we're adding it to the content area only, so we need to offset it to the same Y value of contentArea
layoutParams.setMargins(0,-pos[1],0,0);
contentArea.addView(mFrameLayout, layoutParams);
}
private void performAnimationOn(final View view){
if (mTechnique != null && mTechnique == Technique.HorizontalLeft){
final AnimatorSet animatorSet = new AnimatorSet();
final AnimatorSet animatorSet2 = new AnimatorSet();
Animator.AnimatorListener lis1 = new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animator) {}
@Override public void onAnimationCancel(Animator animator) {}
@Override public void onAnimationRepeat(Animator animator) {}
@Override
public void onAnimationEnd(Animator animator) {
view.setScaleX(1f);
view.setScaleY(1f);
view.setTranslationX(0);
animatorSet2.start();
}
};
Animator.AnimatorListener lis2 = new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animator) {}
@Override public void onAnimationCancel(Animator animator) {}
@Override public void onAnimationRepeat(Animator animator) {}
@Override
public void onAnimationEnd(Animator animator) {
view.setScaleX(1f);
view.setScaleY(1f);
view.setTranslationX(0);
animatorSet.start();
}
};
long fadeInDuration = 800;
long scaleDownDuration = 800;
long goLeftXDuration = 2000;
long fadeOutDuration = goLeftXDuration;
float translationX = getScreenWidth()/2;
final ValueAnimator fadeInAnim = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
fadeInAnim.setDuration(fadeInDuration);
final ObjectAnimator scaleDownX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.85f);
scaleDownX.setDuration(scaleDownDuration);
final ObjectAnimator scaleDownY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.85f);
scaleDownY.setDuration(scaleDownDuration);
final ObjectAnimator goLeftX = ObjectAnimator.ofFloat(view, "translationX", -translationX);
goLeftX.setDuration(goLeftXDuration);
final ValueAnimator fadeOutAnim = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
fadeOutAnim.setDuration(fadeOutDuration);
final ValueAnimator fadeInAnim2 = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
fadeInAnim2.setDuration(fadeInDuration);
final ObjectAnimator scaleDownX2 = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.85f);
scaleDownX2.setDuration(scaleDownDuration);
final ObjectAnimator scaleDownY2 = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.85f);
scaleDownY2.setDuration(scaleDownDuration);
final ObjectAnimator goLeftX2 = ObjectAnimator.ofFloat(view, "translationX", -translationX);
goLeftX2.setDuration(goLeftXDuration);
final ValueAnimator fadeOutAnim2 = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
fadeOutAnim2.setDuration(fadeOutDuration);
animatorSet.play(fadeInAnim);
animatorSet.play(scaleDownX).with(scaleDownY).after(fadeInAnim);
animatorSet.play(goLeftX).with(fadeOutAnim).after(scaleDownY);
animatorSet2.play(fadeInAnim2);
animatorSet2.play(scaleDownX2).with(scaleDownY2).after(fadeInAnim2);
animatorSet2.play(goLeftX2).with(fadeOutAnim2).after(scaleDownY2);
animatorSet.addListener(lis1);
animatorSet2.addListener(lis2);
animatorSet.start();
/* these animatorSets are kept track in FrameLayout, so that they can be cleaned up when FrameLayout is detached from window */
mFrameLayout.addAnimatorSet(animatorSet);
mFrameLayout.addAnimatorSet(animatorSet2);
} else if (mTechnique != null && mTechnique == Technique.HorizontalRight){
} else if (mTechnique != null && mTechnique == Technique.VerticalUpward){
} else if (mTechnique != null && mTechnique == Technique.VerticalDownward){
} else { // do click for default case
final AnimatorSet animatorSet = new AnimatorSet();
final AnimatorSet animatorSet2 = new AnimatorSet();
Animator.AnimatorListener lis1 = new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animator) {}
@Override public void onAnimationCancel(Animator animator) {}
@Override public void onAnimationRepeat(Animator animator) {}
@Override
public void onAnimationEnd(Animator animator) {
view.setScaleX(1f);
view.setScaleY(1f);
view.setTranslationX(0);
animatorSet2.start();
}
};
Animator.AnimatorListener lis2 = new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animator) {}
@Override public void onAnimationCancel(Animator animator) {}
@Override public void onAnimationRepeat(Animator animator) {}
@Override
public void onAnimationEnd(Animator animator) {
view.setScaleX(1f);
view.setScaleY(1f);
view.setTranslationX(0);
animatorSet.start();
}
};
long fadeInDuration = 800;
long scaleDownDuration = 800;
long fadeOutDuration = 800;
long delay = 1000;
final ValueAnimator delayAnim = ObjectAnimator.ofFloat(view, "translationX", 0);
delayAnim.setDuration(delay);
final ValueAnimator fadeInAnim = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
fadeInAnim.setDuration(fadeInDuration);
final ObjectAnimator scaleDownX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.85f);
scaleDownX.setDuration(scaleDownDuration);
final ObjectAnimator scaleDownY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.85f);
scaleDownY.setDuration(scaleDownDuration);
final ObjectAnimator scaleUpX = ObjectAnimator.ofFloat(view, "scaleX", 0.85f, 1f);
scaleUpX.setDuration(scaleDownDuration);
final ObjectAnimator scaleUpY = ObjectAnimator.ofFloat(view, "scaleY", 0.85f, 1f);
scaleUpY.setDuration(scaleDownDuration);
final ValueAnimator fadeOutAnim = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
fadeOutAnim.setDuration(fadeOutDuration);
final ValueAnimator delayAnim2 = ObjectAnimator.ofFloat(view, "translationX", 0);
delayAnim2.setDuration(delay);
final ValueAnimator fadeInAnim2 = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
fadeInAnim2.setDuration(fadeInDuration);
final ObjectAnimator scaleDownX2 = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.85f);
scaleDownX2.setDuration(scaleDownDuration);
final ObjectAnimator scaleDownY2 = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.85f);
scaleDownY2.setDuration(scaleDownDuration);
final ObjectAnimator scaleUpX2 = ObjectAnimator.ofFloat(view, "scaleX", 0.85f, 1f);
scaleUpX2.setDuration(scaleDownDuration);
final ObjectAnimator scaleUpY2 = ObjectAnimator.ofFloat(view, "scaleY", 0.85f, 1f);
scaleUpY2.setDuration(scaleDownDuration);
final ValueAnimator fadeOutAnim2 = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
fadeOutAnim2.setDuration(fadeOutDuration);
view.setAlpha(0);
animatorSet.setStartDelay(mToolTip != null ? mToolTip.mEnterAnimation.getDuration() : 0);
animatorSet.play(fadeInAnim);
animatorSet.play(scaleDownX).with(scaleDownY).after(fadeInAnim);
animatorSet.play(scaleUpX).with(scaleUpY).with(fadeOutAnim).after(scaleDownY);
animatorSet.play(delayAnim).after(scaleUpY);
animatorSet2.play(fadeInAnim2);
animatorSet2.play(scaleDownX2).with(scaleDownY2).after(fadeInAnim2);
animatorSet2.play(scaleUpX2).with(scaleUpY2).with(fadeOutAnim2).after(scaleDownY2);
animatorSet2.play(delayAnim2).after(scaleUpY2);
animatorSet.addListener(lis1);
animatorSet2.addListener(lis2);
animatorSet.start();
/* these animatorSets are kept track in FrameLayout, so that they can be cleaned up when FrameLayout is detached from window */
mFrameLayout.addAnimatorSet(animatorSet);
mFrameLayout.addAnimatorSet(animatorSet2);
}
}
private int getScreenWidth(){
if (mActivity!=null) {
return mActivity.getResources().getDisplayMetrics().widthPixels;
} else {
return 0;
}
}
}