I'm trying to setup a FragmentStatePagerAdapter to create a scrollable tab interface on one of my fragments similar to this example. In my activity's onCreate:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.base);
setVersionString();
setupActionBar();
setupNavigationDrawer();
setupFragments();
}
I set the content view to the following layout (R.layout.base):
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BaseActivity" >
<!-- The main content view -->
<FrameLayout
android:id="#+id/main_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- The navigation drawer -->
<ListView
android:id="#+id/nav_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#FFF" />
</android.support.v4.widget.DrawerLayout>
I then call the following function:
private void setupFragments() {
// Check that the activity is using the correct layout
if (findViewById(R.id.main_frame) != null) {
// Initialize the first fragment
MainFragment firstFrag = new MainFragment();
// Pass any extras from an intent to the Fragment
firstFrag.setArguments(getIntent().getExtras());
// Add the first Fragment to the screen
getSupportFragmentManager().beginTransaction()
.add(R.id.main_frame, firstFrag).commit();
tabsAdapter = firstFrag.getAdapter();
Log.d("Base.LOG", "tabsAdapter = " + tabsAdapter);
tabsAdapter.addTab(DummyFragment.class, null);
}
}
In an attempt to set a FragmentStatePagerAdapter as the adapter for the view in the fragment's layout (R.layout.main):
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainFragment" >
<android.support.v4.view.PagerTitleStrip
android:id="#+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:textColor="#fff"
android:paddingTop="4dp"
android:paddingBottom="4dp" />
</android.support.v4.view.ViewPager>
And my fragment's code looks lie this:
private View mView;
private TabbedPagerAdapter mTabPageAdapter;
private ViewPager mTabPager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("Main.LOG", "onCreate called");
mView = inflater.inflate(R.layout.main, container, false);
Log.d("Main.LOG", "Layout inflated");
setupTabs();
Log.d("Main.LOG", "Tabs setup");
return mView;
}
#Override
public void onResume() {
super.onResume();
Log.d("Main.LOG", "onResume called");
}
/** Set up the ViewPager and Adapter to handle the primary tabs */
private void setupTabs() {
// Initialize the adapter
mTabPageAdapter = new TabbedPagerAdapter(getActivity(),
getActivity().getSupportFragmentManager());
// Initialize the view pager
mTabPager = (ViewPager) mView.findViewById(R.id.main_page);
Log.d("Main.LOG", "mTabPager = " + mTabPager);
Log.d("Main.LOG", "mTabPageAdapter = " + mTabPageAdapter);
mTabPager.setAdapter(mTabPageAdapter);
}
/* Accessor for TabbedPagerAdapter */
public TabbedPagerAdapter getAdapter() {
return mTabPageAdapter;
}
The app force closes when I try to add a tab with a NullPointerException. The debug messages in the log show that:
D/Base.LOG(27173): tabsAdapter = null
is displayed before any of the fragment's debug messages. How can I ensure that the layout is inflated before trying to access any of it's Views or subclasses?
As per my comment
public class MainFragment extends Fragment
{
private MainInterface mInterface = null;
//make sure to set the above somehow
public interface MainInterface{
public void onFragmentReady(.....);
}
... //Do regular stuff here
public void onResume()
{
//Do what you need to do in onResume
if(mInterface != null)
{
mInteface.onFragmentReady();
}
}
}
This should allow you to know when the fragment is ready and you can do your getTabAdapter calls then.
Are you calling setContentView(...) inside the onCreate(...) method of your Activity before calling setupFragments(...)?
If yes: Does the layout you are assigning to the Activity contain the ViewPager?
My recommendation: Since you are obviously using a ViewPager inside a Fragment, do the initialization of the ViewPager inside the Fragments onCreateView(...) method.
Example:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ASSUMING your layout.main contains the ViewPager
View v = inflater.inflate(R.layout.main, container, false);
ViewPager pager = (ViewPager) v.findViewById(R.id.pager);
// and so on ...
return v;
}
Related
in my app I have an ActionBarActivity (I'm using support library with AppCompat) that uses the SlidingTabLayout class from Google (taken from here). So this is the XML code of the activity's layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_series_details"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".SeriesDetailsActivity">
<com.my.package.SlidingTabLayout
android:id="#+id/series_details_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="2dp"
android:background="#color/primary_material_dark" />
<android.support.v4.view.ViewPager
android:id="#+id/series_details_pager"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1" />
</LinearLayout>
In this activty, when user press an option in the action bar, I want to add a Fragment with a custom animation. This is the code that handle menu click:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
/* ... OTHER CASES ... */
case R.id.menu_voption:
newFragment = MyNewFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(
R.anim.slide_up,
R.anim.slide_down
)
.add(R.id.activity_series_details, newFragment)
.commit();
editing = true;
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
Doing this, my new fragment is correctly added to the activty and replace the currente fragment but not tab layout, tha remains visible. So I've tryed to add this line before start transaction:
tabsHost.setVisibility(View.GONE);
where tabsHost is the SlidingTabLayout. With this modification, the tabs layout disappear and the new fragment is correctly shown, but only in the API Level >= 21. In my Samsung Galaxy S4 (that runs API 19) and in all other emulators with lesser API level than 21 (my target is 11+), the tabs layout disappear but new fragment is not visible. I'm pretty sure is my fault, but I can't figure why. Thanks all for attention.
Since the SlidingTabLayout is not a fragment, it cannot be managed by the FragmentManager. You would have to make it part of a fragment and add it to your Activity. This is possible with getChildFragmentManager.
Move your activity layout to a fragment:
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_series_details"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
fragment_sliding_tab.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_series_details"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.my.package.SlidingTabLayout
android:id="#+id/series_details_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="2dp"
android:background="#color/primary_material_dark" />
<android.support.v4.view.ViewPager
android:id="#+id/series_details_pager"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1" />
</LinearLayout>
SlidingTabFragment.java
public class SlidingTabFragment extends Fragment {
private PagerAdapter mPagerAdapter;
private ViewPager mViewPager;
public static SlidingTabFragment newInstance() {
SlidingTabFragment fragment = new SlidingTabFragment();
return fragment;
}
public SlidingTabFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mPagerAdapter = new PagerAdapter(getChildFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) view.findViewById(R.id.series_details_pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
SlidingTabLayout tabs = (SlidingTabLayout) view.findViewById(R.id.series_details_tabs);
tabs.setViewPager(mViewPager);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_sliding_tab, container, false);
}
}
Add your fragment in your Activity's onCreate:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment slidingTabFragment = SlidingTabFragment.newInstance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.activity_series_details, slidingTabFragment).commit();
}
I'm trying to put together a simple view pager app that I want to extend latter on.
Currently when I'm calling ViewPager vPager = (ViewPager) findViewById(R.id.viewPager); in the onCreate method of my FragmentActivity it returns null.
Here is the full explanation:
My GUI xml looks like this:
\res\layout\view_fragment.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Medium Text"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
The corresponding Fragment class looks like this:
public class ViewFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.view_fragment, container, false);
}
}
and I have a pager xml like this:
\res\layout\view_pager.xml
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And I have the Pager Activity which looks like this:
public class ScreenPagerActivity extends FragmentActivity {
private ViewPager vPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_fragment); // The view
vPager = (ViewPager) findViewById(R.id.viewPager);
ScreenPagerAdapter pagerAdapter = new ScreenPagerAdapter(getSupportFragmentManager());
vPager.setAdapter(pagerAdapter);
}
#Override
public void onBackPressed() {
if (vPager.getCurrentItem() == 0) {
// If the user is currently looking at the first step, allow the system to handle the
// Back button. This calls finish() on this activity and pops the back stack.
super.onBackPressed();
} else {
// Otherwise, select the previous step.
vPager.setCurrentItem(vPager.getCurrentItem() - 1);
}
}
private class ScreenPagerAdapter extends FragmentStatePagerAdapter {
public ScreenPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
return new ViewFragment();
}
#Override
public int getCount() {
return 5;
}
}
}
However, the statement vPager = (ViewPager) findViewById(R.id.viewPager); always returns null. Can anyone tell me where I am going wrong.
Just incase it helps, my Manifest file for the activity snippet looks like this:
<activity
android:name="uk.co.jeeni.android.androidpagerview.ScreenPagerActivity"
android:label="#string/app_name"
android:parentActivityName="uk.co.jeeni.android.androidpagerview.MainActivity"
android:uiOptions="splitActionBarWhenNarrow" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="uk.co.jeeni.android.androidpagerview.MainActivity" />
</activity>
Please help!!!
Thanks
I think you choose the wrong view. Try this:
setContentView(R.layout.view_pager); // The view where your ViewPager is!
instead of
setContentView(R.layout.view_fragment); // The view for your Fragment.
That's why your ViewPager returns null, because the method can't find it on the choosen layout.
HTH
I have an activity with two ViewPagers both working with a FragmentStatePagerAdapter. But when the ViewPagers have no id, or both have the same id, they will not work correctly.
Activity layout (activity.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.app.MainActivity">
<FrameLayout
android:id="#+id/frame1"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp">
<include layout="#layout/merge_pager" />
</FrameLayout>
<FrameLayout
android:id="#+id/frame2"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp">
<include layout="#layout/merge_pager" />
</FrameLayout>
</LinearLayout>
Merge layout (merge_pager.xml):
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</merge>
Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
FrameLayout frame1 = (FrameLayout) findViewById(R.id.frame1);
FrameLayout frame2 = (FrameLayout) findViewById(R.id.frame2);
ViewPager vp1 = (ViewPager) frame1.getChildAt(0);
ViewPager vp2 = (ViewPager) frame2.getChildAt(0);
if (vp1 == null | vp2 == null) {
throw new NullPointerException();
}
vp1.setAdapter(new ColorAdapter(getSupportFragmentManager()));
vp2.setAdapter(new ColorAdapter(getSupportFragmentManager()));
}
private class ColorAdapter extends FragmentStatePagerAdapter {
public ColorAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public Fragment getItem(int i) {
return new ColorFragment();
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
}
public static class ColorFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
TextView tv = new TextView(container.getContext());
Drawable d = new ColorDrawable(randomColor());
tv.setBackgroundDrawable(d);
return tv;
}
public static int randomColor() {
return Color.rgb(random255(), random255(), random255());
}
public static int random255() {
return
(int) (Math.random() * (double) 255);
}
Using this code, the second ViewPager won't work. I suspected it might be due to clashing ids (R.id.view_pager), so I removed the id from the merge file. However, this results in a fatal exception:
FATAL EXCEPTION: main
android.content.res.Resources$NotFoundException: Unable to find resource ID #0xffffffff
What is up with this?
It says in the docs:
When using FragmentPagerAdapter the host ViewPager must have a valid ID set.
So at least it's documented that the adapter won't work without an id for its pager. I suppose you can then take 'valid' to mean 'unique in the activity view hierarchy'.
My application uses multiple nested fragments. Some of these fragments are placeholders. When I try to replace placeholders with initial set of fragments I get
java.lang.IllegalStateException: Activity has been destroyed
I am sure that activity is fine and the issue is caused by fragments not being attached to the activity. So how do know when all nested fragments are attached to the activity?
Additional information about my setup:
I have an Activity that has fragment declaration in it's layout file:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainLayout"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/rightPane"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.company.PagerFragment"/>
</LinearLayout>
The PagerFragment is the host for view pager.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
I put two Fragments into the pager. They serve as placeholders.
I want to replace placeholders with initial fragments but when I initiate the replacement in activity's onCreate method I get the aforementioned exception.
Everything works fine if I use new Handler().postDelayed(..) in activity's onCreate method. I would rather to not use the delay approach for obvious reasons.
I tried to replace placeholders in other activity's methods like onStart(), onResume(), onResumeFragments(), onPostResume() but that didn't help.
EDIT:
public class PagerFragment extends Fragment
{
..
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_right_pane, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null)
{
m_primary = new PlaceholderFragment();
m_secondary = new PlaceholderFragment();
}
else
{
FragmentManager fm = getChildFragmentManager();
m_primary = (PlaceholderFragment)fm.getFragment(savedInstanceState, FRAGMENT_TAG_PRIMARY);
m_secondary = (PlaceholderFragment)fm.getFragment(savedInstanceState, FRAGMENT_TAG_SECONDARY);
}
Fragment[] fragments = new Fragment[] { m_primary, m_secondary };
PagerAdapter pagerAdapter = new FragmentPagerAdapter(getChildFragmentManager(), fragments);
m_viewPager = (ViewPager)view.findViewById(R.id.pager);
m_viewPager.setAdapter(pagerAdapter);
m_viewPager.setOnPageChangeListener(this);
m_viewPager.setCurrentItem(0, true);
}
..
}
I have a Fragment that is part of a ViewPager. In this Fragment I have a ViewGroup child. Now, why in my MainActivity's onCreate() after having instantiated my ViewPager and adapter, my Container is getting null?
Here is my onCreate():
private MyAdapter mAdapter;
private ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mContainerView = (ViewGroup) findViewById(R.id.container);
//Here mContainerView is already null
...
}
Here is the Fragment that is part of a ViewPager that contains mContainerView
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- This is my ViewGroup -->
<LinearLayout android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:showDividers="middle"
android:divider="?android:dividerHorizontal"
android:animateLayoutChanges="true"
android:paddingLeft="16dp"
android:paddingRight="16dp" />
</ScrollView>
<TextView android:id="#android:id/empty"
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="32dp"
android:text="#string/message_empty_layout_changes"
android:textColor="?android:textColorSecondary" />
</FrameLayout>
If I'm reading your question correctly, you're trying to access the Fragment's View (and children) using the Activity's findViewById() method. That shouldn't work since the Fragment inflates its own layout and Fragments are not traditional Views.
If you know the Fragment is instantiated and you can retrieve it, you can get an instance of the ViewGroup using
yourFragment#getView().findViewById()
If not, you can make an interface that your Activity implements with a method that accepts a ViewGroup as the argument. Then in the Fragment's onCreateView(), have the Fragment pass the ViewGroup off to the interface. You can cast directly to your Activity, but an interface is cleaner.
Eg
public class Fragment {
public interface ViewGroupCreateListener{
public void onViewGroupCreated (ViewGroup v);
}
private ViewGroupCreateListener listener;
public void onAttach (Activity a){
super.onAttach (a);
listener = (ViewGroupCreateListener) a;
}
public View onCreateView (/*all its arguments here*/){
View v = inflater.inflate (R.layout.your_layout);
ViewGroup group = v.findViewById (R.id.container);
listener.onViewGroupCreated(group);
return v;
}
}
Your Activity would look something like:
public class MainActivity extends Activity implements ViewGroupCreateListener, OtherInterface1, OtherInterface2{
private ViewGroup mViewGroup;
public void onViewGroupCreated (ViewGroup v){
mViewGroup = v;
}
}
This is nice since if the pager re-instantiates the Fragment, the Activity still gets a valid instance of the ViewGroup.
Or, if depending on what you're actually trying to achieve with this ViewGroup, you may be able to do this processing inside the Fragment itself.