Monday, January 09, 2012

Fragmentを表示したり消したりする

画面方向の回転のタイミングで、Fragmentを表示したり消したりします。
方法は、回転のタイミングで呼ばれるActivityのonResumeのオーバーライドです。

 @Override
 public void onResume() {
  super.onResume();

  //画面のサイズを取得
  WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
  Display disp = wm.getDefaultDisplay();
  int width = disp.getWidth();

  //表示したり消したりするためのマネージャーを取得。
  FragmentManager fm = getFragmentManager();
  FragmentTransaction ft = fm.beginTransaction();

  //表示したり消したりするフラグメントを取得。
  Fragment mFragment1 = fm.findFragmentById(R.id.number_list);

  //画面の幅が900pxより小さかったら、消します。それ以外なら表示します。
  if (mFragment1 != null) {
   if (width < 900) {
    ft.hide(mFragment1);
   } else
    ft.show(mFragment1);
  }
  ft.commit();
 }

Galaxy Nexusで実行した結果ですが、横向きの時は画面幅が900px超えるので「1,2,3・・」と表示されるListFragmentが表示されます。
縦向きの時は画面幅は900pxを下回るのでこのListViewは表示されません。



Fragmentでエラー

Android 3.0で追加されたFragmentが使えるかテストをしていたのだが、
XMLでFragmentを含むレイアウトを記載したアプリをGalaraxy Nexusにインストールし画面を回転させたときにアプリが異常停止する。
特に、自分でFragmentクラスを継承させてonCreateViewをオーバーライドしたクラスを、
XML上に配置するとこの異常停止になる。

エラーは下記。
01-09 21:51:39.086: E/AndroidRuntime(5798): Caused by: java.lang.IllegalStateException: Fragment com.bambooflower.fragmenttest.MyFragment did not create a view.
01-09 21:51:39.086: E/AndroidRuntime(5798): at android.app.Activity.onCreateView(Activity.java:4266)
01-09 21:51:39.086: E/AndroidRuntime(5798): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:673)

このログを頼りに検索すると、XMLを使うな、というQ&Aと、
XMLを使わないプログラマティックな実装方法を紹介しているサイトを発見。

原因はおそらくActivityと異なるライフサイクルでFragmentの画面が形成されるためだろう。

プログラマティックにFragmentを作る方法とは、XMLにはを一切書かないで、
例えばActivityのonCreate実装の中に下記のようにFragment生成文を書くことのようだ。


FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (null == fm.findFragmentByTag(FRAG_TAG)) {
    xact.add(R.id.parent, new MyFragment(), FRAG_TAG);
}
じゃあ何のためにXMLがあるんだろうと不思議に思ってしまうが、これで回避できそうだ。

追記:
確かにこれでエラーは回避できた。
自分で実装したFragmentを静的にレイアウトにのせるのはもうやめようかな、と思う。
今確かめたところ、ListFragmentでonCreateView内でinflateするだけであれば問題ないようだ。

Saturday, January 07, 2012

Galaxy NexusでFragment I

http://android-developers.blogspot.com/2011/02/android-30-fragments-api.html

上記サイトが一番ためになるようだ。
ただ、Layoutをxmlでやったり、Java内で動的にやったり、
なんだか関係がよくわからない、というレベルの私なので、
下記を参考にした。


おそらく一番わかり易い記事だ。


[PROCESS]

[#1 Make Project]
eclipseで新規作成メニューからAndroidプロジェクトを選択。
あなたの好きなプロジェクト名を作成する。

[#2 Configure main.xml]
さっき作ったプロジェクトで最初に動く画面を作るmain.xml。
どこにあるかは下記図を参照。これをダブルクリック。

これを下記に上書き。



    

    

上書き編集画面はこんな感じ。


この"com.bambooflower.fragmenttest"はあなたの決めたプロジェクト名に変更。

[#3 Add codes to MainActivity]
こんどは、プロジェクトを仕切るActivityを書き換え。
プロジェクト名+Activityのファイルをダブルクリック。
クラス内にコードを追加。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ListView l = (ListView) findViewById(R.id.number_list);
        ArrayAdapter numbers = new ArrayAdapter(getApplicationContext(),
                        android.R.layout.simple_list_item_1, 
                        new String [] {
                "one", "two", "three", "four", "five", "six"
        });
        l.setAdapter(numbers);
        l.setOnItemClickListener(this);
    }

    
    /**
     * Add a Fragment to our stack with n Androids in it
     */
    private void stackAFragment(int nAndroids) {
        Fragment f = new MyFragment(nAndroids);
        
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.replace(R.id.the_frag, f);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        ft.addToBackStack(null);
        ft.commit();
    }

        /**
         * Called when a number gets clicked
         */
        public void onItemClick(AdapterView parent, View view, int position, long id) {
                stackAFragment(position + 1);
    }



書き換え後はこんな感じ。

eclipse上で赤線が出てたら、依存関係が解消されていないので、
マウスオーバー(赤線の上にカーソルを合わせる)してサジェスト(カーソルの下に出てくる助言)野中の"import ****"を選択する。


[#4 Add Fragment Class]
プロジェクト名を右クリックして、クラスを追加する。

クラス名は、またあなたがつけてください。
クラス作成後のソースに下記を追加。


  private int nAndroids;
  
  public MyFragment() {
      
  }

 /**
  * Constructor for being created explicitly
  */
 public MyFragment(int nAndroids) {
              this.nAndroids = nAndroids;
  }

  /**
   * If we are being created with saved state, restore our state
   */
  @Override
  public void onCreate(Bundle saved) {
      super.onCreate(saved);
      if (null != saved) {
              nAndroids = saved.getInt("nAndroids");
      }
  }
  
  /**
   * Save the number of Androids to be displayed
   */
  @Override
  public void onSaveInstanceState(Bundle toSave) {
      toSave.putInt("nAndroids", nAndroids);
  }

  /**
   * Make a grid and fill it with n Androids
   */
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
      int n;
      Context c = getActivity().getApplicationContext();
      LinearLayout l = new LinearLayout(c);
      for (n = 0; n < nAndroids; n++) {
              ImageView i = new ImageView(c);
              i.setImageResource(R.drawable.gallery_thumb);
              l.addView(i);
      }
      return l;
  }
追加後はこんな感じ。

最後に、main.xmlにもどって、"MyFragment"と適当に私が入れていた名前を、あなたのクラス名にしてください。


[#5 build]
ビルドしてみましょう。
Android 4.0.3の横長画面エミュレータではこんな感じ。


Galaxy Nexusではこんな画面。なんか意図したものと違う・・・。
私が作りたいのは、Galaxy Nexusでは横に並ぶのではなく、画面遷移・・
そうかListViewもFragmentにしないといけないのかな。
次回はちゃんと画面遷移ができるようにするぞー!