() {
779 | public ProgressSavedState createFromParcel(Parcel in) {
780 | return new ProgressSavedState(in);
781 | }
782 |
783 | public ProgressSavedState[] newArray(int size) {
784 | return new ProgressSavedState[size];
785 | }
786 | };
787 | }
788 |
789 | /* ===== API methods ===== */
790 |
791 | @Override
792 | public void setImageDrawable(Drawable drawable) {
793 | if (mIcon != drawable) {
794 | mIcon = drawable;
795 | updateBackground();
796 | }
797 | }
798 |
799 | @Override
800 | public void setImageResource(int resId) {
801 | Drawable drawable = getResources().getDrawable(resId);
802 | if (mIcon != drawable) {
803 | mIcon = drawable;
804 | updateBackground();
805 | }
806 | }
807 |
808 | @Override
809 | public void setOnClickListener(final OnClickListener l) {
810 | super.setOnClickListener(l);
811 | mClickListener = l;
812 | View label = (View) getTag(R.id.fab_label);
813 | if (label != null) {
814 | label.setOnClickListener(new OnClickListener() {
815 | @Override
816 | public void onClick(View v) {
817 | if (mClickListener != null) {
818 | mClickListener.onClick(FloatingActionButton.this);
819 | }
820 | }
821 | });
822 | }
823 | }
824 |
825 | /**
826 | * Sets the size of the FloatingActionButton and invalidates its layout.
827 | *
828 | * @param size size of the FloatingActionButton. Accepted values: SIZE_NORMAL, SIZE_MINI.
829 | */
830 | public void setButtonSize(int size) {
831 | if (size != SIZE_NORMAL && size != SIZE_MINI) {
832 | throw new IllegalArgumentException("Use @FabSize constants only!");
833 | }
834 |
835 | if (mFabSize != size) {
836 | mFabSize = size;
837 | updateBackground();
838 | }
839 | }
840 |
841 | public int getButtonSize() {
842 | return mFabSize;
843 | }
844 |
845 | public void setColorNormal(int color) {
846 | if (mColorNormal != color) {
847 | mColorNormal = color;
848 | updateBackground();
849 | }
850 | }
851 |
852 | public void setColorNormalResId(int colorResId) {
853 | setColorNormal(getResources().getColor(colorResId));
854 | }
855 |
856 | public int getColorNormal() {
857 | return mColorNormal;
858 | }
859 |
860 | public void setColorPressed(int color) {
861 | if (color != mColorPressed) {
862 | mColorPressed = color;
863 | updateBackground();
864 | }
865 | }
866 |
867 | public void setColorPressedResId(int colorResId) {
868 | setColorPressed(getResources().getColor(colorResId));
869 | }
870 |
871 | public int getColorPressed() {
872 | return mColorPressed;
873 | }
874 |
875 | public void setColorRipple(int color) {
876 | if (color != mColorRipple) {
877 | mColorRipple = color;
878 | updateBackground();
879 | }
880 | }
881 |
882 | public void setColorRippleResId(int colorResId) {
883 | setColorRipple(getResources().getColor(colorResId));
884 | }
885 |
886 | public int getColorRipple() {
887 | return mColorRipple;
888 | }
889 |
890 | public void setColorDisabled(int color) {
891 | if (color != mColorDisabled) {
892 | mColorDisabled = color;
893 | updateBackground();
894 | }
895 | }
896 |
897 | public void setColorDisabledResId(int colorResId) {
898 | setColorDisabled(getResources().getColor(colorResId));
899 | }
900 |
901 | public int getColorDisabled() {
902 | return mColorDisabled;
903 | }
904 |
905 | public void setShowShadow(boolean show) {
906 | if (mShowShadow != show) {
907 | mShowShadow = show;
908 | updateBackground();
909 | }
910 | }
911 |
912 | public boolean hasShadow() {
913 | return !mUsingElevation && mShowShadow;
914 | }
915 |
916 | /**
917 | * Sets the shadow radius of the FloatingActionButton and invalidates its layout.
918 | *
919 | * @param dimenResId the resource identifier of the dimension
920 | */
921 | public void setShadowRadius(int dimenResId) {
922 | int shadowRadius = getResources().getDimensionPixelSize(dimenResId);
923 | if (mShadowRadius != shadowRadius) {
924 | mShadowRadius = shadowRadius;
925 | requestLayout();
926 | updateBackground();
927 | }
928 | }
929 |
930 | /**
931 | * Sets the shadow radius of the FloatingActionButton and invalidates its layout.
932 | *
933 | * Must be specified in density-independent (dp) pixels, which are then converted into actual
934 | * pixels (px).
935 | *
936 | * @param shadowRadiusDp shadow radius specified in density-independent (dp) pixels
937 | */
938 | public void setShadowRadius(float shadowRadiusDp) {
939 | mShadowRadius = Util.dpToPx(getContext(), shadowRadiusDp);
940 | requestLayout();
941 | updateBackground();
942 | }
943 |
944 | public int getShadowRadius() {
945 | return mShadowRadius;
946 | }
947 |
948 | /**
949 | * Sets the shadow x offset of the FloatingActionButton and invalidates its layout.
950 | *
951 | * @param dimenResId the resource identifier of the dimension
952 | */
953 | public void setShadowXOffset(int dimenResId) {
954 | int shadowXOffset = getResources().getDimensionPixelSize(dimenResId);
955 | if (mShadowXOffset != shadowXOffset) {
956 | mShadowXOffset = shadowXOffset;
957 | requestLayout();
958 | updateBackground();
959 | }
960 | }
961 |
962 | /**
963 | * Sets the shadow x offset of the FloatingActionButton and invalidates its layout.
964 | *
965 | * Must be specified in density-independent (dp) pixels, which are then converted into actual
966 | * pixels (px).
967 | *
968 | * @param shadowXOffsetDp shadow radius specified in density-independent (dp) pixels
969 | */
970 | public void setShadowXOffset(float shadowXOffsetDp) {
971 | mShadowXOffset = Util.dpToPx(getContext(), shadowXOffsetDp);
972 | requestLayout();
973 | updateBackground();
974 | }
975 |
976 | public int getShadowXOffset() {
977 | return mShadowXOffset;
978 | }
979 |
980 | /**
981 | * Sets the shadow y offset of the FloatingActionButton and invalidates its layout.
982 | *
983 | * @param dimenResId the resource identifier of the dimension
984 | */
985 | public void setShadowYOffset(int dimenResId) {
986 | int shadowYOffset = getResources().getDimensionPixelSize(dimenResId);
987 | if (mShadowYOffset != shadowYOffset) {
988 | mShadowYOffset = shadowYOffset;
989 | requestLayout();
990 | updateBackground();
991 | }
992 | }
993 |
994 | /**
995 | * Sets the shadow y offset of the FloatingActionButton and invalidates its layout.
996 | *
997 | * Must be specified in density-independent (dp) pixels, which are then converted into actual
998 | * pixels (px).
999 | *
1000 | * @param shadowYOffsetDp shadow radius specified in density-independent (dp) pixels
1001 | */
1002 | public void setShadowYOffset(float shadowYOffsetDp) {
1003 | mShadowYOffset = Util.dpToPx(getContext(), shadowYOffsetDp);
1004 | requestLayout();
1005 | updateBackground();
1006 | }
1007 |
1008 | public int getShadowYOffset() {
1009 | return mShadowYOffset;
1010 | }
1011 |
1012 | public void setShadowColorResource(int colorResId) {
1013 | int shadowColor = getResources().getColor(colorResId);
1014 | if (mShadowColor != shadowColor) {
1015 | mShadowColor = shadowColor;
1016 | updateBackground();
1017 | }
1018 | }
1019 |
1020 | public void setShadowColor(int color) {
1021 | if (mShadowColor != color) {
1022 | mShadowColor = color;
1023 | updateBackground();
1024 | }
1025 | }
1026 |
1027 | public int getShadowColor() {
1028 | return mShadowColor;
1029 | }
1030 |
1031 | /**
1032 | * Checks whether FloatingActionButton is hidden
1033 | *
1034 | * @return true if FloatingActionButton is hidden, false otherwise
1035 | */
1036 | public boolean isHidden() {
1037 | return getVisibility() == INVISIBLE;
1038 | }
1039 |
1040 | /**
1041 | * Makes the FloatingActionButton to appear and sets its visibility to {@link #VISIBLE}
1042 | *
1043 | * @param animate if true - plays "show animation"
1044 | */
1045 | public void show(boolean animate) {
1046 | if (isHidden()) {
1047 | if (animate) {
1048 | playShowAnimation();
1049 | }
1050 | super.setVisibility(VISIBLE);
1051 | }
1052 | }
1053 |
1054 | /**
1055 | * Makes the FloatingActionButton to disappear and sets its visibility to {@link #INVISIBLE}
1056 | *
1057 | * @param animate if true - plays "hide animation"
1058 | */
1059 | public void hide(boolean animate) {
1060 | if (!isHidden()) {
1061 | if (animate) {
1062 | playHideAnimation();
1063 | }
1064 | super.setVisibility(INVISIBLE);
1065 | }
1066 | }
1067 |
1068 | public void toggle(boolean animate) {
1069 | if (isHidden()) {
1070 | show(animate);
1071 | } else {
1072 | hide(animate);
1073 | }
1074 | }
1075 |
1076 | public void setLabelText(String text) {
1077 | mLabelText = text;
1078 | TextView labelView = getLabelView();
1079 | if (labelView != null) {
1080 | labelView.setText(text);
1081 | }
1082 | }
1083 |
1084 | public String getLabelText() {
1085 | return mLabelText;
1086 | }
1087 |
1088 | public void setShowAnimation(Animation showAnimation) {
1089 | mShowAnimation = showAnimation;
1090 | }
1091 |
1092 | public void setHideAnimation(Animation hideAnimation) {
1093 | mHideAnimation = hideAnimation;
1094 | }
1095 |
1096 | public void setLabelVisibility(int visibility) {
1097 | Label labelView = getLabelView();
1098 | if (labelView != null) {
1099 | labelView.setVisibility(visibility);
1100 | labelView.setHandleVisibilityChanges(visibility == VISIBLE);
1101 | }
1102 | }
1103 |
1104 | public int getLabelVisibility() {
1105 | TextView labelView = getLabelView();
1106 | if (labelView != null) {
1107 | return labelView.getVisibility();
1108 | }
1109 |
1110 | return -1;
1111 | }
1112 |
1113 | @Override
1114 | public void setElevation(float elevation) {
1115 | if (Util.hasLollipop() && elevation > 0) {
1116 | super.setElevation(elevation);
1117 | if (!isInEditMode()) {
1118 | mUsingElevation = true;
1119 | mShowShadow = false;
1120 | }
1121 | updateBackground();
1122 | }
1123 | }
1124 |
1125 | /**
1126 | * Sets the shadow color and radius to mimic the native elevation.
1127 | *
1128 | *
API 21+: Sets the native elevation of this view, in pixels. Updates margins to
1129 | * make the view hold its position in layout across different platform versions.
1130 | */
1131 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1132 | public void setElevationCompat(float elevation) {
1133 | mShadowColor = 0x26000000;
1134 | mShadowRadius = Math.round(elevation / 2);
1135 | mShadowXOffset = 0;
1136 | mShadowYOffset = Math.round(mFabSize == SIZE_NORMAL ? elevation : elevation / 2);
1137 |
1138 | if (Util.hasLollipop()) {
1139 | super.setElevation(elevation);
1140 | mUsingElevationCompat = true;
1141 | mShowShadow = false;
1142 | updateBackground();
1143 |
1144 | ViewGroup.LayoutParams layoutParams = getLayoutParams();
1145 | if (layoutParams != null) {
1146 | setLayoutParams(layoutParams);
1147 | }
1148 | } else {
1149 | mShowShadow = true;
1150 | updateBackground();
1151 | }
1152 | }
1153 |
1154 | /**
1155 | * Change the indeterminate mode for the progress bar. In indeterminate
1156 | * mode, the progress is ignored and the progress bar shows an infinite
1157 | * animation instead.
1158 | *
1159 | * @param indeterminate true to enable the indeterminate mode
1160 | */
1161 | public synchronized void setIndeterminate(boolean indeterminate) {
1162 | if (!indeterminate) {
1163 | mCurrentProgress = 0.0f;
1164 | }
1165 |
1166 | mProgressBarEnabled = indeterminate;
1167 | mShouldUpdateButtonPosition = true;
1168 | mProgressIndeterminate = indeterminate;
1169 | mLastTimeAnimated = SystemClock.uptimeMillis();
1170 | setupProgressBounds();
1171 | // saveButtonOriginalPosition();
1172 | updateBackground();
1173 | }
1174 |
1175 | public synchronized void setMax(int max) {
1176 | mProgressMax = max;
1177 | }
1178 |
1179 | public synchronized int getMax() {
1180 | return mProgressMax;
1181 | }
1182 |
1183 | public synchronized void setProgress(int progress, boolean animate) {
1184 | if (mProgressIndeterminate) return;
1185 |
1186 | mProgress = progress;
1187 | mAnimateProgress = animate;
1188 |
1189 | if (!mButtonPositionSaved) {
1190 | mShouldSetProgress = true;
1191 | return;
1192 | }
1193 |
1194 | mProgressBarEnabled = true;
1195 | mShouldUpdateButtonPosition = true;
1196 | setupProgressBounds();
1197 | saveButtonOriginalPosition();
1198 | updateBackground();
1199 |
1200 | if (progress < 0) {
1201 | progress = 0;
1202 | } else if (progress > mProgressMax) {
1203 | progress = mProgressMax;
1204 | }
1205 |
1206 | if (progress == mTargetProgress) {
1207 | return;
1208 | }
1209 |
1210 | mTargetProgress = mProgressMax > 0 ? (progress / (float) mProgressMax) * 360 : 0;
1211 | mLastTimeAnimated = SystemClock.uptimeMillis();
1212 |
1213 | if (!animate) {
1214 | mCurrentProgress = mTargetProgress;
1215 | }
1216 |
1217 | invalidate();
1218 | }
1219 |
1220 | public synchronized int getProgress() {
1221 | return mProgressIndeterminate ? 0 : mProgress;
1222 | }
1223 |
1224 | public synchronized void hideProgress() {
1225 | mProgressBarEnabled = false;
1226 | mShouldUpdateButtonPosition = true;
1227 | updateBackground();
1228 | }
1229 |
1230 | public synchronized void setShowProgressBackground(boolean show) {
1231 | mShowProgressBackground = show;
1232 | }
1233 |
1234 | public synchronized boolean isProgressBackgroundShown() {
1235 | return mShowProgressBackground;
1236 | }
1237 |
1238 | @Override
1239 | public void setEnabled(boolean enabled) {
1240 | super.setEnabled(enabled);
1241 | Label label = (Label) getTag(R.id.fab_label);
1242 | if (label != null) {
1243 | label.setEnabled(enabled);
1244 | }
1245 | }
1246 |
1247 | @Override
1248 | public void setVisibility(int visibility) {
1249 | super.setVisibility(visibility);
1250 | Label label = (Label) getTag(R.id.fab_label);
1251 | if (label != null) {
1252 | label.setVisibility(visibility);
1253 | }
1254 | }
1255 |
1256 | /**
1257 | * This will clear all AnimationListeners.
1258 | */
1259 | public void hideButtonInMenu(boolean animate) {
1260 | if (!isHidden() && getVisibility() != GONE) {
1261 | hide(animate);
1262 |
1263 | Label label = getLabelView();
1264 | if (label != null) {
1265 | label.hide(animate);
1266 | }
1267 |
1268 | getHideAnimation().setAnimationListener(new Animation.AnimationListener() {
1269 | @Override
1270 | public void onAnimationStart(Animation animation) {
1271 | }
1272 |
1273 | @Override
1274 | public void onAnimationEnd(Animation animation) {
1275 | setVisibility(GONE);
1276 | getHideAnimation().setAnimationListener(null);
1277 | }
1278 |
1279 | @Override
1280 | public void onAnimationRepeat(Animation animation) {
1281 | }
1282 | });
1283 | }
1284 | }
1285 |
1286 | public void showButtonInMenu(boolean animate) {
1287 | if (getVisibility() == VISIBLE) return;
1288 |
1289 | setVisibility(INVISIBLE);
1290 | show(animate);
1291 | Label label = getLabelView();
1292 | if (label != null) {
1293 | label.show(animate);
1294 | }
1295 | }
1296 |
1297 | /**
1298 | * Set the label's background colors
1299 | */
1300 | public void setLabelColors(int colorNormal, int colorPressed, int colorRipple) {
1301 | Label label = getLabelView();
1302 |
1303 | int left = label.getPaddingLeft();
1304 | int top = label.getPaddingTop();
1305 | int right = label.getPaddingRight();
1306 | int bottom = label.getPaddingBottom();
1307 |
1308 | label.setColors(colorNormal, colorPressed, colorRipple);
1309 | label.updateBackground();
1310 | label.setPadding(left, top, right, bottom);
1311 | }
1312 |
1313 | public void setLabelTextColor(int color) {
1314 | getLabelView().setTextColor(color);
1315 | }
1316 |
1317 | public void setLabelTextColor(ColorStateList colors) {
1318 | getLabelView().setTextColor(colors);
1319 | }
1320 | }
1321 |
--------------------------------------------------------------------------------
/library/src/main/java/com/github/clans/fab/FloatingActionMenu.java:
--------------------------------------------------------------------------------
1 | package com.github.clans.fab;
2 |
3 | import android.animation.AnimatorSet;
4 | import android.animation.ObjectAnimator;
5 | import android.animation.ValueAnimator;
6 | import android.content.Context;
7 | import android.content.res.ColorStateList;
8 | import android.content.res.TypedArray;
9 | import android.graphics.Color;
10 | import android.graphics.Typeface;
11 | import android.graphics.drawable.Drawable;
12 | import android.os.Handler;
13 | import android.text.TextUtils;
14 | import android.util.AttributeSet;
15 | import android.util.TypedValue;
16 | import android.view.ContextThemeWrapper;
17 | import android.view.GestureDetector;
18 | import android.view.MotionEvent;
19 | import android.view.View;
20 | import android.view.ViewGroup;
21 | import android.view.animation.Animation;
22 | import android.view.animation.AnimationUtils;
23 | import android.view.animation.AnticipateInterpolator;
24 | import android.view.animation.Interpolator;
25 | import android.view.animation.OvershootInterpolator;
26 | import android.widget.ImageView;
27 |
28 | import java.util.ArrayList;
29 | import java.util.List;
30 |
31 | public class FloatingActionMenu extends ViewGroup {
32 |
33 | private static final int ANIMATION_DURATION = 300;
34 | private static final float CLOSED_PLUS_ROTATION = 0f;
35 | private static final float OPENED_PLUS_ROTATION_LEFT = -90f - 45f;
36 | private static final float OPENED_PLUS_ROTATION_RIGHT = 90f + 45f;
37 |
38 | private static final int OPEN_UP = 0;
39 | private static final int OPEN_DOWN = 1;
40 |
41 | private static final int LABELS_POSITION_LEFT = 0;
42 | private static final int LABELS_POSITION_RIGHT = 1;
43 |
44 | private AnimatorSet mOpenAnimatorSet = new AnimatorSet();
45 | private AnimatorSet mCloseAnimatorSet = new AnimatorSet();
46 | private AnimatorSet mIconToggleSet;
47 |
48 | private int mButtonSpacing = Util.dpToPx(getContext(), 0f);
49 | private FloatingActionButton mMenuButton;
50 | private int mMaxButtonWidth;
51 | private int mLabelsMargin = Util.dpToPx(getContext(), 0f);
52 | private int mLabelsVerticalOffset = Util.dpToPx(getContext(), 0f);
53 | private int mButtonsCount;
54 | private boolean mMenuOpened;
55 | private boolean mIsMenuOpening;
56 | private Handler mUiHandler = new Handler();
57 | private int mLabelsShowAnimation;
58 | private int mLabelsHideAnimation;
59 | private int mLabelsPaddingTop = Util.dpToPx(getContext(), 4f);
60 | private int mLabelsPaddingRight = Util.dpToPx(getContext(), 8f);
61 | private int mLabelsPaddingBottom = Util.dpToPx(getContext(), 4f);
62 | private int mLabelsPaddingLeft = Util.dpToPx(getContext(), 8f);
63 | private ColorStateList mLabelsTextColor;
64 | private float mLabelsTextSize;
65 | private int mLabelsCornerRadius = Util.dpToPx(getContext(), 3f);
66 | private boolean mLabelsShowShadow;
67 | private int mLabelsColorNormal;
68 | private int mLabelsColorPressed;
69 | private int mLabelsColorRipple;
70 | private boolean mMenuShowShadow;
71 | private int mMenuShadowColor;
72 | private float mMenuShadowRadius = 4f;
73 | private float mMenuShadowXOffset = 1f;
74 | private float mMenuShadowYOffset = 3f;
75 | private int mMenuColorNormal;
76 | private int mMenuColorPressed;
77 | private int mMenuColorRipple;
78 | private Drawable mIcon;
79 | private int mAnimationDelayPerItem;
80 | private Interpolator mOpenInterpolator;
81 | private Interpolator mCloseInterpolator;
82 | private boolean mIsAnimated = true;
83 | private boolean mLabelsSingleLine;
84 | private int mLabelsEllipsize;
85 | private int mLabelsMaxLines;
86 | private int mMenuFabSize;
87 | private int mLabelsStyle;
88 | private Typeface mCustomTypefaceFromFont;
89 | private boolean mIconAnimated = true;
90 | private ImageView mImageToggle;
91 | private Animation mMenuButtonShowAnimation;
92 | private Animation mMenuButtonHideAnimation;
93 | private Animation mImageToggleShowAnimation;
94 | private Animation mImageToggleHideAnimation;
95 | private boolean mIsMenuButtonAnimationRunning;
96 | private boolean mIsSetClosedOnTouchOutside;
97 | private int mOpenDirection;
98 | private OnMenuToggleListener mToggleListener;
99 |
100 | private ValueAnimator mShowBackgroundAnimator;
101 | private ValueAnimator mHideBackgroundAnimator;
102 | private int mBackgroundColor;
103 |
104 | private int mLabelsPosition;
105 | private Context mLabelsContext;
106 | private String mMenuLabelText;
107 | private boolean mUsingMenuLabel;
108 |
109 | public interface OnMenuToggleListener {
110 | void onMenuToggle(boolean opened);
111 | }
112 |
113 | public FloatingActionMenu(Context context) {
114 | this(context, null);
115 | }
116 |
117 | public FloatingActionMenu(Context context, AttributeSet attrs) {
118 | this(context, attrs, 0);
119 | }
120 |
121 | public FloatingActionMenu(Context context, AttributeSet attrs, int defStyleAttr) {
122 | super(context, attrs, defStyleAttr);
123 | init(context, attrs);
124 | }
125 |
126 | private void init(Context context, AttributeSet attrs) {
127 | TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionMenu, 0, 0);
128 | mButtonSpacing = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_buttonSpacing, mButtonSpacing);
129 | mLabelsMargin = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_margin, mLabelsMargin);
130 | mLabelsPosition = attr.getInt(R.styleable.FloatingActionMenu_menu_labels_position, LABELS_POSITION_LEFT);
131 | mLabelsShowAnimation = attr.getResourceId(R.styleable.FloatingActionMenu_menu_labels_showAnimation,
132 | mLabelsPosition == LABELS_POSITION_LEFT ? R.anim.fab_slide_in_from_right : R.anim.fab_slide_in_from_left);
133 | mLabelsHideAnimation = attr.getResourceId(R.styleable.FloatingActionMenu_menu_labels_hideAnimation,
134 | mLabelsPosition == LABELS_POSITION_LEFT ? R.anim.fab_slide_out_to_right : R.anim.fab_slide_out_to_left);
135 | mLabelsPaddingTop = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_paddingTop, mLabelsPaddingTop);
136 | mLabelsPaddingRight = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_paddingRight, mLabelsPaddingRight);
137 | mLabelsPaddingBottom = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_paddingBottom, mLabelsPaddingBottom);
138 | mLabelsPaddingLeft = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_paddingLeft, mLabelsPaddingLeft);
139 | mLabelsTextColor = attr.getColorStateList(R.styleable.FloatingActionMenu_menu_labels_textColor);
140 | // set default value if null same as for textview
141 | if (mLabelsTextColor == null) {
142 | mLabelsTextColor = ColorStateList.valueOf(Color.WHITE);
143 | }
144 | mLabelsTextSize = attr.getDimension(R.styleable.FloatingActionMenu_menu_labels_textSize, getResources().getDimension(R.dimen.labels_text_size));
145 | mLabelsCornerRadius = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_cornerRadius, mLabelsCornerRadius);
146 | mLabelsShowShadow = attr.getBoolean(R.styleable.FloatingActionMenu_menu_labels_showShadow, true);
147 | mLabelsColorNormal = attr.getColor(R.styleable.FloatingActionMenu_menu_labels_colorNormal, 0xFF333333);
148 | mLabelsColorPressed = attr.getColor(R.styleable.FloatingActionMenu_menu_labels_colorPressed, 0xFF444444);
149 | mLabelsColorRipple = attr.getColor(R.styleable.FloatingActionMenu_menu_labels_colorRipple, 0x66FFFFFF);
150 | mMenuShowShadow = attr.getBoolean(R.styleable.FloatingActionMenu_menu_showShadow, true);
151 | mMenuShadowColor = attr.getColor(R.styleable.FloatingActionMenu_menu_shadowColor, 0x66000000);
152 | mMenuShadowRadius = attr.getDimension(R.styleable.FloatingActionMenu_menu_shadowRadius, mMenuShadowRadius);
153 | mMenuShadowXOffset = attr.getDimension(R.styleable.FloatingActionMenu_menu_shadowXOffset, mMenuShadowXOffset);
154 | mMenuShadowYOffset = attr.getDimension(R.styleable.FloatingActionMenu_menu_shadowYOffset, mMenuShadowYOffset);
155 | mMenuColorNormal = attr.getColor(R.styleable.FloatingActionMenu_menu_colorNormal, 0xFFDA4336);
156 | mMenuColorPressed = attr.getColor(R.styleable.FloatingActionMenu_menu_colorPressed, 0xFFE75043);
157 | mMenuColorRipple = attr.getColor(R.styleable.FloatingActionMenu_menu_colorRipple, 0x99FFFFFF);
158 | mAnimationDelayPerItem = attr.getInt(R.styleable.FloatingActionMenu_menu_animationDelayPerItem, 50);
159 | mIcon = attr.getDrawable(R.styleable.FloatingActionMenu_menu_icon);
160 | if (mIcon == null) {
161 | mIcon = getResources().getDrawable(R.drawable.fab_add);
162 | }
163 | mLabelsSingleLine = attr.getBoolean(R.styleable.FloatingActionMenu_menu_labels_singleLine, false);
164 | mLabelsEllipsize = attr.getInt(R.styleable.FloatingActionMenu_menu_labels_ellipsize, 0);
165 | mLabelsMaxLines = attr.getInt(R.styleable.FloatingActionMenu_menu_labels_maxLines, -1);
166 | mMenuFabSize = attr.getInt(R.styleable.FloatingActionMenu_menu_fab_size, FloatingActionButton.SIZE_NORMAL);
167 | mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionMenu_menu_labels_style, 0);
168 | String customFont = attr.getString(R.styleable.FloatingActionMenu_menu_labels_customFont);
169 | try {
170 | if (!TextUtils.isEmpty(customFont)) {
171 | mCustomTypefaceFromFont = Typeface.createFromAsset(getContext().getAssets(), customFont);
172 | }
173 | } catch (RuntimeException ex) {
174 | throw new IllegalArgumentException("Unable to load specified custom font: " + customFont, ex);
175 | }
176 | mOpenDirection = attr.getInt(R.styleable.FloatingActionMenu_menu_openDirection, OPEN_UP);
177 | mBackgroundColor = attr.getColor(R.styleable.FloatingActionMenu_menu_backgroundColor, Color.TRANSPARENT);
178 |
179 | if (attr.hasValue(R.styleable.FloatingActionMenu_menu_fab_label)) {
180 | mUsingMenuLabel = true;
181 | mMenuLabelText = attr.getString(R.styleable.FloatingActionMenu_menu_fab_label);
182 | }
183 |
184 | if (attr.hasValue(R.styleable.FloatingActionMenu_menu_labels_padding)) {
185 | int padding = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_padding, 0);
186 | initPadding(padding);
187 | }
188 |
189 | mOpenInterpolator = new OvershootInterpolator();
190 | mCloseInterpolator = new AnticipateInterpolator();
191 | mLabelsContext = new ContextThemeWrapper(getContext(), mLabelsStyle);
192 |
193 | initBackgroundDimAnimation();
194 | createMenuButton();
195 | initMenuButtonAnimations(attr);
196 |
197 | attr.recycle();
198 | }
199 |
200 | private void initMenuButtonAnimations(TypedArray attr) {
201 | int showResId = attr.getResourceId(R.styleable.FloatingActionMenu_menu_fab_show_animation, R.anim.fab_scale_up);
202 | setMenuButtonShowAnimation(AnimationUtils.loadAnimation(getContext(), showResId));
203 | mImageToggleShowAnimation = AnimationUtils.loadAnimation(getContext(), showResId);
204 |
205 | int hideResId = attr.getResourceId(R.styleable.FloatingActionMenu_menu_fab_hide_animation, R.anim.fab_scale_down);
206 | setMenuButtonHideAnimation(AnimationUtils.loadAnimation(getContext(), hideResId));
207 | mImageToggleHideAnimation = AnimationUtils.loadAnimation(getContext(), hideResId);
208 | }
209 |
210 | private void initBackgroundDimAnimation() {
211 | final int maxAlpha = Color.alpha(mBackgroundColor);
212 | final int red = Color.red(mBackgroundColor);
213 | final int green = Color.green(mBackgroundColor);
214 | final int blue = Color.blue(mBackgroundColor);
215 |
216 | mShowBackgroundAnimator = ValueAnimator.ofInt(0, maxAlpha);
217 | mShowBackgroundAnimator.setDuration(ANIMATION_DURATION);
218 | mShowBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
219 | @Override
220 | public void onAnimationUpdate(ValueAnimator animation) {
221 | Integer alpha = (Integer) animation.getAnimatedValue();
222 | setBackgroundColor(Color.argb(alpha, red, green, blue));
223 | }
224 | });
225 |
226 | mHideBackgroundAnimator = ValueAnimator.ofInt(maxAlpha, 0);
227 | mHideBackgroundAnimator.setDuration(ANIMATION_DURATION);
228 | mHideBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
229 | @Override
230 | public void onAnimationUpdate(ValueAnimator animation) {
231 | Integer alpha = (Integer) animation.getAnimatedValue();
232 | setBackgroundColor(Color.argb(alpha, red, green, blue));
233 | }
234 | });
235 | }
236 |
237 | private boolean isBackgroundEnabled() {
238 | return mBackgroundColor != Color.TRANSPARENT;
239 | }
240 |
241 | private void initPadding(int padding) {
242 | mLabelsPaddingTop = padding;
243 | mLabelsPaddingRight = padding;
244 | mLabelsPaddingBottom = padding;
245 | mLabelsPaddingLeft = padding;
246 | }
247 |
248 | private void createMenuButton() {
249 | mMenuButton = new FloatingActionButton(getContext());
250 |
251 | mMenuButton.mShowShadow = mMenuShowShadow;
252 | if (mMenuShowShadow) {
253 | mMenuButton.mShadowRadius = Util.dpToPx(getContext(), mMenuShadowRadius);
254 | mMenuButton.mShadowXOffset = Util.dpToPx(getContext(), mMenuShadowXOffset);
255 | mMenuButton.mShadowYOffset = Util.dpToPx(getContext(), mMenuShadowYOffset);
256 | }
257 | mMenuButton.setColors(mMenuColorNormal, mMenuColorPressed, mMenuColorRipple);
258 | mMenuButton.mShadowColor = mMenuShadowColor;
259 | mMenuButton.mFabSize = mMenuFabSize;
260 | mMenuButton.updateBackground();
261 | mMenuButton.setLabelText(mMenuLabelText);
262 |
263 | mImageToggle = new ImageView(getContext());
264 | mImageToggle.setImageDrawable(mIcon);
265 |
266 | addView(mMenuButton, super.generateDefaultLayoutParams());
267 | addView(mImageToggle);
268 |
269 | createDefaultIconAnimation();
270 | }
271 |
272 | private void createDefaultIconAnimation() {
273 | float collapseAngle;
274 | float expandAngle;
275 | if (mOpenDirection == OPEN_UP) {
276 | collapseAngle = mLabelsPosition == LABELS_POSITION_LEFT ? OPENED_PLUS_ROTATION_LEFT : OPENED_PLUS_ROTATION_RIGHT;
277 | expandAngle = mLabelsPosition == LABELS_POSITION_LEFT ? OPENED_PLUS_ROTATION_LEFT : OPENED_PLUS_ROTATION_RIGHT;
278 | } else {
279 | collapseAngle = mLabelsPosition == LABELS_POSITION_LEFT ? OPENED_PLUS_ROTATION_RIGHT : OPENED_PLUS_ROTATION_LEFT;
280 | expandAngle = mLabelsPosition == LABELS_POSITION_LEFT ? OPENED_PLUS_ROTATION_RIGHT : OPENED_PLUS_ROTATION_LEFT;
281 | }
282 |
283 | ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(
284 | mImageToggle,
285 | "rotation",
286 | collapseAngle,
287 | CLOSED_PLUS_ROTATION
288 | );
289 |
290 | ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(
291 | mImageToggle,
292 | "rotation",
293 | CLOSED_PLUS_ROTATION,
294 | expandAngle
295 | );
296 |
297 | mOpenAnimatorSet.play(expandAnimator);
298 | mCloseAnimatorSet.play(collapseAnimator);
299 |
300 | mOpenAnimatorSet.setInterpolator(mOpenInterpolator);
301 | mCloseAnimatorSet.setInterpolator(mCloseInterpolator);
302 |
303 | mOpenAnimatorSet.setDuration(ANIMATION_DURATION);
304 | mCloseAnimatorSet.setDuration(ANIMATION_DURATION);
305 | }
306 |
307 | @Override
308 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
309 | int width = 0;
310 | int height = 0;
311 | mMaxButtonWidth = 0;
312 | int maxLabelWidth = 0;
313 |
314 | measureChildWithMargins(mImageToggle, widthMeasureSpec, 0, heightMeasureSpec, 0);
315 |
316 | for (int i = 0; i < mButtonsCount; i++) {
317 | View child = getChildAt(i);
318 |
319 | if (child.getVisibility() == GONE || child == mImageToggle) continue;
320 |
321 | measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
322 | mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());
323 | }
324 |
325 | for (int i = 0; i < mButtonsCount; i++) {
326 | int usedWidth = 0;
327 | View child = getChildAt(i);
328 |
329 | if (child.getVisibility() == GONE || child == mImageToggle) continue;
330 |
331 | usedWidth += child.getMeasuredWidth();
332 | height += child.getMeasuredHeight();
333 |
334 | Label label = (Label) child.getTag(R.id.fab_label);
335 | if (label != null) {
336 | int labelOffset = (mMaxButtonWidth - child.getMeasuredWidth()) / (mUsingMenuLabel ? 1 : 2);
337 | int labelUsedWidth = child.getMeasuredWidth() + label.calculateShadowWidth() + mLabelsMargin + labelOffset;
338 | measureChildWithMargins(label, widthMeasureSpec, labelUsedWidth, heightMeasureSpec, 0);
339 | usedWidth += label.getMeasuredWidth();
340 | maxLabelWidth = Math.max(maxLabelWidth, usedWidth + labelOffset);
341 | }
342 | }
343 |
344 | width = Math.max(mMaxButtonWidth, maxLabelWidth + mLabelsMargin) + getPaddingLeft() + getPaddingRight();
345 |
346 | height += mButtonSpacing * (mButtonsCount - 1) + getPaddingTop() + getPaddingBottom();
347 | height = adjustForOvershoot(height);
348 |
349 | if (getLayoutParams().width == LayoutParams.MATCH_PARENT) {
350 | width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
351 | }
352 |
353 | if (getLayoutParams().height == LayoutParams.MATCH_PARENT) {
354 | height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
355 | }
356 |
357 | setMeasuredDimension(width, height);
358 | }
359 |
360 | @Override
361 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
362 | int buttonsHorizontalCenter = mLabelsPosition == LABELS_POSITION_LEFT
363 | ? r - l - mMaxButtonWidth / 2 - getPaddingRight()
364 | : mMaxButtonWidth / 2 + getPaddingLeft();
365 | boolean openUp = mOpenDirection == OPEN_UP;
366 |
367 | int menuButtonTop = openUp
368 | ? b - t - mMenuButton.getMeasuredHeight() - getPaddingBottom()
369 | : getPaddingTop();
370 | int menuButtonLeft = buttonsHorizontalCenter - mMenuButton.getMeasuredWidth() / 2;
371 |
372 | mMenuButton.layout(menuButtonLeft, menuButtonTop, menuButtonLeft + mMenuButton.getMeasuredWidth(),
373 | menuButtonTop + mMenuButton.getMeasuredHeight());
374 |
375 | int imageLeft = buttonsHorizontalCenter - mImageToggle.getMeasuredWidth() / 2;
376 | int imageTop = menuButtonTop + mMenuButton.getMeasuredHeight() / 2 - mImageToggle.getMeasuredHeight() / 2;
377 |
378 | mImageToggle.layout(imageLeft, imageTop, imageLeft + mImageToggle.getMeasuredWidth(),
379 | imageTop + mImageToggle.getMeasuredHeight());
380 |
381 | int nextY = openUp
382 | ? menuButtonTop + mMenuButton.getMeasuredHeight() + mButtonSpacing
383 | : menuButtonTop;
384 |
385 | for (int i = mButtonsCount - 1; i >= 0; i--) {
386 | View child = getChildAt(i);
387 |
388 | if (child == mImageToggle) continue;
389 |
390 | FloatingActionButton fab = (FloatingActionButton) child;
391 |
392 | if (fab.getVisibility() == GONE) continue;
393 |
394 | int childX = buttonsHorizontalCenter - fab.getMeasuredWidth() / 2;
395 | int childY = openUp ? nextY - fab.getMeasuredHeight() - mButtonSpacing : nextY;
396 |
397 | if (fab != mMenuButton) {
398 | fab.layout(childX, childY, childX + fab.getMeasuredWidth(),
399 | childY + fab.getMeasuredHeight());
400 |
401 | if (!mIsMenuOpening) {
402 | fab.hide(false);
403 | }
404 | }
405 |
406 | View label = (View) fab.getTag(R.id.fab_label);
407 | if (label != null) {
408 | int labelsOffset = (mUsingMenuLabel ? mMaxButtonWidth / 2 : fab.getMeasuredWidth() / 2) + mLabelsMargin;
409 | int labelXNearButton = mLabelsPosition == LABELS_POSITION_LEFT
410 | ? buttonsHorizontalCenter - labelsOffset
411 | : buttonsHorizontalCenter + labelsOffset;
412 |
413 | int labelXAwayFromButton = mLabelsPosition == LABELS_POSITION_LEFT
414 | ? labelXNearButton - label.getMeasuredWidth()
415 | : labelXNearButton + label.getMeasuredWidth();
416 |
417 | int labelLeft = mLabelsPosition == LABELS_POSITION_LEFT
418 | ? labelXAwayFromButton
419 | : labelXNearButton;
420 |
421 | int labelRight = mLabelsPosition == LABELS_POSITION_LEFT
422 | ? labelXNearButton
423 | : labelXAwayFromButton;
424 |
425 | int labelTop = childY - mLabelsVerticalOffset + (fab.getMeasuredHeight()
426 | - label.getMeasuredHeight()) / 2;
427 |
428 | label.layout(labelLeft, labelTop, labelRight, labelTop + label.getMeasuredHeight());
429 |
430 | if (!mIsMenuOpening) {
431 | label.setVisibility(INVISIBLE);
432 | }
433 | }
434 |
435 | nextY = openUp
436 | ? childY - mButtonSpacing
437 | : childY + child.getMeasuredHeight() + mButtonSpacing;
438 | }
439 | }
440 |
441 | private int adjustForOvershoot(int dimension) {
442 | return (int) (dimension * 0.03 + dimension);
443 | }
444 |
445 | @Override
446 | protected void onFinishInflate() {
447 | super.onFinishInflate();
448 | bringChildToFront(mMenuButton);
449 | bringChildToFront(mImageToggle);
450 | mButtonsCount = getChildCount();
451 | createLabels();
452 | }
453 |
454 | private void createLabels() {
455 | for (int i = 0; i < mButtonsCount; i++) {
456 |
457 | if (getChildAt(i) == mImageToggle) continue;
458 |
459 | final FloatingActionButton fab = (FloatingActionButton) getChildAt(i);
460 |
461 | if (fab.getTag(R.id.fab_label) != null) continue;
462 |
463 | addLabel(fab);
464 |
465 | if (fab == mMenuButton) {
466 | mMenuButton.setOnClickListener(new OnClickListener() {
467 | @Override
468 | public void onClick(View v) {
469 | toggle(mIsAnimated);
470 | }
471 | });
472 | }
473 | }
474 | }
475 |
476 | private void addLabel(FloatingActionButton fab) {
477 | String text = fab.getLabelText();
478 |
479 | if (TextUtils.isEmpty(text)) return;
480 |
481 | final Label label = new Label(mLabelsContext);
482 | label.setClickable(true);
483 | label.setFab(fab);
484 | label.setShowAnimation(AnimationUtils.loadAnimation(getContext(), mLabelsShowAnimation));
485 | label.setHideAnimation(AnimationUtils.loadAnimation(getContext(), mLabelsHideAnimation));
486 |
487 | if (mLabelsStyle > 0) {
488 | label.setTextAppearance(getContext(), mLabelsStyle);
489 | label.setShowShadow(false);
490 | label.setUsingStyle(true);
491 | } else {
492 | label.setColors(mLabelsColorNormal, mLabelsColorPressed, mLabelsColorRipple);
493 | label.setShowShadow(mLabelsShowShadow);
494 | label.setCornerRadius(mLabelsCornerRadius);
495 | if (mLabelsEllipsize > 0) {
496 | setLabelEllipsize(label);
497 | }
498 | label.setMaxLines(mLabelsMaxLines);
499 | label.updateBackground();
500 |
501 | label.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLabelsTextSize);
502 | label.setTextColor(mLabelsTextColor);
503 |
504 | int left = mLabelsPaddingLeft;
505 | int top = mLabelsPaddingTop;
506 | if (mLabelsShowShadow) {
507 | left += fab.getShadowRadius() + Math.abs(fab.getShadowXOffset());
508 | top += fab.getShadowRadius() + Math.abs(fab.getShadowYOffset());
509 | }
510 |
511 | label.setPadding(
512 | left,
513 | top,
514 | mLabelsPaddingLeft,
515 | mLabelsPaddingTop
516 | );
517 |
518 | if (mLabelsMaxLines < 0 || mLabelsSingleLine) {
519 | label.setSingleLine(mLabelsSingleLine);
520 | }
521 | }
522 |
523 | if (mCustomTypefaceFromFont != null) {
524 | label.setTypeface(mCustomTypefaceFromFont);
525 | }
526 | label.setText(text);
527 | label.setOnClickListener(fab.getOnClickListener());
528 |
529 | addView(label);
530 | fab.setTag(R.id.fab_label, label);
531 | }
532 |
533 | private void setLabelEllipsize(Label label) {
534 | switch (mLabelsEllipsize) {
535 | case 1:
536 | label.setEllipsize(TextUtils.TruncateAt.START);
537 | break;
538 | case 2:
539 | label.setEllipsize(TextUtils.TruncateAt.MIDDLE);
540 | break;
541 | case 3:
542 | label.setEllipsize(TextUtils.TruncateAt.END);
543 | break;
544 | case 4:
545 | label.setEllipsize(TextUtils.TruncateAt.MARQUEE);
546 | break;
547 | }
548 | }
549 |
550 | @Override
551 | public MarginLayoutParams generateLayoutParams(AttributeSet attrs) {
552 | return new MarginLayoutParams(getContext(), attrs);
553 | }
554 |
555 | @Override
556 | protected MarginLayoutParams generateLayoutParams(LayoutParams p) {
557 | return new MarginLayoutParams(p);
558 | }
559 |
560 | @Override
561 | protected MarginLayoutParams generateDefaultLayoutParams() {
562 | return new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT,
563 | MarginLayoutParams.WRAP_CONTENT);
564 | }
565 |
566 | @Override
567 | protected boolean checkLayoutParams(LayoutParams p) {
568 | return p instanceof MarginLayoutParams;
569 | }
570 |
571 | private void hideMenuButtonWithImage(boolean animate) {
572 | if (!isMenuButtonHidden()) {
573 | mMenuButton.hide(animate);
574 | if (animate) {
575 | mImageToggle.startAnimation(mImageToggleHideAnimation);
576 | }
577 | mImageToggle.setVisibility(INVISIBLE);
578 | mIsMenuButtonAnimationRunning = false;
579 | }
580 | }
581 |
582 | private void showMenuButtonWithImage(boolean animate) {
583 | if (isMenuButtonHidden()) {
584 | mMenuButton.show(animate);
585 | if (animate) {
586 | mImageToggle.startAnimation(mImageToggleShowAnimation);
587 | }
588 | mImageToggle.setVisibility(VISIBLE);
589 | }
590 | }
591 |
592 | @Override
593 | public boolean onTouchEvent(MotionEvent event) {
594 | if (mIsSetClosedOnTouchOutside) {
595 | boolean handled = false;
596 | switch (event.getAction()) {
597 | case MotionEvent.ACTION_DOWN:
598 | handled = isOpened();
599 | break;
600 | case MotionEvent.ACTION_UP:
601 | close(mIsAnimated);
602 | handled = true;
603 | }
604 |
605 | return handled;
606 | }
607 |
608 | return super.onTouchEvent(event);
609 | }
610 |
611 | /* ===== API methods ===== */
612 |
613 | public boolean isOpened() {
614 | return mMenuOpened;
615 | }
616 |
617 | public void toggle(boolean animate) {
618 | if (isOpened()) {
619 | close(animate);
620 | } else {
621 | open(animate);
622 | }
623 | }
624 |
625 | public void open(final boolean animate) {
626 | if (!isOpened()) {
627 | if (isBackgroundEnabled()) {
628 | mShowBackgroundAnimator.start();
629 | }
630 |
631 | if (mIconAnimated) {
632 | if (mIconToggleSet != null) {
633 | mIconToggleSet.start();
634 | } else {
635 | mCloseAnimatorSet.cancel();
636 | mOpenAnimatorSet.start();
637 | }
638 | }
639 |
640 | int delay = 0;
641 | int counter = 0;
642 | mIsMenuOpening = true;
643 | for (int i = getChildCount() - 1; i >= 0; i--) {
644 | View child = getChildAt(i);
645 | if (child instanceof FloatingActionButton && child.getVisibility() != GONE) {
646 | counter++;
647 |
648 | final FloatingActionButton fab = (FloatingActionButton) child;
649 | mUiHandler.postDelayed(new Runnable() {
650 | @Override
651 | public void run() {
652 | if (isOpened()) return;
653 |
654 | if (fab != mMenuButton) {
655 | fab.show(animate);
656 | }
657 |
658 | Label label = (Label) fab.getTag(R.id.fab_label);
659 | if (label != null && label.isHandleVisibilityChanges()) {
660 | label.show(animate);
661 | }
662 | }
663 | }, delay);
664 | delay += mAnimationDelayPerItem;
665 | }
666 | }
667 |
668 | mUiHandler.postDelayed(new Runnable() {
669 | @Override
670 | public void run() {
671 | mMenuOpened = true;
672 |
673 | if (mToggleListener != null) {
674 | mToggleListener.onMenuToggle(true);
675 | }
676 | }
677 | }, ++counter * mAnimationDelayPerItem);
678 | }
679 | }
680 |
681 | public void close(final boolean animate) {
682 | if (isOpened()) {
683 | if (isBackgroundEnabled()) {
684 | mHideBackgroundAnimator.start();
685 | }
686 |
687 | if (mIconAnimated) {
688 | if (mIconToggleSet != null) {
689 | mIconToggleSet.start();
690 | } else {
691 | mCloseAnimatorSet.start();
692 | mOpenAnimatorSet.cancel();
693 | }
694 | }
695 |
696 | int delay = 0;
697 | int counter = 0;
698 | mIsMenuOpening = false;
699 | for (int i = 0; i < getChildCount(); i++) {
700 | View child = getChildAt(i);
701 | if (child instanceof FloatingActionButton && child.getVisibility() != GONE) {
702 | counter++;
703 |
704 | final FloatingActionButton fab = (FloatingActionButton) child;
705 | mUiHandler.postDelayed(new Runnable() {
706 | @Override
707 | public void run() {
708 | if (!isOpened()) return;
709 |
710 | if (fab != mMenuButton) {
711 | fab.hide(animate);
712 | }
713 |
714 | Label label = (Label) fab.getTag(R.id.fab_label);
715 | if (label != null && label.isHandleVisibilityChanges()) {
716 | label.hide(animate);
717 | }
718 | }
719 | }, delay);
720 | delay += mAnimationDelayPerItem;
721 | }
722 | }
723 |
724 | mUiHandler.postDelayed(new Runnable() {
725 | @Override
726 | public void run() {
727 | mMenuOpened = false;
728 |
729 | if (mToggleListener != null) {
730 | mToggleListener.onMenuToggle(false);
731 | }
732 | }
733 | }, ++counter * mAnimationDelayPerItem);
734 | }
735 | }
736 |
737 | /**
738 | * Sets the {@link android.view.animation.Interpolator} for FloatingActionButton's icon animation.
739 | *
740 | * @param interpolator the Interpolator to be used in animation
741 | */
742 | public void setIconAnimationInterpolator(Interpolator interpolator) {
743 | mOpenAnimatorSet.setInterpolator(interpolator);
744 | mCloseAnimatorSet.setInterpolator(interpolator);
745 | }
746 |
747 | public void setIconAnimationOpenInterpolator(Interpolator openInterpolator) {
748 | mOpenAnimatorSet.setInterpolator(openInterpolator);
749 | }
750 |
751 | public void setIconAnimationCloseInterpolator(Interpolator closeInterpolator) {
752 | mCloseAnimatorSet.setInterpolator(closeInterpolator);
753 | }
754 |
755 | /**
756 | * Sets whether open and close actions should be animated
757 | *
758 | * @param animated if false - menu items will appear/disappear instantly without any animation
759 | */
760 | public void setAnimated(boolean animated) {
761 | mIsAnimated = animated;
762 | mOpenAnimatorSet.setDuration(animated ? ANIMATION_DURATION : 0);
763 | mCloseAnimatorSet.setDuration(animated ? ANIMATION_DURATION : 0);
764 | }
765 |
766 | public boolean isAnimated() {
767 | return mIsAnimated;
768 | }
769 |
770 | public void setAnimationDelayPerItem(int animationDelayPerItem) {
771 | mAnimationDelayPerItem = animationDelayPerItem;
772 | }
773 |
774 | public int getAnimationDelayPerItem() {
775 | return mAnimationDelayPerItem;
776 | }
777 |
778 | public void setOnMenuToggleListener(OnMenuToggleListener listener) {
779 | mToggleListener = listener;
780 | }
781 |
782 | public void setIconAnimated(boolean animated) {
783 | mIconAnimated = animated;
784 | }
785 |
786 | public boolean isIconAnimated() {
787 | return mIconAnimated;
788 | }
789 |
790 | public ImageView getMenuIconView() {
791 | return mImageToggle;
792 | }
793 |
794 | public void setIconToggleAnimatorSet(AnimatorSet toggleAnimatorSet) {
795 | mIconToggleSet = toggleAnimatorSet;
796 | }
797 |
798 | public AnimatorSet getIconToggleAnimatorSet() {
799 | return mIconToggleSet;
800 | }
801 |
802 | public void setMenuButtonShowAnimation(Animation showAnimation) {
803 | mMenuButtonShowAnimation = showAnimation;
804 | mMenuButton.setShowAnimation(showAnimation);
805 | }
806 |
807 | public void setMenuButtonHideAnimation(Animation hideAnimation) {
808 | mMenuButtonHideAnimation = hideAnimation;
809 | mMenuButton.setHideAnimation(hideAnimation);
810 | }
811 |
812 | public boolean isMenuHidden() {
813 | return getVisibility() == INVISIBLE;
814 | }
815 |
816 | public boolean isMenuButtonHidden() {
817 | return mMenuButton.isHidden();
818 | }
819 |
820 | /**
821 | * Makes the whole {@link #FloatingActionMenu} to appear and sets its visibility to {@link #VISIBLE}
822 | *
823 | * @param animate if true - plays "show animation"
824 | */
825 | public void showMenu(boolean animate) {
826 | if (isMenuHidden()) {
827 | if (animate) {
828 | startAnimation(mMenuButtonShowAnimation);
829 | }
830 | setVisibility(VISIBLE);
831 | }
832 | }
833 |
834 | /**
835 | * Makes the {@link #FloatingActionMenu} to disappear and sets its visibility to {@link #INVISIBLE}
836 | *
837 | * @param animate if true - plays "hide animation"
838 | */
839 | public void hideMenu(final boolean animate) {
840 | if (!isMenuHidden() && !mIsMenuButtonAnimationRunning) {
841 | mIsMenuButtonAnimationRunning = true;
842 | if (isOpened()) {
843 | close(animate);
844 | mUiHandler.postDelayed(new Runnable() {
845 | @Override
846 | public void run() {
847 | if (animate) {
848 | startAnimation(mMenuButtonHideAnimation);
849 | }
850 | setVisibility(INVISIBLE);
851 | mIsMenuButtonAnimationRunning = false;
852 | }
853 | }, mAnimationDelayPerItem * mButtonsCount);
854 | } else {
855 | if (animate) {
856 | startAnimation(mMenuButtonHideAnimation);
857 | }
858 | setVisibility(INVISIBLE);
859 | mIsMenuButtonAnimationRunning = false;
860 | }
861 | }
862 | }
863 |
864 | public void toggleMenu(boolean animate) {
865 | if (isMenuHidden()) {
866 | showMenu(animate);
867 | } else {
868 | hideMenu(animate);
869 | }
870 | }
871 |
872 | /**
873 | * Makes the {@link FloatingActionButton} to appear inside the {@link #FloatingActionMenu} and
874 | * sets its visibility to {@link #VISIBLE}
875 | *
876 | * @param animate if true - plays "show animation"
877 | */
878 | public void showMenuButton(boolean animate) {
879 | if (isMenuButtonHidden()) {
880 | showMenuButtonWithImage(animate);
881 | }
882 | }
883 |
884 | /**
885 | * Makes the {@link FloatingActionButton} to disappear inside the {@link #FloatingActionMenu} and
886 | * sets its visibility to {@link #INVISIBLE}
887 | *
888 | * @param animate if true - plays "hide animation"
889 | */
890 | public void hideMenuButton(final boolean animate) {
891 | if (!isMenuButtonHidden() && !mIsMenuButtonAnimationRunning) {
892 | mIsMenuButtonAnimationRunning = true;
893 | if (isOpened()) {
894 | close(animate);
895 | mUiHandler.postDelayed(new Runnable() {
896 | @Override
897 | public void run() {
898 | hideMenuButtonWithImage(animate);
899 | }
900 | }, mAnimationDelayPerItem * mButtonsCount);
901 | } else {
902 | hideMenuButtonWithImage(animate);
903 | }
904 | }
905 | }
906 |
907 | public void toggleMenuButton(boolean animate) {
908 | if (isMenuButtonHidden()) {
909 | showMenuButton(animate);
910 | } else {
911 | hideMenuButton(animate);
912 | }
913 | }
914 |
915 | public void setClosedOnTouchOutside(boolean close) {
916 | mIsSetClosedOnTouchOutside = close;
917 | }
918 |
919 | public void setMenuButtonColorNormal(int color) {
920 | mMenuColorNormal = color;
921 | mMenuButton.setColorNormal(color);
922 | }
923 |
924 | public void setMenuButtonColorNormalResId(int colorResId) {
925 | mMenuColorNormal = getResources().getColor(colorResId);
926 | mMenuButton.setColorNormalResId(colorResId);
927 | }
928 |
929 | public int getMenuButtonColorNormal() {
930 | return mMenuColorNormal;
931 | }
932 |
933 | public void setMenuButtonColorPressed(int color) {
934 | mMenuColorPressed = color;
935 | mMenuButton.setColorPressed(color);
936 | }
937 |
938 | public void setMenuButtonColorPressedResId(int colorResId) {
939 | mMenuColorPressed = getResources().getColor(colorResId);
940 | mMenuButton.setColorPressedResId(colorResId);
941 | }
942 |
943 | public int getMenuButtonColorPressed() {
944 | return mMenuColorPressed;
945 | }
946 |
947 | public void setMenuButtonColorRipple(int color) {
948 | mMenuColorRipple = color;
949 | mMenuButton.setColorRipple(color);
950 | }
951 |
952 | public void setMenuButtonColorRippleResId(int colorResId) {
953 | mMenuColorRipple = getResources().getColor(colorResId);
954 | mMenuButton.setColorRippleResId(colorResId);
955 | }
956 |
957 | public int getMenuButtonColorRipple() {
958 | return mMenuColorRipple;
959 | }
960 |
961 | public void addMenuButton(FloatingActionButton fab) {
962 | addView(fab, mButtonsCount - 2);
963 | mButtonsCount++;
964 | addLabel(fab);
965 | }
966 |
967 | public void removeMenuButton(FloatingActionButton fab) {
968 | removeView(fab.getLabelView());
969 | removeView(fab);
970 | mButtonsCount--;
971 | }
972 |
973 | public void addMenuButton(FloatingActionButton fab, int index) {
974 | int size = mButtonsCount - 2;
975 | if (index < 0) {
976 | index = 0;
977 | } else if (index > size) {
978 | index = size;
979 | }
980 |
981 | addView(fab, index);
982 | mButtonsCount++;
983 | addLabel(fab);
984 | }
985 |
986 | public void removeAllMenuButtons() {
987 | close(true);
988 |
989 | List viewsToRemove = new ArrayList<>();
990 | for (int i = 0; i < getChildCount(); i++) {
991 | View v = getChildAt(i);
992 | if (v != mMenuButton && v != mImageToggle && v instanceof FloatingActionButton) {
993 | viewsToRemove.add((FloatingActionButton) v);
994 | }
995 | }
996 | for (FloatingActionButton v : viewsToRemove) {
997 | removeMenuButton(v);
998 | }
999 | }
1000 |
1001 | public void setMenuButtonLabelText(String text) {
1002 | mMenuButton.setLabelText(text);
1003 | }
1004 |
1005 | public String getMenuButtonLabelText() {
1006 | return mMenuLabelText;
1007 | }
1008 |
1009 | public void setOnMenuButtonClickListener(OnClickListener clickListener) {
1010 | mMenuButton.setOnClickListener(clickListener);
1011 | }
1012 |
1013 | public void setOnMenuButtonLongClickListener(OnLongClickListener longClickListener) {
1014 | mMenuButton.setOnLongClickListener(longClickListener);
1015 | }
1016 | }
1017 |
--------------------------------------------------------------------------------
/library/src/main/java/com/github/clans/fab/Label.java:
--------------------------------------------------------------------------------
1 | package com.github.clans.fab;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.res.ColorStateList;
6 | import android.graphics.Canvas;
7 | import android.graphics.ColorFilter;
8 | import android.graphics.Outline;
9 | import android.graphics.Paint;
10 | import android.graphics.PorterDuff;
11 | import android.graphics.PorterDuffXfermode;
12 | import android.graphics.RectF;
13 | import android.graphics.Xfermode;
14 | import android.graphics.drawable.Drawable;
15 | import android.graphics.drawable.LayerDrawable;
16 | import android.graphics.drawable.RippleDrawable;
17 | import android.graphics.drawable.ShapeDrawable;
18 | import android.graphics.drawable.StateListDrawable;
19 | import android.graphics.drawable.shapes.RoundRectShape;
20 | import android.os.Build;
21 | import android.util.AttributeSet;
22 | import android.view.GestureDetector;
23 | import android.view.MotionEvent;
24 | import android.view.View;
25 | import android.view.ViewOutlineProvider;
26 | import android.view.animation.Animation;
27 | import android.widget.TextView;
28 |
29 | public class Label extends TextView {
30 |
31 | private static final Xfermode PORTER_DUFF_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
32 |
33 | private int mShadowRadius;
34 | private int mShadowXOffset;
35 | private int mShadowYOffset;
36 | private int mShadowColor;
37 | private Drawable mBackgroundDrawable;
38 | private boolean mShowShadow = true;
39 | private int mRawWidth;
40 | private int mRawHeight;
41 | private int mColorNormal;
42 | private int mColorPressed;
43 | private int mColorRipple;
44 | private int mCornerRadius;
45 | private FloatingActionButton mFab;
46 | private Animation mShowAnimation;
47 | private Animation mHideAnimation;
48 | private boolean mUsingStyle;
49 | private boolean mHandleVisibilityChanges = true;
50 |
51 | public Label(Context context) {
52 | super(context);
53 | }
54 |
55 | public Label(Context context, AttributeSet attrs) {
56 | super(context, attrs);
57 | }
58 |
59 | public Label(Context context, AttributeSet attrs, int defStyleAttr) {
60 | super(context, attrs, defStyleAttr);
61 | }
62 |
63 | @Override
64 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
65 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
66 | setMeasuredDimension(calculateMeasuredWidth(), calculateMeasuredHeight());
67 | }
68 |
69 | private int calculateMeasuredWidth() {
70 | if (mRawWidth == 0) {
71 | mRawWidth = getMeasuredWidth();
72 | }
73 | return getMeasuredWidth() + calculateShadowWidth();
74 | }
75 |
76 | private int calculateMeasuredHeight() {
77 | if (mRawHeight == 0) {
78 | mRawHeight = getMeasuredHeight();
79 | }
80 | return getMeasuredHeight() + calculateShadowHeight();
81 | }
82 |
83 | int calculateShadowWidth() {
84 | return mShowShadow ? (mShadowRadius + Math.abs(mShadowXOffset)) : 0;
85 | }
86 |
87 | int calculateShadowHeight() {
88 | return mShowShadow ? (mShadowRadius + Math.abs(mShadowYOffset)) : 0;
89 | }
90 |
91 | void updateBackground() {
92 | LayerDrawable layerDrawable;
93 | if (mShowShadow) {
94 | layerDrawable = new LayerDrawable(new Drawable[]{
95 | new Shadow(),
96 | createFillDrawable()
97 | });
98 |
99 | int leftInset = mShadowRadius + Math.abs(mShadowXOffset);
100 | int topInset = mShadowRadius + Math.abs(mShadowYOffset);
101 | int rightInset = (mShadowRadius + Math.abs(mShadowXOffset));
102 | int bottomInset = (mShadowRadius + Math.abs(mShadowYOffset));
103 |
104 | layerDrawable.setLayerInset(
105 | 1,
106 | leftInset,
107 | topInset,
108 | rightInset,
109 | bottomInset
110 | );
111 | } else {
112 | layerDrawable = new LayerDrawable(new Drawable[]{
113 | createFillDrawable()
114 | });
115 | }
116 |
117 | setBackgroundCompat(layerDrawable);
118 | }
119 |
120 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
121 | private Drawable createFillDrawable() {
122 | StateListDrawable drawable = new StateListDrawable();
123 | drawable.addState(new int[]{android.R.attr.state_pressed}, createRectDrawable(mColorPressed));
124 | drawable.addState(new int[]{}, createRectDrawable(mColorNormal));
125 |
126 | if (Util.hasLollipop()) {
127 | RippleDrawable ripple = new RippleDrawable(new ColorStateList(new int[][]{{}},
128 | new int[]{mColorRipple}), drawable, null);
129 | setOutlineProvider(new ViewOutlineProvider() {
130 | @Override
131 | public void getOutline(View view, Outline outline) {
132 | outline.setOval(0, 0, view.getWidth(), view.getHeight());
133 | }
134 | });
135 | setClipToOutline(true);
136 | mBackgroundDrawable = ripple;
137 | return ripple;
138 | }
139 |
140 | mBackgroundDrawable = drawable;
141 | return drawable;
142 | }
143 |
144 | private Drawable createRectDrawable(int color) {
145 | RoundRectShape shape = new RoundRectShape(
146 | new float[]{
147 | mCornerRadius,
148 | mCornerRadius,
149 | mCornerRadius,
150 | mCornerRadius,
151 | mCornerRadius,
152 | mCornerRadius,
153 | mCornerRadius,
154 | mCornerRadius
155 | },
156 | null,
157 | null);
158 | ShapeDrawable shapeDrawable = new ShapeDrawable(shape);
159 | shapeDrawable.getPaint().setColor(color);
160 | return shapeDrawable;
161 | }
162 |
163 | private void setShadow(FloatingActionButton fab) {
164 | mShadowColor = fab.getShadowColor();
165 | mShadowRadius = fab.getShadowRadius();
166 | mShadowXOffset = fab.getShadowXOffset();
167 | mShadowYOffset = fab.getShadowYOffset();
168 | mShowShadow = fab.hasShadow();
169 | }
170 |
171 | @SuppressWarnings("deprecation")
172 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
173 | private void setBackgroundCompat(Drawable drawable) {
174 | if (Util.hasJellyBean()) {
175 | setBackground(drawable);
176 | } else {
177 | setBackgroundDrawable(drawable);
178 | }
179 | }
180 |
181 | private void playShowAnimation() {
182 | if (mShowAnimation != null) {
183 | mHideAnimation.cancel();
184 | startAnimation(mShowAnimation);
185 | }
186 | }
187 |
188 | private void playHideAnimation() {
189 | if (mHideAnimation != null) {
190 | mShowAnimation.cancel();
191 | startAnimation(mHideAnimation);
192 | }
193 | }
194 |
195 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
196 | void onActionDown() {
197 | if (mUsingStyle) {
198 | mBackgroundDrawable = getBackground();
199 | }
200 |
201 | if (mBackgroundDrawable instanceof StateListDrawable) {
202 | StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable;
203 | drawable.setState(new int[]{android.R.attr.state_pressed});
204 | } else if (Util.hasLollipop() && mBackgroundDrawable instanceof RippleDrawable) {
205 | RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable;
206 | ripple.setState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed});
207 | ripple.setHotspot(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
208 | ripple.setVisible(true, true);
209 | }
210 | // setPressed(true);
211 | }
212 |
213 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
214 | void onActionUp() {
215 | if (mUsingStyle) {
216 | mBackgroundDrawable = getBackground();
217 | }
218 |
219 | if (mBackgroundDrawable instanceof StateListDrawable) {
220 | StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable;
221 | drawable.setState(new int[]{});
222 | } else if (Util.hasLollipop() && mBackgroundDrawable instanceof RippleDrawable) {
223 | RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable;
224 | ripple.setState(new int[]{});
225 | ripple.setHotspot(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
226 | ripple.setVisible(true, true);
227 | }
228 | // setPressed(false);
229 | }
230 |
231 | void setFab(FloatingActionButton fab) {
232 | mFab = fab;
233 | setShadow(fab);
234 | }
235 |
236 | void setShowShadow(boolean show) {
237 | mShowShadow = show;
238 | }
239 |
240 | void setCornerRadius(int cornerRadius) {
241 | mCornerRadius = cornerRadius;
242 | }
243 |
244 | void setColors(int colorNormal, int colorPressed, int colorRipple) {
245 | mColorNormal = colorNormal;
246 | mColorPressed = colorPressed;
247 | mColorRipple = colorRipple;
248 | }
249 |
250 | void show(boolean animate) {
251 | if (animate) {
252 | playShowAnimation();
253 | }
254 | setVisibility(VISIBLE);
255 | }
256 |
257 | void hide(boolean animate) {
258 | if (animate) {
259 | playHideAnimation();
260 | }
261 | setVisibility(INVISIBLE);
262 | }
263 |
264 | void setShowAnimation(Animation showAnimation) {
265 | mShowAnimation = showAnimation;
266 | }
267 |
268 | void setHideAnimation(Animation hideAnimation) {
269 | mHideAnimation = hideAnimation;
270 | }
271 |
272 | void setUsingStyle(boolean usingStyle) {
273 | mUsingStyle = usingStyle;
274 | }
275 |
276 | void setHandleVisibilityChanges(boolean handle) {
277 | mHandleVisibilityChanges = handle;
278 | }
279 |
280 | boolean isHandleVisibilityChanges() {
281 | return mHandleVisibilityChanges;
282 | }
283 |
284 | @Override
285 | public boolean onTouchEvent(MotionEvent event) {
286 | if (mFab == null || mFab.getOnClickListener() == null || !mFab.isEnabled()) {
287 | return super.onTouchEvent(event);
288 | }
289 |
290 | int action = event.getAction();
291 | switch (action) {
292 | case MotionEvent.ACTION_UP:
293 | onActionUp();
294 | mFab.onActionUp();
295 | break;
296 |
297 | case MotionEvent.ACTION_CANCEL:
298 | onActionUp();
299 | mFab.onActionUp();
300 | break;
301 | }
302 |
303 | mGestureDetector.onTouchEvent(event);
304 | return super.onTouchEvent(event);
305 | }
306 |
307 | GestureDetector mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
308 |
309 | @Override
310 | public boolean onDown(MotionEvent e) {
311 | onActionDown();
312 | if (mFab != null) {
313 | mFab.onActionDown();
314 | }
315 | return super.onDown(e);
316 | }
317 |
318 | @Override
319 | public boolean onSingleTapUp(MotionEvent e) {
320 | onActionUp();
321 | if (mFab != null) {
322 | mFab.onActionUp();
323 | }
324 | return super.onSingleTapUp(e);
325 | }
326 | });
327 |
328 | private class Shadow extends Drawable {
329 |
330 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
331 | private Paint mErase = new Paint(Paint.ANTI_ALIAS_FLAG);
332 |
333 | private Shadow() {
334 | this.init();
335 | }
336 |
337 | private void init() {
338 | setLayerType(LAYER_TYPE_SOFTWARE, null);
339 | mPaint.setStyle(Paint.Style.FILL);
340 | mPaint.setColor(mColorNormal);
341 |
342 | mErase.setXfermode(PORTER_DUFF_CLEAR);
343 |
344 | if (!isInEditMode()) {
345 | mPaint.setShadowLayer(mShadowRadius, mShadowXOffset, mShadowYOffset, mShadowColor);
346 | }
347 | }
348 |
349 | @Override
350 | public void draw(Canvas canvas) {
351 | RectF shadowRect = new RectF(
352 | mShadowRadius + Math.abs(mShadowXOffset),
353 | mShadowRadius + Math.abs(mShadowYOffset),
354 | mRawWidth,
355 | mRawHeight
356 | );
357 |
358 | canvas.drawRoundRect(shadowRect, mCornerRadius, mCornerRadius, mPaint);
359 | canvas.drawRoundRect(shadowRect, mCornerRadius, mCornerRadius, mErase);
360 | }
361 |
362 | @Override
363 | public void setAlpha(int alpha) {
364 |
365 | }
366 |
367 | @Override
368 | public void setColorFilter(ColorFilter cf) {
369 |
370 | }
371 |
372 | @Override
373 | public int getOpacity() {
374 | return 0;
375 | }
376 | }
377 | }
378 |
--------------------------------------------------------------------------------
/library/src/main/java/com/github/clans/fab/Util.java:
--------------------------------------------------------------------------------
1 | package com.github.clans.fab;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 |
6 | final class Util {
7 |
8 | private Util() {
9 | }
10 |
11 | static int dpToPx(Context context, float dp) {
12 | final float scale = context.getResources().getDisplayMetrics().density;
13 | return Math.round(dp * scale);
14 | }
15 |
16 | static boolean hasJellyBean() {
17 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
18 | }
19 |
20 | static boolean hasLollipop() {
21 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/fab_scale_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/fab_scale_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/fab_slide_in_from_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/fab_slide_in_from_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/fab_slide_out_to_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/fab_slide_out_to_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable-hdpi/fab_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/library/src/main/res/drawable-hdpi/fab_add.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-mdpi/fab_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/library/src/main/res/drawable-mdpi/fab_add.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-xhdpi/fab_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/library/src/main/res/drawable-xhdpi/fab_add.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-xxhdpi/fab_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/library/src/main/res/drawable-xxhdpi/fab_add.png
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/library/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 56dp
5 | 40dp
6 | 14sp
7 |
8 |
--------------------------------------------------------------------------------
/library/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.github.fab.sample"
9 | minSdkVersion 14
10 | targetSdkVersion 23
11 | versionCode Integer.parseInt(project.VERSION_CODE)
12 | versionName project.VERSION_NAME
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | debuggable false
18 | proguardFiles 'proguard-rules.pro'
19 | }
20 |
21 | debug {
22 | minifyEnabled false
23 | debuggable true
24 | proguardFiles 'proguard-rules.pro'
25 | }
26 | }
27 | }
28 |
29 | dependencies {
30 | compile fileTree(dir: 'libs', include: ['*.jar'])
31 | compile 'com.android.support:appcompat-v7:23.3.0'
32 | compile 'com.android.support:recyclerview-v7:23.3.0'
33 | compile 'com.android.support:design:23.3.0'
34 | compile project(':library')
35 | }
36 |
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
--------------------------------------------------------------------------------
/sample/src/androidTest/java/com/dmytrotarianyk/fab/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.dmytrotarianyk.fab;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/github/clans/fab/sample/HomeFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.clans.fab.sample;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.support.annotation.Nullable;
7 | import android.support.v4.app.Fragment;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.view.animation.AnimationUtils;
12 | import android.widget.AbsListView;
13 | import android.widget.ArrayAdapter;
14 | import android.widget.ListView;
15 |
16 | import com.github.clans.fab.FloatingActionButton;
17 | import com.github.fab.sample.R;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Locale;
22 |
23 | public class HomeFragment extends Fragment {
24 |
25 | private ListView mListView;
26 | private FloatingActionButton mFab;
27 | private int mPreviousVisibleItem;
28 |
29 | @Nullable
30 | @Override
31 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
32 | return inflater.inflate(R.layout.home_fragment, container, false);
33 | }
34 |
35 | @Override
36 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
37 | super.onViewCreated(view, savedInstanceState);
38 | mListView = (ListView) view.findViewById(R.id.list);
39 | mFab = (FloatingActionButton) view.findViewById(R.id.fab);
40 | }
41 |
42 | @Override
43 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
44 | super.onActivityCreated(savedInstanceState);
45 |
46 | Locale[] availableLocales = Locale.getAvailableLocales();
47 | List locales = new ArrayList<>();
48 | for (Locale locale : availableLocales) {
49 | locales.add(locale.getDisplayName());
50 | }
51 |
52 | mListView.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1,
53 | android.R.id.text1, locales));
54 |
55 | mFab.hide(false);
56 | new Handler().postDelayed(new Runnable() {
57 | @Override
58 | public void run() {
59 | mFab.show(true);
60 | mFab.setShowAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.show_from_bottom));
61 | mFab.setHideAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.hide_to_bottom));
62 | }
63 | }, 300);
64 |
65 | mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
66 | @Override
67 | public void onScrollStateChanged(AbsListView view, int scrollState) {
68 | }
69 |
70 | @Override
71 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
72 | if (firstVisibleItem > mPreviousVisibleItem) {
73 | mFab.hide(true);
74 | } else if (firstVisibleItem < mPreviousVisibleItem) {
75 | mFab.show(true);
76 | }
77 | mPreviousVisibleItem = firstVisibleItem;
78 | }
79 | });
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/github/clans/fab/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.clans.fab.sample;
2 |
3 | import android.os.Bundle;
4 | import android.support.design.widget.NavigationView;
5 | import android.support.v4.app.Fragment;
6 | import android.support.v4.app.FragmentTransaction;
7 | import android.support.v4.view.GravityCompat;
8 | import android.support.v4.widget.DrawerLayout;
9 | import android.support.v7.app.ActionBarDrawerToggle;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.widget.Toolbar;
12 | import android.view.MenuItem;
13 |
14 | import com.github.fab.sample.R;
15 |
16 | public class MainActivity extends AppCompatActivity {
17 |
18 | private DrawerLayout mDrawerLayout;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.main_activity);
24 |
25 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
26 | setSupportActionBar(toolbar);
27 |
28 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
29 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
30 | this, mDrawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
31 | mDrawerLayout.addDrawerListener(toggle);
32 | toggle.syncState();
33 |
34 | NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
35 | navigationView.setNavigationItemSelectedListener(navigationItemSelectedListener);
36 |
37 | if (savedInstanceState == null) {
38 | getSupportFragmentManager().beginTransaction()
39 | .add(R.id.fragment, new HomeFragment()).commit();
40 | }
41 |
42 | navigationView.setCheckedItem(R.id.home);
43 | }
44 |
45 | NavigationView.OnNavigationItemSelectedListener navigationItemSelectedListener = new NavigationView.OnNavigationItemSelectedListener() {
46 | @Override
47 | public boolean onNavigationItemSelected(MenuItem item) {
48 | mDrawerLayout.closeDrawer(GravityCompat.START);
49 |
50 | Fragment fragment = null;
51 | final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
52 | switch (item.getItemId()) {
53 | case R.id.home:
54 | fragment = new HomeFragment();
55 | break;
56 | case R.id.menus:
57 | fragment = new MenusFragment();
58 | break;
59 | case R.id.progress:
60 | fragment = new ProgressFragment();
61 | break;
62 | }
63 |
64 | ft.replace(R.id.fragment, fragment).commit();
65 | return true;
66 | }
67 | };
68 |
69 | @Override
70 | public void onBackPressed() {
71 | if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
72 | mDrawerLayout.closeDrawer(GravityCompat.START);
73 | } else {
74 | super.onBackPressed();
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/github/clans/fab/sample/MenusFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.clans.fab.sample;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ObjectAnimator;
7 | import android.os.Bundle;
8 | import android.os.Handler;
9 | import android.support.annotation.Nullable;
10 | import android.support.v4.app.Fragment;
11 | import android.support.v4.content.ContextCompat;
12 | import android.view.ContextThemeWrapper;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.view.animation.AnimationUtils;
17 | import android.view.animation.OvershootInterpolator;
18 | import android.widget.Toast;
19 |
20 | import com.github.clans.fab.FloatingActionButton;
21 | import com.github.clans.fab.FloatingActionMenu;
22 | import com.github.fab.sample.R;
23 |
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | public class MenusFragment extends Fragment {
28 |
29 | private FloatingActionMenu menuRed;
30 | private FloatingActionMenu menuYellow;
31 | private FloatingActionMenu menuGreen;
32 | private FloatingActionMenu menuBlue;
33 | private FloatingActionMenu menuDown;
34 | private FloatingActionMenu menuLabelsRight;
35 |
36 | private FloatingActionButton fab1;
37 | private FloatingActionButton fab2;
38 | private FloatingActionButton fab3;
39 |
40 | private FloatingActionButton fabEdit;
41 |
42 | private List menus = new ArrayList<>();
43 | private Handler mUiHandler = new Handler();
44 |
45 | @Nullable
46 | @Override
47 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
48 | return inflater.inflate(R.layout.menus_fragment, container, false);
49 | }
50 |
51 | @Override
52 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
53 | super.onViewCreated(view, savedInstanceState);
54 |
55 | menuRed = (FloatingActionMenu) view.findViewById(R.id.menu_red);
56 | menuYellow = (FloatingActionMenu) view.findViewById(R.id.menu_yellow);
57 | menuGreen = (FloatingActionMenu) view.findViewById(R.id.menu_green);
58 | menuBlue = (FloatingActionMenu) view.findViewById(R.id.menu_blue);
59 | menuDown = (FloatingActionMenu) view.findViewById(R.id.menu_down);
60 | menuLabelsRight = (FloatingActionMenu) view.findViewById(R.id.menu_labels_right);
61 |
62 | fab1 = (FloatingActionButton) view.findViewById(R.id.fab1);
63 | fab2 = (FloatingActionButton) view.findViewById(R.id.fab2);
64 | fab3 = (FloatingActionButton) view.findViewById(R.id.fab3);
65 |
66 | final FloatingActionButton programFab1 = new FloatingActionButton(getActivity());
67 | programFab1.setButtonSize(FloatingActionButton.SIZE_MINI);
68 | programFab1.setLabelText(getString(R.string.lorem_ipsum));
69 | programFab1.setImageResource(R.drawable.ic_edit);
70 | menuRed.addMenuButton(programFab1);
71 | programFab1.setOnClickListener(new View.OnClickListener() {
72 | @Override
73 | public void onClick(View v) {
74 | programFab1.setLabelColors(ContextCompat.getColor(getActivity(), R.color.grey),
75 | ContextCompat.getColor(getActivity(), R.color.light_grey),
76 | ContextCompat.getColor(getActivity(), R.color.white_transparent));
77 | programFab1.setLabelTextColor(ContextCompat.getColor(getActivity(), R.color.black));
78 | }
79 | });
80 |
81 | ContextThemeWrapper context = new ContextThemeWrapper(getActivity(), R.style.MenuButtonsStyle);
82 | FloatingActionButton programFab2 = new FloatingActionButton(context);
83 | programFab2.setLabelText("Programmatically added button");
84 | programFab2.setImageResource(R.drawable.ic_edit);
85 | menuYellow.addMenuButton(programFab2);
86 |
87 | fab1.setEnabled(false);
88 | menuRed.setClosedOnTouchOutside(true);
89 | menuBlue.setIconAnimated(false);
90 |
91 | menuDown.hideMenuButton(false);
92 | menuRed.hideMenuButton(false);
93 | menuYellow.hideMenuButton(false);
94 | menuGreen.hideMenuButton(false);
95 | menuBlue.hideMenuButton(false);
96 | menuLabelsRight.hideMenuButton(false);
97 |
98 | fabEdit = (FloatingActionButton) view.findViewById(R.id.fab_edit);
99 | fabEdit.setShowAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.scale_up));
100 | fabEdit.setHideAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.scale_down));
101 | }
102 |
103 | @Override
104 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
105 | super.onActivityCreated(savedInstanceState);
106 |
107 | menus.add(menuDown);
108 | menus.add(menuRed);
109 | menus.add(menuYellow);
110 | menus.add(menuGreen);
111 | menus.add(menuBlue);
112 | menus.add(menuLabelsRight);
113 |
114 | menuYellow.setOnMenuToggleListener(new FloatingActionMenu.OnMenuToggleListener() {
115 | @Override
116 | public void onMenuToggle(boolean opened) {
117 | String text;
118 | if (opened) {
119 | text = "Menu opened";
120 | } else {
121 | text = "Menu closed";
122 | }
123 | Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
124 | }
125 | });
126 |
127 | fab1.setOnClickListener(clickListener);
128 | fab2.setOnClickListener(clickListener);
129 | fab3.setOnClickListener(clickListener);
130 |
131 | int delay = 400;
132 | for (final FloatingActionMenu menu : menus) {
133 | mUiHandler.postDelayed(new Runnable() {
134 | @Override
135 | public void run() {
136 | menu.showMenuButton(true);
137 | }
138 | }, delay);
139 | delay += 150;
140 | }
141 |
142 | new Handler().postDelayed(new Runnable() {
143 | @Override
144 | public void run() {
145 | fabEdit.show(true);
146 | }
147 | }, delay + 150);
148 |
149 | menuRed.setOnMenuButtonClickListener(new View.OnClickListener() {
150 | @Override
151 | public void onClick(View v) {
152 | if (menuRed.isOpened()) {
153 | Toast.makeText(getActivity(), menuRed.getMenuButtonLabelText(), Toast.LENGTH_SHORT).show();
154 | }
155 |
156 | menuRed.toggle(true);
157 | }
158 | });
159 |
160 | createCustomAnimation();
161 | }
162 |
163 | private void createCustomAnimation() {
164 | AnimatorSet set = new AnimatorSet();
165 |
166 | ObjectAnimator scaleOutX = ObjectAnimator.ofFloat(menuGreen.getMenuIconView(), "scaleX", 1.0f, 0.2f);
167 | ObjectAnimator scaleOutY = ObjectAnimator.ofFloat(menuGreen.getMenuIconView(), "scaleY", 1.0f, 0.2f);
168 |
169 | ObjectAnimator scaleInX = ObjectAnimator.ofFloat(menuGreen.getMenuIconView(), "scaleX", 0.2f, 1.0f);
170 | ObjectAnimator scaleInY = ObjectAnimator.ofFloat(menuGreen.getMenuIconView(), "scaleY", 0.2f, 1.0f);
171 |
172 | scaleOutX.setDuration(50);
173 | scaleOutY.setDuration(50);
174 |
175 | scaleInX.setDuration(150);
176 | scaleInY.setDuration(150);
177 |
178 | scaleInX.addListener(new AnimatorListenerAdapter() {
179 | @Override
180 | public void onAnimationStart(Animator animation) {
181 | menuGreen.getMenuIconView().setImageResource(menuGreen.isOpened()
182 | ? R.drawable.ic_close : R.drawable.ic_star);
183 | }
184 | });
185 |
186 | set.play(scaleOutX).with(scaleOutY);
187 | set.play(scaleInX).with(scaleInY).after(scaleOutX);
188 | set.setInterpolator(new OvershootInterpolator(2));
189 |
190 | menuGreen.setIconToggleAnimatorSet(set);
191 | }
192 |
193 | private View.OnClickListener clickListener = new View.OnClickListener() {
194 | @Override
195 | public void onClick(View v) {
196 | switch (v.getId()) {
197 | case R.id.fab1:
198 | break;
199 | case R.id.fab2:
200 | fab2.setVisibility(View.GONE);
201 | break;
202 | case R.id.fab3:
203 | fab2.setVisibility(View.VISIBLE);
204 | break;
205 | }
206 | }
207 | };
208 | }
209 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/github/clans/fab/sample/ProgressFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.clans.fab.sample;
2 |
3 | import android.os.Bundle;
4 | import android.os.Handler;
5 | import android.support.annotation.Nullable;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v7.widget.LinearLayoutManager;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.TextView;
13 |
14 | import com.github.clans.fab.FloatingActionButton;
15 | import com.github.fab.sample.R;
16 |
17 | import java.util.LinkedList;
18 | import java.util.Locale;
19 |
20 | public class ProgressFragment extends Fragment {
21 |
22 | private int mScrollOffset = 4;
23 | private int mMaxProgress = 100;
24 | private LinkedList mProgressTypes;
25 | private Handler mUiHandler = new Handler();
26 |
27 | @Nullable
28 | @Override
29 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
30 | return inflater.inflate(R.layout.progress_fragment, container, false);
31 | }
32 |
33 | @Override
34 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
35 | super.onViewCreated(view, savedInstanceState);
36 |
37 | Locale[] availableLocales = Locale.getAvailableLocales();
38 | mProgressTypes = new LinkedList<>();
39 | for (ProgressType type : ProgressType.values()) {
40 | mProgressTypes.offer(type);
41 | }
42 |
43 | final FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab);
44 | fab.setMax(mMaxProgress);
45 |
46 | RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.my_recycler_view);
47 | recyclerView.setHasFixedSize(true);
48 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
49 | recyclerView.setAdapter(new LanguageAdapter(availableLocales));
50 | fab.setOnClickListener(new View.OnClickListener() {
51 | @Override
52 | public void onClick(View v) {
53 | ProgressType type = mProgressTypes.poll();
54 | switch (type) {
55 | case INDETERMINATE:
56 | fab.setShowProgressBackground(true);
57 | fab.setIndeterminate(true);
58 | mProgressTypes.offer(ProgressType.INDETERMINATE);
59 | break;
60 | case PROGRESS_POSITIVE:
61 | fab.setIndeterminate(false);
62 | fab.setProgress(70, true);
63 | mProgressTypes.offer(ProgressType.PROGRESS_POSITIVE);
64 | break;
65 | case PROGRESS_NEGATIVE:
66 | fab.setProgress(30, true);
67 | mProgressTypes.offer(ProgressType.PROGRESS_NEGATIVE);
68 | break;
69 | case HIDDEN:
70 | fab.hideProgress();
71 | mProgressTypes.offer(ProgressType.HIDDEN);
72 | break;
73 | case PROGRESS_NO_ANIMATION:
74 | increaseProgress(fab, 0);
75 | break;
76 | case PROGRESS_NO_BACKGROUND:
77 | fab.setShowProgressBackground(false);
78 | fab.setIndeterminate(true);
79 | mProgressTypes.offer(ProgressType.PROGRESS_NO_BACKGROUND);
80 | break;
81 | }
82 | }
83 | });
84 |
85 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
86 | @Override
87 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
88 | super.onScrolled(recyclerView, dx, dy);
89 | if (Math.abs(dy) > mScrollOffset) {
90 | if (dy > 0) {
91 | fab.hide(true);
92 | } else {
93 | fab.show(true);
94 | }
95 | }
96 | }
97 | });
98 | }
99 |
100 | private void increaseProgress(final FloatingActionButton fab, int i) {
101 | if (i <= mMaxProgress) {
102 | fab.setProgress(i, false);
103 | final int progress = ++i;
104 | mUiHandler.postDelayed(new Runnable() {
105 | @Override
106 | public void run() {
107 | increaseProgress(fab, progress);
108 | }
109 | }, 30);
110 | } else {
111 | mUiHandler.postDelayed(new Runnable() {
112 | @Override
113 | public void run() {
114 | fab.hideProgress();
115 | }
116 | }, 200);
117 | mProgressTypes.offer(ProgressType.PROGRESS_NO_ANIMATION);
118 | }
119 | }
120 |
121 | private class LanguageAdapter extends RecyclerView.Adapter {
122 |
123 | private Locale[] mLocales;
124 |
125 | private LanguageAdapter(Locale[] mLocales) {
126 | this.mLocales = mLocales;
127 | }
128 |
129 | @Override
130 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
131 | TextView tv = (TextView) LayoutInflater.from(parent.getContext())
132 | .inflate(android.R.layout.simple_list_item_1, parent, false);
133 |
134 | return new ViewHolder(tv);
135 | }
136 |
137 | @Override
138 | public void onBindViewHolder(ViewHolder holder, int position) {
139 | holder.mTextView.setText(mLocales[position].getDisplayName());
140 | }
141 |
142 | @Override
143 | public int getItemCount() {
144 | return mLocales.length;
145 | }
146 | }
147 |
148 | private static class ViewHolder extends RecyclerView.ViewHolder {
149 |
150 | public TextView mTextView;
151 |
152 | public ViewHolder(TextView v) {
153 | super(v);
154 | mTextView = v;
155 | }
156 | }
157 |
158 | private enum ProgressType {
159 | INDETERMINATE, PROGRESS_POSITIVE, PROGRESS_NEGATIVE, HIDDEN, PROGRESS_NO_ANIMATION, PROGRESS_NO_BACKGROUND
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/hide_to_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/jump_from_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/jump_to_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/scale_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/scale_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/show_from_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_close.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_edit.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_menu.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_nav_item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_nav_item.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_progress.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_star.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_close.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_edit.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_menu.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_nav_item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_nav_item.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_progress.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_star.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v21/fab_label_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | -
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 | -
18 |
19 |
20 |
-
21 |
22 |
23 |
28 |
29 |
30 |
31 |
32 | -
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_close.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_edit.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_menu.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_nav_item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_nav_item.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_progress.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_star.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_close.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_edit.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_menu.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_nav_item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_nav_item.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_progress.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_star.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxxhdpi/ic_nav_item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxxhdpi/ic_nav_item.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxxhdpi/ic_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxxhdpi/ic_progress.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/fab_label_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | -
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/drawer_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/home_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
21 |
22 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/menus_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
24 |
25 |
32 |
33 |
51 |
52 |
59 |
60 |
67 |
68 |
75 |
76 |
77 |
78 |
95 |
96 |
102 |
103 |
109 |
110 |
116 |
117 |
118 |
119 |
133 |
134 |
140 |
141 |
147 |
148 |
154 |
155 |
156 |
157 |
172 |
173 |
188 |
189 |
195 |
196 |
202 |
203 |
209 |
210 |
211 |
212 |
225 |
226 |
233 |
234 |
241 |
242 |
249 |
250 |
251 |
252 |
264 |
265 |
271 |
272 |
278 |
279 |
285 |
286 |
287 |
288 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/progress_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
23 |
24 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/menu_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #1976D2
5 | #1565C0
6 | #DDDDDD
7 | #EEEEEE
8 | #99FFFFFF
9 | #000
10 |
11 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Floating Action Button
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur magna tortor, semper eu feugiat eu, vehicula vitae lacus.
4 | RecyclerView FAB example
5 | Floating Action Menu
6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis sed ultricies risus. Integer nisi orci, lacinia sit amet mi in, mollis scelerisque purus. Nunc risus ligula, maximus eu orci a, facilisis dictum velit. Proin nec laoreet magna. Nulla ut sagittis lorem. Morbi id enim fermentum, semper diam et, tempus leo. Aliquam vel congue orci. Suspendisse potenti. Curabitur finibus diam augue, vel bibendum sapien ultrices non.
7 | Navigation open
8 | Navigation close
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
12 |
13 |
16 |
17 |
24 |
25 |
34 |
35 |
41 |
42 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/screenshots/main_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/main_screen.png
--------------------------------------------------------------------------------
/screenshots/menu_closed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_closed.png
--------------------------------------------------------------------------------
/screenshots/menu_custom_opened.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_custom_opened.png
--------------------------------------------------------------------------------
/screenshots/menu_default_opened.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_default_opened.png
--------------------------------------------------------------------------------
/screenshots/menu_down_opened.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_down_opened.png
--------------------------------------------------------------------------------
/screenshots/menu_mini_opened.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_mini_opened.png
--------------------------------------------------------------------------------
/screenshots/menu_right_opened.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_right_opened.png
--------------------------------------------------------------------------------
/screenshots/progress_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/progress_background.png
--------------------------------------------------------------------------------
/screenshots/progress_no_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/progress_no_background.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample', ':library'
2 |
--------------------------------------------------------------------------------