Skip to content

Instantly share code, notes, and snippets.

@FabianTerhorst
Last active April 4, 2016 13:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FabianTerhorst/6fefdce4a4b9c5bc2a60e66a6452bc4a to your computer and use it in GitHub Desktop.
Save FabianTerhorst/6fefdce4a4b9c5bc2a60e66a6452bc4a to your computer and use it in GitHub Desktop.
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
public FloatingActionButtonBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;// || dependency instanceof RecyclerView;
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child, int layoutDirection) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
params.bottomMargin = (int) UIUtils.convertDpToPixel(16, child.getContext()) + UIUtils.getNavigationBarHeight(child.getContext());
child.setLayoutParams(params);
return super.onLayoutChild(parent, child, layoutDirection);
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
float lastTranslationY = child.getTranslationY();
int bottomMargin = ((ViewGroup.MarginLayoutParams) dependency.getLayoutParams()).bottomMargin;
float translationY = Math.min(0, (dependency.getTranslationY() + Math.abs(bottomMargin)) - dependency.getHeight());
child.setTranslationY(translationY);
return lastTranslationY != translationY;
}
return false;
}
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
if (child.getTranslationY() != 0.0F && dependency instanceof Snackbar.SnackbarLayout)
child.animate().translationY(0.0F).start();
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
FloatingActionButton child,
View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|| super.onStartNestedScroll(coordinatorLayout, child,
directTargetChild, target, nestedScrollAxes);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout,
FloatingActionButton child,
View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE)
child.hide();
else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE)
child.show();
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/md_white_1000"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<!--android:fitsSystemWindows="true"-->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
<!--app:layout_scrollFlags="scroll|enterAlways|snap"-->
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fitsSystemWindows="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"
app:layout_anchor="@id/recyclerView"
app:layout_anchorGravity="bottom|right|end"
app:layout_behavior="@string/floating_actionbutton_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
public class ScrollingViewBehavior extends AppBarLayout.ScrollingViewBehavior {
public ScrollingViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
if (child instanceof RecyclerView)
child.setPadding(0, 0, 0, child.getPaddingBottom() != 0 ? child.getPaddingBottom() * 2 - child.getPaddingTop() : child.getPaddingTop());
return super.onLayoutChild(parent, child, layoutDirection);
}
}
<string name="appbar_scrolling_view_behavior" translatable="false">io.github.massivezerginc.chest.ScrollingViewBehavior</string>
<string name="floating_actionbutton_scrolling_view_behavior" translatable="false">io.github.massivezerginc.chest.FloatingActionButtonBehavior</string>
/**
* https://github.com/mikepenz/Materialize/blob/988d9809a9c6ea72c41fdbfdfa68fc20bd88e3af/library/src/main/java/com/mikepenz/materialize/util/UIUtils.java
*
**/
@SuppressLint("InlinedApi")
public class UIUtils {
/**
* a helper method to get the color from an attribute
*
* @param ctx
* @param attr
* @return
*/
public static int getThemeColor(Context ctx, @AttrRes int attr) {
TypedValue tv = new TypedValue();
if (ctx.getTheme().resolveAttribute(attr, tv, true)) {
return tv.data;
}
return 0;
}
/**
* helper method to get the color by attr (which is defined in the style) or by resource.
*
* @param ctx
* @param attr
* @param res
* @return
*/
public static int getThemeColorFromAttrOrRes(Context ctx, @AttrRes int attr, @ColorRes int res) {
int color = getThemeColor(ctx, attr);
if (color == 0) {
color = ContextCompat.getColor(ctx, res);
}
return color;
}
/**
* helper method to set the background depending on the android version
*
* @param v
* @param d
*/
@SuppressLint("NewApi")
public static void setBackground(View v, Drawable d) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
v.setBackgroundDrawable(d);
} else {
v.setBackground(d);
}
}
/**
* helper method to set the background depending on the android version
*
* @param v
* @param drawableRes
*/
public static void setBackground(View v, @DrawableRes int drawableRes) {
setBackground(v, getCompatDrawable(v.getContext(), drawableRes));
}
/**
* helper method to get the drawable by its resource. specific to the correct android version
*
* @param c
* @param drawableRes
* @return
*/
public static Drawable getCompatDrawable(Context c, @DrawableRes int drawableRes) {
Drawable d = null;
try {
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
d = c.getResources().getDrawable(drawableRes);
} else {
d = c.getResources().getDrawable(drawableRes, c.getTheme());
}
} catch (Exception ex) {
}
return d;
}
/**
* Returns the size in pixels of an attribute dimension
*
* @param context the context to get the resources from
* @param attr is the attribute dimension we want to know the size from
* @return the size in pixels of an attribute dimension
*/
public static int getThemeAttributeDimensionSize(Context context, @AttrRes int attr) {
TypedArray a = null;
try {
a = context.getTheme().obtainStyledAttributes(new int[]{attr});
return a.getDimensionPixelSize(0, 0);
} finally {
if (a != null) {
a.recycle();
}
}
}
/**
* helper to calculate the navigationBar height
*
* @param context
* @return
*/
public static int getNavigationBarHeight(Context context) {
Resources resources = context.getResources();
int id = resources.getIdentifier(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android");
if (id > 0) {
return resources.getDimensionPixelSize(id);
}
return 0;
}
/**
* helper to calculate the actionBar height
*
* @param context
* @return
*/
public static int getActionBarHeight(Context context) {
int actionBarHeight = UIUtils.getThemeAttributeDimensionSize(context, R.attr.actionBarSize);
if (actionBarHeight == 0) {
actionBarHeight = context.getResources().getDimensionPixelSize(R.dimen.abc_action_bar_default_height_material);
}
return actionBarHeight;
}
/**
* helper to calculate the statusBar height
*
* @param context
* @return
*/
public static int getStatusBarHeight(Context context) {
return getStatusBarHeight(context, false);
}
/**
* helper to calculate the statusBar height
*
* @param context
* @param force pass true to get the height even if the device has no translucent statusBar
* @return
*/
public static int getStatusBarHeight(Context context, boolean force) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
int dimenResult = context.getResources().getDimensionPixelSize(R.dimen.tool_bar_top_padding);
//if our dimension is 0 return 0 because on those devices we don't need the height
if (dimenResult == 0 && !force) {
return 0;
} else {
//if our dimens is > 0 && the result == 0 use the dimenResult else the result;
return result == 0 ? dimenResult : result;
}
}
/**
* This method converts dp unit to equivalent pixels, depending on device density.
*
* @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
* @param context Context to get resources and device specific display metrics
* @return A float value to represent px equivalent to dp depending on device density
*/
public static float convertDpToPixel(float dp, Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = dp * (metrics.densityDpi / 160f);
return px;
}
/**
* This method converts device specific pixels to density independent pixels.
*
* @param px A value in px (pixels) unit. Which we need to convert into db
* @param context Context to get resources and device specific display metrics
* @return A float value to represent dp equivalent to px value
*/
public static float convertPixelsToDp(float px, Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = px / (metrics.densityDpi / 160f);
return dp;
}
/**
* helper method to set the TranslucentStatusFlag
*
* @param on
*/
public static void setTranslucentStatusFlag(Activity activity, boolean on) {
if (Build.VERSION.SDK_INT >= 19) {
setFlag(activity, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, on);
}
}
/**
* helper method to set the TranslucentNavigationFlag
*
* @param on
*/
public static void setTranslucentNavigationFlag(Activity activity, boolean on) {
if (Build.VERSION.SDK_INT >= 19) {
setFlag(activity, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, on);
}
}
/**
* helper method to activate or deactivate a specific flag
*
* @param bits
* @param on
*/
public static void setFlag(Activity activity, final int bits, boolean on) {
Window win = activity.getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
if (on) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
win.setAttributes(winParams);
}
/**
* helper to create a stateListDrawable for the icon
*
* @param icon
* @param selectedIcon
* @return
*/
public static StateListDrawable getIconStateList(Drawable icon, Drawable selectedIcon) {
StateListDrawable iconStateListDrawable = new StateListDrawable();
iconStateListDrawable.addState(new int[]{android.R.attr.state_selected}, selectedIcon);
iconStateListDrawable.addState(new int[]{}, icon);
return iconStateListDrawable;
}
/**
* helper to get the system default selectable background inclusive an active state
*
* @param ctx the context
* @param selected_color the selected color
* @param animate true if you want to fade over the states (only animates if API newer than Build.VERSION_CODES.HONEYCOMB)
* @return the StateListDrawable
*/
public static StateListDrawable getSelectableBackground(Context ctx, int selected_color, boolean animate) {
StateListDrawable states = new StateListDrawable();
ColorDrawable clrActive = new ColorDrawable(selected_color);
states.addState(new int[]{android.R.attr.state_selected}, clrActive);
states.addState(new int[]{}, getSelectableBackground(ctx));
//if possible we enable animating across states
if (animate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
int duration = ctx.getResources().getInteger(android.R.integer.config_shortAnimTime);
states.setEnterFadeDuration(duration);
states.setExitFadeDuration(duration);
}
return states;
}
/**
* helper to get the system default selectable background inclusive an active and pressed state
*
* @param ctx the context
* @param selected_color the selected color
* @param pressed_alpha 0-255
* @param animate true if you want to fade over the states (only animates if API newer than Build.VERSION_CODES.HONEYCOMB)
* @return the StateListDrawable
*/
public static StateListDrawable getSelectablePressedBackground(Context ctx, int selected_color, int pressed_alpha, boolean animate) {
StateListDrawable states = getSelectableBackground(ctx, selected_color, animate);
ColorDrawable clrPressed = new ColorDrawable(adjustAlpha(selected_color, pressed_alpha));
states.addState(new int[]{android.R.attr.state_pressed}, clrPressed);
return states;
}
/**
* adjusts the alpha of a color
*
* @param color the color
* @param alpha the alpha value we want to set 0-255
* @return the adjusted color
*/
public static int adjustAlpha(int color, int alpha) {
return (alpha << 24) | (color & 0x00ffffff);
}
/**
* helper to get the system default selectable background res
*
* @param ctx
* @return
*/
public static int getSelectableBackgroundRes(Context ctx) {
if (Build.VERSION.SDK_INT >= 11) {
// If we're running on Honeycomb or newer, then we can use the Theme's
// selectableItemBackground to ensure that the View has a pressed state
TypedValue outValue = new TypedValue();
//it is important here to not use the android.R because this wouldn't add the latest drawable
ctx.getTheme().resolveAttribute(R.attr.selectableItemBackground, outValue, true);
return outValue.resourceId;
} else {
TypedValue outValue = new TypedValue();
ctx.getTheme().resolveAttribute(android.R.attr.itemBackground, outValue, true);
return outValue.resourceId;
}
}
/**
* helper to get the system default selectable background
*
* @param ctx
* @return
*/
public static Drawable getSelectableBackground(Context ctx) {
int selectableBackgroundRes = getSelectableBackgroundRes(ctx);
if (Build.VERSION.SDK_INT >= 11) {
return ContextCompat.getDrawable(ctx, selectableBackgroundRes);
} else {
return UIUtils.getCompatDrawable(ctx, selectableBackgroundRes);
}
}
/**
* Returns the screen width in pixels
*
* @param context is the context to get the resources
* @return the screen width in pixels
*/
public static int getScreenWidth(Context context) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return metrics.widthPixels;
}
/**
* Returns the screen height in pixels
*
* @param context is the context to get the resources
* @return the screen height in pixels
*/
public static int getScreenHeight(Context context) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return metrics.heightPixels;
}
/**
* set the alpha of a specific view
*
* @param v
* @param value
*/
public static void setAlpha(View v, float value) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
AlphaAnimation alpha = new AlphaAnimation(value, value);
alpha.setDuration(0); // Make animation instant
alpha.setFillAfter(true); // Tell it to persist after the animation ends
v.startAnimation(alpha);
} else {
v.setAlpha(value);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment