FloatingActionMenu.java 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. package com.novaapps.floatingactionmenu;
  2. import android.animation.Animator;
  3. import android.animation.AnimatorSet;
  4. import android.animation.ObjectAnimator;
  5. import android.animation.TimeInterpolator;
  6. import android.animation.ValueAnimator;
  7. import android.content.Context;
  8. import android.graphics.Color;
  9. import android.graphics.drawable.ColorDrawable;
  10. import android.graphics.drawable.Drawable;
  11. import android.os.Bundle;
  12. import android.os.Parcelable;
  13. import android.support.annotation.NonNull;
  14. import android.support.design.widget.FloatingActionButton;
  15. import android.util.AttributeSet;
  16. import android.util.Log;
  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.AnticipateInterpolator;
  22. import android.view.animation.OvershootInterpolator;
  23. import android.widget.ImageView;
  24. import android.widget.TextView;
  25. import java.util.ArrayList;
  26. /**
  27. * Component for a FloatingActionMenu.
  28. *
  29. * Created by charry on 2015/6/11. https://gist.github.com/douo/dfde289778a9b3b6918f and modified by Tristan Wiley
  30. */
  31. public class FloatingActionMenu extends ViewGroup {
  32. //-- Properties --//
  33. /**
  34. * Defines the rate of change for the open animation.
  35. */
  36. static final TimeInterpolator DEFAULT_OPEN_INTERPOLATOR = new OvershootInterpolator();
  37. /**
  38. * Defines the rate of change for the close animation.
  39. */
  40. static final TimeInterpolator DEFAULT_CLOSE_INTERPOLATOR = new AnticipateInterpolator();
  41. /**
  42. * The main menu button that is always visible. Clicking this will open/close the menu.
  43. */
  44. private FloatingActionButton mMenuButton;
  45. /**
  46. * The list of menu items to appear when the menu opens.
  47. */
  48. private ArrayList<FloatingActionButton> mMenuItems;
  49. /**
  50. * The list of labels to appear next to the menu items.
  51. */
  52. private ArrayList<TextView> mMenuItemLabels;
  53. /**
  54. * Animators to animate the appearance/disappearance of the menu items.
  55. */
  56. private ArrayList<ItemAnimator> mMenuItemAnimators;
  57. /**
  58. * The set of animations to occur when the menu opens.
  59. */
  60. private AnimatorSet mOpenAnimatorSet = new AnimatorSet();
  61. /**
  62. * The set of animations to occur when the menu closes.
  63. */
  64. private AnimatorSet mCloseAnimatorSet = new AnimatorSet();
  65. /**
  66. * The image that will appear inside the main menu item.
  67. */
  68. private ImageView mIcon;
  69. /**
  70. * A flag representing the open state of the menu.
  71. */
  72. private boolean mOpen;
  73. /**
  74. * A flag representing the current animation stte of the menu.
  75. */
  76. private boolean animating;
  77. /**
  78. * A flag representing whether or not the menu should close if the user touches outside of the menu items.
  79. */
  80. private boolean mIsSetClosedOnTouchOutside = true;
  81. /**
  82. * The duration of the open/close animations.
  83. */
  84. private long duration = 300;
  85. /**
  86. * A flag representing whether or not the menu appearance is a circle (true) or linear menu (false).
  87. */
  88. private boolean isCircle = false;
  89. /**
  90. * The radius of the circular menu (if application).
  91. */
  92. private int mRadius = 256;
  93. /**
  94. * If the radius of the circle is a multiple of the FB width, this is what that ratio is.
  95. */
  96. private float multipleOfFB = 0;
  97. /**
  98. * The gap between menu items.
  99. */
  100. private int mItemGap = 0;
  101. /**
  102. * A click listener for the main menu item.
  103. */
  104. private OnMenuItemClickListener onMenuItemClickListener;
  105. /**
  106. * A listener for when the menu toggles between open and closed.
  107. */
  108. private OnMenuToggleListener onMenuToggleListener;
  109. /**
  110. * A GestureDetector that looks for gestures outside the FAM and closes it if necessary.
  111. */
  112. GestureDetector mGestureDetector = new GestureDetector(getContext(),
  113. new GestureDetector.SimpleOnGestureListener() {
  114. @Override
  115. public boolean onDown(MotionEvent e) {
  116. return mIsSetClosedOnTouchOutside && isOpened();
  117. }
  118. @Override
  119. public boolean onSingleTapUp(MotionEvent e) {
  120. close();
  121. return true;
  122. }
  123. }
  124. );
  125. /**
  126. * An OnItemClickListener that handles clicks on a menu item or one of its labels.
  127. */
  128. private OnClickListener mOnItemClickListener = new OnClickListener() {
  129. @Override
  130. public void onClick(View v) {
  131. // If we click a menu item, call our MenuItemClickListener.
  132. // Else - we are clicking a label, call our MenuItemClickListener.
  133. // This is split into an if/else since we access different arrays for each.
  134. if (v instanceof FloatingActionButton) {
  135. int i = mMenuItems.indexOf(v);
  136. if (onMenuItemClickListener != null) {
  137. onMenuItemClickListener
  138. .onMenuItemClick(FloatingActionMenu.this, i, (FloatingActionButton) v);
  139. }
  140. } else if (v instanceof TextView) {
  141. int i = mMenuItemLabels.indexOf(v);
  142. if (onMenuItemClickListener != null) {
  143. onMenuItemClickListener.onMenuItemClick(FloatingActionMenu.this, i, mMenuItems.get(i));
  144. }
  145. }
  146. close();
  147. }
  148. };
  149. //-- Constructors --//
  150. public FloatingActionMenu(Context context) {
  151. this(context, null, 0);
  152. }
  153. public FloatingActionMenu(Context context, AttributeSet attrs) {
  154. this(context, attrs, 0);
  155. }
  156. public FloatingActionMenu(Context context, AttributeSet attrs, int defStyleAttr) {
  157. super(context, attrs, defStyleAttr);
  158. // Default all lists to 5 items.
  159. mMenuItems = new ArrayList<>(5);
  160. mMenuItemAnimators = new ArrayList<>(5);
  161. mMenuItemLabels = new ArrayList<>(5);
  162. mIcon = new ImageView(context);
  163. }
  164. //-- Overriden methods --//
  165. @Override
  166. protected void onFinishInflate() {
  167. bringChildToFront(mMenuButton);
  168. bringChildToFront(mIcon);
  169. super.onFinishInflate();
  170. }
  171. @Override
  172. public void addView(@NonNull View child, int index, LayoutParams params) {
  173. super.addView(child, index, params);
  174. // If the child count is greater than one, we are adding menu items.
  175. // If the child count is not greater than one, we are adding the initial menu item.
  176. if (getChildCount() > 1) {
  177. if (child instanceof FloatingActionButton) {
  178. addMenuItem((FloatingActionButton) child);
  179. }
  180. } else {
  181. mMenuButton = (FloatingActionButton) child;
  182. mIcon.setImageDrawable(mMenuButton.getDrawable());
  183. addView(mIcon);
  184. mMenuButton.setImageDrawable(mMenuButton.getDrawable());
  185. createDefaultIconAnimation();
  186. mMenuButton.setOnClickListener(new OnClickListener() {
  187. @Override
  188. public void onClick(View v) {
  189. toggle();
  190. }
  191. });
  192. }
  193. }
  194. /**
  195. * Handles the measuring of the FAM, and sets the size according to the number of children.
  196. */
  197. @Override
  198. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  199. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  200. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  201. int width;
  202. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  203. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  204. int height;
  205. final int count = getChildCount();
  206. int maxChildWidth = 0;
  207. for (int i = 0; i < count; i++) {
  208. View child = getChildAt(i);
  209. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  210. }
  211. for (int i = 0; i < mMenuItems.size(); i++) {
  212. FloatingActionButton fab = mMenuItems.get(i);
  213. TextView label = mMenuItemLabels.get(i);
  214. maxChildWidth = Math.max(maxChildWidth,
  215. label.getMeasuredWidth() + fab.getMeasuredWidth());
  216. }
  217. maxChildWidth = Math.max(mMenuButton.getMeasuredWidth(), maxChildWidth);
  218. if (widthMode == MeasureSpec.EXACTLY) {
  219. width = widthSize;
  220. } else {
  221. width = maxChildWidth + 30;
  222. }
  223. if (heightMode == MeasureSpec.EXACTLY) {
  224. height = heightSize;
  225. } else {
  226. int heightSum = 0;
  227. for (int i = 0; i < count; i++) {
  228. View child = getChildAt(i);
  229. heightSum += child.getMeasuredHeight();
  230. }
  231. height = heightSum + 20;
  232. }
  233. setMeasuredDimension(resolveSize(width, widthMeasureSpec),
  234. resolveSize(height, heightMeasureSpec));
  235. }
  236. /**
  237. * Handles a touch event in the ViewGroup and closes the FAM if necessary.
  238. */
  239. @Override
  240. public boolean onTouchEvent(@NonNull MotionEvent event) {
  241. if (mIsSetClosedOnTouchOutside) {
  242. return mGestureDetector.onTouchEvent(event);
  243. } else {
  244. return super.onTouchEvent(event);
  245. }
  246. }
  247. /**
  248. * Sets the layout of the ViewGroup dependent on the number of menu items as well as menu direction.
  249. */
  250. @Override
  251. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  252. System.out.println("onLayout:" + changed);
  253. if (changed) {
  254. int right = r - getPaddingRight();
  255. int bottom = b - getPaddingBottom();
  256. int top = bottom - mMenuButton.getMeasuredHeight();
  257. mMenuButton.layout(right - mMenuButton.getMeasuredWidth(), top, right, bottom);
  258. int dw = (mMenuButton.getMeasuredWidth() - mIcon.getMeasuredWidth()) / 2;
  259. int dh = (mMenuButton.getMeasuredHeight() - mIcon.getMeasuredHeight()) / 2;
  260. mIcon.layout(right - mIcon.getMeasuredWidth() - dw,
  261. bottom - mIcon.getMeasuredHeight() - dh, right - dw, bottom - dh);
  262. if (isCircle) {
  263. if (mMenuItems.size() < 2) {
  264. Log.e("onLayout", "Floating Action Buttons must more then one!");
  265. return;
  266. }
  267. double angle = Math.PI/2d/(mMenuItems.size() - 1);
  268. for (int i = 0; i < mMenuItems.size(); i++) {
  269. FloatingActionButton itemFB = mMenuItems.get(i);
  270. int fbWidth = itemFB.getMeasuredWidth();
  271. int fbHeight = itemFB.getMeasuredHeight();
  272. if (0 != multipleOfFB) {
  273. mRadius = (int) (fbWidth * multipleOfFB);
  274. }
  275. int itemDw = (mMenuButton.getMeasuredWidth() - fbWidth) / 2;
  276. int itemDh = (mMenuButton.getMeasuredHeight() - fbHeight) / 2;
  277. int itemX = (int) (mRadius*Math.cos(i*angle));
  278. int itemY = (int) (mRadius*Math.sin(i*angle));
  279. itemFB.layout(right - itemX - fbWidth - itemDw, bottom - itemY - fbHeight - itemDh,
  280. right - itemX - itemDw, bottom - itemY - itemDh);
  281. if (!animating) {
  282. if (!mOpen) {
  283. itemFB.setTranslationY(mMenuButton.getTop() - itemFB.getTop());
  284. itemFB.setTranslationX(mMenuButton.getLeft() - itemFB.getLeft());
  285. itemFB.setVisibility(GONE);
  286. } else {
  287. itemFB.setTranslationY(0);
  288. itemFB.setTranslationX(0);
  289. itemFB.setVisibility(VISIBLE);
  290. }
  291. }
  292. }
  293. } else {
  294. for (int i = 0; i < mMenuItems.size(); i++) {
  295. FloatingActionButton item = mMenuItems.get(i);
  296. TextView label = mMenuItemLabels.get(i);
  297. label.setBackgroundResource(R.drawable.rounded_corners);
  298. bottom = top -= mItemGap;
  299. top -= item.getMeasuredHeight();
  300. int width = item.getMeasuredWidth();
  301. int d = (mMenuButton.getMeasuredWidth() - width) / 2;
  302. item.layout(right - width - d, top, right - d, bottom);
  303. d = (item.getMeasuredHeight() - label.getMeasuredHeight()) / 2;
  304. label.layout(item.getLeft() - label.getMeasuredWidth() - 50,
  305. item.getTop() + d, item.getLeft(),
  306. item.getTop() + d + label.getMeasuredHeight());
  307. if (!animating) {
  308. if (!mOpen) {
  309. item.setTranslationY(mMenuButton.getTop() - item.getTop());
  310. item.setVisibility(GONE);
  311. label.setVisibility(GONE);
  312. } else {
  313. item.setTranslationY(0);
  314. item.setVisibility(VISIBLE);
  315. label.setVisibility(VISIBLE);
  316. }
  317. }
  318. }
  319. }
  320. if (!animating && getBackground() != null) {
  321. if (!mOpen) {
  322. getBackground().setAlpha(0);
  323. } else {
  324. getBackground().setAlpha(0xff);
  325. }
  326. }
  327. }
  328. }
  329. /**
  330. * Saves the state of the menu item as open or close to be able to handle device rotations.
  331. */
  332. @Override
  333. public Parcelable onSaveInstanceState() {
  334. d("onSaveInstanceState");
  335. Bundle bundle = new Bundle();
  336. bundle.putParcelable("instanceState", super.onSaveInstanceState());
  337. bundle.putBoolean("mOpen", mOpen);
  338. // ... save everything
  339. return bundle;
  340. }
  341. /**
  342. * Restores the state of the FAM after a rotation.
  343. */
  344. @Override
  345. public void onRestoreInstanceState(Parcelable state) {
  346. d("onRestoreInstanceState");
  347. if (state instanceof Bundle) {
  348. Bundle bundle = (Bundle) state;
  349. mOpen = bundle.getBoolean("mOpen");
  350. // ... load everything
  351. state = bundle.getParcelable("instanceState");
  352. }
  353. super.onRestoreInstanceState(state);
  354. }
  355. @Override
  356. protected void onDetachedFromWindow() {
  357. d("onDetachedFromWindow");
  358. //getBackground().setAlpha(bgAlpha);//reset default alpha
  359. super.onDetachedFromWindow();
  360. }
  361. @Override
  362. public void setBackground(Drawable background) {
  363. if (background instanceof ColorDrawable) {
  364. // after activity finish and relaucher , background drawable state still remain?
  365. int bgAlpha = Color.alpha(((ColorDrawable) background).getColor());
  366. d("bg:" + Integer.toHexString(bgAlpha));
  367. super.setBackground(background);
  368. } else {
  369. throw new IllegalArgumentException("floating only support color background");
  370. }
  371. }
  372. //-- Open and close methods methods --//
  373. /**
  374. * Toggles the menu between open and closed, depending on its current state.
  375. */
  376. public void toggle() {
  377. if (!mOpen) {
  378. open();
  379. } else {
  380. close();
  381. }
  382. }
  383. /**
  384. * Opens the FloatingActionMenu.
  385. */
  386. public void open() {
  387. d("open");
  388. startOpenAnimator();
  389. mOpen = true;
  390. if (onMenuToggleListener != null) {
  391. onMenuToggleListener.onMenuToggle(true);
  392. }
  393. }
  394. /**
  395. * Closes the FloatingActionMenu.
  396. */
  397. public void close() {
  398. startCloseAnimator();
  399. mOpen = false;
  400. if (onMenuToggleListener != null) {
  401. onMenuToggleListener.onMenuToggle(true);
  402. }
  403. }
  404. //-- Animation methods. --//
  405. /**
  406. * Initiates all of the closing animations.
  407. */
  408. protected void startCloseAnimator() {
  409. mCloseAnimatorSet.start();
  410. for (ItemAnimator anim : mMenuItemAnimators) {
  411. anim.startCloseAnimator();
  412. }
  413. }
  414. /**
  415. * Initiating all of the opening animations.
  416. */
  417. protected void startOpenAnimator() {
  418. mOpenAnimatorSet.start();
  419. for (ItemAnimator anim : mMenuItemAnimators) {
  420. anim.startOpenAnimator();
  421. }
  422. }
  423. /**
  424. * Adds a new menu item to the FloatingActionMenu.
  425. * @param item The FloatingActionButton to add to the menu.
  426. */
  427. public void addMenuItem(FloatingActionButton item) {
  428. mMenuItems.add(item);
  429. mMenuItemAnimators.add(new ItemAnimator(item));
  430. TextView button = new TextView(getContext());
  431. LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  432. button.setLayoutParams(params);
  433. button.setBackgroundResource(R.drawable.rounded_corners);
  434. button.setTextColor(Color.WHITE);
  435. button.setText(item.getContentDescription());
  436. Integer paddingSize = (int)button.getTextSize() / 3;
  437. button.setPadding(paddingSize, paddingSize, paddingSize, paddingSize);
  438. addView(button);
  439. mMenuItemLabels.add(button);
  440. item.setTag(button);
  441. item.setOnClickListener(mOnItemClickListener);
  442. button.setOnClickListener(mOnItemClickListener);
  443. }
  444. /**
  445. * Sets the default animation for the FAM icon, which is simply rotation.
  446. */
  447. private void createDefaultIconAnimation() {
  448. Animator.AnimatorListener listener = new Animator.AnimatorListener() {
  449. @Override
  450. public void onAnimationStart(Animator animation) {
  451. animating = true;
  452. }
  453. @Override
  454. public void onAnimationEnd(Animator animation) {
  455. animating = false;
  456. }
  457. @Override
  458. public void onAnimationCancel(Animator animation) {
  459. animating = false;
  460. }
  461. @Override
  462. public void onAnimationRepeat(Animator animation) {
  463. }
  464. };
  465. ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(
  466. mIcon,
  467. "rotation",
  468. 135f,
  469. 0f
  470. );
  471. ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(
  472. mIcon,
  473. "rotation",
  474. 0f,
  475. 135f
  476. );
  477. if (getBackground() != null) {
  478. ValueAnimator hideBackgroundAnimator = ObjectAnimator.ofInt(0xff, 0);
  479. hideBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  480. @Override
  481. public void onAnimationUpdate(ValueAnimator animation) {
  482. Integer alpha = (Integer) animation.getAnimatedValue();
  483. //System.out.println(alpha);
  484. getBackground().setAlpha(alpha > 0xff ? 0xff : alpha);
  485. }
  486. });
  487. ValueAnimator showBackgroundAnimator = ObjectAnimator.ofInt(0, 0xff);
  488. showBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  489. @Override
  490. public void onAnimationUpdate(ValueAnimator animation) {
  491. Integer alpha = (Integer) animation.getAnimatedValue();
  492. //System.out.println(alpha);
  493. getBackground().setAlpha(alpha > 0xff ? 0xff : alpha);
  494. }
  495. });
  496. mOpenAnimatorSet.playTogether(expandAnimator, showBackgroundAnimator);
  497. mCloseAnimatorSet.playTogether(collapseAnimator, hideBackgroundAnimator);
  498. } else {
  499. mOpenAnimatorSet.playTogether(expandAnimator);
  500. mCloseAnimatorSet.playTogether(collapseAnimator);
  501. }
  502. mOpenAnimatorSet.setInterpolator(DEFAULT_OPEN_INTERPOLATOR);
  503. mCloseAnimatorSet.setInterpolator(DEFAULT_CLOSE_INTERPOLATOR);
  504. mOpenAnimatorSet.setDuration(duration);
  505. mCloseAnimatorSet.setDuration(duration);
  506. mOpenAnimatorSet.addListener(listener);
  507. mCloseAnimatorSet.addListener(listener);
  508. }
  509. //-- Accessors --//
  510. /**
  511. * Determines whether or not the menu is open.
  512. * @return True if the menu is open, false otherwise.
  513. */
  514. public boolean isOpened() {
  515. return mOpen;
  516. }
  517. /**
  518. * Retrieves the OnMenuToggleListener that is applied to the FloatingActionMenu.
  519. */
  520. public OnMenuToggleListener getOnMenuToggleListener() {
  521. return onMenuToggleListener;
  522. }
  523. /**
  524. * Retrieves the OnMenuItemClickListener that is applied to the FloatingActionMenu.
  525. */
  526. public OnMenuItemClickListener getOnMenuItemClickListener() {
  527. return onMenuItemClickListener;
  528. }
  529. //-- Mutators --//
  530. /**
  531. * Assigns an OnMenuToggleListener to the FloatingActionMenu.
  532. */
  533. public void setOnMenuToggleListener(OnMenuToggleListener onMenuToggleListener) {
  534. this.onMenuToggleListener = onMenuToggleListener;
  535. }
  536. /**
  537. * Assigns an OnMenuItemClickListener to the FloatingActionMenu.
  538. */
  539. public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
  540. this.onMenuItemClickListener = onMenuItemClickListener;
  541. }
  542. /**
  543. * Set as circle(default) or line pattern
  544. */
  545. public void setIsCircle(boolean isCircle) {
  546. this.isCircle = isCircle;
  547. }
  548. /**
  549. * Set the radius of menu, default 256
  550. */
  551. public void setmRadius(int mRadius) {
  552. this.mRadius = mRadius;
  553. }
  554. /**
  555. * Set radius as multiple of width of floating action button
  556. */
  557. public void setMultipleOfFB(float multipleOfFB) {
  558. this.multipleOfFB = multipleOfFB;
  559. }
  560. /**
  561. * Duration of anim, default 300
  562. */
  563. public void setDuration(long duration) {
  564. this.duration = duration;
  565. }
  566. /**
  567. * Only usefully in Line pattern - sets the gap between menu items.
  568. */
  569. public void setmItemGap(int mItemGap) {
  570. this.mItemGap = mItemGap;
  571. }
  572. //-- Misc/Helper methods --//
  573. protected void d(String msg) {
  574. Log.d("FAM", msg == null ? null : msg);
  575. }
  576. //-- Interfaces --//
  577. /**
  578. * Interface that handles a change in the open/close state of the FloatingActionMenu.
  579. */
  580. public interface OnMenuToggleListener {
  581. void onMenuToggle(boolean opened);
  582. }
  583. /**
  584. * Interface that handles the click action of a MenuItem.
  585. */
  586. public interface OnMenuItemClickListener {
  587. void onMenuItemClick(FloatingActionMenu fam, int index, FloatingActionButton item);
  588. }
  589. /**
  590. * Animator that controls the open/close animation of menu items.
  591. */
  592. private class ItemAnimator implements Animator.AnimatorListener {
  593. private View mView;
  594. private boolean playingOpenAnimator;
  595. public ItemAnimator(View v) {
  596. v.animate().setListener(this);
  597. mView = v;
  598. }
  599. public void startOpenAnimator() {
  600. mView.animate().cancel();
  601. playingOpenAnimator = true;
  602. mView.animate()
  603. .translationY(0)
  604. .translationX(0)
  605. .setInterpolator(DEFAULT_OPEN_INTERPOLATOR)
  606. .start();
  607. mMenuButton.animate()
  608. .rotation(135f)
  609. .setInterpolator(DEFAULT_OPEN_INTERPOLATOR)
  610. .start();
  611. }
  612. public void startCloseAnimator() {
  613. mView.animate().cancel();
  614. playingOpenAnimator = false;
  615. mView.animate()
  616. .translationX(mMenuButton.getLeft() - mView.getLeft())
  617. .translationY((mMenuButton.getTop() - mView.getTop()))
  618. .setInterpolator(DEFAULT_CLOSE_INTERPOLATOR)
  619. .start();
  620. mMenuButton.animate()
  621. .rotation(0f)
  622. .setInterpolator(DEFAULT_CLOSE_INTERPOLATOR)
  623. .start();
  624. }
  625. @Override
  626. public void onAnimationStart(Animator animation) {
  627. if (playingOpenAnimator) {
  628. mView.setVisibility(VISIBLE);
  629. } else {
  630. ((TextView) mView.getTag()).setVisibility(GONE);
  631. }
  632. }
  633. @Override
  634. public void onAnimationEnd(Animator animation) {
  635. if (!playingOpenAnimator) {
  636. mView.setVisibility(GONE);
  637. } else {
  638. ((TextView) mView.getTag()).setVisibility(VISIBLE);
  639. }
  640. }
  641. @Override
  642. public void onAnimationCancel(Animator animation) {
  643. }
  644. @Override
  645. public void onAnimationRepeat(Animator animation) {
  646. }
  647. }
  648. }