卡住了从活动到片段的移植

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();

【讨论】:

以上是关于卡住了从活动到片段的移植的主要内容,如果未能解决你的问题,请参考以下文章

将synproxy移植到用户空间

在android中使用底部导航的最佳实践:活动与片段

方向更改时,片段视图为空

将非框架 PHP 项目移植到 Laravel 4.x

Part 22:Cocos2d-x开发实战-移植-从Win32到Android-关东升-专题视频课程

Part 23:Cocos2d-x开发实战-移植-从Win32到Windows Phone8-关东升-专题视频课程