Sencha Advent Calendar 2014 – 12月5日 スクロールで隠れるツールバー的なもの

こんにちは、一昨日から胃腸炎で満身創痍な tnker です。

ちょっとCordova関連でガッツリ書こうと思ってましたが、準備が間に合ってないのでまた部品的なエントリーを書きます ;(

今度はこんなやつ

このブラウザ上部のURIバーみたいな感じのものをプラグイン形式で作ってみたいと思うよ!

※ 細かい挙動は異なるよ!

前提条件

  • 隠したり表示したりするツールバーは docked: top の1つのみ
  • ツールバーが縮小するんじゃなくて単純に隠れるだけの挙動

ということで作っていく

スケルトンのプロジェクトの作り方は前回のエントリーに書いてあるので、そっちを見てね! :)

Sencha Advent Calendar 2014 – 12月3日 比較的軽めのドロワーメニューを作ってみよう
http://blog.tnker.com/114

空っぽのプラグイン作る

下記のような感じでプラグイン用のJSファイル作ります。

app/
├ controller/
├ store/
├ model/
... 中略
├ view/
└ plugin/
    └ ToolbarAutoHide.js <- New!!

で、とりあえずプラグインとして使うために次のような定義を入れておきます。

app/plugin/ToolbarAutoHide.js

Ext.define('App.plugin.ToolbarAutoHide', {
    alias: 'plugin.toolbarautohide',
    init: function(c) {
        console.log(c);
    }
});

これでプラグインできました! :-)

メインビューにプラグインを設定する

何もしれくれないけど、プラグインは出来たのでメインビューに設定だけしておきましょう。

app/view/Main.js

Ext.define('App.view.Main', {
    extend: 'Ext.tab.Panel',
    xtype: 'main',
    requires: [
        'Ext.TitleBar',
        'Ext.Video',
        'App.plugin.ToolbarAutoHide' <- NEW!!
    ],
    config: {
        tabBarPosition: 'bottom',
        items: [{
            title: 'Welcome',
            iconCls: 'home',
            styleHtmlContent: true,
            scrollable: true,
            plugins: [{ <- NEW!!
                xclass: 'App.plugin.ToolbarAutoHide'
            }],
            // 中略
        }, {
            // 中略
        }]
    }
});

こんな感じで requires への登録と、items 内の1つ目のコンテナにプラグインの設定を追加してあげるだけでOKです。

※ NEW!! って書いてあるところです

プラグイン側で alias 指定してるので ptype で行きたいところですが、現在非推奨になっているみたいなので、xclass 使って設定をしています。

これを追加してもらったらブラウザでスケルトンのアプリにアクセスしてみてコンソール上に

こんな感じの出力出てれば多分OKです。

pluginとして設定を行うとプラグイン側の init メソッドが必ず実行され、引数にプラグインを設定している対象のコンポーネントの参照が渡ってくるので、その参照を使いつつ振る舞いの設定をしていきます。

実際の処理を実装していく

今回利用する側から設定することを考慮した実装にはしていませんが、configプロパティの定義をしておけば、利用する側からプラグインのconfigを直接設定することも可能です

今回で言う所のMain.js

ということで、プラグイン側で保持しておきたいプロパティと init 内の処理は次のような感じに

Ext.define('App.plugin.ToolbarAutoHide', {
    alias: 'plugin.toolbarautohide',
    config: {
        cmp: null,
        bar: null
    },
    init: function(c) {
        var me = this, items, bar;
        if (c) {
            me.setCmp(c);
            items = c.getDockedItems();
            bar = Ext.Array.filter(items, function(item) {
                return item.getDocked() === 'top';
            });
            bar = bar.length ? bar[0] : null;
            me.events();
        }
        if (bar) {
            me.setBar(bar);
            bar.setConfig({
                zIndex  : 100,
                width   : '100%',
                style   : {
                    'position'  : 'absolute',
                    'opacity'   : '0.9'
                }
            });
            c.setConfig({
                padding: '50 0 0 0'
            });
        }
    }
});

init メソッド内の前半のif文では、設定されたコンテナをcmpプロパティに保持し、pluginが設定された対象のコンテナに利用されている docked: top のコンポーネントを取得、pluginにこれから実装するevents メソッドを実行しています。

この実装だと docked: top されていれば、ツールバーじゃなくても引っ張れてきちゃいますので、実際にちゃんとやる場合はxtypeとか見ましょう

後半のif文では、docked: top のコンポーネントがあればbarプロパティに保持し、それぞれのコンポーネントにこのプラグインを反映する際のスタイルなどの微調整を行っています。

上記のコードで記載されていたeventsメソッド

eventsメソッド内で行っているは、対象のコンテナに設定されたスクロールクラスのインスタンスを取得し、これから処理を挟み込むためのリスナーをそれぞれに登録します。

Ext.define('App.plugin.ToolbarAutoHide', {
    // 中略
    init: function(c) {
        // 中略
    },
    events: function() {
        var me  = this,
            cmp = me.getCmp(),
            scl = cmp.getScrollable().getScroller();
        scl.on({
            scrollstart : me.onStart,
            scroll      : me.onScroll,
            scrollend   : me.onEnd,
            scope       : me
        });
    }
});
  • scrollstart
  • scroll
  • scrollend
  • それぞれリスナーに登録しているメソッドのシグネチャは同じで
    • 第1引数:Ext.scroll.Scrollerのインスタンス
    • 第2引数:スクロール開始時のx値
    • 第3引数:スクロール開始時のy値

になります。

なので、events メソッド内で登録しているメソッドを一通り用意すると

Ext.define('App.plugin.ToolbarAutoHide', {
    // 中略
    init: function(c) {
        // 中略
    },
    events: function() {
        // 中略
        scl.on({
            scrollstart : me.onStart,
            scroll      : me.onScroll,
            scrollend   : me.onEnd,
            scope       : me
        });
    },
    onStart: function(c, x, y) {
    },
    onScroll: function(c, x, y) {
    },
    onEnd: function(c, x, y) {
    }
});

こんな感じになります。

あとはスクロールの移動量算出時にスクロール開始地点の座標の保持をしておきたいので、プラグインに _startPoint プロパティを1個用意しておきます。

Ext.define('App.plugin.ToolbarAutoHide', {
    alias: 'plugin.toolbarautohide',
    config: {
        cmp: null,
        bar: null
    },
    _startPoint: {
        x: 0,
        y: 0
    },
    init: function(c) {
        // 中略
    },
    // 中略
});

ここまで出来たらあとは計算するだけ

あとはスクロール開始時に座標を保持し、それを元にスクロールイベント内でオフセット値の計算を行い、一番最初に configプロパティに保持していた barプロパティ からツールバーを取得して、いい感じにずらしてあげれば完成です。

※ 一時的な変数の保持をelementにやらせてしまってますが(el.translateY)、これはやらないほうが良いです。(実際はtranslate3Dの値をちゃんと取得しましょう)

Ext.define('App.plugin.ToolbarAutoHide', {
    // 中略
    init: function(c) {
        // 中略
    },
    events: function() {
        // 中略
    },
    onStart: function(c, x, y) {
        var me = this,
            bar = me.getBar(),
            el  = bar.element;
        me._startPoint = {
            x: x,
            y: y + (el.translateY || 0)
        };
    },
    onScroll: function(c, x, y) {
        var me  = this,
            sp  = me._startPoint,
            bar = me.getBar(),
            el  = bar.element,
            offset;
        if (!Ext.isEmpty(sp)) {
            if (y < 0) {
                offset = 0;
            } else {
                offset = sp.y - y;
                if (offset > 0) {
                    offset = 0;
                }
                if (offset < -el.getHeight()) {
                    offset = -el.getHeight();
                }
            }
            el.translate(0, offset, 0);
            el.translateY = offset;
        }
    },
    onEnd: function(c, x, y) {
        var me = this;
        me.onScroll(c, x, y);
    }
});

これを実行してみると

こんな感じになります。

※ 今気づいたけどツールバーの色がURIバーの後ろに写ってますね ;(

言い訳

ちょっと絶賛病み上がりのため、ざっくりすぎるのはご了承ください ;(


ということで、わたしくめはお布団に籠ることにします。
お次は、@ispernさんでーす :-)