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)
67 // b.btnAddTransaction.show();
68 if (this.fabSlideAnimator != null) {
69 this.fabSlideAnimator.cancel();
73 wantedFabState = FAB_SHOWN;
75 com.google.android.material.animation.AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
77 public void hideFab() {
78 if (wantedFabState == FAB_HIDDEN)
81 calcVerticalFabOffset();
83 // b.btnAddTransaction.hide();
84 if (this.fabSlideAnimator != null) {
85 this.fabSlideAnimator.cancel();
89 wantedFabState = FAB_HIDDEN;
90 slideFabTo(fabVerticalOffset, 150L,
91 com.google.android.material.animation.AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
93 private void calcVerticalFabOffset() {
94 if (fabVerticalOffset > 0)
95 return;// already calculated
98 int height = fab.getMeasuredHeight();
102 ViewGroup.LayoutParams layoutParams = fab.getLayoutParams();
103 if (layoutParams instanceof ViewGroup.MarginLayoutParams)
104 bottomMargin = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
106 throw new RuntimeException("Unsupported layout params " + layoutParams.getClass()
107 .getCanonicalName());
109 fabVerticalOffset = height + bottomMargin;
111 public interface FabHandler {
112 Context getContext();
114 void showManagedFab();
116 void hideManagedFab();
119 public static class ScrollFabHandler {
120 final private FabHandler fabHandler;
121 private int generation = 0;
122 @SuppressLint("ClickableViewAccessibility")
123 ScrollFabHandler(FabHandler fabHandler, RecyclerView recyclerView) {
124 this.fabHandler = fabHandler;
125 final float triggerAbsolutePixels = DimensionUtils.dp2px(fabHandler.getContext(), 20f);
126 final float triggerRelativePixels = triggerAbsolutePixels / 4f;
127 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
129 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
130 Logger.debug("touch", "Scrolled " + dy);
137 super.onScrolled(recyclerView, dx, dy);
140 recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
141 private float absoluteAnchor = -1;
143 public boolean onInterceptTouchEvent(@NonNull RecyclerView rv,
144 @NonNull MotionEvent e) {
145 switch (e.getActionMasked()) {
146 case MotionEvent.ACTION_DOWN:
147 absoluteAnchor = e.getRawY();
148 // Logger.debug("touch",
149 // String.format(Locale.US, "Touch down at %4.2f", absoluteAnchor));
151 case MotionEvent.ACTION_MOVE:
152 if (absoluteAnchor < 0)
155 final float absoluteY = e.getRawY();
156 // Logger.debug("touch", String.format(Locale.US, "Move to %4.2f",
159 if (absoluteY > absoluteAnchor + triggerAbsolutePixels) {
161 // Logger.debug("touch", "SHOW");
163 absoluteAnchor = absoluteY;
165 else if (absoluteY < absoluteAnchor - triggerAbsolutePixels) {
167 // Logger.debug("touch", "HIDE");
169 absoluteAnchor = absoluteY;
178 private void hideFab() {
180 int thisGeneration = generation;
181 fabHandler.hideManagedFab();
182 new Handler().postDelayed(() -> {
183 if (generation != thisGeneration)
187 }, AUTO_SHOW_DELAY_MILLS);
189 private void showFab() {
191 fabHandler.showManagedFab();