Help us understand the problem. What is going on with this article?

NativeScriptでxmlからViewを作成する+ListViewでの実践

More than 3 years have passed since last update.

タイトルをご覧になって、何言ってるの、NativeScriptでxmlからUIレイアウトを作れるなんて常識じゃん、と思われた方も多いと思うので、もう少し補足します。
自分が好きなタイミングでxmlからViewを作りたい! という事です。
現状は、js(ts)モジュールと同名のxmlファイルを自動的に読み込んでくれるオンリーですよね。

AndroidにはlayoutInflaterという仕組みがあって、xmlからいつでもViewを作り出すことができますよね。
iOSもloadFromNibなんてのがありますね。

我らがNativeScriptにも同じようなのがあるはず!
・・・と鼻息を荒くしてたら、ど真ん中直球なのはたぶん無いです。

なので、自分で用意する事にしました。
オープンソースの素晴らしいところは、隅から隅までソースを読めるので、内部的に何をやっているか丸わかりな所。
色々読んでいると、ui/builderモジュールというのがあって、loadという関数が用意されてるのでこれを利用できそうだと、試してみました。

view_a.xml
<StackLayout xmlns="http://schemas.nativescript.org/tns.xsd">
    <Label text="ほげ"/>
</StackLayout>

こんなのを用意します。

typescript
var view = builder.load('view_a.xml');

うん、フツーにダメでした。nullが返ってきてしまいますね。
さらにソースを読んでみると、この関数は絶対パスを与えてあげないといけないらしい。
色々悩みましたが、

typescript
function resolveFileName(moduleName, ext) {
    var fs = require('file-system');
    var currentAppPath = fs.knownFolders.currentApp().path;
    var moduleNamePath = fs.path.join(currentAppPath, moduleName);
    return require('file-system/file-name-resolver').resolveFileName(moduleNamePath, ext);
}

こんな関数を用意してみました。

typescript
var fileName = resolveFileName('view_a', 'xml');
var view = builder.load(fileName);

今度はちゃんとviewにxmlの内容が格納されたようです。やったぜ。

気を良くして、

typescript
function inflateLayout(moduleName, context?) {
    var fileName = resolveFileName(moduleName, 'xml');
    var Builder = require('ui/builder');
    return Builder.load(fileName, context);
}

こんな関数も追加してみました。
ん? contextとはなんぞや? と思われた方、鋭いです。

view_b.xml
<StackLayout xmlns="http://schemas.nativescript.org/tns.xsd">
    <Label text="{{text}}"/>
</StackLayout>
typescript
var view = inflateLayout('view_b', {'text': 'ほげ'});

こんな風に、bindできちゃうんですね。NativeScriptならではです。
試してませんが、Obsarbaleでも通りそうな感じの実装でした。

contextなんて名前にダマされましたが、Builder.loadの第2引数はxmlの挙動を操作するjsをrequireしたもののようです。

さらに改良しました。

typescript
function inflateLayout(moduleName, context?) {
    var fileName = resolveFileName(moduleName, 'xml');
    if (fileName) {
        var Builder = require('ui/builder');
        var jsFileName = resolveFileName(moduleName, 'js');
        var exports = null;
        if (jsFileName) {
            exports = require(jsFileName);
        }
        var view = Builder.load(fileName, exports);
        view.bindingContext = context;
        return view;
    } else {
        return null;
    }
}

こんな感じでしょうか。
jsの方の読みこみは検証していないです。。。

これをListViewに応用してみたいと思います。

main.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
    <StackLayout>
        <ListView items="{{items}}" itemLoading="onItemLoading"/>
    </StackLayout>
</Page>
cell_small.xml
<StackLayout xmlns="http://schemas.nativescript.org/tns.xsd">
    <Label text="{{text}}" fontSize="16"/>
</StackLayout>
cell_big.xml
<StackLayout xmlns="http://schemas.nativescript.org/tns.xsd">
    <Label text="{{text}}" fontSize="32"/>
</StackLayout>
main.ts
import {EventData} from "data/observable";
import {Page} from "ui/page";
import {ItemEventData} from "ui/list-view";

var page: Page;

export function navigatingTo(args: EventData) {
    page = <Page>args.object;
    page.bindingContext = {
        'items': [
            {'text': 'test1'},
            {'text': 'test2'},
            {'text': 'test3'},
            {'text': 'test4'},
            {'text': 'test5'},
            {'text': 'test6'},
            {'text': 'test7'},
        ]
    };
}

export function onItemLoading(args: ItemEventData) {
    var position = args.index;
    var file;
    if (position % 2 == 0) {
        file = 'cell_small';
    } else {
        file = 'cell_big';
    }
    args.view = inflateLayout(file);
}

<以下略>

こんな風にすると、ListViewのテンプレートを書かなくても、1行おきに小さいラベルと大きなラベルが交互に表示されるようなものが出来てしまいます。
片方はラベルじゃなくて画像でももちろんOKです。
各行のUIパーツの高さは異なりますが、きちんと反映されます。

ちなみに、inflateしたviewのbindingContextはどうなってるのか? と思われるかも知れませんが、onItemLoadingの中でviewのbindingContextをいくらいじっても、この関数を呼び出した後、ListViewが無慈悲にも自分のitemsの内容で上書きします。

NativeScriptが公式に複数のテンプレートに対応してくれれば良いのですがねえ。

terushu
過去色々とかじってきましたが、最近はもっぱらアプリ開発及び、Webサービス開発にいそしんでおります。 ソフトは飽きたのでそろそろハードで何か企みたいが個人ではハードルが高いですね。
http://www.springboard-inc.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした