お久しぶりです。アライドのザックこと高畑です。
以前からかっこいいなぁと思っていたPathの「ヒュン!ヒュン!ヒュン!」って飛び出るのメニュー部分をAndroidで実装してみたいと思っていたので、今回このブログを書くにあたって
良い機会なので、それっぽく動くように頑張って実装してみました。
とりあえず、完成系はこんな感じになりました。
Pathっぽいメニューのレイアウトを用意
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 |
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_gravity="center_vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/menu1" android:src="@drawable/icon_reflection_none_red" android:layout_gravity="center"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/menu2" android:src="@drawable/icon_reflection_arrow_red" android:layout_gravity="center"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/menu3" android:src="@drawable/icon_reflection_exclamation_red" android:layout_gravity="center"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/menu4" android:src="@drawable/icon_reflection_home_red" android:layout_gravity="center"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/menu5" android:src="@drawable/icon_reflection_heart_red" android:layout_gravity="center"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/menuBase" android:background="@drawable/icon_reflection_home_red" android:layout_gravity="center"/> </FrameLayout> |
レイアウトプレビューで見るとこんな感じです。
ベースとなるボタンの下に、メニューとなるボタンを5個重ねています。
下に重なっているメニュー画像達
PathっぽいメニューのFragmentのソースコード
このFragmentをActivityに追加するだけで動くようになります。
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
public class MenuFragment extends Fragment { private List<ImageView> viewList; //メニューとなるのImageViewを格納 private boolean isOpenMenu = false;//メニューが開いているかどうか判定用 private final int RADIUS = 100; //メニューが開いたときの半径の長さ private final int DEGREE = 180; //メニューが開く角度 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.path, container, false); //適当に各メニューにクリック処理追加 ImageView menu1 = (ImageView) v.findViewById(R.id.menu1); menu1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getActivity(), "メニュー1押した", Toast.LENGTH_SHORT).show(); } }); ImageView menu2 = (ImageView) v.findViewById(R.id.menu2); menu2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getActivity(), "メニュー2押した", Toast.LENGTH_SHORT).show(); } }); ImageView menu3 = (ImageView) v.findViewById(R.id.menu3); menu3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getActivity(), "メニュー3押した", Toast.LENGTH_SHORT).show(); } }); ImageView menu4 = (ImageView) v.findViewById(R.id.menu4); menu4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getActivity(), "メニュー4押した", Toast.LENGTH_SHORT).show(); } }); ImageView menu5 = (ImageView) v.findViewById(R.id.menu5); menu5.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getActivity(), "メニュー5押した", Toast.LENGTH_SHORT).show(); } }); viewList = new ArrayList<ImageView>(); viewList.add(menu1); viewList.add(menu2); viewList.add(menu3); viewList.add(menu4); viewList.add(menu5); //ベースとなるボタンを押したら、アニメーション開始する v.findViewById(R.id.menuBase).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (!isOpenMenu) { openAnimation(); } else { closeAnimation(); } isOpenMenu = !isOpenMenu; } }); return v; } //全体の角度から1つのメニュー同士の間の角度を取得 public float getDegree() { return DEGREE / (viewList.size() - 1); } //角度と半径からx軸方向にどれだけ移動するか取得 public int getTranslateX(float degree) { return (int) (RADIUS * Math.cos(Math.toRadians(degree))); } //角度と半径からy軸方向にどれだけ移動するか取得 public int getTranslateY(float degree) { return (int) (RADIUS * Math.sin(Math.toRadians(degree))); } //メニューをオープンするメソッド public void openAnimation() { for (int i = 0; i < viewList.size(); i++) { //アニメーションで移動する分だけマージンを取る setMargin(i); //メニューが開くアニメーションを設定。 TranslateAnimation openAnimation = new TranslateAnimation(-getTranslateX(getDegree() * i), 0, getTranslateY(getDegree() * i), 0); openAnimation.setDuration(500); openAnimation.setStartOffset(100 * i); AnticipateOvershootInterpolator overshootInterpolator = new AnticipateOvershootInterpolator(2); openAnimation.setInterpolator(overshootInterpolator); viewList.get(i).startAnimation(openAnimation); } } //メニューをクローズするアニメーション public void closeAnimation() { for (int i = 0; i < viewList.size(); i++) { //マージンを元に戻す resetMargin(i); //メニューが閉じるアニメーションを設定する TranslateAnimation closeAnimation = new TranslateAnimation(getTranslateX(getDegree() * i), 0, -getTranslateY(getDegree() * i), 0); closeAnimation.setDuration(300); closeAnimation.setStartOffset(100 * i); viewList.get(i).startAnimation(closeAnimation); } } //アニメーション後の座標までマージンを取るメソッド public void setMargin(int index) { FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) viewList.get(index).getLayoutParams(); params.bottomMargin = getTranslateY(getDegree() * index); params.leftMargin = getTranslateX(getDegree() * index); viewList.get(index).setLayoutParams(params); viewList.get(index).setVisibility(View.VISIBLE); } //マージンを元に戻すメソッド public void resetMargin(int index) { FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) viewList.get(index).getLayoutParams(); params.bottomMargin = 0; params.leftMargin = 0; viewList.get(index).setLayoutParams(params); } } |
やっていることは
開くアニメーションが走る前に、先に各メニューを移動先までマージンを取る
開くアニメーションが走るときに、各メニューはすでにマージンを取った後なので
アニメーションのfromXとfromYの位置をマージンを取る前の位置にしてアニメーションを実行する
という流れになっています。
閉じるアニメーションも同様に、先にマージンを元の位置に戻してから
アニメーションを実行しています。
先にマージンを取ってからアニメーションを実行しているのは
アニメーション後にマージンを設定すると、どうしても画像がチラついたり
アニメーション完了後の位置からさらにマージンを取って変な位置にメニューが表示されたり
色々と問題があったので、先にマージン取ってからアニメーションするようにしました。
実行してみる
上記で作成したFragmentをActivityに貼付けて実行してメニューを開くと
この様に広がります
開く角度を180度にした場合
180度にメニューが広がります
開く角度を270度にして、メニューの個数を4個にした場合
十字に広がってかっこいいです
最後に・・・
今回のサンプルのアニメーションではTranslateするだけですが
これにRotateやAlphaのアニメーションなどを追加すれば、さらにかっこ良くなります。
機会があればどこかで使ってみたいです。
今回のソースコードをgithubに上げておきました。
iOSに浮気を始めたAndroidエンジニア? Androidはほとんど書いてない…