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