2012年3月27日火曜日

Viewはshowしたときに描き直されるもの?

Titanimu Mobileを使って、新しいアプリを開発中です。いろいろと試しながらの開発なので、実際に作る工程よりも、どんな動きをするのか細かな部分の解析に時間を取られています。使い始めて2つめのアプリなので、仕方ないでしょうね。

 

今回は、Viewの描画で困ったことが起こりました。普通にViewを作成して、普通に表示しているだけなら、とくに問題はありません。でも、少し凝った機能が必要になって、ViewにaddしたUI部品のレイアウトを途中で何度も変更する機能を実装しました。UI部品のtopやleftを途中で変更し、1つのViewのままで、複数のレイアウトを実現する機能です。メモリー効率も良さそうですし。

さすがに、表示したままレイアウトを変更するわけにはいきません。Viewをいったんhideして、hideした状態でUI部品のレイアウトを変更し、変更が終わってからViewをshowしたら問題ないだろう考えました。他の工夫(viewをhideしても問題が生じない工夫)と組み合わせる必要はありますが、Viewに関するJavaScriptは、次のようになります。

view1.hide();      // view1を非表示にします
label1.top = 40;   // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();      // view1を再表示します

これが期待した動きになりませんでした。UI部品のレイアウトを変えてから、Viewをshowした場合は、ほんの一瞬だけ変更前のレイアウトが表示され、直後に新しいレイアウトを描いて安定します。フラッシュバックで一瞬だけ古い画像が表示されたような感じです。バグでしょうか、仕様なのでしょうか。こんな使い方をしている人がいないためか、検索しても情報は見付かりませんでした。普通に考えると、表示する内容を内部で作り終わってから、画面に描画するように作るのではと思います。でも、動きから推測するに、そうなってはいないようです。

真剣に困りました。この方法が使えないと、複数のレイアウトのViewを用意しておき、それぞれに値を設定し直してから切り替える必要があります。レイアウトを増やす場合も、新しいViewを追加しなければなりません。美しくないですね。何とか回避できないかと、かなり悩みました。

悩んだ結果、1つ思い付きました。透明度を変えるopacityを使えないかと。opacityの値で限りなく透明にした状態でViewを描かせ、直後に不透明状態に戻せば、フラッシュバックが消えるのではないかと。完全に透明にしてしまうと、描く処理が開始しないと思い、限りなく透明な値として0.001を選んでいます。さっそく、次のようなJavaScriptで試しました。

view1.hide();          // view1を非表示にします
view1.opacity = 0.001; // view1を限りなく透明にします
label1.top = 40;       // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();          // view1を再表示します

結果は、惨敗でした。何も変わらず、フラッシュバックが再現されました。まだ、あきらめません。限りなく透明にする位置が悪いのではないかと考えました。hideしてから限りなく透明にしても、その状態を描いていません。限りなく透明にするのを、hideする前に行えば、表示されている時間内に限りなく透明で描いたことになり、有効だと考えました。試したJavaScriptは、次のような形です。

view1.opacity = 0.001; // view1を限りなく透明にします
view1.hide();          // view1を非表示にします
label1.top = 40;       // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();          // view1を再表示します

またまた惨敗しました。ここでいったん敗北宣言です。こんなときこそ、美味しい珈琲を飲んで休息です。時間を置いてから、再び考えました。もしかしたら、時間差攻撃が有効かもと。Viewをshowした直後にopacityを戻しているからダメなので、少し時間を経過してからなら大丈夫ではないかと。限りなく透明に設定する処理をhideの後ろに戻し、次のJavaScriptで試しました。

view1.hide();          // view1を非表示にします
view1.opacity = 0.001; // view1を限りなく透明にします
label1.top = 40;       // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();                  // view1を再表示します
setTimeout(resetOpacityF, 50); // 50ms後に、不透明に戻すfunctionを起動させます
// この関数は、ここで終了

function resetOpacityF(){      // 時間差攻撃で、view1を不透明に戻します
    view1.opacity = 1;
}

今度は、大成功でした。フラッシュバックがまったく出ません。勘で決めた50msですが、時間差攻撃が成功です。最近の高速コンピュータにとっては、50msでも長い時間なのでしょう。もっと短くなるかも知れませんが、不安が増すので今のところ50msで固定です。

以上は、シミュレータでの動きでした。実機ではフラッシュバックが出ないかも知れませんし、この解決方法で実機も大丈夫という保障もありません。ただし、show後に描き直しているという動きから考えて、非常に有効な解決方法です。

開発の基本としては、実機でもシミュレータでも正常に動作することが大事です。実機で動いたからといって、その実機だけかも知れません。機種もOSバージョンも違う環境が、何種類も存在します。すべての環境でテストすることは無理なので、実機でもシミュレータでも動くことを、最低条件とするわけです。また、問題の解決にあたっては、たまたま直ったのではなく、こういう現象だからこうすれば直るはずと、理論的な裏付けのある解決方法が大事です。

今回の問題は苦労しましたが、何とか解決できて良かったです。まだ実機でのテストが残っていますが、最悪の場合でも、時間差の数値を変更するだけで大丈夫でしょう。複数レイアウトのViewを用意することだけは、ぜったいにやりたくないですからね。

 

Titanimu Mobileの経験が浅いので、もしかしたら、別な解決方法があるかも知れません。ご存じのことがいたら、ぜひ教えてください。一応、viewのプロパティなども本家のAPIページで全部見ましたが、それらしいものは見付かりませんでした。唯一使えそうだったのが、opacityというわけです。

0 件のコメント:

コメントを投稿