2 * Copyright © 2021 Damyan Ivanov.
3 * This file is part of MoLe.
4 * MoLe is free software: you can distribute it and/or modify it
5 * under the term of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your opinion), any later version.
9 * MoLe is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License terms for details.
14 * You should have received a copy of the GNU General Public License
15 * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
18 package net.ktnx.mobileledger.ui;
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.TimeInterpolator;
23 import android.annotation.SuppressLint;
24 import android.content.Context;
25 import android.os.Handler;
26 import android.view.MotionEvent;
27 import android.view.ViewGroup;
28 import android.view.ViewPropertyAnimator;
30 import androidx.annotation.NonNull;
31 import androidx.recyclerview.widget.RecyclerView;
33 import com.google.android.material.floatingactionbutton.FloatingActionButton;
35 import net.ktnx.mobileledger.utils.DimensionUtils;
36 import net.ktnx.mobileledger.utils.Logger;
38 public class FabManager {
39 private static final boolean FAB_SHOWN = true;
40 private static final boolean FAB_HIDDEN = false;
41 private static final int AUTO_SHOW_DELAY_MILLS = 4000;
42 private final FloatingActionButton fab;
43 private boolean wantedFabState = FAB_SHOWN;
44 private ViewPropertyAnimator fabSlideAnimator;
45 private int fabVerticalOffset;
46 public FabManager(FloatingActionButton fab) {
49 public static void handle(FabHandler fabHandler, RecyclerView recyclerView) {
50 new ScrollFabHandler(fabHandler, recyclerView);
52 private void slideFabTo(int target, long duration, TimeInterpolator interpolator) {
53 fabSlideAnimator = fab.animate()
54 .translationY((float) target)
55 .setInterpolator(interpolator)
56 .setDuration(duration)
57 .setListener(new AnimatorListenerAdapter() {
58 public void onAnimationEnd(Animator animation) {
59 fabSlideAnimator = null;
63 public void showFab() {
64 if (wantedFabState == FAB_SHOWN) {
65 // Logger.debug("fab", "Ignoring request to show already visible FAB");
69 // b.btnAddTransaction.show();
70 if (this.fabSlideAnimator != null) {
71 this.fabSlideAnimator.cancel();
75 Logger.debug("fab", "Showing FAB");
76 wantedFabState = FAB_SHOWN;
78 com.google.android.material.animation.AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
80 public void hideFab() {
81 if (wantedFabState == FAB_HIDDEN) {
82 // Logger.debug("fab", "Ignoring request to hide FAB -- already hidden");
86 calcVerticalFabOffset();
88 // b.btnAddTransaction.hide();
89 if (this.fabSlideAnimator != null) {
90 this.fabSlideAnimator.cancel();
94 Logger.debug("fab", "Hiding FAB");
95 wantedFabState = FAB_HIDDEN;
96 slideFabTo(fabVerticalOffset, 150L,
97 com.google.android.material.animation.AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
99 private void calcVerticalFabOffset() {
100 if (fabVerticalOffset > 0)
101 return;// already calculated
104 int height = fab.getMeasuredHeight();
108 ViewGroup.LayoutParams layoutParams = fab.getLayoutParams();
109 if (layoutParams instanceof ViewGroup.MarginLayoutParams)
110 bottomMargin = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
112 throw new RuntimeException("Unsupported layout params " + layoutParams.getClass()
113 .getCanonicalName());
115 fabVerticalOffset = height + bottomMargin;
117 public interface FabHandler {
118 Context getContext();
120 void showManagedFab();
122 void hideManagedFab();
125 public static class ScrollFabHandler {
126 final private FabHandler fabHandler;
127 private int generation = 0;
128 @SuppressLint("ClickableViewAccessibility")
129 ScrollFabHandler(FabHandler fabHandler, RecyclerView recyclerView) {
130 this.fabHandler = fabHandler;
131 final float triggerAbsolutePixels = DimensionUtils.dp2px(fabHandler.getContext(), 20f);
132 final float triggerRelativePixels = triggerAbsolutePixels / 4f;
133 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
135 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
136 // Logger.debug("touch", "Scrolled " + dy);
143 super.onScrolled(recyclerView, dx, dy);
146 recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
147 private float absoluteAnchor = -1;
149 public boolean onInterceptTouchEvent(@NonNull RecyclerView rv,
150 @NonNull MotionEvent e) {
151 switch (e.getActionMasked()) {
152 case MotionEvent.ACTION_DOWN:
153 absoluteAnchor = e.getRawY();
154 // Logger.debug("touch",
155 // String.format(Locale.US, "Touch down at %4.2f", absoluteAnchor));
157 case MotionEvent.ACTION_MOVE:
158 if (absoluteAnchor < 0)
161 final float absoluteY = e.getRawY();
162 // Logger.debug("touch", String.format(Locale.US, "Move to %4.2f",
165 if (absoluteY > absoluteAnchor + triggerAbsolutePixels) {
167 // Logger.debug("touch", "SHOW");
169 absoluteAnchor = absoluteY;
171 else if (absoluteY < absoluteAnchor - triggerAbsolutePixels) {
173 // Logger.debug("touch", "HIDE");
175 absoluteAnchor = absoluteY;
184 private void hideFab() {
186 int thisGeneration = generation;
187 fabHandler.hideManagedFab();
188 new Handler().postDelayed(() -> {
189 if (generation != thisGeneration)
193 }, AUTO_SHOW_DELAY_MILLS);
195 private void showFab() {
197 fabHandler.showManagedFab();