我试图使用片段与一个ViewPager使用FragmentPagerAdapter。
我想要实现的是替换一个片段,位于ViewPager的第一页,与另一个。
寻呼机由两个页面组成。第一个是FirstPagerFragment,第二个是SecondPagerFragment。点击第一页的一个按钮。我想用NextFragment替换FirstPagerFragment。
下面是我的代码。
public class FragmentPagerActivity extends FragmentActivity {
static final int NUM_ITEMS = 2;
MyAdapter mAdapter;
ViewPager mPager;
@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);
}
/**
* Pager Adapter
*/
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
if(position == 0) {
return FirstPageFragment.newInstance();
} else {
return SecondPageFragment.newInstance();
}
}
}
/**
* Second Page FRAGMENT
*/
public static class SecondPageFragment extends Fragment {
public static SecondPageFragment newInstance() {
SecondPageFragment f = new SecondPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.second, container, false);
}
}
/**
* FIRST PAGE FRAGMENT
*/
public static class FirstPageFragment extends Fragment {
Button button;
public static FirstPageFragment newInstance() {
FirstPageFragment f = new FirstPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
View root = inflater.inflate(R.layout.first, container, false);
button = (Button) root.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(null);
trans.commit();
}
});
return root;
}
/**
* Next Page FRAGMENT in the First Page
*/
public static class NextFragment extends Fragment {
public static NextFragment newInstance() {
NextFragment f = new NextFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.next, container, false);
}
}
}
...这里是XML文件
fragment_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
</LinearLayout>
first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/first_fragment_root_id"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/button"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="to next"/>
</LinearLayout>
现在的问题是……我应该使用哪个ID
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
如果我用r。id。first_fragment_root_id,替换工作,但Hierarchy Viewer显示一个奇怪的行为,如下所示。
一开始的情况是
更换后的情况是
正如你所看到的,有一些错误,我希望在替换片段后找到与第一张图片中相同的状态。
要替换ViewPager中的片段,你可以将ViewPager、PagerAdapter和FragmentStatePagerAdapter类的源代码移动到你的项目中,并添加以下代码。
分为:viewpage:
public void notifyItemChanged(Object oldItem, Object newItem) {
if (mItems != null) {
for (ItemInfo itemInfo : mItems) {
if (itemInfo.object.equals(oldItem)) {
itemInfo.object = newItem;
}
}
}
invalidate();
}
FragmentStatePagerAdapter:
public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) {
startUpdate(container);
// remove old fragment
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
int position = getFragmentPosition(oldFragment);
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, null);
mFragments.set(position, null);
mCurTransaction.remove(oldFragment);
// add new fragment
while (mFragments.size() <= position) {
mFragments.add(null);
}
mFragments.set(position, newFragment);
mCurTransaction.add(container.getId(), newFragment);
finishUpdate(container);
// ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
handleGetItemInbalidated(container, oldFragment, newFragment);
container.notifyItemChanged(oldFragment, newFragment);
}
protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment);
protected abstract int getFragmentPosition(Fragment fragment);
handleGetItemInvalidated()确保下一次调用getItem()后返回newFragment
getFragmentPosition()返回片段在适配器中的位置。
现在,替换片段调用
mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);
如果你对一个示例项目感兴趣,请向我索取资源。
我做了一些类似于wize的事情,但在我的回答中,你可以随时在两个片段之间更改。在改变屏幕方向之类的时候,我遇到了一些问题。这是PagerAdapter的样子:
public class MyAdapter extends FragmentPagerAdapter
{
static final int NUM_ITEMS = 2;
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
private Map<Integer, String> mFragmentTags;
private boolean isNextFragment=false;
public MyAdapter(FragmentManager fm)
{
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<Integer, String>();
}
@Override
public Fragment getItem(int position)
{
if (position == 0)
{
if (isPager) {
mFragmentAtPos0 = new FirstPageFragment();
} else {
mFragmentAtPos0 = new NextFragment();
}
return mFragmentAtPos0;
}
else
return SecondPageFragment.newInstance();
}
@Override
public int getCount()
{
return NUM_ITEMS;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object obj = super.instantiateItem(container, position);
if (obj instanceof Fragment) {
// record the fragment tag here.
Fragment f = (Fragment) obj;
String tag = f.getTag();
mFragmentTags.put(position, tag);
}
return obj;
}
public void onChange(boolean isNextFragment) {
if (mFragmentAtPos0 == null)
mFragmentAtPos0 = getFragment(0);
if (mFragmentAtPos0 != null)
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
if (!isNextFragment) {
mFragmentAtFlashcards = new FirstPageFragment();
} else {
mFragmentAtFlashcards = new NextFragment();
}
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object)
{
if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
public Fragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return mFragmentManager.findFragmentByTag(tag);
}
}
我在适配器容器活动中实现的侦听器,用于在附加片段时将其放到片段中,这是活动:
public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {
//...
@Override
public void onChange(boolean isNextFragment) {
if (pagerAdapter != null)
pagerAdapter.onChange(isNextFragment);
}
//...
}
然后在片段中放置侦听器,当附加调用它:
public class FirstPageFragment extends Fragment{
private ChangeFragmentListener changeFragmentListener;
//...
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
changeFragmentListener = ((PagerContainerActivity) activity);
}
@Override
public void onDetach() {
super.onDetach();
changeFragmentListener = null;
}
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}
最后是听众:
public interface changeFragmentListener {
void onChange(boolean isNextFragment);
}
Works Great with AndroidTeam's solution, however I found that I needed the ability to go back much like FrgmentTransaction.addToBackStack(null) But merely adding this will only cause the Fragment to be replaced without notifying the ViewPager. Combining the provided solution with this minor enhancement will allow you to return to the previous state by merely overriding the activity's onBackPressed() method. The biggest drawback is that it will only go back one at a time which may result in multiple back clicks
private ArrayList<Fragment> bFragments = new ArrayList<Fragment>();
private ArrayList<Integer> bPosition = new ArrayList<Integer>();
public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) {
startUpdate(container);
// remove old fragment
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
int position = getFragmentPosition(oldFragment);
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
//Add Fragment to Back List
bFragments.add(oldFragment);
//Add Pager Position to Back List
bPosition.add(position);
mSavedState.set(position, null);
mFragments.set(position, null);
mCurTransaction.remove(oldFragment);
// add new fragment
while (mFragments.size() <= position) {
mFragments.add(null);
}
mFragments.set(position, newFragment);
mCurTransaction.add(container.getId(), newFragment);
finishUpdate(container);
// ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
handleGetItemInvalidated(container, oldFragment, newFragment);
container.notifyItemChanged(oldFragment, newFragment);
}
public boolean popBackImmediate(ViewPager container){
int bFragSize = bFragments.size();
int bPosSize = bPosition.size();
if(bFragSize>0 && bPosSize>0){
if(bFragSize==bPosSize){
int last = bFragSize-1;
int position = bPosition.get(last);
//Returns Fragment Currently at this position
Fragment replacedFragment = mFragments.get(position);
Fragment originalFragment = bFragments.get(last);
this.replaceFragments(container, replacedFragment, originalFragment);
bPosition.remove(last);
bFragments.remove(last);
return true;
}
}
return false;
}
希望这能帮助到一些人。
同样,就getFragmentPosition()而言,它几乎是反向的getItem()。你知道哪些片段去哪里,只是确保你返回正确的位置它将在。这里有一个例子:
@Override
protected int getFragmentPosition(Fragment fragment) {
if(fragment.equals(originalFragment1)){
return 0;
}
if(fragment.equals(replacementFragment1)){
return 0;
}
if(fragment.equals(Fragment2)){
return 1;
}
return -1;
}
还有另一种解决方案,不需要修改ViewPager和FragmentStatePagerAdapter的源代码,它与作者使用的FragmentPagerAdapter基类一起工作。
我想先回答作者的问题,他应该使用哪个ID;它是容器的ID,也就是视图分页器本身的ID。但是,正如您自己可能注意到的那样,在代码中使用该ID不会发生任何事情。我将解释为什么:
首先,要使ViewPager重新填充页面,需要调用驻留在适配器基类中的notifyDataSetChanged()。
其次,ViewPager使用getItemPosition()抽象方法来检查哪些页面应该销毁,哪些页面应该保留。这个函数的默认实现总是返回POSITION_UNCHANGED,这将导致ViewPager保留所有当前页面,从而不会附加新页面。因此,为了使片段替换工作,getItemPosition()需要在适配器中被重写,并且在使用旧的、要隐藏的片段作为参数调用时必须返回POSITION_NONE。
这也意味着你的适配器总是需要知道哪个片段应该显示在位置0,FirstPageFragment还是NextFragment。一种方法是在创建FirstPageFragment时提供一个侦听器,当切换片段时将调用该侦听器。我认为这是一件好事,让你的片段适配器处理所有的片段切换和调用ViewPager和FragmentManager。
第三,FragmentPagerAdapter通过从位置派生的名称来缓存使用的片段,因此,如果在位置0有一个片段,即使类是新的,它也不会被替换。有两种解决方案,但最简单的是使用FragmentTransaction的remove()函数,该函数也将删除其标记。
这是大量的文本,这里是代码,应该在你的情况下工作:
public class MyAdapter extends FragmentPagerAdapter
{
static final int NUM_ITEMS = 2;
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
public MyAdapter(FragmentManager fm)
{
super(fm);
mFragmentManager = fm;
}
@Override
public Fragment getItem(int position)
{
if (position == 0)
{
if (mFragmentAtPos0 == null)
{
mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()
{
public void onSwitchToNextFragment()
{
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
mFragmentAtPos0 = NextFragment.newInstance();
notifyDataSetChanged();
}
});
}
return mFragmentAtPos0;
}
else
return SecondPageFragment.newInstance();
}
@Override
public int getCount()
{
return NUM_ITEMS;
}
@Override
public int getItemPosition(Object object)
{
if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
}
public interface FirstPageFragmentListener
{
void onSwitchToNextFragment();
}