Skip to content

Instantly share code, notes, and snippets.

@mmurph211
Last active December 27, 2015 19:09
Show Gist options
  • Save mmurph211/7374562 to your computer and use it in GitHub Desktop.
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.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/achievement_banner" android:layout_width="fill_parent"
android:layout_height="0dp" android:orientation="horizontal"
android:background="@drawable/achievement_banner" android:clickable="true">
<ImageView android:layout_width="25dp" android:layout_height="25dp"
android:layout_gravity="left|top" android:src="@drawable/achievement_logo"
android:layout_marginRight="10dp" android:layout_marginLeft="6dp"
android:layout_marginTop="5dp" />
<LinearLayout android:id="@+id/achievement_messages" android:orientation="vertical"
android:layout_width="0dp" android:layout_height="@dimen/achievement_banner_height"
android:layout_weight="1" android:layout_gravity="left|top" android:clipChildren="true"
android:scrollY="@dimen/achievement_banner_initial_scrollY">
<TextView android:id="@+id/achievement_claim" android:layout_width="fill_parent"
android:layout_height="@dimen/achievement_banner_height"
android:gravity="left|center" style="@style/AchievementTxt" />
<TextView android:id="@+id/achievement_action" android:layout_width="fill_parent"
android:layout_height="@dimen/achievement_banner_height"
android:gravity="left|center" style="@style/AchievementTxt" />
<TextView android:id="@+id/achievement_unlocked" android:layout_width="fill_parent"
android:layout_height="@dimen/achievement_banner_height" android:gravity="left|center"
style="@style/AchievementTxt" android:text="@string/achievement_unlocked" />
</LinearLayout>
<ImageButton android:id="@+id/achievement_dismiss" android:layout_gravity="top"
android:background="@color/transparent" android:src="@drawable/achievement_close"
android:layout_width="@dimen/achievement_banner_height"
android:layout_height="@dimen/achievement_banner_height"
android:paddingTop="11dp" android:paddingBottom="9dp"
android:paddingRight="10dp" android:paddingLeft="10dp"
android:scaleType="centerInside" android:cropToPadding="true" />
</LinearLayout>
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) {
}
}
<dimen name="achievement_banner_height">35dp</dimen>
<dimen name="achievement_banner_initial_scrollY">70dp</dimen>
private AchievementsController achievementsController;
@Override
protected void onCreate(final Bundle savedInstanceState) {
// ...
achievementsController = AchievementsController.getInstance();
achievementsController.setActivity(this);
// ...
}
public void presentAnyQueuedAchievement(long delay) {
achievementsController.presentAnyQueuedAchievement(delay);
}
<string name="achievement_unlocked">ACHIEVEMENT UNLOCKED</string>
<string name="achievement_claim">Claim %1$d mPOINTS!</string>
<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>
public static void logAction(String action) {
if (!SessionM.getInstance().getUser().isOptedOut()) {
SessionM.getInstance().logAction(action);
}
}
public static void updateBadgeView(BadgeView badgeView) {
SessionM instance = SessionM.getInstance();
final int unclaimedCount = instance.getUser().getUnclaimedAchievementCount();
final boolean isOnline = (instance.getSessionState() == State.STARTED_ONLINE);
if (!isOnline || instance.getUser().isOptedOut() || unclaimedCount <= 0) {
badgeView.hide();
} else {
badgeView.setText("" + unclaimedCount);
badgeView.show();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment