Last active
December 27, 2015 19:09
-
-
Save mmurph211/7374562 to your computer and use it in GitHub Desktop.
Example animated, custom achievements implementation for SessionM's Android SDK v1.9.5.Check out https://developer.sessionm.com/docs#android_custom_achievement_guide for more information.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Timer; | |
import java.util.TimerTask; | |
import android.app.Activity; | |
import android.content.res.Resources; | |
import android.util.Log; | |
import android.view.View; | |
import android.view.animation.Animation; | |
import android.view.animation.Animation.AnimationListener; | |
import android.view.animation.Transformation; | |
import android.widget.ImageButton; | |
import android.widget.LinearLayout; | |
import android.widget.TextView; | |
import com.sessionm.api.AchievementActivity; | |
import com.sessionm.api.AchievementActivity.AchievementDismissType; | |
import com.sessionm.api.AchievementActivityIllegalStateException; | |
import com.sessionm.api.AchievementData; | |
import com.sessionm.api.SessionListener; | |
import com.sessionm.api.SessionM; | |
import com.sessionm.api.SessionM.State; | |
import com.sessionm.api.User; | |
public final class AchievementsController implements SessionListener { | |
private Activity activity; | |
private AchievementActivity achievementActivity; | |
private AchievementData queuedAchievement; | |
private View bannerParent; | |
private LinearLayout banner; | |
private LinearLayout bannerMessages; | |
private TextView bannerAction; | |
private TextView bannerClaim; | |
private ImageButton bannerDismiss; | |
private Timer presentTimer; | |
private Timer textTimer; | |
private Timer dismissTimer; | |
private int bannerHeight = 0; | |
private int bannerStartScrollY = 0; | |
private boolean queuedAchievementIsPresenting = false; | |
private static final int DROPDOWN_ANIM_DURATION = 250; | |
private static final int TEXT_ANIM_DURATION = 300; | |
private static final int TEXT_DURATION = 4000; | |
private static final int PRESENT_DURATION = 14000; | |
private static final String TAG = AchievementsController.class.getSimpleName(); | |
private AchievementsController() { | |
SessionM.getInstance().setSessionListener(this); | |
} | |
private static class InstanceLoader { | |
private static final AchievementsController INSTANCE = new AchievementsController(); | |
} | |
public static AchievementsController getInstance() { | |
return InstanceLoader.INSTANCE; | |
} | |
public void setActivity(Activity activity) { | |
this.activity = activity; | |
banner = (LinearLayout) activity.findViewById(R.id.achievement_banner); | |
bannerParent = (View) banner.getParent(); | |
bannerMessages = (LinearLayout) activity.findViewById(R.id.achievement_messages); | |
bannerAction = (TextView) activity.findViewById(R.id.achievement_action); | |
bannerClaim = (TextView) activity.findViewById(R.id.achievement_claim); | |
bannerDismiss = (ImageButton) activity.findViewById(R.id.achievement_dismiss); | |
banner.setOnClickListener(new BannerListener()); | |
bannerDismiss.setOnClickListener(new BannerDismissListener()); | |
} | |
public void presentAnyQueuedAchievement() { | |
AchievementData unclaimedAchievement = SessionM.getInstance().getUnclaimedAchievement(); | |
if (unclaimedAchievement != null && unclaimedAchievement.isCustom()) { | |
if (!queuedAchievementIsPresenting) { | |
queuedAchievement = unclaimedAchievement; | |
present(); | |
} | |
} | |
} | |
public void presentAnyQueuedAchievement(long delay) { | |
if (presentTimer == null) { | |
presentTimer = new Timer(); | |
presentTimer.schedule(new PresentTask(), delay); | |
} | |
} | |
private class PresentTask extends TimerTask { | |
@Override | |
public void run() { | |
activity.runOnUiThread(new Runnable() { | |
@Override | |
public void run() { | |
presentAnyQueuedAchievement(); | |
cancelPresentTimer(); | |
} | |
}); | |
} | |
} | |
private void present() { | |
try { | |
cancelActiveTimersAndAnimations(); | |
queuedAchievementIsPresenting = true; | |
achievementActivity = new AchievementActivity(queuedAchievement); | |
achievementActivity.notifyPresented(); | |
initializeBannerMessages(queuedAchievement); | |
presentBanner(); | |
} catch (AchievementActivityIllegalStateException e) { | |
clearQueuedAchievement(); | |
if (Log.isLoggable(TAG, Log.ERROR)) { | |
Log.e(TAG, "Error presenting achievement.", e); | |
} | |
} | |
} | |
private void dismiss(AchievementDismissType dismissType) { | |
cancelActiveTimersAndAnimations(); | |
notifyDismissed(dismissType); | |
clearQueuedAchievement(); | |
dismissBanner(); | |
} | |
private void initializeBannerMessages(AchievementData achievement) { | |
Resources res = activity.getResources(); | |
String claim = res.getString(R.string.achievement_claim); | |
bannerAction.setText(achievement.getName()); | |
bannerClaim.setText(String.format(claim, achievement.getMpointValue())); | |
} | |
private void presentBanner() { | |
int displayedHeight = getBannerDisplayedHeight(banner); | |
int measuredHeight = getBannerHeight(banner); | |
DropDownAnim anim = new DropDownAnim(banner, displayedHeight, measuredHeight); | |
anim.setDuration(getAnimationDuration(displayedHeight, measuredHeight, true)); | |
anim.setFillAfter(true); | |
anim.setAnimationListener(new DropDownAnimListener()); | |
bannerMessages.scrollTo(0, bannerStartScrollY); | |
banner.startAnimation(anim); | |
bannerParent.invalidate(); | |
scheduleTextAnimationTimer(); | |
scheduleDismissTimer(); | |
} | |
private void dismissBanner() { | |
int displayedHeight = getBannerDisplayedHeight(banner); | |
int measuredHeight = getBannerHeight(banner); | |
DropDownAnim anim = new DropDownAnim(banner, displayedHeight, 0); | |
anim.setDuration(getAnimationDuration(displayedHeight, measuredHeight, false)); | |
anim.setFillAfter(true); | |
anim.setAnimationListener(new DropDownAnimListener()); | |
banner.startAnimation(anim); | |
bannerParent.invalidate(); | |
} | |
private int getBannerDisplayedHeight(LinearLayout banner) { | |
return banner.getLayoutParams().height; | |
} | |
private int getBannerHeight(LinearLayout banner) { | |
if (bannerHeight == 0) { | |
Resources res = activity.getResources(); | |
bannerHeight = res.getDimensionPixelSize(R.dimen.achievement_banner_height); | |
bannerStartScrollY = bannerHeight * 2; | |
} | |
return bannerHeight; | |
} | |
private int getAnimationDuration(int displayedHeight, int measuredHeight, boolean expanding) { | |
double pctVisible = ((double) displayedHeight) / measuredHeight; | |
if (expanding) { | |
return (int) (DROPDOWN_ANIM_DURATION * (1.0 - pctVisible)); | |
} | |
return (int) (DROPDOWN_ANIM_DURATION * pctVisible); | |
} | |
private class DropDownAnim extends Animation { | |
private final View view; | |
private final int startingHeight; | |
private final int targetHeight; | |
public DropDownAnim(View view, int startingHeight, int targetHeight) { | |
this.view = view; | |
this.startingHeight = startingHeight; | |
this.targetHeight = targetHeight; | |
} | |
@Override | |
protected void applyTransformation(float interpolatedTime, Transformation t) { | |
int newHeight = (int) (((targetHeight - startingHeight) * interpolatedTime) + startingHeight); | |
view.getLayoutParams().height = newHeight; | |
view.requestLayout(); | |
} | |
@Override | |
public boolean willChangeBounds() { | |
return true; | |
} | |
} | |
private class DropDownAnimListener implements AnimationListener { | |
@Override | |
public void onAnimationEnd(Animation animation) { | |
animation.setAnimationListener(null); | |
banner.clearAnimation(); | |
} | |
@Override | |
public void onAnimationRepeat(Animation animation) { | |
// Auto-generated method stub | |
} | |
@Override | |
public void onAnimationStart(Animation animation) { | |
// Auto-generated method stub | |
} | |
} | |
private void scheduleTextAnimationTimer() { | |
textTimer = new Timer(); | |
textTimer.schedule(new TextAnimationTask(bannerStartScrollY), TEXT_DURATION); | |
textTimer.schedule(new TextAnimationTask(bannerStartScrollY - bannerHeight), TEXT_DURATION * 2); | |
} | |
private class TextAnimationTask extends TimerTask { | |
private final int startingScrollY; | |
public TextAnimationTask(int startingScrollY) { | |
this.startingScrollY = startingScrollY; | |
} | |
@Override | |
public void run() { | |
activity.runOnUiThread(new Runnable() { | |
@Override | |
public void run() { | |
int targetScrollY = startingScrollY - bannerHeight; | |
TextAnim anim = new TextAnim(bannerMessages, startingScrollY, targetScrollY); | |
anim.setDuration(TEXT_ANIM_DURATION); | |
bannerMessages.startAnimation(anim); | |
} | |
}); | |
} | |
} | |
private class TextAnim extends Animation { | |
private final View view; | |
private final int startingScrollY; | |
private final int targetScrollY; | |
public TextAnim(View view, int startingScrollY, int targetScrollY) { | |
this.view = view; | |
this.startingScrollY = startingScrollY; | |
this.targetScrollY = targetScrollY; | |
} | |
@Override | |
protected void applyTransformation(float interpolatedTime, Transformation t) { | |
int newScrollY = (int) (((targetScrollY - startingScrollY) * interpolatedTime) + startingScrollY); | |
view.scrollTo(0, newScrollY); | |
} | |
@Override | |
public boolean willChangeBounds() { | |
return false; | |
} | |
} | |
private void cancelTextAnimationTimer() { | |
if (textTimer != null) { | |
textTimer.cancel(); | |
textTimer = null; | |
} | |
} | |
private void scheduleDismissTimer() { | |
dismissTimer = new Timer(); | |
dismissTimer.schedule(new DismissTask(), PRESENT_DURATION); | |
} | |
private class DismissTask extends TimerTask { | |
@Override | |
public void run() { | |
activity.runOnUiThread(new Runnable() { | |
@Override | |
public void run() { | |
dismiss(AchievementDismissType.CANCELLED); | |
} | |
}); | |
} | |
} | |
private void cancelDismissTimer() { | |
if (dismissTimer != null) { | |
dismissTimer.cancel(); | |
dismissTimer = null; | |
} | |
} | |
private void cancelPresentTimer() { | |
if (presentTimer != null) { | |
presentTimer.cancel(); | |
presentTimer = null; | |
} | |
} | |
private void cancelActiveTimersAndAnimations() { | |
cancelPresentTimer(); | |
cancelTextAnimationTimer(); | |
cancelDismissTimer(); | |
bannerMessages.clearAnimation(); | |
int currentHeight = getBannerDisplayedHeight(banner); | |
banner.clearAnimation(); // Resets height | |
banner.getLayoutParams().height = currentHeight; | |
banner.requestLayout(); | |
} | |
private void clearQueuedAchievement() { | |
queuedAchievementIsPresenting = false; | |
queuedAchievement = null; | |
} | |
private void notifyDismissed(AchievementDismissType dismissType) { | |
try { | |
if (achievementActivity != null) { | |
achievementActivity.notifyDismissed(dismissType); | |
} | |
} catch (AchievementActivityIllegalStateException e) { | |
clearQueuedAchievement(); | |
if (Log.isLoggable(TAG, Log.ERROR)) { | |
Log.e(TAG, "Error dismissing achievement.", e); | |
} | |
} | |
} | |
private class BannerListener implements View.OnClickListener { | |
@Override | |
public void onClick(View v) { | |
dismiss(AchievementDismissType.CLAIMED); | |
} | |
} | |
private class BannerDismissListener implements View.OnClickListener { | |
@Override | |
public void onClick(View v) { | |
dismiss(AchievementDismissType.CANCELLED); | |
} | |
} | |
@Override | |
public void onUserUpdated(SessionM session, final User user) { | |
} | |
@Override | |
public void onSessionFailed(SessionM session, int error) { | |
} | |
@Override | |
public void onSessionStateChanged(SessionM session, State state) { | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dimen name="achievement_banner_height">35dp</dimen> | |
<dimen name="achievement_banner_initial_scrollY">70dp</dimen> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private AchievementsController achievementsController; | |
@Override | |
protected void onCreate(final Bundle savedInstanceState) { | |
// ... | |
achievementsController = AchievementsController.getInstance(); | |
achievementsController.setActivity(this); | |
// ... | |
} | |
public void presentAnyQueuedAchievement(long delay) { | |
achievementsController.presentAnyQueuedAchievement(delay); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<string name="achievement_unlocked">ACHIEVEMENT UNLOCKED</string> | |
<string name="achievement_claim">Claim %1$d mPOINTS!</string> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<style name="AchievementTxt"> | |
<item name="android:typeface">sans</item> | |
<item name="android:textSize">14sp</item> | |
<item name="android:ellipsize">end</item> | |
<item name="android:singleLine">true</item> | |
<item name="android:textColor">@color/white</item> | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment