Skip to content

Commit 3613c00

Browse files
committed
experimenting with a coordinator layout behavior
1 parent 925080b commit 3613c00

9 files changed

Lines changed: 523 additions & 9 deletions

File tree

library/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ dependencies {
2222
compile fileTree(dir: 'libs', include: ['*.jar'])
2323
testCompile 'junit:junit:4.12'
2424
compile 'com.android.support:appcompat-v7:23.4.0'
25+
compile 'com.android.support:design:23.4.0'
2526
}

library/src/main/java/com/akiniyalocts/minor/MinorLayout.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ private void initView(Context context, AttributeSet attrs) {
5151

5252
shouldCenter = a.getBoolean(R.styleable.MinorLayout_minor_center_items, false);
5353

54-
5554
this.setOrientation(HORIZONTAL);
5655

5756
if(shouldCenter) {
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
package com.akiniyalocts.minor.behavior;
2+
3+
import android.animation.ObjectAnimator;
4+
import android.animation.ValueAnimator;
5+
import android.content.Context;
6+
import android.os.Build;
7+
import android.support.design.widget.CoordinatorLayout;
8+
import android.support.design.widget.FloatingActionButton;
9+
import android.support.design.widget.Snackbar;
10+
import android.support.v4.view.ViewCompat;
11+
import android.support.v4.view.ViewPropertyAnimatorCompat;
12+
import android.support.v4.view.ViewPropertyAnimatorUpdateListener;
13+
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
14+
import android.util.AttributeSet;
15+
import android.view.View;
16+
import android.view.ViewGroup;
17+
import android.view.animation.Interpolator;
18+
19+
/**
20+
* Repurposed from: https://github.com/aurelhubert/ahbottomnavigation
21+
*/
22+
public class MinorBehavior<V extends View> extends VerticalScrollingBehavior<V> {
23+
24+
private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator();
25+
private static final int ANIM_DURATION = 300;
26+
27+
private boolean hidden = false;
28+
private ViewPropertyAnimatorCompat translationAnimator;
29+
private ObjectAnimator translationObjectAnimator;
30+
private Snackbar.SnackbarLayout snackbarLayout;
31+
private FloatingActionButton floatingActionButton;
32+
private int mSnackbarHeight = -1;
33+
private boolean fabBottomMarginInitialized = false;
34+
private float targetOffset = 0, fabTargetOffset = 0, fabDefaultBottomMargin = 0, snackBarY = 0;
35+
private boolean behaviorTranslationEnabled = true;
36+
37+
public MinorBehavior() {
38+
super();
39+
}
40+
41+
public MinorBehavior(boolean behaviorTranslationEnabled) {
42+
super();
43+
this.behaviorTranslationEnabled = behaviorTranslationEnabled;
44+
}
45+
46+
public MinorBehavior(Context context, AttributeSet attrs) {
47+
super(context, attrs);
48+
}
49+
50+
@Override
51+
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
52+
boolean layoutChild = super.onLayoutChild(parent, child, layoutDirection);
53+
return layoutChild;
54+
}
55+
56+
@Override
57+
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
58+
return super.onDependentViewChanged(parent, child, dependency);
59+
}
60+
61+
@Override
62+
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
63+
super.onDependentViewRemoved(parent, child, dependency);
64+
}
65+
66+
@Override
67+
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
68+
updateSnackbar(child, dependency);
69+
updateFloatingActionButton(dependency);
70+
return super.layoutDependsOn(parent, child, dependency);
71+
}
72+
73+
@Override
74+
public void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll) {
75+
}
76+
77+
@Override
78+
public void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection) {
79+
handleDirection(child, scrollDirection);
80+
}
81+
82+
83+
private void handleDirection(V child, int scrollDirection) {
84+
if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) {
85+
hidden = false;
86+
animateOffset(child, 0, false, true);
87+
} else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) {
88+
hidden = true;
89+
animateOffset(child, child.getHeight(), false, true);
90+
}
91+
}
92+
93+
@Override
94+
protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) {
95+
handleDirection(child, scrollDirection);
96+
return true;
97+
}
98+
99+
/**
100+
* Animate offset
101+
*
102+
* @param child
103+
* @param offset
104+
*/
105+
private void animateOffset(final V child, final int offset, boolean forceAnimation, boolean withAnimation) {
106+
if (!behaviorTranslationEnabled && !forceAnimation) {
107+
return;
108+
}
109+
110+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
111+
ensureOrCancelObjectAnimation(child, offset, withAnimation);
112+
translationObjectAnimator.start();
113+
} else {
114+
ensureOrCancelAnimator(child, withAnimation);
115+
translationAnimator.translationY(offset).start();
116+
}
117+
}
118+
119+
/**
120+
* Manage animation for Android >= KITKAT
121+
*
122+
* @param child
123+
*/
124+
private void ensureOrCancelAnimator(V child, boolean withAnimation) {
125+
if (translationAnimator == null) {
126+
translationAnimator = ViewCompat.animate(child);
127+
translationAnimator.setDuration(withAnimation ? ANIM_DURATION : 0);
128+
translationAnimator.setUpdateListener(new ViewPropertyAnimatorUpdateListener() {
129+
@Override
130+
public void onAnimationUpdate(View view) {
131+
// Animate snackbar
132+
if (snackbarLayout != null && snackbarLayout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
133+
targetOffset = view.getMeasuredHeight() - view.getTranslationY();
134+
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) snackbarLayout.getLayoutParams();
135+
p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) targetOffset);
136+
snackbarLayout.requestLayout();
137+
}
138+
// Animate Floating Action Button
139+
if (floatingActionButton != null && floatingActionButton.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
140+
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) floatingActionButton.getLayoutParams();
141+
fabTargetOffset = fabDefaultBottomMargin - view.getTranslationY() + snackBarY;
142+
p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) fabTargetOffset);
143+
floatingActionButton.requestLayout();
144+
}
145+
}
146+
});
147+
translationAnimator.setInterpolator(INTERPOLATOR);
148+
} else {
149+
translationAnimator.setDuration(withAnimation ? ANIM_DURATION : 0);
150+
translationAnimator.cancel();
151+
}
152+
}
153+
154+
/**
155+
* Manage animation for Android < KITKAT
156+
*
157+
* @param child
158+
*/
159+
private void ensureOrCancelObjectAnimation(final V child, final int offset, boolean withAnimation) {
160+
161+
if (translationObjectAnimator != null) {
162+
translationObjectAnimator.cancel();
163+
}
164+
165+
translationObjectAnimator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, offset);
166+
translationObjectAnimator.setDuration(withAnimation ? ANIM_DURATION : 0);
167+
translationObjectAnimator.setInterpolator(INTERPOLATOR);
168+
translationObjectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
169+
@Override
170+
public void onAnimationUpdate(ValueAnimator animation) {
171+
if (snackbarLayout != null && snackbarLayout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
172+
targetOffset = child.getMeasuredHeight() - child.getTranslationY();
173+
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) snackbarLayout.getLayoutParams();
174+
p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) targetOffset);
175+
snackbarLayout.requestLayout();
176+
}
177+
// Animate Floating Action Button
178+
if (floatingActionButton != null && floatingActionButton.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
179+
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) floatingActionButton.getLayoutParams();
180+
fabTargetOffset = fabDefaultBottomMargin - child.getTranslationY() + snackBarY;
181+
p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) fabTargetOffset);
182+
floatingActionButton.requestLayout();
183+
}
184+
}
185+
});
186+
}
187+
188+
189+
public static <V extends View> MinorBehavior<V> from(V view) {
190+
ViewGroup.LayoutParams params = view.getLayoutParams();
191+
if (!(params instanceof CoordinatorLayout.LayoutParams)) {
192+
throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
193+
}
194+
CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params)
195+
.getBehavior();
196+
if (!(behavior instanceof MinorBehavior)) {
197+
throw new IllegalArgumentException(
198+
"The view is not associated with MinorBehavior");
199+
}
200+
return (MinorBehavior<V>) behavior;
201+
}
202+
203+
204+
/**
205+
* Update Snackbar bottom margin
206+
*/
207+
public void updateSnackbar(final View child, View dependency) {
208+
209+
if (dependency != null && dependency instanceof Snackbar.SnackbarLayout) {
210+
211+
snackbarLayout = (Snackbar.SnackbarLayout) dependency;
212+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
213+
snackbarLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
214+
@Override
215+
public void onLayoutChange(View v, int left, int top, int right, int bottom,
216+
int oldLeft, int oldTop, int oldRight, int oldBottom) {
217+
if (floatingActionButton != null &&
218+
floatingActionButton.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
219+
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) floatingActionButton.getLayoutParams();
220+
snackBarY = bottom - v.getY();
221+
fabTargetOffset = fabDefaultBottomMargin - child.getTranslationY() + snackBarY;
222+
p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) fabTargetOffset);
223+
floatingActionButton.requestLayout();
224+
}
225+
}
226+
});
227+
}
228+
229+
if (mSnackbarHeight == -1) {
230+
mSnackbarHeight = dependency.getHeight();
231+
}
232+
233+
int targetMargin = (int) (child.getMeasuredHeight() - child.getTranslationY());
234+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
235+
child.bringToFront();
236+
}
237+
238+
if (dependency.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
239+
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams();
240+
p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, targetMargin);
241+
dependency.requestLayout();
242+
}
243+
}
244+
}
245+
246+
/**
247+
* Update floating action button bottom margin
248+
*/
249+
public void updateFloatingActionButton(View dependency) {
250+
if (dependency != null && dependency instanceof FloatingActionButton) {
251+
floatingActionButton = (FloatingActionButton) dependency;
252+
if (!fabBottomMarginInitialized && dependency.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
253+
fabBottomMarginInitialized = true;
254+
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams();
255+
fabDefaultBottomMargin = p.bottomMargin;
256+
}
257+
}
258+
}
259+
}

0 commit comments

Comments
 (0)