在ViewPager中用另一个Fragment替换一个Fragment

54

我在尝试在 ViewPager 中用一个 Fragment 替换另一个 Fragment 时遇到了一些问题。

当前情况:

我有一个包含3个页面的 ViewPager,每个页面都是一个 Fragment。在第一页中,我有一个 ListFragment ("FacturasFragment") 中包含一个 ListView。当我点击列表中的项目时,我使用 onListItemClick 方法来处理该事件。

我的要求:

  • 当单击列表项时,我想用另一个 Fragment ("DetallesFacturaFragment",包含发票详细信息) 替换 ListFragment (包含发票列表)。
  • 当我在 "DetallesFacturaFragment" 中按返回键时,应返回到 ListFragment
  • 在页面之间滚动不应更改第一个页面中显示的 Fragment。即,如果我在第一页中使用 "DetallesFacturaFragment",并滚动到第二页,当返回第一页时应继续显示 "DetallesFacturaFragment"。

代码:

FragmentActivity

public class TabsFacturasActivity extends SherlockFragmentActivity {

    private MyAdapter mAdapter;
    private ViewPager mPager;
    private PageIndicator mIndicator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_pager);
        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager)findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);

        mIndicator = (TitlePageIndicator)findViewById(R.id.indicator);
        mIndicator.setViewPager(mPager);
    }

    private static class MyAdapter extends FragmentPagerAdapter {

        private String[] titles = { "VER FACTURAS", "VER CONSUMO", "INTRODUCIR LECTURA" };
        private final FragmentManager mFragmentManager;
        private Fragment mFragmentAtPos0;
        private Context context;

        public MyAdapter(FragmentManager fragmentManager) {
            super(fragmentManager);
            mFragmentManager = fragmentManager;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return titles[position];
        }

        @Override
        public Fragment getItem(int position) {
            switch (position) {
            case 0: // Fragment # 0
                return new FacturasFragment();
            case 1: // Fragment # 1
                return new ConsumoFragment();
            case 2:// Fragment # 2
                return new LecturaFragment();
            }
            return null;
        }

        @Override
        public int getCount() {
            return titles.length;
        }

        @Override
        public int getItemPosition(Object object)
        {
            if (object instanceof FacturasFragment && mFragmentAtPos0 instanceof DetallesFacturaFragment)
                return POSITION_NONE;
            return POSITION_UNCHANGED;
        }
    }

}

ListFragment

public class FacturasFragment extends ListFragment {

    private ListView lista;

    private ArrayList<TuplaFacturaWS> facturas;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);    
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.activity_facturas, container, false);
        facturas = myApplication.getFacturas();

        lista = (ListView) view.findViewById(id.list);

        MyAdapter myAdaptador = new MyAdapter(this, facturas);
        setListAdapter(myAdaptador);

        return view;
    }

    public void onListItemClick (ListView l, View v, int position, long id) {
        myApplication.setFacturaActual(position);
        mostrarDatosFactura();
    }


    private void mostrarDatosFactura() {
        final DetallesFacturaFragment fragment = new DetallesFacturaFragment();
        FragmentTransaction transaction = null;
        transaction = getFragmentManager().beginTransaction();
        transaction.replace(R.id.pager, fragment); //id of ViewPager
        transaction.addToBackStack(null);
        transaction.commit();
    }

    private class MyAdapter extends BaseAdapter {
        private final FacturasFragment actividad;
        private final ArrayList<TuplaFacturaWS> facturas;

        public MyAdapter(FacturasFragment facturasActivity, ArrayList<TuplaFacturaWS> facturas) {
            super();
            this.actividad = facturasActivity;
            this.facturas = facturas;
        }

        @Override
        public View getView(int position, View convertView, 
                ViewGroup parent) {
            LayoutInflater inflater = actividad.getLayoutInflater(null);
            View view = inflater.inflate(R.layout.list_row, null, true);
            //Set data to view
            return view;
        }

        @Override
        public int getCount() {
            return facturas.size();
        }

        @Override
        public Object getItem(int pos) {
            return facturas.get(pos);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        private OnClickListener checkListener = new OnClickListener()
        {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub

            }

        };
    }
}

片段

public class DetallesFacturaFragment extends SherlockFragment {

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);    
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.activity_factura, container, false);

        //Set data to view

        return view;
    }
}

目前,当我点击列表项时,白色视图出现在第一页上。我已经验证了“DetallesFacturaFragment”的onCreateView方法被执行,但是该页面上没有显示任何内容。

第一次单击列表项时,它显示白屏。但在返回列表后,我必须单击两次列表项才能显示白屏。

我一直在谷歌搜索并查看很多问题,但找不到任何使用完整代码解决的人。


1
使用嵌套片段。第一页将是一个容器片段,在创建当前的ListFragment时,您将首先添加它。当您单击列表项时,将在同一容器片段中用嵌套详细信息片段替换嵌套的ListFragment。不要在该详细信息片段上使用setRetainInstance() - user
为什么要使用嵌套片段?我不清楚。 - Lyd
因为你不能像平常一样将一个片段放在ViewPager上。了解一下嵌套片段,它们并不难处理。 - user
最终我达成了目标,可以替换Fragments - Lyd
1
@Lyd,你介意分享一下你是怎么做到的吗? - user2924127
3个回答

50

在花费了许多时间后,我修改了那个答案中的代码,并找到了正确的解决方案。

它用另一个Fragment替换了ViewPager第一页中的Fragment,如果您从第二个Fragment返回,第一个Fragment将正确显示。无论Fragment在第一页中显示什么,如果您从一个页面滑动到另一个页面,它不会更改其Fragment

这是我的代码:

FragmentActivity

public class TabsFacturasActivity extends SherlockFragmentActivity {

    public void onBackPressed() {
        if(mPager.getCurrentItem() == 0) {
            if (mAdapter.getItem(0) instanceof DetallesFacturaFragment) {
                ((DetallesFacturaFragment) mAdapter.getItem(0)).backPressed();
            }
            else if (mAdapter.getItem(0) instanceof FacturasFragment) {
                finish();
            }
        }
    }

    private static class MyAdapter extends FragmentPagerAdapter {

        private final class FirstPageListener implements
        FirstPageFragmentListener {
            public void onSwitchToNextFragment() {
                mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                .commit();
                if (mFragmentAtPos0 instanceof FacturasFragment){
                    mFragmentAtPos0 = new DetallesFacturaFragment(listener);
                }else{ // Instance of NextFragment
                    mFragmentAtPos0 = new FacturasFragment(listener);
                }
                notifyDataSetChanged();
            }
        }

        private String[] titles = { "VER FACTURAS", "VER CONSUMO", "INTRODUCIR LECTURA" };
        private final FragmentManager mFragmentManager;
        public Fragment mFragmentAtPos0;
        private Context context;
        FirstPageListener listener = new FirstPageListener();

        public MyAdapter(FragmentManager fragmentManager) {
            super(fragmentManager);
            mFragmentManager = fragmentManager;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return titles[position];
        }

        @Override
        public Fragment getItem(int position) {
            switch (position) {
            case 0: // Fragment # 0
                if (mFragmentAtPos0 == null)
                {
                    mFragmentAtPos0 = new FacturasFragment(listener);
                }
                return mFragmentAtPos0;

            case 1: // Fragment # 1
                return new ConsumoFragment();
            case 2:// Fragment # 2
                return new LecturaFragment();
            }
            return null;
        }

        @Override
        public int getCount() {
            return titles.length;
        }

        @Override
        public int getItemPosition(Object object)
        {
            if (object instanceof FacturasFragment && 
                    mFragmentAtPos0 instanceof DetallesFacturaFragment) {
                return POSITION_NONE;
            }
            if (object instanceof DetallesFacturaFragment && 
                    mFragmentAtPos0 instanceof FacturasFragment) {
                return POSITION_NONE;
            }
            return POSITION_UNCHANGED;
        }

    }

}

FirstPageFragmentListener

public interface FirstPageFragmentListener {
    void onSwitchToNextFragment();
}

FacturasFragment(FirstFragment)

public class FacturasFragment extends ListFragment implements FirstPageFragmentListener {

    static FirstPageFragmentListener firstPageListener;

    public FacturasFragment() {
    }

    public FacturasFragment(FirstPageFragmentListener listener) {
        firstPageListener = listener;
    }

    public void onListItemClick (ListView l, View v, int position, long id) {
        firstPageListener.onSwitchToNextFragment();
    }
}

DetallesFacturaFragment(SecondFragment)

public class DetallesFacturaFragment extends SherlockFragment {

    static FirstPageFragmentListener firstPageListener;

    public DetallesFacturaFragment() {
    }

    public DetallesFacturaFragment(FirstPageFragmentListener listener) {
        firstPageListener = listener;
    }

    public void backPressed() {
        firstPageListener.onSwitchToNextFragment();
    }
}

2
你能否在Github或其他平台上发布一个教程或示例?@Lyd - Shihab Uddin
2
你太棒了..!! 感谢解决方案 (+1).. 不过我有一个问题,为什么使用ViewPager时会出现这种行为? - Rat-a-tat-a-tat Ratatouille
1
我真的不知道...我花了很多时间才找到它起作用! - Lyd
1
这个解决方案有一些问题。当旋转设备时,FragmentPagerAdapter将被重新实例化并且mFragmentAtPos0将被设置为null。如果您在查看详细页面时按下返回按钮,则adapter.getItem(0)将创建第一个片段的新实例,测试类型并完成()。必须在此处实现更健壮的getItem()方法。 - Oyvind
3
直到我将 commit 改为 commitNow(),它才开始工作。在 Fragment 移除时使用代码 mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit(); - ahmadalibaloch
显示剩余10条评论

0

为解决Android建议不使用非默认构造函数的小问题,您应该将构造函数更改为以下内容:

在FacturasFragment中:

 public static FacturasFragment createInstance(FirstPageFragmentListener listener) {
    FacturasFragment facturasFragment = new FacturasFragment(); 
    facturasFragment.firstPageListener = listener;
    return facturasFragment;
 }

然后你可以在getItem函数中这样调用它:

 mFragmentAtPos0 = FacturasFragment.createInstance(listener);

在DetallesFacturaFragment中:

 public static DetallesFacturaFragment createInstance(FirstPageFragmentListener listener) {
    DetallesFacturaFragment detallesFacturaFragment= new DetallesFacturaFragment(); 
    detallesFacturaFragment.firstPageListener = listener;
    return detallesFacturaFragment;
 }

然后你可以像这样从FirstPageListener.onSwitchToNextFragment()函数中调用它:

 mFragmentAtPos0 = DetallesFacturaFragment.createInstance(listener);

0
我发现了两种替换viewpager中fragment的方式。
方式一:通过更新ViewPagerAdapter。 方式二:通过使用根片段。 方式一: 这种方式需要从viewpager适配器的fragment列表中更新fragment,并通知fragment进行更改。 例如:
  private void changefragment(int position) {
        Fragment newFragment = CategoryPostFragment.newInstance();
        adapter.fragments.set(position, newFragment);
        adapter.notifyDataSetChanged();
        viewPager.setCurrentItem(position);

    }

这是与ViewPager相关的适配器代码:
import java.util.List;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.PagerAdapter;

import com.trickbd.trickbdapp.ui.activity.homeactivity.fragments.FavFragment;
import com.trickbd.trickbdapp.ui.activity.homeactivity.fragments.HomePostFragment;

public class ViewPagerAdapter extends FragmentStatePagerAdapter {
    public List<Fragment> fragments;

    public ViewPagerAdapter(FragmentManager manager, List<Fragment> fragments) {
        super(manager);
        this.fragments = fragments;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        if (fragments.get(position)!=null){
            return fragments.get(position);
        }else {
            if (position==0){
                return new HomePostFragment();
            }else {
                return new FavFragment();
            }
        }

    }

    @Override
    public int getCount() {
        return fragments.size();
    }

    //method to add fragment
    public void addFragment(Fragment fragment) {
        fragments.add(fragment);
    }


    @Override
    public int getItemPosition(@NonNull Object object) {
        if (object instanceof FavFragment) {
            return POSITION_UNCHANGED; // don't force a reload
        } else {
            // POSITION_NONE means something like: this fragment is no longer valid
            // triggering the ViewPager to re-build the instance of this fragment.
            return POSITION_NONE;
        }
    }

}

我之前使用这段代码时一切正常。但当FragmentStatePagerAdapter的super(manager)方法在api 28中被弃用时,我开始感到担忧。 当我使用"super(manager,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);"以及替换fragment的第一种方式时,应用程序在运行时崩溃。

此外,当我们从viewpager适配器调用adapter.notifydatasetchanged()时,存在一个缺陷,它不是很有效率。它会重新创建整个viewpager,而我们只是想更改特定的fragment。

因此,第二种方法解决了所有问题。

方法2: 在将实际fragment添加到viewpager之前,我们应该向viewpager添加一个根fragment。在根fragment中,将添加先前添加到viewpager中的实际fragment。然后,我们可以轻松地替换任何fragment,而无需FragmentStatePagerAdapter的帮助。 根fragment的代码:

Rootfragment.java

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentTransaction;

import com.trickbd.trickbdapp.R;

import dagger.android.support.DaggerFragment;

public class RootFragment extends DaggerFragment {
    public RootFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }


    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.root_layout, container, false);
        if (getFragmentManager() != null) {
            FragmentTransaction transaction = getFragmentManager()
                    .beginTransaction();
            transaction.replace(R.id.root_frame, new HomePostFragment());

            transaction.commit();
        }

        return view;

    }
}

root_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/root_frame">

</FrameLayout>

因此,以前的替换代码将会被更新如下

private void changefragment(int position) {
        Fragment newFragment = CategoryPostFragment.newInstance();
        replacefragment(newFragment);
        viewPager.setCurrentItem(position);

    }

public void replacefragment(Fragment fragment){
        getSupportFragmentManager();
        FragmentTransaction trans = getSupportFragmentManager()
                .beginTransaction();
        trans.replace(R.id.root_frame, fragment);
        trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        trans.addToBackStack(null);
        trans.commit();
    }

通过这种方式,我们可以更高效地替换片段。
要获取 rootfragment 中当前添加的片段,我们可以使用以下代码。
if (getSupportFragmentManager().findFragmentById(R.id.root_frame) instanceof HomePostFragment){
            HomePostFragment postFragment = (HomePostFragment) getSupportFragmentManager().findFragmentById(R.id.root_frame);

 }

您可以在这里了解更多相关信息。


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