FloatingActionMenu.java 18 KB

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