toolbar: refactor the logic of swapping navigation items and their animations to a presenter.

crowdin
Floens 7 years ago committed by Florens
parent d42b9dfa06
commit b93397825c
  1. 4
      Clover/app/src/main/java/org/floens/chan/ui/controller/ToolbarNavigationController.java
  2. 7
      Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java
  3. 23
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/NavigationItem.java
  4. 322
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
  5. 358
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarContainer.java
  6. 159
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarPresenter.java

@ -61,7 +61,7 @@ public abstract class ToolbarNavigationController extends NavigationController i
toolbar.processScrollCollapse(Toolbar.TOOLBAR_COLLAPSE_SHOW, true);
toolbar.beginTransition(to.navigation);
toolbar.transitionProgress(0f, false);
toolbar.transitionProgress(0f);
return true;
}
@ -70,7 +70,7 @@ public abstract class ToolbarNavigationController extends NavigationController i
public void swipeTransitionProgress(float progress) {
super.swipeTransitionProgress(progress);
toolbar.transitionProgress(progress, false);
toolbar.transitionProgress(progress);
}
@Override

@ -130,12 +130,7 @@ public class SearchLayout extends LinearLayout {
}
public void openKeyboard() {
searchView.post(new Runnable() {
@Override
public void run() {
AndroidUtils.requestViewAndKeyboardFocus(searchView);
}
});
searchView.post(() -> AndroidUtils.requestViewAndKeyboardFocus(searchView));
}
public interface SearchLayoutCallback {

@ -43,7 +43,9 @@ public class NavigationItem {
public ToolbarMiddleMenu middleMenu;
public View rightView;
public ToolbarMenuItem createOverflow(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, List<FloatingMenuItem> items) {
public ToolbarMenuItem createOverflow(
Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback,
List<FloatingMenuItem> items) {
ToolbarMenuItem overflow = menu.createOverflow(callback);
FloatingMenu overflowMenu = new FloatingMenu(context, overflow.getView(), items);
overflow.setSubMenu(overflowMenu);
@ -53,4 +55,23 @@ public class NavigationItem {
public void setTitle(int resId) {
title = getString(resId);
}
public NavigationItem copy() {
NavigationItem c = new NavigationItem();
c.title = title;
c.subtitle = subtitle;
c.hasBack = hasBack;
c.hasDrawer = hasDrawer;
c.handlesToolbarInset = handlesToolbarInset;
c.swipeable = swipeable;
c.search = search;
c.searchText = searchText;
// c.menu = menu;
c.middleMenu = middleMenu;
// c.rightView = rightView;
return c;
}
}

@ -17,10 +17,6 @@
*/
package org.floens.chan.ui.toolbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
@ -44,7 +40,6 @@ import org.floens.chan.R;
import org.floens.chan.ui.drawable.ArrowMenuDrawable;
import org.floens.chan.ui.drawable.DropdownArrowDrawable;
import org.floens.chan.ui.layout.SearchLayout;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList;
@ -52,10 +47,12 @@ import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.hideKeyboard;
import static org.floens.chan.utils.AndroidUtils.removeFromParentView;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
public class Toolbar extends LinearLayout implements View.OnClickListener {
public class Toolbar extends LinearLayout implements
View.OnClickListener, ToolbarPresenter.Callback {
public static final int TOOLBAR_COLLAPSE_HIDE = 1000000;
public static final int TOOLBAR_COLLAPSE_SHOW = -1000000;
@ -74,23 +71,18 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
};
private ToolbarPresenter presenter;
private ImageView arrowMenuView;
private ArrowMenuDrawable arrowMenuDrawable;
private LoadView navigationItemContainer;
private ToolbarContainer navigationItemContainer;
private ToolbarCallback callback;
private boolean openKeyboardAfterSearchViewCreated = false;
private int lastScrollDeltaOffset;
private int scrollOffset;
private List<ToolbarCollapseCallback> collapseCallbacks = new ArrayList<>();
private boolean transitioning = false;
private NavigationItem fromItem;
private LinearLayout fromView;
private NavigationItem toItem;
private LinearLayout toView;
public Toolbar(Context context) {
this(context, null);
}
@ -106,7 +98,13 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return transitioning || super.dispatchTouchEvent(ev);
return isTransitioning() || super.dispatchTouchEvent(ev);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
public int getToolbarHeight() {
@ -143,7 +141,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
scrollOffset = Math.max(0, Math.min(getHeight(), scrollOffset));
if (animated) {
animate().translationY(-scrollOffset).setDuration(300).setInterpolator(new DecelerateInterpolator(2f)).start();
animate().translationY(-scrollOffset)
.setDuration(300)
.setInterpolator(new DecelerateInterpolator(2f))
.start();
boolean collapse = scrollOffset > 0;
for (ToolbarCollapseCallback c : collapseCallbacks) {
@ -181,94 +182,45 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
}
// public void updateNavigation() {
// closeSearchInternal();
// setNavigationItem(false, false, toItem);
// }
public NavigationItem getNavigationItem() {
return toItem;
public void openSearch() {
presenter.openSearch();
}
public boolean openSearch() {
return openSearchInternal();
public boolean closeSearch() {
return presenter.closeSearch();
}
public boolean closeSearch() {
return closeSearchInternal();
public boolean isTransitioning() {
return navigationItemContainer.isTransitioning();
}
public void setNavigationItem(final boolean animate, final boolean pushing, final NavigationItem item) {
setNavigationItemInternal(animate, pushing, item);
ToolbarPresenter.AnimationStyle animationStyle;
if (!animate) {
animationStyle = ToolbarPresenter.AnimationStyle.NONE;
} else if (pushing) {
animationStyle = ToolbarPresenter.AnimationStyle.PUSH;
} else {
animationStyle = ToolbarPresenter.AnimationStyle.POP;
}
presenter.set(item, animationStyle);
}
public void setArrowMenuIconShown(boolean show) {
arrowMenuView.setVisibility(show ? View.VISIBLE : View.GONE);
}
public boolean isTransitioning() {
return transitioning;
}
public void beginTransition(NavigationItem newItem) {
if (transitioning) {
throw new IllegalStateException("beginTransition called when already transitioning");
}
attachNavigationItem(newItem);
navigationItemContainer.addView(toView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
transitioning = true;
presenter.startTransition(newItem, ToolbarPresenter.TransitionAnimationStyle.POP);
}
public void transitionProgress(float progress, boolean pushing) {
if (!transitioning) {
throw new IllegalStateException("transitionProgress called while not transitioning");
}
progress = Math.max(0f, Math.min(1f, progress));
final int offset = dp(16);
toView.setTranslationY((pushing ? offset : -offset) * (1f - progress));
toView.setAlpha(progress);
if (fromItem != null) {
fromView.setTranslationY((pushing ? -offset : offset) * progress);
fromView.setAlpha(1f - progress);
}
float arrowEnd = toItem.hasBack || toItem.search ? 1f : 0f;
if (arrowMenuDrawable.getProgress() != arrowEnd) {
arrowMenuDrawable.setProgress(toItem.hasBack || toItem.search ? progress : 1f - progress);
}
public void transitionProgress(float progress) {
presenter.setTransitionProgress(progress);
}
public void finishTransition(boolean finished) {
if (!transitioning) {
throw new IllegalStateException("finishTransition called when not transitioning");
}
if (finished) {
if (fromItem != null) {
// From a search otherwise
if (fromItem != toItem) {
removeNavigationItem(fromItem, fromView);
fromView = null;
}
}
setArrowMenuProgress(toItem.hasBack || toItem.search ? 1f : 0f);
} else {
removeNavigationItem(toItem, toView);
setArrowMenuProgress(fromItem.hasBack || fromItem.search ? 1f : 0f);
toItem = fromItem;
toView = fromView;
}
fromItem = null;
fromView = null;
transitioning = false;
public void finishTransition(boolean completed) {
presenter.stopTransition(completed);
}
public void setCallback(ToolbarCallback callback) {
@ -282,11 +234,6 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
public void setArrowMenuProgress(float progress) {
arrowMenuDrawable.setProgress(progress);
}
@ -300,20 +247,7 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
public void updateTitle(NavigationItem navigationItem) {
LinearLayout view = navigationItem == fromItem ? fromView : (navigationItem == toItem ? toView : null);
if (view != null) {
TextView titleView = view.findViewById(R.id.title);
if (titleView != null) {
titleView.setText(navigationItem.title);
}
if (!TextUtils.isEmpty(navigationItem.subtitle)) {
TextView subtitleView = view.findViewById(R.id.subtitle);
if (subtitleView != null) {
subtitleView.setText(navigationItem.subtitle);
}
}
}
presenter.update(navigationItem);
}
private void init() {
@ -321,6 +255,13 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
if (isInEditMode()) return;
presenter = new ToolbarPresenter();
presenter.create(this);
initView();
}
private void initView() {
FrameLayout leftButtonContainer = new FrameLayout(getContext());
addView(leftButtonContainer, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
@ -333,11 +274,16 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
setRoundItemBackground(arrowMenuView);
leftButtonContainer.addView(arrowMenuView, new FrameLayout.LayoutParams(getResources().getDimensionPixelSize(R.dimen.toolbar_height), FrameLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER_VERTICAL));
int toolbarSize = getResources().getDimensionPixelSize(R.dimen.toolbar_height);
FrameLayout.LayoutParams leftButtonContainerLp = new FrameLayout.LayoutParams(
toolbarSize, FrameLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER_VERTICAL);
leftButtonContainer.addView(arrowMenuView, leftButtonContainerLp);
navigationItemContainer = new LoadView(getContext());
navigationItemContainer = new ToolbarContainer(getContext());
addView(navigationItemContainer, new LayoutParams(0, LayoutParams.MATCH_PARENT, 1f));
navigationItemContainer.setArrowMenu(arrowMenuDrawable);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (getElevation() == 0f) {
setElevation(dp(4f));
@ -345,116 +291,51 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
}
private boolean openSearchInternal() {
if (toItem != null && !toItem.search) {
toItem.search = true;
openKeyboardAfterSearchViewCreated = true;
setNavigationItemInternal(true, false, toItem);
callback.onSearchVisibilityChanged(toItem, true);
return true;
} else {
return false;
}
}
@Override
public void showForNavigationItem(
NavigationItem item, ToolbarPresenter.AnimationStyle animation) {
View view = createNavigationItemView(item);
boolean arrow = item.hasBack || item.search;
private boolean closeSearchInternal() {
if (toItem != null && toItem.search) {
toItem.search = false;
toItem.searchText = null;
setNavigationItemInternal(true, false, toItem);
callback.onSearchVisibilityChanged(toItem, false);
return true;
} else {
return false;
}
navigationItemContainer.set(view, arrow, animation);
}
private void setNavigationItemInternal(boolean animate, final boolean pushing, NavigationItem newItem) {
if (transitioning) {
throw new IllegalStateException("setNavigationItemInternal called when already transitioning");
}
attachNavigationItem(newItem);
transitioning = true;
@Override
public void containerStartTransition(
NavigationItem item, ToolbarPresenter.TransitionAnimationStyle animation) {
View view = createNavigationItemView(item);
boolean arrow = item.hasBack || item.search;
if (fromItem == toItem) {
// Search toggled
navigationItemContainer.setListener(new LoadView.Listener() {
@Override
public void onLoadViewRemoved(View view) {
// Remove the menu from the navigation item
((ViewGroup) view).removeAllViews();
finishTransition(true);
navigationItemContainer.setListener(null);
}
});
navigationItemContainer.setView(toView, animate);
navigationItemContainer.startTransition(view, arrow, animation);
}
animateArrow(toItem.hasBack || toItem.search);
} else {
navigationItemContainer.addView(toView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
if (animate) {
toView.setAlpha(0f);
final ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f);
animator.setDuration(300);
animator.setInterpolator(new DecelerateInterpolator(2f));
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finishTransition(true);
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
transitionProgress((float) animation.getAnimatedValue(), pushing);
}
});
if (!pushing) {
animator.setStartDelay(100);
}
// hack to avoid the animation jumping when the current frame has a lot to do,
// which is often because setting a new navigationitem is almost always done
// when setting a new controller.
post(new Runnable() {
@Override
public void run() {
animator.start();
}
});
} else {
arrowMenuDrawable.setProgress(toItem.hasBack || toItem.search ? 1f : 0f);
finishTransition(true);
}
}
@Override
public void containerStopTransition(boolean didComplete) {
navigationItemContainer.stopTransition(didComplete);
}
private void attachNavigationItem(NavigationItem newItem) {
if (transitioning) {
throw new IllegalStateException("attachNavigationItem called while transitioning");
}
@Override
public void containerSetTransitionProgress(float progress) {
navigationItemContainer.setTransitionProgress(progress);
}
fromItem = toItem;
fromView = toView;
toItem = newItem;
toView = createNavigationItemView(toItem);
@Override
public void onSearchVisibilityChanged(NavigationItem item, boolean visible) {
callback.onSearchVisibilityChanged(item, visible);
if (!toItem.search) {
AndroidUtils.hideKeyboard(navigationItemContainer);
if (!visible) {
hideKeyboard(navigationItemContainer);
}
}
private void removeNavigationItem(NavigationItem item, LinearLayout view) {
if (!transitioning) {
throw new IllegalStateException("removeNavigationItem called while not transitioning");
}
@Override
public void onSearchInput(NavigationItem item, String input) {
callback.onSearchEntered(item, input);
}
view.removeAllViews();
navigationItemContainer.removeView(view);
@Override
public void updateViewForItem(NavigationItem item, boolean current) {
navigationItemContainer.update(item, current);
}
private LinearLayout createNavigationItemView(final NavigationItem item) {
@ -483,12 +364,7 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
int arrowPressedColor = getAttrColor(getContext(), R.attr.dropdown_light_pressed_color);
Drawable drawable = new DropdownArrowDrawable(dp(12), dp(12), true, arrowColor, arrowPressedColor);
titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null);
titleView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
item.middleMenu.show(titleView);
}
});
titleView.setOnClickListener(v -> item.middleMenu.show(titleView));
}
TextView subtitleView = menu.findViewById(R.id.subtitle);
@ -521,12 +397,8 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
private LinearLayout createSearchLayout(NavigationItem item) {
SearchLayout searchLayout = new SearchLayout(getContext());
searchLayout.setCallback(new SearchLayout.SearchLayoutCallback() {
@Override
public void onSearchEntered(String entered) {
item.searchText = entered;
callback.onSearchEntered(item, entered);
}
searchLayout.setCallback(input -> {
presenter.searchInput(input);
});
if (item.searchText != null) {
@ -534,29 +406,11 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
searchLayout.setHint(callback.getSearchHint(item));
if (openKeyboardAfterSearchViewCreated) {
openKeyboardAfterSearchViewCreated = false;
searchLayout.openKeyboard();
}
searchLayout.setPadding(dp(16), searchLayout.getPaddingTop(), searchLayout.getPaddingRight(), searchLayout.getPaddingBottom());
return searchLayout;
}
private void animateArrow(boolean toArrow) {
float to = toArrow ? 1f : 0f;
if (to != arrowMenuDrawable.getProgress()) {
ValueAnimator arrowAnimation = ValueAnimator.ofFloat(arrowMenuDrawable.getProgress(), to);
arrowAnimation.setDuration(300);
arrowAnimation.setInterpolator(new DecelerateInterpolator(2f));
arrowAnimation.addUpdateListener(animation ->
setArrowMenuProgress((float) animation.getAnimatedValue()));
arrowAnimation.start();
}
}
public interface ToolbarCallback {
void onMenuOrBackClicked(boolean isArrow);

@ -0,0 +1,358 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.toolbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.ui.drawable.ArrowMenuDrawable;
import java.util.HashMap;
import java.util.Map;
import static org.floens.chan.utils.AndroidUtils.dp;
/**
* The container for the views created by the toolbar for the navigation items.
* <p>
* It will strictly only transition between two views. If a new view is set
* and a transition is in progress, it is stopped before adding the new view.
* <p>
* For normal animations the previousView is the view that is animated away from, and the
* currentView is the view where is animated to. The previousView is removed and cleared if the
* animation finished.
* <p>
* Transitions are user-controlled animations that can be cancelled of finished. For that the
* currentView describes the view that was originally there, and the transitionView is the view
* what is possibly transitioned to.
* <p>
* This is also the class that is responsible for the orientation and animation of the arrow-menu
* drawable.
*/
public class ToolbarContainer extends FrameLayout {
private ArrowMenuDrawable arrowMenu;
private View previousView;
private boolean previousArrow;
private View currentView;
private boolean currentArrow;
private View transitionView;
private boolean transitionArrow;
private ToolbarPresenter.TransitionAnimationStyle transitionAnimationStyle;
private Map<View, Animator> animatorSet = new HashMap<>();
public ToolbarContainer(Context context) {
this(context, null);
}
public ToolbarContainer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ToolbarContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setArrowMenu(ArrowMenuDrawable arrowMenu) {
this.arrowMenu = arrowMenu;
}
public void set(View view, boolean arrow, ToolbarPresenter.AnimationStyle animation) {
if (transitionView != null) {
throw new IllegalStateException("Currently in transition mode");
}
endAnimations();
previousView = currentView;
previousArrow = currentArrow;
currentView = view;
currentArrow = arrow;
addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
if (getChildCount() > 2) {
throw new IllegalArgumentException("More than 2 child views attached");
}
// Can't run the animation if there is no previous view
// Otherwise just show it without an animation.
if (animation != ToolbarPresenter.AnimationStyle.NONE && previousView != null) {
setAnimation(view, previousView, animation);
} else {
if (previousView != null) {
removeView(previousView);
previousView = null;
}
}
if (animation == ToolbarPresenter.AnimationStyle.NONE) {
setArrowProgress(1f, !currentArrow);
}
}
public void update(NavigationItem item, boolean current) {
// TODO: clean up
View view = current ? currentView : (previousView != null ? previousView : transitionView);
if (view != null) {
TextView titleView = view.findViewById(R.id.title);
if (titleView != null) {
titleView.setText(item.title);
}
if (!TextUtils.isEmpty(item.subtitle)) {
TextView subtitleView = view.findViewById(R.id.subtitle);
if (subtitleView != null) {
subtitleView.setText(item.subtitle);
}
}
}
}
public boolean isTransitioning() {
return transitionView != null || previousView != null;
}
public void startTransition(
View view, boolean arrow, ToolbarPresenter.TransitionAnimationStyle style) {
if (transitionView != null) {
throw new IllegalStateException("Already in transition mode");
}
endAnimations();
transitionView = view;
transitionArrow = arrow;
transitionAnimationStyle = style;
addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
if (getChildCount() > 2) {
throw new IllegalArgumentException("More than 2 child views attached");
}
}
public void stopTransition(boolean didComplete) {
if (transitionView == null) {
throw new IllegalStateException("Not in transition mode");
}
if (didComplete) {
removeView(currentView);
currentView = transitionView;
currentArrow = transitionArrow;
transitionView = null;
} else {
removeView(transitionView);
transitionView = null;
}
if (getChildCount() != 1) {
throw new IllegalStateException("Not 1 view attached");
}
}
public void setTransitionProgress(float progress) {
if (transitionView == null) {
throw new IllegalStateException("Not in transition mode");
}
transitionProgressAnimation(progress, transitionAnimationStyle);
}
private void endAnimations() {
if (previousView != null) {
endAnimation(previousView);
if (previousView != null) {
throw new IllegalStateException("Animation end did not remove view");
}
}
if (currentView != null) {
endAnimation(currentView);
}
}
private void endAnimation(View view) {
Animator a = animatorSet.remove(view);
if (a != null) {
a.end();
}
}
private void setAnimation(View view, View previousView,
ToolbarPresenter.AnimationStyle animationStyle) {
if (animationStyle == ToolbarPresenter.AnimationStyle.PUSH ||
animationStyle == ToolbarPresenter.AnimationStyle.POP) {
final boolean pushing = animationStyle == ToolbarPresenter.AnimationStyle.PUSH;
// Previous animation
ValueAnimator previousAnimation = getShortAnimator();
previousAnimation.addUpdateListener(a -> {
float value = (float) a.getAnimatedValue();
setPreviousAnimationProgress(previousView, pushing, value);
});
previousAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animatorSet.remove(previousView);
removeView(previousView);
ToolbarContainer.this.previousView = null;
}
});
if (!pushing) previousAnimation.setStartDelay(100);
animatorSet.put(previousView, previousAnimation);
post(previousAnimation::start);
// Current animation + arrow
view.setAlpha(0f);
ValueAnimator animation = getShortAnimator();
animation.addUpdateListener(a -> {
float value = (float) a.getAnimatedValue();
setAnimationProgress(view, pushing, value);
if (previousArrow != currentArrow) {
setArrowProgress(value, !currentArrow);
}
});
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animatorSet.remove(view);
}
});
if (!pushing) animation.setStartDelay(100);
animatorSet.put(view, animation);
post(animation::start);
} else if (animationStyle == ToolbarPresenter.AnimationStyle.FADE) {
// Previous animation
ValueAnimator previousAnimation =
ObjectAnimator.ofFloat(previousView, View.ALPHA, 1f, 0f);
previousAnimation.setDuration(300);
previousAnimation.setInterpolator(new LinearInterpolator());
previousAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animatorSet.remove(previousView);
removeView(previousView);
ToolbarContainer.this.previousView = null;
}
});
animatorSet.put(previousView, previousAnimation);
post(previousAnimation::start);
// Current animation + arrow
view.setAlpha(0f);
ValueAnimator animation = ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f);
animation.setDuration(300);
animation.setInterpolator(new LinearInterpolator());
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animatorSet.remove(view);
}
});
// A different animator for the arrow because that one needs the deceleration
// interpolator.
ValueAnimator arrow = ValueAnimator.ofFloat(0f, 1f);
arrow.setDuration(300);
arrow.setInterpolator(new DecelerateInterpolator(2f));
arrow.addUpdateListener(a -> {
float value = (float) a.getAnimatedValue();
if (previousArrow != currentArrow) {
setArrowProgress(value, !currentArrow);
}
});
AnimatorSet animationAndArrow = new AnimatorSet();
animationAndArrow.playTogether(animation, arrow);
animationAndArrow.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animatorSet.remove(view);
}
});
animatorSet.put(view, animationAndArrow);
post(animationAndArrow::start);
}
}
private void setPreviousAnimationProgress(View view, boolean pushing, float progress) {
final int offset = dp(16);
view.setTranslationY((pushing ? -offset : offset) * progress);
view.setAlpha(1f - progress);
}
private void setAnimationProgress(View view, boolean pushing, float progress) {
final int offset = dp(16);
view.setTranslationY((pushing ? offset : -offset) * (1f - progress));
view.setAlpha(progress);
}
private void setArrowProgress(float progress, boolean reverse) {
if (reverse) {
progress = 1f - progress;
}
progress = Math.max(0f, Math.min(1f, progress));
arrowMenu.setProgress(progress);
}
private void transitionProgressAnimation(
float progress, ToolbarPresenter.TransitionAnimationStyle style) {
progress = Math.max(0f, Math.min(1f, progress));
final int offset = dp(16);
boolean pushing = style == ToolbarPresenter.TransitionAnimationStyle.PUSH;
transitionView.setTranslationY((pushing ? offset : -offset) * (1f - progress));
transitionView.setAlpha(progress);
currentView.setTranslationY((pushing ? -offset : offset) * progress);
currentView.setAlpha(1f - progress);
if (transitionArrow != currentArrow) {
setArrowProgress(progress, !transitionArrow);
}
}
private ValueAnimator getShortAnimator() {
final ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f);
animator.setDuration(300);
animator.setInterpolator(new DecelerateInterpolator(2f));
return animator;
}
}

@ -0,0 +1,159 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.toolbar;
public class ToolbarPresenter {
public enum AnimationStyle {
NONE,
PUSH,
POP,
FADE
}
public enum TransitionAnimationStyle {
PUSH,
POP
}
private Callback callback;
private NavigationItem item;
private NavigationItem search;
private NavigationItem transition;
void create(Callback callback) {
this.callback = callback;
}
void set(NavigationItem newItem, AnimationStyle animation) {
cancelTransitionIfNeeded();
if (closeSearchIfNeeded()) {
animation = AnimationStyle.FADE;
}
item = newItem;
callback.showForNavigationItem(item, animation);
}
void update(NavigationItem updatedItem) {
callback.updateViewForItem(updatedItem, updatedItem == item);
}
void startTransition(NavigationItem newItem, TransitionAnimationStyle animation) {
cancelTransitionIfNeeded();
if (closeSearchIfNeeded()) {
callback.showForNavigationItem(item, AnimationStyle.NONE);
}
transition = newItem;
callback.containerStartTransition(transition, animation);
}
void stopTransition(boolean didComplete) {
if (transition == null) {
return;
}
callback.containerStopTransition(didComplete);
if (didComplete) {
item = transition;
callback.showForNavigationItem(item, AnimationStyle.NONE);
}
transition = null;
}
void setTransitionProgress(float progress) {
if (transition == null) {
return;
}
callback.containerSetTransitionProgress(progress);
}
void openSearch() {
if (search != null) {
return;
}
cancelTransitionIfNeeded();
search = new NavigationItem();
search.search = true;
callback.showForNavigationItem(search, AnimationStyle.FADE);
callback.onSearchVisibilityChanged(item, true);
}
boolean closeSearch() {
if (search == null) {
return false;
}
search = null;
set(item, AnimationStyle.FADE);
callback.onSearchVisibilityChanged(item, false);
return true;
}
private void cancelTransitionIfNeeded() {
if (transition != null) {
callback.containerStopTransition(false);
transition = null;
}
}
private boolean closeSearchIfNeeded() {
// Cancel search
if (search != null) {
search = null;
callback.onSearchVisibilityChanged(item, false);
return true;
}
return false;
}
void searchInput(String input) {
if (search == null) {
return;
}
search.searchText = input;
callback.onSearchInput(item, search.searchText);
}
interface Callback {
void showForNavigationItem(NavigationItem item, AnimationStyle animation);
void containerStartTransition(NavigationItem item, TransitionAnimationStyle animation);
void containerStopTransition(boolean didComplete);
void containerSetTransitionProgress(float progress);
void onSearchVisibilityChanged(NavigationItem item, boolean visible);
void onSearchInput(NavigationItem item, String input);
void updateViewForItem(NavigationItem item, boolean current);
}
}
Loading…
Cancel
Save