Merge pull request #4683 from goblinr/MAPSME-73-ext-nav-buttons-animations

[android] Navigation buttons animations.
This commit is contained in:
Aleksandr Zatsepin 2016-11-23 10:53:27 +03:00 committed by GitHub
commit 595934c340
9 changed files with 381 additions and 9 deletions

View file

@ -29,6 +29,7 @@ import com.mapswithme.maps.api.ParsedMwmRequest;
import com.mapswithme.maps.api.ParsedRoutingData;
import com.mapswithme.maps.api.ParsedUrlMwmRequest;
import com.mapswithme.maps.api.RoutePoint;
import com.mapswithme.maps.routing.RoutingPlanController;
import com.mapswithme.maps.uber.Uber;
import com.mapswithme.maps.uber.UberInfo;
import com.mapswithme.maps.base.BaseMwmFragmentActivity;
@ -92,12 +93,15 @@ public class MwmActivity extends BaseMwmFragmentActivity
implements MapObjectListener,
View.OnTouchListener,
BasePlacePageAnimationController.OnVisibilityChangedListener,
BasePlacePageAnimationController.OnAnimationListener,
OnClickListener,
MapFragment.MapRenderingListener,
CustomNavigateUpListener,
ChooseBookmarkCategoryFragment.Listener,
RoutingController.Container,
LocationHelper.UiCallback
LocationHelper.UiCallback,
RoutingPlanController.OnToggleListener,
FloatingSearchToolbarController.VisibilityListener
{
public static final String EXTRA_TASK = "map_task";
private static final String EXTRA_CONSUMED = "mwm.extra.intent.processed";
@ -134,6 +138,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
private View mNavZoomIn;
private View mNavZoomOut;
private MyPositionButton mNavMyPosition;
@Nullable
private NavigationButtonsAnimationController mNavAnimationController;
private View mPositionChooser;
@ -414,6 +420,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
Framework.nativeSetMapObjectListener(this);
mSearchController = new FloatingSearchToolbarController(this);
mSearchController.setVisibilityListener(this);
processIntent(getIntent());
SharingHelper.prepare();
@ -434,10 +441,12 @@ public class MwmActivity extends BaseMwmFragmentActivity
mPlacePage = (PlacePageView) findViewById(R.id.info_box);
mPlacePage.setOnVisibilityChangedListener(this);
mPlacePage.setOnAnimationListener(this);
if (!mIsFragmentContainer)
{
mRoutingPlanInplaceController = new RoutingPlanInplaceController(this);
mRoutingPlanInplaceController.setOnToggleListener(this);
removeCurrentFragment(false);
}
@ -525,7 +534,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
mNavZoomIn.setOnClickListener(this);
mNavZoomOut = frame.findViewById(R.id.nav_zoom_out);
mNavZoomOut.setOnClickListener(this);
mNavMyPosition = new MyPositionButton(frame.findViewById(R.id.my_position));
View myPosition = frame.findViewById(R.id.my_position);
mNavMyPosition = new MyPositionButton(myPosition);
mNavAnimationController = new NavigationButtonsAnimationController(mNavZoomIn, mNavZoomOut, myPosition,
frame.findViewById(R.id.anchor_center));
}
public boolean closePlacePage()
@ -1099,6 +1111,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
Animations.disappearSliding(mNavZoomOut, Animations.RIGHT, null);
Animations.disappearSliding(mNavZoomIn, Animations.RIGHT, null);
mNavMyPosition.hide();
}
}
else
@ -1125,6 +1138,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
{
Animations.appearSliding(mNavZoomOut, Animations.RIGHT, null);
Animations.appearSliding(mNavZoomIn, Animations.RIGHT, null);
mNavMyPosition.show();
}
}
@ -1166,6 +1180,13 @@ public class MwmActivity extends BaseMwmFragmentActivity
: AlohaHelper.PP_CLOSE);
}
@Override
public void onProgress(float translationX, float translationY)
{
if (mNavAnimationController != null)
mNavAnimationController.onPlacePageMoved(translationY);
}
@Override
public void onClick(View v)
{
@ -1371,6 +1392,15 @@ public class MwmActivity extends BaseMwmFragmentActivity
mMainMenu.showLineFrame(true);
}
private void setNavButtonsTopLimit(float limit)
{
if (mNavAnimationController == null)
return;
mNavAnimationController.setTopLimit(limit);
mNavAnimationController.update();
}
@Override
public void showRoutePlan(boolean show, @Nullable Runnable completionListener)
{
@ -1387,6 +1417,9 @@ public class MwmActivity extends BaseMwmFragmentActivity
mRoutingPlanInplaceController.show(true);
if (completionListener != null)
completionListener.run();
if (mRoutingPlanInplaceController.getHeight() > 0)
setNavButtonsTopLimit(mRoutingPlanInplaceController.getHeight());
}
}
else
@ -1398,11 +1431,31 @@ public class MwmActivity extends BaseMwmFragmentActivity
if (completionListener != null)
completionListener.run();
setNavButtonsTopLimit(0);
}
mPlacePage.refreshViews();
}
@Override
public void onToggle(boolean state)
{
if (mNavAnimationController == null)
return;
setNavButtonsTopLimit(mRoutingPlanInplaceController.getHeight());
}
@Override
public void onSearchVisibilityChanged(boolean visible)
{
if (mNavAnimationController == null)
return;
setNavButtonsTopLimit(visible ? mSearchController.getToolbar().getHeight() : 0);
}
@Override
public void showNavigation(boolean show)
{

View file

@ -0,0 +1,183 @@
package com.mapswithme.maps;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import com.mapswithme.maps.routing.RoutingController;
import com.mapswithme.util.Animations;
import com.mapswithme.util.UiUtils;
class NavigationButtonsAnimationController
{
@NonNull
private final View mZoomIn;
@NonNull
private final View mZoomOut;
@NonNull
private final View mMyPosition;
@Nullable
private final View mCenter;
private final float mMargin;
private float mBottom;
private float mTop;
private boolean mIsZoomAnimate;
private boolean mIsMyPosAnimate;
NavigationButtonsAnimationController(@NonNull View zoomIn, @NonNull View zoomOut,
@NonNull View myPosition, @Nullable View center)
{
mZoomIn = zoomIn;
mZoomOut = zoomOut;
mMyPosition = myPosition;
mCenter = center;
Resources res = mZoomIn.getResources();
mMargin = res.getDimension(R.dimen.margin_base_plus);
calculateLimitTranslations();
}
private void calculateLimitTranslations()
{
mTop = mMargin;
mMyPosition.addOnLayoutChangeListener(new View.OnLayoutChangeListener()
{
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom)
{
mBottom = bottom + mMargin;
mMyPosition.removeOnLayoutChangeListener(this);
}
});
}
void setTopLimit(float limit)
{
mTop = limit + mMargin;
}
private void fadeOutZoom()
{
mIsZoomAnimate = true;
Animations.fadeOutView(mZoomIn, new Runnable()
{
@Override
public void run()
{
mZoomIn.setVisibility(View.INVISIBLE);
mIsZoomAnimate = false;
}
});
Animations.fadeOutView(mZoomOut, new Runnable()
{
@Override
public void run()
{
mZoomOut.setVisibility(View.INVISIBLE);
}
});
}
private void fadeInZoom()
{
mIsZoomAnimate = true;
mZoomIn.setVisibility(View.VISIBLE);
mZoomOut.setVisibility(View.VISIBLE);
Animations.fadeInView(mZoomIn, new Runnable()
{
@Override
public void run()
{
mIsZoomAnimate = false;
}
});
Animations.fadeInView(mZoomOut, null);
}
private void fadeOutMyPosition()
{
mIsMyPosAnimate = true;
Animations.fadeOutView(mMyPosition, new Runnable()
{
@Override
public void run()
{
UiUtils.invisible(mMyPosition);
mIsMyPosAnimate = false;
}
});
}
private void fadeInMyPosition()
{
mIsMyPosAnimate = true;
mMyPosition.setVisibility(View.VISIBLE);
Animations.fadeInView(mMyPosition, new Runnable()
{
@Override
public void run()
{
mIsMyPosAnimate = false;
}
});
}
void onPlacePageMoved(float translationY)
{
if (mCenter == null || mBottom == 0)
return;
final float translation = translationY - mBottom;
update(translation <= 0 ? translation : 0);
}
void update()
{
update(mZoomIn.getTranslationY());
}
private void update(final float translation)
{
mMyPosition.setTranslationY(translation);
mZoomOut.setTranslationY(translation);
mZoomIn.setTranslationY(translation);
if (!mIsZoomAnimate && isOverTopLimit(mZoomIn))
{
fadeOutZoom();
}
else if (!mIsZoomAnimate && satisfyTopLimit(mZoomIn))
{
fadeInZoom();
}
if (!shouldBeHidden() && !mIsMyPosAnimate
&& isOverTopLimit(mMyPosition))
{
fadeOutMyPosition();
}
else if (!shouldBeHidden() && !mIsMyPosAnimate
&& satisfyTopLimit(mMyPosition))
{
fadeInMyPosition();
}
}
private boolean isOverTopLimit(@NonNull View view)
{
return view.getVisibility() == View.VISIBLE && view.getY() <= mTop;
}
private boolean satisfyTopLimit(@NonNull View view)
{
return view.getVisibility() == View.INVISIBLE && view.getY() >= mTop;
}
private boolean shouldBeHidden()
{
return LocationState.getMode() == LocationState.FOLLOW_AND_ROTATE
&& (RoutingController.get().isPlanning() || RoutingController.get().isNavigating());
}
}

View file

@ -1,5 +1,6 @@
package com.mapswithme.maps.routing;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.graphics.Bitmap;
@ -70,6 +71,14 @@ public class RoutingPlanController extends ToolbarController
@Nullable
private UberInfo.Product mUberProduct;
@Nullable
private OnToggleListener mToggleListener;
public interface OnToggleListener
{
void onToggle(boolean state);
}
private RadioButton setupRouterButton(@IdRes int buttonId, final @DrawableRes int iconRes, View.OnClickListener clickListener)
{
CompoundButton.OnCheckedChangeListener listener = new CompoundButton.OnCheckedChangeListener()
@ -352,6 +361,14 @@ public class RoutingPlanController extends ToolbarController
mToggleImage.setAngle((1.0f - fraction) * 180.0f);
}
});
animator.addListener(new UiUtils.SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation)
{
if (mToggleListener != null)
mToggleListener.onToggle(mOpen);
}
});
animator.setDuration(ANIM_TOGGLE);
animator.start();
@ -362,6 +379,15 @@ public class RoutingPlanController extends ToolbarController
animateSlotFrame(mOpen ? 0 : mFrameHeight);
mToggleImage.setAngle(mOpen ? 180.0f : 0.0f);
mSlotFrame.unfadeSlots();
mSlotFrame.post(new Runnable()
{
@Override
public void run()
{
if (mToggleListener != null)
mToggleListener.onToggle(mOpen);
}
});
}
}
@ -544,4 +570,14 @@ public class RoutingPlanController extends ToolbarController
Location location = LocationHelper.INSTANCE.getLastKnownLocation();
Statistics.INSTANCE.trackUber(from, to, location, isUberInstalled);
}
public float getHeight()
{
return mFrame.getHeight();
}
public void setOnToggleListener(@Nullable OnToggleListener listener)
{
mToggleListener = listener;
}
}

View file

@ -1,6 +1,7 @@
package com.mapswithme.maps.search;
import android.app.Activity;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.mapswithme.maps.MwmActivity;
import com.mapswithme.maps.api.ParsedMwmRequest;
@ -10,6 +11,14 @@ import com.mapswithme.util.UiUtils;
public class FloatingSearchToolbarController extends SearchToolbarController
{
@Nullable
private VisibilityListener mVisibilityListener;
public interface VisibilityListener
{
void onSearchVisibilityChanged(boolean visible);
}
public FloatingSearchToolbarController(Activity activity)
{
super(activity.getWindow().getDecorView(), activity);
@ -43,12 +52,28 @@ public class FloatingSearchToolbarController extends SearchToolbarController
if (ParsedMwmRequest.hasRequest())
{
Animations.appearSliding(mToolbar, Animations.TOP, null);
Animations.appearSliding(mToolbar, Animations.TOP, new Runnable()
{
@Override
public void run()
{
if (mVisibilityListener != null)
mVisibilityListener.onSearchVisibilityChanged(true);
}
});
setQuery(ParsedMwmRequest.getCurrentRequest().getTitle());
}
else if (!TextUtils.isEmpty(SearchEngine.getQuery()))
{
Animations.appearSliding(mToolbar, Animations.TOP, null);
Animations.appearSliding(mToolbar, Animations.TOP, new Runnable()
{
@Override
public void run()
{
if (mVisibilityListener != null)
mVisibilityListener.onSearchVisibilityChanged(true);
}
});
setQuery(SearchEngine.getQuery());
}
else
@ -74,7 +99,21 @@ public class FloatingSearchToolbarController extends SearchToolbarController
if (!UiUtils.isVisible(mToolbar))
return false;
Animations.disappearSliding(mToolbar, Animations.TOP, null);
Animations.disappearSliding(mToolbar, Animations.TOP, new Runnable()
{
@Override
public void run()
{
if (mVisibilityListener != null)
mVisibilityListener.onSearchVisibilityChanged(false);
}
});
return true;
}
public void setVisibilityListener(@Nullable VisibilityListener visibilityListener)
{
mVisibilityListener = visibilityListener;
}
}

View file

@ -2,23 +2,30 @@ package com.mapswithme.maps.widget.menu;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import android.view.View;
import android.widget.ImageView;
import com.mapswithme.maps.LocationState;
import com.mapswithme.maps.R;
import com.mapswithme.maps.routing.RoutingController;
import com.mapswithme.util.Animations;
import com.mapswithme.util.Graphics;
import com.mapswithme.util.ThemeUtils;
import com.mapswithme.util.UiUtils;
import com.mapswithme.util.statistics.AlohaHelper;
import com.mapswithme.util.statistics.Statistics;
public class MyPositionButton
{
@NonNull
private final ImageView mButton;
private static final SparseArray<Drawable> mIcons = new SparseArray<>(); // Location mode -> Button icon
public MyPositionButton(View button)
private int mMode;
public MyPositionButton(@NonNull View button)
{
mButton = (ImageView) button;
mButton.setOnClickListener(new View.OnClickListener()
@ -38,6 +45,7 @@ public class MyPositionButton
@SuppressWarnings("deprecation")
public void update(int mode)
{
mMode = mode;
Drawable image = mIcons.get(mode);
if (image == null)
{
@ -71,5 +79,23 @@ public class MyPositionButton
if (image instanceof AnimationDrawable)
((AnimationDrawable) image).start();
UiUtils.visibleIf(!shouldBeHidden(), mButton);
}
private boolean shouldBeHidden()
{
return mMode == LocationState.FOLLOW_AND_ROTATE
&& (RoutingController.get().isPlanning() || RoutingController.get().isNavigating());
}
public void show()
{
Animations.appearSliding(mButton, Animations.RIGHT, null);
}
public void hide()
{
Animations.disappearSliding(mButton, Animations.RIGHT, null);
}
}

View file

@ -3,6 +3,7 @@ package com.mapswithme.maps.widget.placepage;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.GestureDetectorCompat;
import android.view.MotionEvent;
import android.view.View;
@ -53,9 +54,10 @@ public abstract class BasePlacePageAnimationController implements ObservableScro
void onPlacePageVisibilityChanged(boolean isVisible);
}
@Nullable
private OnAnimationListener mProgressListener;
interface OnAnimationListener
public interface OnAnimationListener
{
void onProgress(float translationX, float translationY);
}
@ -132,7 +134,7 @@ public abstract class BasePlacePageAnimationController implements ObservableScro
mVisibilityListener = listener;
}
public void setOnProgressListener(OnAnimationListener listener)
void setOnProgressListener(@Nullable OnAnimationListener listener)
{
mProgressListener = listener;
}

View file

@ -313,6 +313,7 @@ class BottomPlacePageAnimationController extends BasePlacePageAnimationControlle
}
mDetailsScroll.setTranslationY(detailsTranslation);
notifyProgress();
refreshToolbarVisibility();
return consumeEvent;
}
@ -489,6 +490,7 @@ class BottomPlacePageAnimationController extends BasePlacePageAnimationControlle
{
initialVisibility();
mPlacePage.setTranslationY(0);
mDetailsScroll.setTranslationY(mDetailsScroll.getHeight());
notifyProgress();
notifyVisibilityListener(false, false);
}
@ -528,7 +530,7 @@ class BottomPlacePageAnimationController extends BasePlacePageAnimationControlle
private void notifyProgress()
{
notifyProgress(0, mPreview.getTranslationY());
notifyProgress(0, mDetailsScroll.getTranslationY() + mPlacePage.getTranslationY());
}
private class UpdateListener implements ValueAnimator.AnimatorUpdateListener

View file

@ -11,6 +11,7 @@ import android.net.Uri;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
@ -1196,6 +1197,11 @@ public class PlacePageView extends RelativeLayout
mAnimationController.setOnVisibilityChangedListener(listener);
}
public void setOnAnimationListener(@Nullable BasePlacePageAnimationController.OnAnimationListener listener)
{
mAnimationController.setOnProgressListener(listener);
}
private void addOrganisation()
{
Statistics.INSTANCE.trackEvent(Statistics.EventName.EDITOR_ADD_CLICK,

View file

@ -2,6 +2,7 @@ package com.mapswithme.util;
import android.animation.Animator;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewPropertyAnimator;
@ -109,4 +110,28 @@ public final class Animations
}
});
}
public static void fadeOutView(@NonNull final View view, @Nullable final Runnable completionListener)
{
view.animate().setDuration(DURATION_DEFAULT).alpha(0).setListener(new UiUtils.SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation)
{
if (completionListener != null)
completionListener.run();
}
});
}
public static void fadeInView(@NonNull final View view, @Nullable final Runnable completionListener)
{
view.animate().setDuration(DURATION_DEFAULT).alpha(1).setListener(new UiUtils.SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation)
{
if (completionListener != null)
completionListener.run();
}
});
}
}