从Activity转换到Fragment时遇到困难

6

我现在已经尝试了一个星期将一个简单的基于活动的应用程序移植到碎片。但我完全卡住了。

这个应用程序是一个简单的列表、详情、添加/编辑应用程序,带有上下文菜单和选项菜单。我试图做得正确:将碎片和活动放在各自的文件中,使用支持电话和平板电脑的v4支持包,在可重复使用的碎片中执行所有操作,并且回调(很多)会通知活动和碎片要做什么。从SQLiteOpenHelper转换为ContentProvider,从optionmenu转换为actionbarmenu,等等,(我使用的几乎所有内容都已经过时)。

这太可怕了。我的简单小巧的基于活动的应用程序现在几乎增加了3倍的大小,而且许多东西还没有运行。

如果需要,我可以在此处添加我的代码-但这是很多东西(你已经被警告了)。

我的问题:有人愿意分享一个完整的包含列表、详细信息添加/编辑的示例吗?这个示例应该使用分开的文件来处理碎片和活动(不是谷歌提供的那个全合一的包)。

请不要对我进行负面评价。我真的很想知道如何做得正确。

非常感谢您的帮助。

编辑:

这是起始活动及其两个布局(电话和平板电脑的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_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <fragment 
        class="com.test.app.FragmentList"
        android:id="@+id/fragmentlist"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:name="com.test.app.FragmentList" />
</LinearLayout>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <fragment 
        class="com.test.app.FragmentList"
        android:id="@+id/fragmentlist"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:layout_width="0dip"
        android:name="com.test.app.FragmentList" />

    <FrameLayout
        android:id="@+id/right"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:layout_width="0dip" />
</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_height="wrap_content"
    android:layout_width="fill_parent"
    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>

这是DetailsActivity:
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_height="wrap_content"
    android:layout_width="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        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_height="wrap_content"
        android:layout_width="match_parent"
        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。


我的建议是一次只做一件事,而不是试图一次完成整个任务。这样也更容易测试。 - Warpzit
谢谢。CursorLoader正在工作,ActionBar几乎可以工作(目前两个活动的侦听器在有人点击ActionBar时会触发,因为一个片段组件有一个启动活动和一个电话活动)。对我来说最大的问题是我无法正确地进行导航。我很想在这里展示我的代码,但我担心人们不喜欢那么多的东西。 - Harald Wilhelm
1
如果您想链接整个项目,请将其放在GitHub上并在那里提供项目链接。 - Warpzit
那么更新后的代码有什么问题呢?你需要更具体地说明。 - Warpzit
我的整个导航都是垃圾。例如:我处于平板模式并打开详细页面。如果我现在旋转设备,我会回到列表页而不是详细页,反之亦然。或者另一个例子:我处于平板模式并打开编辑页面。现在我旋转并点击返回按钮。我希望从编辑页返回到列表页或详细页。但在我的情况下,应用程序结束了。我在付费商店中有很多基于活动的应用程序。我喜欢那种范例。我不知道我尝试理解这个新的混合手机/平板电脑/支持/操作栏/片段的东西多少次了。我一定是非常愚蠢,但我不明白。 - Harald Wilhelm
显示剩余4条评论
2个回答

3
这可能不是完整的答案,但是其中的一部分: 您仍然拥有一个主活动,在您的XML中,您使用ListView的位置现在添加一个FrameLayout。 然后在您的Activity onCreate 中添加以下内容:
        mMainFragment = new ListFragment();
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        fragmentTransaction.replace(R.id.center_container, mMainFragment);

        fragmentTransaction.commit();
        mCurrentFragment = mMainFragment;

在你的ListFragment中
@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;
}

ListFragment的XML:

<somelayout>
    <ListView android:id="@id/android:list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</somelayout>

点击列表在碎片中触发,代码如下:

@Override
public void onListItemClick(ListView list, View view, int position, long id) {
    final Item item = (Item) list.getAdapter().getItem(position);
    mListener.OnListClick(item);
}

这里使用了以下监听器:

public interface 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在每次旋转时都会在您的活动中调用,因此您的活动需要记住要显示哪个片段,就像它需要记住要显示哪个视图一样。 此外,您需要将片段添加到后退堆栈中,否则后退键将无法使用它们。将片段视为带有功能的视图,它们不是活动。

再次感谢。是的,那是容易的部分;-) 我已经做好了。我决定在这里发布我的东西。给我几分钟-我会删除与导航无关的代码。 - Harald Wilhelm

1
我来回答其中一个问题。你写道:
“现在我旋转并点击返回按钮。我期望从编辑页面返回到列表页或详情页。但在我的情况下,应用程序结束了。”
从你的代码示例中看来,你没有将事务添加到后退栈中。在调用commit()之前,调用addToBackStack(),像这样:
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接