FloatingActionMenu.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. package terranovaproductions.newcomicreader;
  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. * Created by charry on 2015/6/11. https://gist.github.com/douo/dfde289778a9b3b6918f and modified by Tristan Wiley
  28. */
  29. public class FloatingActionMenu extends ViewGroup {
  30. static final TimeInterpolator DEFAULT_OPEN_INTERPOLATOR = new OvershootInterpolator();
  31. static final TimeInterpolator DEFAULT_CLOSE_INTERPOLATOR = new AnticipateInterpolator();
  32. private FloatingActionButton mMenuButton;
  33. private ArrayList<FloatingActionButton> mMenuItems;
  34. private ArrayList<TextView> mMenuItemLabels;
  35. private ArrayList<ItemAnimator> mMenuItemAnimators;
  36. private AnimatorSet mOpenAnimatorSet = new AnimatorSet();
  37. private AnimatorSet mCloseAnimatorSet = new AnimatorSet();
  38. private ImageView mIcon;
  39. private boolean mOpen;
  40. private boolean animating;
  41. private boolean mIsSetClosedOnTouchOutside = true;
  42. private long duration = 300;
  43. private boolean isCircle = false;
  44. private int mRadius = 256;
  45. private float multipleOfFB = 0;
  46. private int mItemGap = 0;
  47. private OnMenuItemClickListener onMenuItemClickListener;
  48. private OnMenuToggleListener onMenuToggleListener;
  49. GestureDetector mGestureDetector = new GestureDetector(getContext(),
  50. new GestureDetector.SimpleOnGestureListener() {
  51. @Override
  52. public boolean onDown(MotionEvent e) {
  53. return mIsSetClosedOnTouchOutside && isOpened();
  54. }
  55. @Override
  56. public boolean onSingleTapUp(MotionEvent e) {
  57. close();
  58. return true;
  59. }
  60. });
  61. private OnClickListener mOnItemClickListener = new OnClickListener() {
  62. @Override
  63. public void onClick(View v) {
  64. if (v instanceof FloatingActionButton) {
  65. int i = mMenuItems.indexOf(v);
  66. if (onMenuItemClickListener != null) {
  67. onMenuItemClickListener
  68. .onMenuItemClick(FloatingActionMenu.this, i, (FloatingActionButton) v);
  69. }
  70. } else if (v instanceof TextView) {
  71. int i = mMenuItemLabels.indexOf(v);
  72. if (onMenuItemClickListener != null) {
  73. onMenuItemClickListener.onMenuItemClick(FloatingActionMenu.this, i, mMenuItems.get(i));
  74. }
  75. }
  76. close();
  77. }
  78. };
  79. public FloatingActionMenu(Context context) {
  80. this(context, null, 0);
  81. }
  82. public FloatingActionMenu(Context context, AttributeSet attrs) {
  83. this(context, attrs, 0);
  84. }
  85. public FloatingActionMenu(Context context, AttributeSet attrs, int defStyleAttr) {
  86. super(context, attrs, defStyleAttr);
  87. mMenuItems = new ArrayList<>(5);
  88. mMenuItemAnimators = new ArrayList<>(5);
  89. mMenuItemLabels = new ArrayList<>(5);
  90. mIcon = new ImageView(context);
  91. }
  92. @Override
  93. protected void onFinishInflate() {
  94. bringChildToFront(mMenuButton);
  95. bringChildToFront(mIcon);
  96. super.onFinishInflate();
  97. }
  98. @Override
  99. public void addView(@NonNull View child, int index, LayoutParams params) {
  100. super.addView(child, index, params);
  101. if (getChildCount() > 1) {
  102. if (child instanceof FloatingActionButton) {
  103. addMenuItem((FloatingActionButton) child);
  104. }
  105. } else {
  106. mMenuButton = (FloatingActionButton) child;
  107. mIcon.setImageDrawable(mMenuButton.getDrawable());
  108. addView(mIcon);
  109. mMenuButton.setImageDrawable(mMenuButton.getDrawable());
  110. createDefaultIconAnimation();
  111. mMenuButton.setOnClickListener(new OnClickListener() {
  112. @Override
  113. public void onClick(View v) {
  114. toggle();
  115. }
  116. });
  117. }
  118. }
  119. public void toggle() {
  120. if (!mOpen) {
  121. open();
  122. } else {
  123. close();
  124. }
  125. }
  126. public void open() {
  127. d("open");
  128. startOpenAnimator();
  129. mOpen = true;
  130. if (onMenuToggleListener != null) {
  131. onMenuToggleListener.onMenuToggle(true);
  132. }
  133. }
  134. public void close() {
  135. startCloseAnimator();
  136. mOpen = false;
  137. if (onMenuToggleListener != null) {
  138. onMenuToggleListener.onMenuToggle(true);
  139. }
  140. }
  141. protected void startCloseAnimator() {
  142. mCloseAnimatorSet.start();
  143. for (ItemAnimator anim : mMenuItemAnimators) {
  144. anim.startCloseAnimator();
  145. }
  146. }
  147. protected void startOpenAnimator() {
  148. mOpenAnimatorSet.start();
  149. for (ItemAnimator anim : mMenuItemAnimators) {
  150. anim.startOpenAnimator();
  151. }
  152. }
  153. public void addMenuItem(FloatingActionButton item) {
  154. mMenuItems.add(item);
  155. mMenuItemAnimators.add(new ItemAnimator(item));
  156. TextView button = new TextView(getContext());
  157. LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  158. button.setLayoutParams(params);
  159. button.setBackgroundResource(R.drawable.rounded_corners);
  160. button.setTextColor(Color.WHITE);
  161. button.setText(item.getContentDescription());
  162. Integer paddingSize = (int)button.getTextSize() / 3;
  163. button.setPadding(paddingSize, paddingSize, paddingSize, paddingSize);
  164. addView(button);
  165. mMenuItemLabels.add(button);
  166. item.setTag(button);
  167. item.setOnClickListener(mOnItemClickListener);
  168. button.setOnClickListener(mOnItemClickListener);
  169. }
  170. @Override
  171. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  172. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  173. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  174. int width;
  175. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  176. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  177. int height;
  178. final int count = getChildCount();
  179. int maxChildWidth = 0;
  180. for (int i = 0; i < count; i++) {
  181. View child = getChildAt(i);
  182. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  183. }
  184. for (int i = 0; i < mMenuItems.size(); i++) {
  185. FloatingActionButton fab = mMenuItems.get(i);
  186. TextView label = mMenuItemLabels.get(i);
  187. maxChildWidth = Math.max(maxChildWidth,
  188. label.getMeasuredWidth() + fab.getMeasuredWidth());
  189. }
  190. maxChildWidth = Math.max(mMenuButton.getMeasuredWidth(), maxChildWidth);
  191. if (widthMode == MeasureSpec.EXACTLY) {
  192. width = widthSize;
  193. } else {
  194. width = maxChildWidth + 30;
  195. }
  196. if (heightMode == MeasureSpec.EXACTLY) {
  197. height = heightSize;
  198. } else {
  199. int heightSum = 0;
  200. for (int i = 0; i < count; i++) {
  201. View child = getChildAt(i);
  202. heightSum += child.getMeasuredHeight();
  203. }
  204. height = heightSum + 20;
  205. }
  206. setMeasuredDimension(resolveSize(width, widthMeasureSpec),
  207. resolveSize(height, heightMeasureSpec));
  208. }
  209. @Override
  210. public boolean onTouchEvent(@NonNull MotionEvent event) {
  211. if (mIsSetClosedOnTouchOutside) {
  212. return mGestureDetector.onTouchEvent(event);
  213. } else {
  214. return super.onTouchEvent(event);
  215. }
  216. }
  217. @Override
  218. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  219. System.out.println("onLayout:" + changed);
  220. if (changed) {
  221. int right = r - getPaddingRight();
  222. int bottom = b - getPaddingBottom();
  223. int top = bottom - mMenuButton.getMeasuredHeight();
  224. mMenuButton.layout(right - mMenuButton.getMeasuredWidth(), top, right, bottom);
  225. int dw = (mMenuButton.getMeasuredWidth() - mIcon.getMeasuredWidth()) / 2;
  226. int dh = (mMenuButton.getMeasuredHeight() - mIcon.getMeasuredHeight()) / 2;
  227. mIcon.layout(right - mIcon.getMeasuredWidth() - dw,
  228. bottom - mIcon.getMeasuredHeight() - dh, right - dw, bottom - dh);
  229. if (isCircle) {
  230. if (mMenuItems.size() < 2) {
  231. Log.e("onLayout", "Floating Action Buttons must more then one!");
  232. return;
  233. }
  234. double angle = Math.PI/2d/(mMenuItems.size() - 1);
  235. for (int i = 0; i < mMenuItems.size(); i++) {
  236. FloatingActionButton itemFB = mMenuItems.get(i);
  237. int fbWidth = itemFB.getMeasuredWidth();
  238. int fbHeight = itemFB.getMeasuredHeight();
  239. if (0 != multipleOfFB) {
  240. mRadius = (int) (fbWidth * multipleOfFB);
  241. }
  242. int itemDw = (mMenuButton.getMeasuredWidth() - fbWidth) / 2;
  243. int itemDh = (mMenuButton.getMeasuredHeight() - fbHeight) / 2;
  244. int itemX = (int) (mRadius*Math.cos(i*angle));
  245. int itemY = (int) (mRadius*Math.sin(i*angle));
  246. itemFB.layout(right - itemX - fbWidth - itemDw, bottom - itemY - fbHeight - itemDh,
  247. right - itemX - itemDw, bottom - itemY - itemDh);
  248. if (!animating) {
  249. if (!mOpen) {
  250. itemFB.setTranslationY(mMenuButton.getTop() - itemFB.getTop());
  251. itemFB.setTranslationX(mMenuButton.getLeft() - itemFB.getLeft());
  252. itemFB.setVisibility(GONE);
  253. } else {
  254. itemFB.setTranslationY(0);
  255. itemFB.setTranslationX(0);
  256. itemFB.setVisibility(VISIBLE);
  257. }
  258. }
  259. }
  260. } else {
  261. for (int i = 0; i < mMenuItems.size(); i++) {
  262. FloatingActionButton item = mMenuItems.get(i);
  263. TextView label = mMenuItemLabels.get(i);
  264. label.setBackgroundResource(R.drawable.rounded_corners);
  265. bottom = top -= mItemGap;
  266. top -= item.getMeasuredHeight();
  267. int width = item.getMeasuredWidth();
  268. int d = (mMenuButton.getMeasuredWidth() - width) / 2;
  269. item.layout(right - width - d, top, right - d, bottom);
  270. d = (item.getMeasuredHeight() - label.getMeasuredHeight()) / 2;
  271. label.layout(item.getLeft() - label.getMeasuredWidth() - 50,
  272. item.getTop() + d, item.getLeft(),
  273. item.getTop() + d + label.getMeasuredHeight());
  274. if (!animating) {
  275. if (!mOpen) {
  276. item.setTranslationY(mMenuButton.getTop() - item.getTop());
  277. item.setVisibility(GONE);
  278. label.setVisibility(GONE);
  279. } else {
  280. item.setTranslationY(0);
  281. item.setVisibility(VISIBLE);
  282. label.setVisibility(VISIBLE);
  283. }
  284. }
  285. }
  286. }
  287. if (!animating && getBackground() != null) {
  288. if (!mOpen) {
  289. getBackground().setAlpha(0);
  290. } else {
  291. getBackground().setAlpha(0xff);
  292. }
  293. }
  294. }
  295. }
  296. private void createDefaultIconAnimation() {
  297. Animator.AnimatorListener listener = new Animator.AnimatorListener() {
  298. @Override
  299. public void onAnimationStart(Animator animation) {
  300. animating = true;
  301. }
  302. @Override
  303. public void onAnimationEnd(Animator animation) {
  304. animating = false;
  305. }
  306. @Override
  307. public void onAnimationCancel(Animator animation) {
  308. animating = false;
  309. }
  310. @Override
  311. public void onAnimationRepeat(Animator animation) {
  312. }
  313. };
  314. ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(
  315. mIcon,
  316. "rotation",
  317. 135f,
  318. 0f
  319. );
  320. ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(
  321. mIcon,
  322. "rotation",
  323. 0f,
  324. 135f
  325. );
  326. if (getBackground() != null) {
  327. ValueAnimator hideBackgroundAnimator = ObjectAnimator.ofInt(0xff, 0);
  328. hideBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  329. @Override
  330. public void onAnimationUpdate(ValueAnimator animation) {
  331. Integer alpha = (Integer) animation.getAnimatedValue();
  332. //System.out.println(alpha);
  333. getBackground().setAlpha(alpha > 0xff ? 0xff : alpha);
  334. }
  335. });
  336. ValueAnimator showBackgroundAnimator = ObjectAnimator.ofInt(0, 0xff);
  337. showBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  338. @Override
  339. public void onAnimationUpdate(ValueAnimator animation) {
  340. Integer alpha = (Integer) animation.getAnimatedValue();
  341. //System.out.println(alpha);
  342. getBackground().setAlpha(alpha > 0xff ? 0xff : alpha);
  343. }
  344. });
  345. mOpenAnimatorSet.playTogether(expandAnimator, showBackgroundAnimator);
  346. mCloseAnimatorSet.playTogether(collapseAnimator, hideBackgroundAnimator);
  347. } else {
  348. mOpenAnimatorSet.playTogether(expandAnimator);
  349. mCloseAnimatorSet.playTogether(collapseAnimator);
  350. }
  351. mOpenAnimatorSet.setInterpolator(DEFAULT_OPEN_INTERPOLATOR);
  352. mCloseAnimatorSet.setInterpolator(DEFAULT_CLOSE_INTERPOLATOR);
  353. mOpenAnimatorSet.setDuration(duration);
  354. mCloseAnimatorSet.setDuration(duration);
  355. mOpenAnimatorSet.addListener(listener);
  356. mCloseAnimatorSet.addListener(listener);
  357. }
  358. public boolean isOpened() {
  359. return mOpen;
  360. }
  361. @Override
  362. public Parcelable onSaveInstanceState() {
  363. d("onSaveInstanceState");
  364. Bundle bundle = new Bundle();
  365. bundle.putParcelable("instanceState", super.onSaveInstanceState());
  366. bundle.putBoolean("mOpen", mOpen);
  367. // ... save everything
  368. return bundle;
  369. }
  370. @Override
  371. public void onRestoreInstanceState(Parcelable state) {
  372. d("onRestoreInstanceState");
  373. if (state instanceof Bundle) {
  374. Bundle bundle = (Bundle) state;
  375. mOpen = bundle.getBoolean("mOpen");
  376. // ... load everything
  377. state = bundle.getParcelable("instanceState");
  378. }
  379. super.onRestoreInstanceState(state);
  380. }
  381. @Override
  382. protected void onDetachedFromWindow() {
  383. d("onDetachedFromWindow");
  384. //getBackground().setAlpha(bgAlpha);//reset default alpha
  385. super.onDetachedFromWindow();
  386. }
  387. @Override
  388. public void setBackground(Drawable background) {
  389. if (background instanceof ColorDrawable) {
  390. // after activity finish and relaucher , background drawable state still remain?
  391. int bgAlpha = Color.alpha(((ColorDrawable) background).getColor());
  392. d("bg:" + Integer.toHexString(bgAlpha));
  393. super.setBackground(background);
  394. } else {
  395. throw new IllegalArgumentException("floating only support color background");
  396. }
  397. }
  398. public OnMenuToggleListener getOnMenuToggleListener() {
  399. return onMenuToggleListener;
  400. }
  401. public void setOnMenuToggleListener(OnMenuToggleListener onMenuToggleListener) {
  402. this.onMenuToggleListener = onMenuToggleListener;
  403. }
  404. public OnMenuItemClickListener getOnMenuItemClickListener() {
  405. return onMenuItemClickListener;
  406. }
  407. public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
  408. this.onMenuItemClickListener = onMenuItemClickListener;
  409. }
  410. protected void d(String msg) {
  411. Log.d("FAM", msg == null ? null : msg);
  412. }
  413. public interface OnMenuToggleListener {
  414. void onMenuToggle(boolean opened);
  415. }
  416. public interface OnMenuItemClickListener {
  417. void onMenuItemClick(FloatingActionMenu fam, int index, FloatingActionButton item);
  418. }
  419. private class ItemAnimator implements Animator.AnimatorListener {
  420. private View mView;
  421. private boolean playingOpenAnimator;
  422. public ItemAnimator(View v) {
  423. v.animate().setListener(this);
  424. mView = v;
  425. }
  426. public void startOpenAnimator() {
  427. mView.animate().cancel();
  428. playingOpenAnimator = true;
  429. mView.animate()
  430. .translationY(0)
  431. .translationX(0)
  432. .setInterpolator(DEFAULT_OPEN_INTERPOLATOR)
  433. .start();
  434. mMenuButton.animate()
  435. .rotation(135f)
  436. .setInterpolator(DEFAULT_OPEN_INTERPOLATOR)
  437. .start();
  438. }
  439. public void startCloseAnimator() {
  440. mView.animate().cancel();
  441. playingOpenAnimator = false;
  442. mView.animate()
  443. .translationX(mMenuButton.getLeft() - mView.getLeft())
  444. .translationY((mMenuButton.getTop() - mView.getTop()))
  445. .setInterpolator(DEFAULT_CLOSE_INTERPOLATOR)
  446. .start();
  447. mMenuButton.animate()
  448. .rotation(0f)
  449. .setInterpolator(DEFAULT_CLOSE_INTERPOLATOR)
  450. .start();
  451. }
  452. @Override
  453. public void onAnimationStart(Animator animation) {
  454. if (playingOpenAnimator) {
  455. mView.setVisibility(VISIBLE);
  456. } else {
  457. ((TextView) mView.getTag()).setVisibility(GONE);
  458. }
  459. }
  460. @Override
  461. public void onAnimationEnd(Animator animation) {
  462. if (!playingOpenAnimator) {
  463. mView.setVisibility(GONE);
  464. } else {
  465. ((TextView) mView.getTag()).setVisibility(VISIBLE);
  466. }
  467. }
  468. @Override
  469. public void onAnimationCancel(Animator animation) {
  470. }
  471. @Override
  472. public void onAnimationRepeat(Animator animation) {
  473. }
  474. }
  475. /**
  476. * set as circle(default) or line pattern
  477. * @param isCircle
  478. */
  479. public void setIsCircle(boolean isCircle) {
  480. this.isCircle = isCircle;
  481. }
  482. /**
  483. * set the radius of menu, default 256
  484. * @param mRadius
  485. */
  486. public void setmRadius(int mRadius) {
  487. this.mRadius = mRadius;
  488. }
  489. /**
  490. * set radius as multiple of width of floating action button
  491. * @param multipleOfFB
  492. */
  493. public void setMultipleOfFB(float multipleOfFB) {
  494. this.multipleOfFB = multipleOfFB;
  495. }
  496. /**
  497. * duration of anim, default 300
  498. * @param duration
  499. */
  500. public void setDuration(long duration) {
  501. this.duration = duration;
  502. }
  503. /**
  504. * Only usefully in Line pattern
  505. * @param mItemGap
  506. */
  507. public void setmItemGap(int mItemGap) {
  508. this.mItemGap = mItemGap;
  509. }
  510. }