public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainFragment fragment = new MainFragment();
fragment.setOnOkBtnClickListener(new MainFragment.OnOkBtnClickListener() {
@Override
public void onOkClicked() {
// TODO Auto-generated method stub
}
});
getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "MainFragment").commit();
}
}
public class MainFragment extends Fragment {
public interface OnOkBtnClickListener {
public void onOkClicked();
}
private OnOkBtnClickListener mListener;
public void setOnOkBtnClickListener(OnOkBtnClickListener l) {
mListener = l;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Button okBtn = new Button(inflater.getContext());
okBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mListener != null) {
mListener.onOkClicked();
}
}
});
return okBtn;
}
}
このコードには問題があります。
バックグラウンドにある Fragment はメモリが足りなくなると、システムによって破棄され、必要になったときにシステムによって再生成されることがあるため、その際 setOnOkBtnClickListener() 部分は呼ばれないのです。
そのため、システムによって再生成された場合、コールバックを受け取れなくなるということが起こりえます。
以前のエントリで書きましたが、レイアウトで定義した(<fragment> で定義した)Fragment は FragmentTransaction の対象にしてはいけないため、上記のコードの MainFragment が onCreate() 内で生成されるのではなく、レイアウトXMLファイルで定義されている場合は、問題になることは(たぶん)あまりないと思います。
ただ、いずれにしてもこの実装はさけた方が懸命です。 (レイアウトで定義した Fragment を使っているとはいえ、Android UI Cookbook で Activity 側から fragment にリスナーをセットするコードを載せてしまっているので、動的に生成した Fragment には応用しないでください。ごめんなさい。)
で、どうするかというと、Activity 自体にリスナーを実装するようにします。
(私はあまりこの書き方は好きじゃないのですが、Fragment がシステムから再生成されうるのでしょうがないです。。。)
public class MainActivity2 extends Activity implements MainFragment.OnOkBtnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainFragment fragment = new MainFragment();
getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "MainFragment").commit();
}
@Override
public void onOkClicked() {
// TODO Auto-generated method stub
}
}
public class MainFragment2 extends Fragment {
public interface OnOkBtnClickListener {
public void onOkClicked();
}
private OnOkBtnClickListener mListener;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof OnOkBtnClickListener == false) {
throw new ClassCastException("activity が OnOkBtnClickListener を実装していません.");
}
mListener = ((OnOkBtnClickListener) activity);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Button okBtn = new Button(inflater.getContext());
okBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onOkClicked();
}
}
});
return okBtn;
}
}
この場合、Fragment がシステムから再生成されたときも onAttach() を通るので、リスナーがセットされないということにはなりません。
Fragment が Activity の実装に依存しないので、複数の Activity で Fragment を使い回すときに便利です。
Fragment が必ず特定の Activity にしかアタッチされない場合は、次の方法も使えます。
public class MainActivity3 extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainFragment fragment = new MainFragment();
getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "MainFragment").commit();
}
public void onOkClicked() {
// TODO Auto-generated method stub
}
}
public class MainFragment3 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Button okBtn = new Button(inflater.getContext());
okBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
MainActivity3 activity = (MainActivity3) getActivity();
activity.onOkClicked();
} catch (ClassCastException e) {
throw new ClassCastException("activity が OnOkBtnClickListener を実装していません.");
}
}
});
return okBtn;
}
}
これが一番コードが短くてすみます。
これがベスト!という実装方法がないんですよねー。。。そもそも Fragment のコンストラクタは空コンストラクタでないとダメだよとかって制限があるのに、作れてしまったりするというがフレームワーク的にどうなのよ、と。
しかもシステムに再生成されない限りは普通に動いちゃうので、ぜったいはまるだろこれ、って感じなのです。
0 件のコメント:
コメントを投稿