Androidでfacebookのアプリとかである横からうにゅって出てくるメニューのソースコードを読む
android-sliding-menu-demoというライブラリでボタンを押した時に横からメニューがうにゅって出てくる奴が作れます
https://github.com/gitgrimbo/android-sliding-menu-demo
こういうのを普通に作れるようになりたいのでソースコードを読もうと思います
HorzScrollWithListMenuクラスのonCreate
AndroidといったらActivityにあるonCreateが最初に呼ばれるのが有名です
ということでそこから読んでいきます
inflaterはxmlのレイアウトファイルからViewを生成するときに使います。
最初からたくさんレイアウトファイルを読み込みまくってるせいでややこしいです
HorzScrollWithListMenuクラスのonCreate内
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater inflater = LayoutInflater.from(this); //MyHorizontalScrollViewの中にLinearLayout(idはtop)がある scrollView = (MyHorizontalScrollView) inflater.inflate(R.layout.horz_scroll_with_list_menu, null); setContentView(scrollView); //TextViewとListViewがあるlinearレイアウト(メニューの部分) menu = inflater.inflate(R.layout.horz_scroll_menu, null); //width heightを1dbにしてあるメインコンテンツとして表示されている内容 app = inflater.inflate(R.layout.horz_scroll_app, null); ViewGroup tabBar = (ViewGroup) app.findViewById(R.id.tabBar); ListView listView = (ListView) app.findViewById(R.id.list); ViewUtils.initListView(this, listView, "Item ", 30, android.R.layout.simple_list_item_1); listView = (ListView) menu.findViewById(R.id.list); ViewUtils.initListView(this, listView, "Menu ", 30, android.R.layout.simple_list_item_1); btnSlide = (ImageView) tabBar.findViewById(R.id.BtnSlide); btnSlide.setOnClickListener(new ClickListenerForScrolling(scrollView, menu)); final View[] children = new View[] { menu, app }; // Scroll to app (view[1]) when layout finished. int scrollToViewIdx = 1; scrollView.initViews(children, scrollToViewIdx, new SizeCallbackForMenu(btnSlide)); }
流れはこうです
scrollViewっていうレイアウトのViewを作る。
appとmenuっていうレイアウトのViewを作る。
↓
appとmenuにあるListViewにデータを入れる
↓
スライドボタンのクリックリスナを設定
↓
View[] childrenにmenuとappを入れる
↓
scrollView.initViews(children,childrenの中でどのViewに移動させるか?,new スライドした時にViewの横の長さだけ残すメソッドを実装したクラス(横にスライドしていった時に残るボタン));
つまりinitViewsメソッドとスライドボタンが重要!
MyHorizontalScrollViewクラスのinitViews
まずは、
MyHorizontalScrollView.initViewsメソッドを見ていきましょう
/** * @param children * The child Views to add to parent. * @param scrollToViewIdx * The index of the View to scroll to after initialisation. * @param sizeCallback * A SizeCallback to interact with the HSV. */ public void initViews(View[] children, int scrollToViewIdx, SizeCallback sizeCallback) { // A ViewGroup MUST be the only child of the HSV ViewGroup parent = (ViewGroup) getChildAt(0); // Add all the children, but add them invisible so that the layouts are calculated, but you can't see the Views for (int i = 0; i < children.length; i++) { children[i].setVisibility(View.INVISIBLE); parent.addView(children[i]); } // Add a layout listener to this HSV // This listener is responsible for arranging the child views. OnGlobalLayoutListener listener = new MyOnGlobalLayoutListener(parent, children, scrollToViewIdx, sizeCallback); getViewTreeObserver().addOnGlobalLayoutListener(listener); }
このMyHorizontalScrollViewのレイアウトのxml
<?xml version="1.0" encoding="utf-8"?> <grimbo.android.demo.slidingmenu.MyHorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_margin="0px" android:background="#00ffffff" android:fadingEdge="none" android:fadingEdgeLength="0px" android:padding="0px" android:scrollbars="none" > <LinearLayout android:id="@+id/top" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_margin="0px" android:background="#ffffffff" android:orientation="horizontal" android:padding="0px" > </LinearLayout> </grimbo.android.demo.slidingmenu.MyHorizontalScrollView>
つまりgetChildAtメソッドでLinearLayoutのViewを取っていてその中にchildrenで受け取ったViewであるappとmenuを追加しています
その後に getViewTreeObserver().addOnGlobalLayoutListener(listener);しています
これは
http://blog.justoneplanet.info/2012/01/15/android%E3%81%A7%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E5%BE%8C%E3%81%AEview%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B/
で紹介されている通り、レンダリング後のWidthなどをとることができるというものです
つまりレンダリングされた後にlistenerにあるメソッドが呼ばれるということだと考えられます
MyHorizontalScrollView.MyOnGlobalLayoutListenerクラスのonGlobalLayoutメソッド
@Override public void onGlobalLayout() { // System.out.println("onGlobalLayout"); final HorizontalScrollView me = MyHorizontalScrollView.this; // The listener will remove itself as a layout listener to the HSV me.getViewTreeObserver().removeGlobalOnLayoutListener(this); // Allow the SizeCallback to 'see' the Views before we remove them and re-add them. // This lets the SizeCallback prepare View sizes, ahead of calls to SizeCallback.getViewSize(). sizeCallback.onGlobalLayout(); parent.removeViewsInLayout(0, children.length); final int w = me.getMeasuredWidth(); final int h = me.getMeasuredHeight(); // System.out.println("w=" + w + ", h=" + h); // Add each view in turn, and apply the width and height returned by the SizeCallback. int[] dims = new int[2]; scrollToViewPos = 0; for (int i = 0; i < children.length; i++) { sizeCallback.getViewSize(i, w, h, dims); // System.out.println("addView w=" + dims[0] + ", h=" + dims[1]); children[i].setVisibility(View.VISIBLE); parent.addView(children[i], dims[0], dims[1]); if (i < scrollToViewIdx) { scrollToViewPos += dims[0]; } } // For some reason we need to post this action, rather than call immediately. // If we try immediately, it will not scroll. new Handler().post(new Runnable() { @Override public void run() { me.scrollBy(scrollToViewPos, 0); } }); }
流れはこうです
次にレンダリングされるときにはこのメソッドが呼ばれないようにする
↓
widthとheightを取得
↓
そのwidthとheightからsizeCallBackでViewのサイズを取得する
↓
parent.addView(view,width,height);
でparentにViewを追加している
この引数3つのaddViewはこんな説明が書いてある
http://developer.android.com/reference/android/widget/LinearLayout.html
addView(View child, int width, int height)
Adds a child view with this ViewGroup's default layout parameters and the specified width and height.
addViewメソッドの呼び出しを消して実行したりVisibilityをtrueにしたり色々して分かったのは、
HorizontalScrollViewのようなときは追加するときに大きさがないと表示されなくて、
その追加する大きさを取得するためには一回レンダリングしなくてはいけない。だから一回目はINVISIBLEでとりあえずレンダリングさせ、レンダリング結果で大きさを指定してVISIBLEでaddViewで追加している
ということです
続きですが
移動先までViewの横の長さを足していき
そこに移動しています。
だから右に移動していてデフォルトではメニューは見えなくなっていてボタンを押したら表示されます
initViewsの中の追いかけはここで終わりで、どうやってあの形を作っているのか分かりました
ClickListenerForScrollingクラスのonClickメソッド
次はどうやってボタンを押したら移動するのか見ていこう
ClickListenerForScrolling.onClick
@Override public void onClick(View v) { Context context = menu.getContext(); String msg = "Slide " + new Date(); Toast.makeText(context, msg, 1000).show(); System.out.println(msg); int menuWidth = menu.getMeasuredWidth(); // Ensure menu is visible menu.setVisibility(View.VISIBLE); if (!menuOut) { // Scroll to 0 to reveal menu int left = 0; scrollView.smoothScrollTo(left, 0); } else { // Scroll to menuWidth so menu isn't on screen. int left = menuWidth; scrollView.smoothScrollTo(left, 0); } menuOut = !menuOut; }
流れはこうです
メニューの横の長さを取得
↓
menuをVISIBLEに念のためにする
↓
menuOutのtruefalseによってsmoothScrollToでスクロールする
ということです。
時間はかかりましたし、途中までなんでこんなソースコードなのか理解に苦しみましたが読んでよかったです。
今度似たようなライブラリを作成したいです。
takam