我正在尝试使用 ViewPager
使用 ViewPager
使用Fragment . 我想要实现的是将位于 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);
}
}
}
...and here the xml files
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
,则替换有效,但层次结构查看器显示奇怪的行为,如下所示 .
一开始的情况是
更换后的情况是
正如你所看到的那样,有一些问题,我希望在更换片段之后找到与第一张图片中显示的状态相同的状态 .
18 回答
我也提出了一个与 Stacks 合作的解决方案 . 这是一个更加 modular 的方法,所以你不必在
FragmentPagerAdapter
中指定每个片段和细节片段 . 它直接来自谷歌演示应用程序's build on top of the Example from ActionbarSherlock which derives if I' .在MainActivity中为 back button 功能添加此项:
当你被删除时,如果你喜欢 save the Fragment State . 让你的Fragment实现接口
SaveStateBundle
在函数中返回一个带有保存状态的bundle . 通过this.getArguments()
实例化后获取包 .你可以 instantiate 这样一个标签:
如果你想在标签栈顶部添加一个片段,那么工作方式类似 . Important :我认为,如果你想在两个Tabs的顶部有2个相同类的实例,它将无法工作 . 我一起快速完成了这个解决方案,所以我只能在不提供任何经验的情况下分享它 .
还有另一种解决方案不需要修改
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()
函数,它也将删除其标记 .这是很多文本,这里的代码应该适用于您的情况:
希望这有助于任何人!
截至2012年11月13日,在ViewPager中重新调整片段似乎变得更加容易 . Google发布了支持嵌套片段的Android 4.2,并且在新的Android Support Library v11中也支持它,所以这将一直回到1.6
除了使用getChildFragmentManager之外,它与替换片段的常规方法非常相似 . 当用户单击后退按钮时,除了nested fragment backstack isn't popped之外似乎有效 . 根据该链接问题中的解决方案,您需要在片段的子管理器上手动调用popBackStackImmediate() . 所以你需要覆盖ViewPager活动的onBackPressed(),你将获得ViewPager的当前片段并在其上调用getChildFragmentManager() . popBackStackImmediate() .
获取当前正在显示的片段有点hacky,我使用了dirty "android:switcher:VIEWPAGER_ID:INDEX" solution但您也可以自己跟踪ViewPager的所有片段,如第二个解决方案on this page中所述 .
所以这是我的ViewPager的代码,其中包含4个ListViews,当用户点击一行时,ViewPager中显示详细视图,并且后退按钮正常工作 . 为了简洁起见,我尝试仅包含相关代码,如果您希望将完整的应用程序上传到GitHub,请留言 .
HomeActivity.java
ListProductsFragment.java
这是我相对简单的解决方案问题 . 此解决方案的关键是使用
FragmentStatePagerAdapter
而不是FragmentPagerAdapter
,因为前者将为您删除未使用的片段,而后者仍保留其实例 . 第二个是在getItem()中使用POSITION_NONE
. 我用一个简单的List来跟踪我的碎片 . 我的要求是使用新列表立即替换整个片段列表,但可以轻松修改以下内容以替换单个片段:我找到了简单的解决方案,即使您想在中间添加新片段或替换当前片段,也能正常工作 . 在我的解决方案中,您应该覆盖
getItemId()
,它应该返回每个片段的唯一ID . 默认情况下不定位 .有它:
注意:在此示例中,
FirstFragment
和SecondFragment
扩展了抽象类PageFragment,其方法为getPage()
.在
onCreateView
方法中,container
实际上是ViewPager
实例 .所以,只是打电话
将更改
ViewPager
中的当前片段 .我已经为索引2和3创建了一个包含3个元素和2个子元素的ViewPager,这就是我想要做的事情 .
我已经在之前的问题和StackOverFlow的答案的帮助下实现了这一点,这里是链接 .
ViewPagerChildFragments
经过研究,我找到了短代码的解决方案 . 首先在片段上创建一个公共实例,如果片段没有在方向更改时重新创建,则只需删除onSaveInstanceState上的片段 .
我做了类似于wize的事情,但在我的回答中你可以随时在两个片段之间进行更改 . 随着wize的回答,我在改变屏幕的方向时遇到了一些问题 . 这是PagerAdapter的样子:
我在适配器容器活动中实现的侦听器在附加它时将它放到片段中,这是活动:
然后在片段中将监听器附加到调用它时:
最后是听众:
在viewpager中更换片段非常复杂,但是非常有可能并且看起来非常光滑 . 首先,您需要让viewpager本身处理删除和添加片段 . 发生的事情是当您替换SearchFragment内部的片段时,您的viewpager会保留其片段视图 . 所以你最终得到一个空白页面,因为当你尝试替换它时会删除SearchFragment .
解决方案是在viewpager内部创建一个侦听器,该侦听器将处理在其外部进行的更改,因此首先将此代码添加到适配器的底部 .
然后,您需要在viewpager中创建一个私有类,该类在您想要更改片段时成为侦听器 . 例如,你可以添加这样的东西 . 请注意,它实现了刚刚创建的接口 . 因此,无论何时调用此方法,它都将运行下面类中的代码 .
这里有两点需要指出:
fragAt0是"flexible"片段 . 它可以采用您提供的任何片段类型 . 这使它成为你最好的朋友,将位置0的片段更改为你想要的片段 .
注意放在'newInstance(listener)构造函数中的侦听器 . 以下是callfragment0Changed(String newFragmentIdentification)`的方法 . 以下代码显示了如何在片段内创建侦听器 .
static nextFragmentListener listenerSearch;
您可以在
onPostExecute
内调用更改这将触发viewpager内部的代码将片段切换到零fragAt0,成为新的searchResultFragment . 在viewpager运行之前,还需要添加两个小块 .
一个是在viewpager的getItem重写方法中 .
现在没有这个最后一块,你仍然会得到一个空白页面 . 有点蹩脚,但它是viewPager的重要组成部分 . 您必须覆盖viewpager的getItemPosition方法 . 通常这个方法会返回POSITION_UNCHANGED,它告诉viewpager保持一切相同,因此getItem将永远不会被调用以将新片段放在页面上 . 这是你可以做的事情的一个例子
就像我说的,代码非常复杂,但你基本上必须为你的情况创建一个自定义适配器 . 我提到的事情可以改变片段 . 浸泡所有东西可能需要很长时间,所以我会耐心等待,但这一切都有意义 . 完全值得花时间,因为它可以使一个非常光滑的应用程序 .
这是处理后退按钮的金块 . 你把它放在你的MainActivity中
您需要在FragmentSearchResults内部创建一个名为backPressed()的方法,该方法调用fragment0changed . 这与我之前显示的代码一起将按下后退按钮 . 祝你好运用于更改viewpager的代码 . 它需要做很多工作,据我所知,没有任何快速的改编 . 就像我说的,你基本上是创建一个自定义viewpager适配器,并让它使用监听器处理所有必要的更改
tl;dr: 使用负责替换其托管内容的主机片段并跟踪后退导航历史记录(如在浏览器中) .
由于您的用例包含固定数量的选项卡,因此我的解决方案运行良好:我们的想法是使用自定义类
HostFragment
的实例填充ViewPager,该实例能够替换其托管内容并保留其自己的后退导航历史记录 . 要替换托管片段,请调用方法hostfragment.replaceFragment()
:所有方法都是使用提供给方法的片段替换id
R.id.hosted_fragment
的帧布局 .查看我的tutorial on this topic以获取更多详细信息和GitHub上的完整工作示例!
这是我实现这一目标的方法 .
首先在
viewPager
选项卡中添加Root_fragment
,在其中要实现按钮单击fragment
事件 . 例;首先,
RootTabFragment
应该包含FragmentLayout
用于更改片段 .然后,在
RootTabFragment
onCreateView
内,为FirstPagerFragment
实施fragmentChange
之后,为
FirstPagerFragment
中的按钮实现onClick
事件,并再次进行片段更改 .希望这会帮助你的家伙 .
我按照@wize和@mdelolmo的回答,得到了解决方案 . 谢谢吨 . 但是,我稍微调整了这些解决方案以改善内存消耗 .
我观察到的问题:
它们保存了被替换的
Fragment
的实例 . 在我的情况下,它是一个片段,持有MapView
,我认为它的代价高昂 . 所以,我维护FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
而不是Fragment
本身 .这是我的实施 .
演示链接在这里.. https://youtu.be/l_62uhKkLyM
出于演示目的,在位置2使用了2个片段
TabReplaceFragment
和DemoTab2Fragment
. 在所有其他情况下,我正在使用DemoTabFragment
实例 .说明:
我将
Switch
从Activity传递给DemoCollectionPagerAdapter
. 根据此开关的状态,我们将显示正确的片段 . 当切换检查改变时,我正在调用SwitchFragListener
的onSwitchToNextFragment
方法,我将pagerAdapterPosChanged
变量的值更改为POSITION_NONE
. 详细了解POSITION_NONE . 这将使getItem无效,我有逻辑在那里实例化正确的片段 . 对不起,如果解释有点乱 .再次非常感谢@wize和@mdelolmo的最初想法 .
希望这有用 . :)
如果此实施有任何缺陷,请告诉我 . 这对我的项目非常有帮助 .
基于@wize的答案,我发现它有用且优雅,我可以实现我想要的部分,因为我希望能够在更换后回到第一个片段 . 我实现了一点修改他的代码 .
这将是FragmentPagerAdapter:
要完成替换,只需定义一个类型为
CalendarPageFragmentListener
的静态字段,并通过相应片段的newInstance
方法进行初始化,然后再调用FirstFragment.pageListener.onSwitchToNextFragment()
或NextFragment.pageListener.onSwitchToNextFragment()
.希望它清楚有用 .
最好的祝福 .
一些提出的解决方案帮助我部分地解决了这个问题,但是在解决方案中仍然存在一个重要的缺陷,即在某些情况下产生了意外异常和黑页内容而不是片段内容 .
问题是FragmentPagerAdapter类正在使用项ID来将缓存的片段存储到FragmentManager . 因此,您还需要覆盖getItemId(int position)方法,以便返回e . G . 顶级页面的位置和详细页面的100个位置 . 否则,先前创建的顶级片段将从缓存而不是细节级片段返回 .
此外,我在这里分享一个完整的例子,说明如何使用ViewPager和使用RadioGroup的标签按钮实现与Fragment页面类似标签的活动,这些按钮允许用详细页面替换顶级页面,并且还支持后退按钮 . 此实现仅支持一个级别的后向堆栈(项目列表 - 项目详细信息),但多级后台堆叠实现很简单 . 此示例在正常情况下运行良好,除非在切换到e时抛出NullPointerException . G . 第二页,更改第一页的片段(虽然不可见),然后返回第一页 . 我'll post a solution to this issue once I'将弄明白:
我已经实施了一个解决方案:
选项卡内的动态片段替换
维护每个标签的历史记录
使用方向更改
实现这一目标的技巧如下:
使用notifyDataSetChanged()方法应用片段替换
仅将碎片管理器用于后台,不使用碎片更换
使用 . 维护历史记录纪念品图案(堆栈)
适配器代码如下:
第一次添加所有选项卡时,我们需要调用createHistory()方法来创建初始历史记录
每次要将片段替换为特定的选项卡时,都要调用:replace(final int position,final Class fragmentClass,final Bundle args)
在后退时你需要调用back()方法:
该解决方案适用于sherlock操作栏和滑动手势 .
要替换
ViewPager
中的片段,您可以将ViewPager
,PagerAdapter
和FragmentStatePagerAdapter
类的源代码移动到项目中并添加以下代码 .进入
ViewPager
:到FragmentStatePagerAdapter:
handleGetItemInvalidated()
确保在下次调用getItem()
之后返回newFragmentgetFragmentPosition()
返回适配器中片段的位置 .现在,要替换片段调用
如果您对示例项目感兴趣,请向我询问消息来源 .
适用于AndroidTeam的解决方案,但是我发现我需要能够像_196055那样返回,但仅仅添加它只会导致片段被替换而不通知ViewPager . 将提供的解决方案与此次要增强功能相结合,将允许您仅通过覆盖活动的
onBackPressed()
方法返回到先前的状态 . 最大的缺点是它一次只能返回一个可能导致多次后退点击希望这有助于某人 .
另外,就
getFragmentPosition()
来说,它的反面几乎是getItem()
. 你知道哪些碎片去了哪里,只要确保你返回它所在的正确位置 . 这是一个例子: