卡住了从活动到片段的移植
Posted
技术标签:
【中文标题】卡住了从活动到片段的移植【英文标题】:Stuck with porting from activity to fragment 【发布时间】:2012-04-18 06:33:45 【问题描述】:现在正是我尝试将一个简单的基于活动的应用程序移植到片段的一周。我完全被卡住了。
这个野兽是一个简单的列表、详细信息、添加/编辑应用程序,带有上下文菜单和选项菜单。我试图让它正确:片段和活动各自在他们自己的文件中,使用手机和平板电脑的 v4 支持包,片段完成可重用片段应该做的所有事情,并且回调(很多)飞来飞去通知活动和关于做什么的片段。从 SQLiteOpenHelper 转换为 ContentProvider,从 optionmenu 转换为 actionbarmenu,and, and, and, ...(我使用的几乎所有东西现在都已弃用)。
这太可怕了。我的简单而小型的基于工作活动的应用程序现在的大小几乎是 3 倍,而且很多东西还没有工作。
如果需要,我可以在此处添加我的代码 - 但内容很多(已警告您)。
我的问题:是否有人愿意与 List, Details AND Add/Edit 分享一个完整的示例?此示例应为片段和活动使用单独的文件(而不是来自 Google 的一体化包)。
请不要投反对票。我真的很想看看如何使它正确。
非常感谢。
编辑:
这是带有两种布局的起始 Activity(用于手机的 res/layout 和用于平板电脑的 res/layout-large-land)和上下文菜单:
public class ActivityList extends FragmentActivity implements FragmentList.MyContextItemSelectedListener,
FragmentList.MyDeleteListener,
FragmentList.MyListItemClickListener,
FragmentList.MyOptionsItemSelectedListener,
FragmentDetails.MyDeleteListener,
FragmentDetails.MyOptionsItemSelectedListener
@Override
public void myContextItemSelected(final int action, final long id)
if (action == R.id.men_add)
processEdit(0);
else if (action == R.id.men_delete)
processUpdateList();
else if (action == R.id.men_details)
processDetails(id);
else if (action == R.id.men_edit)
processEdit(id);
@Override
public void myDelete(final long id)
processUpdateList();
@Override
public void myListItemClick(final long id)
processDetails(id);
@Override
public void myOptionsItemSelected(final int action)
myOptionsItemSelected(action, 0);
@Override
public void myOptionsItemSelected(final int action, final long id)
if (action == R.id.men_add)
processEdit(0);
else if (action == R.id.men_edit)
processEdit(id);
else if (action == R.id.men_preferences)
processPreferences();
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent intent)
processUpdateList();
@Override
public void onCreate(final Bundle bundle)
super.onCreate(bundle);
setContentView(R.layout.activitylist);
private void processEdit(final long id)
Intent intent = new Intent(this, ActivityEdit.class);
intent.putExtra("ID", id);
startActivityForResult(intent, MyConstants.DLG_TABLE1EDIT);
private void processDetails(final long id)
if (Tools.isXlargeLand(getApplicationContext()))
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.right);
if (fragment == null ||
(fragment instanceof FragmentDetails && ((FragmentDetails) fragment).getCurrentId() != id))
fragment = new FragmentDetails(id);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.right, fragment);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.commit();
else
Intent intent = new Intent(this, ActivityDetails.class);
intent.putExtra("ID", id);
startActivityForResult(intent, MyConstants.DLG_TABLE1SHOW);
private void processPreferences()
Intent intent = new Intent(this, MyPreferenceActivity.class);
startActivityForResult(intent, MyConstants.DLG_PREFERENCES);
private void processUpdateList()
// TODO:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="horizontal" >
<fragment
class="com.test.app.FragmentList"
android:id="@+id/fragmentlist"
android:layout_
android:layout_
android:name="com.test.app.FragmentList" />
</LinearLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="horizontal" >
<fragment
class="com.test.app.FragmentList"
android:id="@+id/fragmentlist"
android:layout_
android:layout_weight="1"
android:layout_
android:name="com.test.app.FragmentList" />
<FrameLayout
android:id="@+id/right"
android:layout_
android:layout_weight="2"
android:layout_ />
</LinearLayout>
这是 ListFragment 及其行布局、选项菜单和上下文菜单:
public class FragmentList extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>
private SimpleCursorAdapter adapter;
private AlertDialog alertDialog;
private Context context;
private MyContextItemSelectedListener contextItemSelectedListener;
private MyDeleteListener deleteListener;
private long id;
private MyListItemClickListener listItemClickListener;
private ListView listView;
private MyOptionsItemSelectedListener optionsItemSelectedListener;
public interface MyContextItemSelectedListener
public void myContextItemSelected(int action, long id);
public interface MyDeleteListener
public void myDelete(long id);
public interface MyListItemClickListener
public void myListItemClick(long id);
public interface MyOptionsItemSelectedListener
public void myOptionsItemSelected(int action);
@Override
public void onActivityCreated(final Bundle bundle)
super.onActivityCreated(bundle);
context = getActivity().getApplicationContext();
listView = getListView();
getActivity().getSupportLoaderManager().initLoader(MyConstants.LDR_TABLE1LIST, null, this);
adapter = new SimpleCursorAdapter(context,
R.layout.fragmentlist_row,
null,
new String[] Table1.DESCRIPTION ,
new int[] R.id.fragmentlist_row_description ,
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
setListAdapter(adapter);
setListShown(false);
registerForContextMenu(listView);
if (bundle != null && bundle.containsKey("ID"))
id = bundle.getLong("ID");
listItemClickListener.myListItemClick(id);
if (Tools.isXlargeLand(context))
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
setHasOptionsMenu(true);
@Override
public void onAttach(final Activity activity)
super.onAttach(activity);
// Reduced: Check for implemented listeners
@Override
public boolean onContextItemSelected(final MenuItem menuItem)
AdapterContextMenuInfo adapterContextMenuInfo = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
final long id = adapterContextMenuInfo.id;
if (menuItem.getItemId() == R.id.men_delete)
processAlertDialog(id);
return true;
else
contextItemSelectedListener.myContextItemSelected(menuItem.getItemId(), adapterContextMenuInfo.id);
return super.onContextItemSelected(menuItem);
@Override
public void onCreateContextMenu(final ContextMenu contextMenu, final View view, final ContextMenuInfo contextMenuInfo)
super.onCreateContextMenu(contextMenu, view, contextMenuInfo);
if (view.getId() == android.R.id.list)
getActivity().getMenuInflater().inflate(R.menu.fragmentlist_context, contextMenu);
@Override
public Loader<Cursor> onCreateLoader(final int id, final Bundle bundle)
MyCursorLoader loader = null;
switch (id)
case MyConstants.LDR_TABLE1LIST:
loader = new MyCursorLoader(context,
mysqliteOpenHelper.TABLE1_FETCH,
null);
break;
return loader;
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater menuInflater)
super.onCreateOptionsMenu(menu, menuInflater);
menu.clear();
menuInflater.inflate(R.menu.fragmentlist, menu);
@Override
public void onListItemClick(final ListView listView, final View view, final int position, final long id)
super.onListItemClick(listView, view, position, id);
this.id = id;
if (Tools.isXlargeLand(context))
listView.setItemChecked(position, true);
listItemClickListener.myListItemClick(id);
@Override
public void onLoaderReset(final Loader<Cursor> loader)
adapter.swapCursor(null);
@Override
public void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor)
adapter.swapCursor(cursor);
setListShown(true);
@Override
public boolean onOptionsItemSelected(final MenuItem menuItem)
optionsItemSelectedListener.myOptionsItemSelected(menuItem.getItemId());
return super.onOptionsItemSelected(menuItem);
@Override
public void onSaveInstanceState(final Bundle bundle)
super.onSaveInstanceState(bundle);
bundle.putLong("ID", id);
private void processAlertDialog(final long id)
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
@Override
public void onClick(final DialogInterface dialogInterface, final int which)
dialogInterface.dismiss();
);
alertDialogBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
@Override
public void onClick(final DialogInterface dialogInterface, final int which)
MyApplication.getSqliteOpenHelper().deleteTable1(id);
alertDialog.dismiss();
deleteListener.myDelete(id);
);
alertDialogBuilder.setCancelable(false);
alertDialogBuilder.setMessage(R.string.txt_reallydelete);
alertDialog = alertDialogBuilder.create();
alertDialog.show();
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="horizontal"
android:paddingBottom="2dip"
android:paddingTop="2dip" >
<TextView
style="@style/TextViewLarge"
android:id="@+id/fragmentlist_row_description"
android:textStyle="bold" />
</LinearLayout>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:icon="@drawable/ic_menu_add"
android:id="@+id/men_add"
android:showAsAction="ifRoom|withText"
android:title="@string/txt_add" />
<item
android:icon="@drawable/ic_menu_preferences"
android:id="@+id/men_preferences"
android:showAsAction="ifRoom|withText"
android:title="@string/txt_preferences" />
</menu>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/men_details"
android:title="@string/txt_details" />
<item
android:id="@+id/men_edit"
android:title="@string/txt_edit" />
<item
android:id="@+id/men_delete"
android:title="@string/txt_delete" />
</menu>
这是详细信息活动:
public class ActivityDetails extends FragmentActivity implements FragmentDetails.MyDeleteListener,
FragmentDetails.MyOptionsItemSelectedListener
private long id;
@Override
public void myDelete(final long id)
setResult(RESULT_OK);
finish();
@Override
public void myOptionsItemSelected(final int action, final long id)
if (action == R.id.men_add)
processEdit(0);
else if (action == R.id.men_edit)
processEdit(id);
else if (action == R.id.men_preferences)
processPreferences();
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent intent)
if (requestCode == MyConstants.DLG_PREFERENCES || requestCode == MyConstants.DLG_TABLE1EDIT)
finish();
startActivity(getIntent());
@Override
protected void onCreate(final Bundle bundle)
super.onCreate(bundle);
if (bundle != null)
if (bundle.containsKey("ID"))
id = bundle.getLong("ID");
else
Bundle bundleExtras = getIntent().getExtras();
if (bundleExtras != null)
id = bundleExtras.getLong("ID");
processDetails(id);
@Override
public void onSaveInstanceState(final Bundle bundle)
super.onSaveInstanceState(bundle);
bundle.putLong("ID", id);
private void processDetails(final long id)
FragmentDetails fragment = new FragmentDetails(id);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, fragment);
transaction.commit();
private void processEdit(final long id)
Intent intent = new Intent(this, ActivityEdit.class);
intent.putExtra("ID", id);
startActivityForResult(intent, MyConstants.DLG_TABLE1EDIT);
private void processPreferences()
Intent intent = new Intent(this, MyPreferenceActivity.class);
startActivityForResult(intent, MyConstants.DLG_PREFERENCES);
这是带有布局和菜单的 DetailsFragment:
public class FragmentDetails extends Fragment
private AlertDialog alertDialog;
private MyDeleteListener deleteListener;
private long id;
private MyOptionsItemSelectedListener optionsItemSelectedListener;
private TextView textViewDescription;
private TextView textViewId;
public FragmentDetails()
id = 0;
public FragmentDetails(final long id)
this.id = id;
public long getCurrentId()
return id;
public interface MyDeleteListener
public void myDelete(long id);
public interface MyOptionsItemSelectedListener
public void myOptionsItemSelected(int action, long id);
@Override
public void onActivityCreated(final Bundle bundle)
super.onActivityCreated(bundle);
if (bundle != null && bundle.containsKey("ID"))
id = bundle.getLong("ID");
setHasOptionsMenu(true);
@Override
public void onAttach(final Activity activity)
super.onAttach(activity);
// Reduced
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater menuInflater)
super.onCreateOptionsMenu(menu, menuInflater);
menu.clear();
menuInflater.inflate(R.menu.fragmentdetails, menu);
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup viewGroup, final Bundle bundle)
View view = inflater.inflate(R.layout.fragmentdetails, null);
textViewDescription = (TextView) view.findViewById(R.id.tv_description);
textViewId = (TextView) view.findViewById(R.id.tv_id);
if (id != 0)
Table1 table1;
if ((table1 = MyApplication.getSqliteOpenHelper().getTable1(id)) != null)
textViewDescription.setText(Tools.defaultString(table1.getDescription()));
textViewId.setText(Tools.defaultString(String.valueOf(table1.getId())));
return view;
@Override
public boolean onOptionsItemSelected(final MenuItem menuItem)
if (menuItem.getItemId() == R.id.men_delete)
processAlertDialog(id);
return true;
else
optionsItemSelectedListener.myOptionsItemSelected(menuItem.getItemId(), id);
return super.onOptionsItemSelected(menuItem);
@Override
public void onSaveInstanceState(final Bundle bundle)
super.onSaveInstanceState(bundle);
bundle.putLong("ID", id);
private void processAlertDialog(final long id)
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
@Override
public void onClick(final DialogInterface dialogInterface, final int which)
alertDialog.dismiss();
alertDialog = null;
);
alertDialogBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
@Override
public void onClick(final DialogInterface dialogInterface, final int which)
MyApplication.getSqliteOpenHelper().deleteTable1(id);
alertDialog.dismiss();
alertDialog = null;
deleteListener.myDelete(id);
);
alertDialogBuilder.setCancelable(false);
alertDialogBuilder.setMessage(R.string.txt_reallydelete);
alertDialog = alertDialogBuilder.create();
alertDialog.show();
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="vertical" >
<LinearLayout
android:layout_
android:layout_
android:orientation="horizontal" >
<TextView
style="@style/TextViewStandard"
android:layout_weight="1"
android:text="@string/txt_id" />
<TextView
style="@style/TextViewStandard"
android:id="@+id/tv_id"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_
android:layout_
android:orientation="horizontal" >
<TextView
style="@style/TextViewStandard"
android:layout_weight="1"
android:text="@string/txt_description" />
<TextView
style="@style/TextViewStandard"
android:id="@+id/tv_description"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:icon="@drawable/ic_menu_add"
android:id="@+id/men_add"
android:showAsAction="ifRoom|withText"
android:title="@string/txt_add" />
<item
android:icon="@drawable/ic_menu_edit"
android:id="@+id/men_edit"
android:showAsAction="ifRoom|withText"
android:title="@string/txt_edit" />
<item
android:icon="@drawable/ic_menu_delete"
android:id="@+id/men_delete"
android:showAsAction="ifRoom|withText"
android:title="@string/txt_delete" />
<item
android:icon="@drawable/ic_menu_preferences"
android:id="@+id/men_preferences"
android:showAsAction="ifRoom|withText"
android:title="@string/txt_preferences" />
</menu>
我不发布 EditActivity,因为它只是一个没有 Fragment 的 FragmentActivity。
【问题讨论】:
我的建议是一次做一件事,而不是一次做一件事。这样也更容易测试。 谢谢。 CursorLoader 正在工作,操作栏几乎可以工作(当前,当有人单击操作栏时,两个活动的侦听器会被触发,因为一个片段组件有一个启动活动和一个电话活动)。对我来说最大的问题是我没有正确导航。我很想在这里展示我的代码,但我担心人们不会喜欢那么多东西。 如果您想链接到整个项目,请将其放在 github 上并在此处提供项目链接。 那么更新后的代码有什么问题呢?你需要更具体。 我的整个导航都是垃圾。例如:我处于平板电脑模式并打开详细信息页面。如果我现在旋转设备,我最终会得到列表页面而不是详细信息页面,反之亦然。或者另一个:我在平板电脑模式下打开了editpage。现在我旋转并单击后退按钮。我希望从编辑返回到列表页或详细信息页。在我的情况下,应用程序结束。我确实在付费商店中有很多基于活动的应用程序。我喜欢那个范式。我不知道有多少次我试图理解这种新的混合手机/平板电脑/支持/操作栏/片段的东西。我一定是完全愚蠢的,但我不明白。 【参考方案1】:这可能不是全部答案,而是部分答案: 您仍然有一个主要活动,在您曾经拥有列表视图的 xml 中,您现在添加了一个框架布局。然后在您的活动 oncreate 中添加以下内容:
mMainFragment = new ListFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.center_container, mMainFragment);
fragmentTransaction.commit();
mCurrentFragment = mMainFragment;
在你的列表片段中
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
// setup view
View view = inflater.inflate(R.layout.calendar_list, null);
mListAdapter = new CustomAdapter(getActivity(), R.layout.calendar_row, (ArrayList<Item>) mFullList);
setListAdapter(mListAdapter);
return view;
列表片段的 XML:
<somelayout>
<ListView android:id="@id/android:list"
android:layout_
android:layout_/>
</somelayout>
在片段中触发点击列表:
@Override
public void onListItemClick(ListView list, View view, int position, long id)
final Item item = (Item) list.getAdapter().getItem(position);
mListener.OnListClick(item);
哪个使用这个监听器:
公共接口 OnListItemClickListener public void OnListClick(Item item);
Listfragment 需要在顶部有这个:
@Override
public void onAttach(Activity activity)
super.onAttach(activity);
try
mListener = (OnListItemClickListener) activity;
catch (ClassCastException e)
throw new ClassCastException(activity.toString() + " must implement OnListItemClickListener");
然后,主要活动通过实现接口来订阅它,并在触发侦听器时启动详细片段。
编辑: 好的,所以您的问题更为根本:) 请记住每次旋转时都会在您的活动中调用 oncreate,因此您的活动需要记住要显示的片段,就像它需要记住要显示的视图一样。 您还需要将片段添加到后退堆栈,否则后退键将无法与它们一起使用。将片段视为具有功能的视图,它们不是活动。
【讨论】:
再次感谢。是的,这很容易;-) 我确实已经在工作了。我决定在这里发布我的东西。请给我几分钟 - 我将删除不属于导航等的代码。【参考方案2】:我将尝试回答其中一个问题。你写:
“现在我旋转并单击返回按钮。我希望从编辑返回到列表页面或详细信息页面。在我的情况下,应用程序结束。”
从您的代码示例看来,您没有将事务添加到后台堆栈。在调用 commit() 之前调用 addToBackStack(),如下所示:
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
【讨论】:
以上是关于卡住了从活动到片段的移植的主要内容,如果未能解决你的问题,请参考以下文章