ViewPager2 preloaing next fragment even when offsetlimit is the default(0) - android

I am facing this rather strange problem with viewPager2.
I did some reading and found out that viewPager2 has a default offset limit of 0 which is perfect for my application.
I'm using it with a tab layout and I have 3 fragments (Home, Profile, Notification).
When the activity loads and the first fragment(Home) loads, I can see in my logcat that the next fragment(Profile) is not loaded, as expected.
But when I click on the profile tab something strange happens, the next tab(Notification) is preloaded. The methods
onAttach,onCreate,onCreateView,onActivityCreated,onStart for the Notification tab is called.
Why is this doing so and how to I fix this ?
Logcat Screenshot
I have attached a screen shot of my logcat here.
Thankyou in advance.

I Assume you mean OFFSCREEN_PAGE_LIMIT_DEFAULT not offsetlimit as you are talking about a preloading of fragments problem.
And the default value is -1 not zero https://developer.android.com/reference/androidx/viewpager2/widget/ViewPager2.html#OFFSCREEN_PAGE_LIMIT_DEFAULT
and the default means
Value to indicate that the default caching mechanism of RecyclerView should be used instead of explicitly prefetch and retain pages to either side of the current page.
As this is a performance optimisation of the recyclerview, I would say it's not a guarantee that it won't preload your fragments, it's just left to the caching mechanism of the recyclerview to decide.
There are a number of factors that can affect the recyclerview's caching mechanism.
If preloading of your fragment is a problem because you have dynamic data in it that you only want to be loaded when the page is shown then it would be better to move your fragment to use a "lazy loading" method i.e. only load the data when it is shown.
I had a similar problem with the original viewpager and solved it with "lazy loading". If the timing of loading of your dynamic data is the problem then update the question and then I can outline a possible solution.
Update:3
It seems that Viewpager2 actually works correctly with the Fragments lifecycle unlike the original Viewpager thus you can call updateView() as shown in update2 example from the Fragments onResume method without having to use the pageSelected callback via the Adapter to trigger the update of the View.
Update:
I believe the actual cause from looking at the viewpager2 code is that selecting the Tab does a fake drag with smooth scrolling and smooth scrolling adds the selected item +1 to the cache, if you swipe from Tab0 to Tab1 yourself it does not create Tab2
ViewPager2 is a bit different and "lazy loading" method I used for the original ViewPager does not totally fit but there is a slightly different way to do the same.
The main idea with the original ViewPager was to update the view ONLY when a page was selected using a onPageChangeListener but ViewPager2 uses a callback instead
So add the following after you have created the ViewPager2 (in the Activity onCreate usually)
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
// Tell the recyclerview that position 2 has changed when selected
// Thus it recreates it updating the dynamic data
if (position == 2) {
// the adapter for the ViewPager needs to a member of the Activity class so accessible here
adapter.notifyItemChanged(position);
}
}
});
This is simpler but has a minor drawback that the dynamic data is loaded when it is preloaded and then again when it is actually displayed.
Update2:
A more efficient addition to first method more similar to my original approach
This is a full working example as it is easier to explain.
The main idea is in your fragment with the dynamic data that you ONLY want to load when it is displayed is to create an empty "placeholder" view item and you don't fill it with data in the Fragments onViewCreated, in this example it is a second textview with no text but could be a recyclerview with zero objects or any other type of view.
In your Fragment you then create a method to update the "placeholder" with the data (in this case the method is called updateView() which sets the textview text to the current date and time)
Then in your Fragment Adapter you store a reference to each fragment it creates in a ArrayList (this allows you get the fragment back) and you then create an updateFragment() method in the adapter that uses the position to get the Fragment to be able to call the updateView() on it.
Finally again you use onPageSelected to call the updateFragment with the position you want to dynamically update.
So textview1 shows the data and time the fragement was created and textview2 is only shown on the third Tab and has a date and time on when page was selected. Note that texview1 on "Tab 2" and "Tab 3" is the same time when you click on the "Tab 2" headers to change tabs (this is the problem in the question)
MainActivity.java
package com.test.viewpager2;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import android.os.Bundle;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
public class MainActivity extends AppCompatActivity {
TabLayout tabLayout;
ViewPager2 viewPager2;
ViewPager2Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager2 = findViewById(R.id.viewpager2);
tabLayout = findViewById(R.id.tabLayout);
viewPager2.setAdapter(createCardAdapter());
new TabLayoutMediator(tabLayout, viewPager2,
new TabLayoutMediator.TabConfigurationStrategy() {
#Override public void onConfigureTab(#NonNull TabLayout.Tab tab, int position) {
tab.setText("Tab " + (position + 1));
}
}).attach();
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
// Tell the recyclerview that position 2 has changed when selected
// Thus it recreates it updating the dynamic data
if (position == 2) {
adapter.updateFragment(position);
}
}
});
}
private ViewPager2Adapter createCardAdapter() {
adapter = new ViewPager2Adapter(this);
return adapter;
}
}
ViewPager2Adapter.java
package com.test.viewpager2;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.ArrayList;
public class ViewPager2Adapter extends FragmentStateAdapter {
private static final int numOfTabs = 3;
private ArrayList<Fragment> fragments = new ArrayList<>();
public ViewPager2Adapter(#NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
#NonNull #Override public Fragment createFragment(int position){
Fragment fragment = TextFragment.newInstance(position);
fragments.add(fragment);
return fragment;
}
#Override
public int getItemCount(){
return numOfTabs;
}
public void updateFragment(int position){
Fragment fragment = fragments.get(position);
// Check fragment type to make sure it is one we know has an updateView Method
if (fragment instanceof TextFragment){
TextFragment textFragment = (TextFragment) fragment;
textFragment.updateView();
}
}
}
TextFragment.java
package com.test.viewpager2;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
public class TextFragment extends Fragment {
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private int mParam1;
private View view;
public TextFragment() {
// Required empty public constructor
}
public static TextFragment newInstance(int param1) {
TextFragment fragment = new TextFragment();
Bundle args = new Bundle();
args.putInt(ARG_PARAM1, param1);
fragment.setArguments(args);
Log.d("Frag", "newInstance");
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getInt(ARG_PARAM1);
}
Log.d("Frag", "onCreate");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.d("Frag", "onCreateView");
view = inflater.inflate(R.layout.fragment_text, container, false);
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
TextView textView1 = view.findViewById(R.id.textview1);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss.sss", Locale.US);
String dt = df.format(Calendar.getInstance().getTime());
textView1.setText(dt);
}
public void updateView(){
Log.d("Frag", "updateView");
TextView textView2 = view.findViewById(R.id.textview2);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss.sss", Locale.US);
String dt = df.format(Calendar.getInstance().getTime());
textView2.setText(dt);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d("Frag", "onAttach");
}
#Override
public void onDetach() {
super.onDetach();
Log.d("Frag", "onDetach");
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.tabs.TabLayout
android:id="#+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewpager2"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tabLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TextFragment">
<TextView
android:id="#+id/textview1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/textview2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/textview1" />/>
</androidx.constraintlayout.widget.ConstraintLayout>

You need to notify adapter on every scroll.
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
}
override fun onPageSelected(position: Int) {
myToast(applicationContext, "1 changes $position")
tapPosition = position
super.onPageSelected(position)
}
override fun onPageScrollStateChanged(state: Int) {
if (state.equals(0))
{
pagerAdapter.notifyItemChanged(tapPosition)
}
super.onPageScrollStateChanged(state)
}
})
onPageScrollStateChanged here you must notify adapter.

Related

Fragment is blank in ViewPager

First off: I am aware there are a number of similar questions, however, none of the suggestions worked.
I have a ViewPager that I would like to use with fragments. The ViewPager works, and I can see in the log that I can scroll left and right. I can see the animation when I try to scroll left and there's no previous item (or right when there's no following item). However, the screen remains blank. The fragment itself is functional, however, I've tried it in a different context.
I am using the androidx namespaced packages.
Things I've tried (<--> means I've tried both):
FragmentPagerAdapter <--> FragmentStatePagerAdapter
getFragmentManager() <--> getChildFragmentManager()as the FragmentManager for the adapter
setSaveFromParentEnabled to false.
setOffscreenPageLimit to the number of fragments.
Relevant code (I have taken out sections of code that are irrelevant to this question):
The Fragment that contains the ViewPager:
public class AddListingMediaFragment extends Fragment {
private ViewPager viewPagerTop;
private AddMediaAdapter addMediaAdapter;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_add_listing_media, container, false);
setupViewPagerTop(view);
return view;
}
private void setupViewPagerTop(View view) {
viewPagerTop = view.findViewById(R.id.view_pager_top);
addMediaAdapter = new AddMediaAdapter(getChildFragmentManager());
viewPagerTop.setAdapter(addMediaAdapter);
viewPagerTop.setSaveFromParentEnabled(false);
viewPagerTop.setOffscreenPageLimit(5);
}
}
The Adapter (please do note, as mentioned, that I've tried both FragmentPagerAdapter and FragmentStatePagerAdapter:
public class AddMediaAdapter extends FragmentPagerAdapter {
private static final String TAG = AddMediaAdapter.class.getSimpleName();
public AddMediaAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 5;
}
#Override
public Fragment getItem(int position) {
Log.i(TAG, "getItem: " + String.valueOf(position));
return new AddListingMediaCameraFragment();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
The layout file:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:id="#+id/view_pager_top"
android:layout_width="300dp"
android:layout_height="300dp"
/>
<include
layout="#layout/bottom_sheet_add_listing_media_gallery"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
You do not need to override isViewFromObject in your adapter, FragmentPagerAdapter has already taken care of it. ViewPager calls this method to decide how to render the pages.
Your view == object will return false because the object is always a Fragment not a View. So just drop the override, and it will fix the issue.

Should I use a nested fragment for tabs in a fragment?

I have a Main Activity which uses an AHBottomNavigationView for a menu at the bottom of the screen. When a different menu item is clicked, it creates a new fragment corresponding to that menu item with logic like so (condensed switch statement for the simplicity of this question):
fragmentManager.beginTransaction().replace(R.id.content_id, New TheFragmentForTheTabClicked).commit();
Where content_id is the ID of the Main Activity's ConstraintLayout.
Within the fragment for my first navigation menu item, there are two more tabs (using TabLayout), which replace the screen space with another fragment. This is done with a FragmentPagerAdapter, which is set onto a ViewPager, so tapping each tab changes the sub fragment. So at this point, there is a fragment nested in a fragment nested in a class. Here is what it generally is:
Main Activity
|
+-- Fragment 1 (selected from AHBottomNavigationView)
| |
| +-- Sub-Fragment 1 (selected by clicking the first tab in Fragment 1)
| |
| +-- Sub-Fragment 2 (selected by clicking the second tab in Fragment 1)
|
+-- Fragment 2 (selected from AHBottomNavigationView)
|
+-- Fragment 3 (selected from AHBottomNavigationView)
|
+-- Fragment 4 (selected from AHBottomNavigationView)
So my question is this:
Is the way I am doing this correct, and if not, what would a better way be?
Also, I'm finding that When I tab to Fragment 1 the first time, the swiping and tapping between the two tabs works fine, however if I tap a different bottom navigation menu item (i.e. Fragment 3) and then go back, I get the following 2 issues:
The content in either of the subfragments is not shown
Swiping between the two tabs no longer works. Instead of one motion moving to the different tab, I have to pull across the screen entirely because the indicator gets "stuck" part way between two tabs.
If there is any more information that I can provide, please let me know and I will.
Fragment1.java:
package com.mypackage.mypackage;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple {#link Fragment} subclass.
*/
public class Fragment1 extends Fragment {
private FragmentActivity mContext;
public Fragment1() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_1, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
// Find the view pager that will allow the user to swipe between fragments
ViewPager viewPager = (ViewPager) getView().findViewById(R.id.viewpager);
// Create an adapter that knows which fragment should be shown on each page
// using getFragmentManager() will work too
Fragment1PagerAdapter adapter = new Fragment1PagerAdapter(mContext.getSupportFragmentManager(), mContext);
// Set the adapter onto the view pager
viewPager.setAdapter(adapter);
TabLayout tabLayout = (TabLayout) getView().findViewById(R.id.sliding_tabs);
tabLayout.setupWithViewPager(viewPager);
}
/**
* Override to set context. This context is used for getSupportFragmentManager in onCreateView
* #param activity
*/
#Override
public void onAttach(Activity activity) {
mContext=(FragmentActivity) activity;
super.onAttach(activity);
}
}
fragment_1.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.TabLayout
android:id="#+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabBackground="#color/fragment1TabBackground"
app:tabIndicatorColor="#color/fragment1TabIndicatorColor"/>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
Fragment1PagerAdapter.java
package com.mypackage.mypackage;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.content.Context;
public class Fragment1PagerAdapter extends FragmentPagerAdapter {
private Context context;
public Fragment1PagerAdapter(FragmentManager fm, Context mContext){
super(fm);
context = mContext;
}
#Override
public Fragment getItem(int position){
if (position == 0){
return new SubFragment1();
}
else{
return new SubFragment2();
}
}
#Override
public int getCount() {return 2;}
#Override
public CharSequence getPageTitle(int position) {
switch(position){
case 0:
return context.getResources().getString(R.string.sub_fragment_1_page_title);
case 1:
return context.getResources().getString(R.string.sub_fragment_2_page_title);
default:
return null;
}
}
}
When nesting Fragments inside Fragment with ViewPager and swipe feature as FragmentManager which needs to be provided to Adapter recommended is to use: getChildFragmentManager() instead of getSupportFragmentManager() or getFragmentManager(). Because both are actually related to Activities instead of getChildFragmentManager(), as documentation says, is related to Fragment:
Return a private FragmentManager for placing and managing Fragments
inside of this Fragment.

Can't add a fragment when Creating a new Activity

i'm trying to create a wizard Like Android application, i want to Create an activity and Two dynamic Fragments, the first one will be added when the Activity is created, and the second when the user clicks on a button in the First fragment, right now i can't even add the Fragment to the activity :
Activity onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment fr = new FragmentNumber();
getSupportFragmentManager().beginTransaction().add(fr, "number_fragment").commit();
}
this is my activity code, when i run this, the screen is blank.
the R.layout.activity_main refer to an empty Linear Layout, i don't want to add the fragments there because i need them to be dynamic.
Thanks in advance.
EDIT : pasting more files
activity_main.XML
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello from main"
/>
</FrameLayout>
MaicActivity.java
package com.example.fragmenttraining;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(findViewById(android.R.id.content) != null)
{
Log.d("main activity", "content found");
}
FragmentNumber fr = new FragmentNumber();
//getSupportFragmentManager().beginTransaction().add(android.R.id.content, fr, "number_fragment").commit();
getSupportFragmentManager().beginTransaction().replace(android.R.id.content, fr).commit();
}
FragmentNumber numberFragment;
FragmentFacebook facebookFragment;
public void facebookClicked(View view)
{
numberFragment = new FragmentNumber();
numberFragment.facebookClicked(view);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
Now it's working, but the fragment is not replaced, it displays the content of the activity_main + the content of the FragmentNumber fragment even id i replace it ...
Follow these steps:
Create your main (activity layout) file. In it, add a frame layout which will act as a container for your fragments.
Now create your two fragments. This involves creating two xml files that will be inflated inside your fragment's onCreateView method.
One of your fragments (the first one) should have a button that the user will be able to click. That means you must attach an onClick listener to it inside the onCreateView method after finding it by id.
Now create an interface inside your first fragment and add a method in it that your activity should override after implementing the interface.
When the user clicks that button, inside onClick method, you should call the interface method to notify the activity of the click event.
Inside the activity, when the method is called, create a new instance of the second fragment and add it to view by replacing the first one - or it depends on whether you are using two-pane layout in your activity - in that case, you just add the fragment.
Remember to check if your fragment exists first before simply adding one to view.
I hope these steps help you.
Sample Code
public class WizardActivity extends Activity implements SecondFragment.OnButtonClickedListener
{
private FirstFragment firstFragment;
public void onCreate(Bundle saveInstanceState)
{
super.onCreate(saveInstanceState);
setContentView(R.layout.main);
firstFragment = new FirstFragment();
setFragment(firstFragment, "firstFragment");
}
#Override
public void loadSecondFragment()
{
SecondFragment secondFragment = new SecondFragment();
setFragment(secondFragment, "secondFragment");
}
public void setFragment(Fragment frag, String tag)
{
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragmentContainer);
if(fragment == null)
{
ft.add(R.id.fragmentContainer, frag, tag);
} else {
ft.replace(R.id.fragmentContainer, frag, tag);
}
ft.addToBackStack(null);
ft.commit()
}
}
Now the xml file for main layout.
<LinearLayout ........>
<!--add whatever you need here-->
<FrameLayout
android:id="#+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Now let us create one of your fragments - the first one:
FirstFragment.java
public class FirstFragment extends Fragment implements View.OnClickListener
{
private Activity mActivity;
#Override
public void onAttach(Activity act)
{
super.onAttach(act);
this.mActivity = act;
/*Initialize whatever you need here*/
}
#Override
public View onCreateView(LayoutInflator inflator, ViewGroup container, Bundle saveInstanceState)
{
View v = inflator.inflate(R.layout.first_fragment, container, false);
Button button = (Button)v.findViewById(R.id.button);
button.setOnClickListener(this);
}
#Override
public void onClick(View v)
{
((OnButtonClickListener), mActivity).loadSecondFragment();
}
public interface OnButtonClickListener
{
void loadSecondFragment();
}
}
You should be able to just create the second fragment and have it loaded in the activity when a button is clicked.
Good luck.

View pager With Fragments and Indicator

I am developing an app in which I have implemented ViewPager I want the user to swipe and get the next screen. I am implementing fragments. All is well but I want one more thing. I want to indicate which screen is active now, just like tabs. I searched over the internet but did not find any thing helpful. If there is an idea that would be appriciated.
Here is my view pager adapter and fragment activity and xml layout
import java.util.ArrayList;
import java.util.List;
import com.viewpagerindicator.TitlePageIndicator;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.widget.Toast;
public class MainActivity extends FragmentActivity implements OnPageChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<Fragment> list = new ArrayList<Fragment>();
list.add(MyFragment.newInstance("fragment 1"));
list.add(MyFragment.newInstance("fragment 2"));
list.add(MyFragment.newInstance("fragment 3"));
MyPagerAdapter a = new MyPagerAdapter(getSupportFragmentManager(), list);
ViewPager pager = (ViewPager) findViewById(R.id.viewpager);
pager.setAdapter(a);
}
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
#Override
public void onPageSelected(int arg0) {
// TODO Auto-generated method stub
}
}
adapter.java
package com.example.fragments;
import java.util.List;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.view.View;
public class MyPagerAdapter extends FragmentPagerAdapter {
List<Fragment> fragments;
public MyPagerAdapter(FragmentManager fm,List<Fragment> f) {
super(fm);
this.fragments = f;
}
#Override
public Fragment getItem(int arg0) {
return fragments.get(arg0);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return fragments.size();
}
}
main.xml
<RelativeLayout 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.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_below="#+id/titles"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>
see for this I have created a class to create page indicator
public class DotsScrollBar
{
LinearLayout main_image_holder;
public static void createDotScrollBar(Context context, LinearLayout main_holder,int selectedPage,int count)
{
for(int i=0;i<count;i++)
{
ImageView dot = null;
dot= new ImageView(context);
LinearLayout.LayoutParams vp =
new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
dot.setLayoutParams(vp);
if(i==selectedPage)
{
try {
dot.setImageResource(R.drawable.paging_h);
} catch (Exception e)
{
Log.d("inside DotsScrollBar.java","could not locate identifier");
}
}else
{
dot.setImageResource(R.drawable.paging_n);
}
main_holder.addView(dot);
}
main_holder.invalidate();
}
}
now in your activity class call the function createDotScrollBar as below:
public void updateIndicator(int currentPage) {
dots_scrollbar_holder.removeAllViews();
DotsScrollBar.createDotScrollBar(this, mDotsScrollbarHolder,
mCurrentPage, totalNumberOfPages);
}
and call updateIndicator function inside onPageScrollStateChanged
like this :
#Override
public void onPageScrollStateChanged(int state) {
// TODO Auto-generated method stub
switch (state) {
case 0:
updateIndicator(mCurrentPage);
break;
}
hope this will do the trick.
You basically have three options:
1. Use the android native PagerTitleStrip
It's very easy to implement, simply add it as a child item to your ViewPager in the xml and define the gravity as TOP or BOTTOM like this:
<RelativeLayout 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.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<android.support.v4.view.PagerTitleStrip
android:id="#+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</android.support.v4.view.ViewPager>
</RelativeLayout>
But to be honest it doesn't look very great and I can't say anything about backwards or forwards compatibility. The above was tested in API 17
2. Use a 3rd party library
Like the insanely good ViewPagerIndictor from Jake Wharton
3. Code an implementation of your own
Like suggested in the answer from shruti. Even in this case though I would recommend you to code alongside Jake Whartons example, it's really that amazing!
I created a library to address the need for a page indicator in a ViewPager. My library contains a View called DotIndicator. To use my library, add compile 'com.matthew-tamlin:sliding-intro-screen:3.2.0' to your gradle build file.
The View can be added to your layout by adding the following:
<com.matthewtamlin.sliding_intro_screen_library.indicators.DotIndicator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:numberOfDots=YOUR_INT_HERE
app:selectedDotIndex=YOUR_INT_HERE/>
The above code perfectly replicates the functionality of the dots on the Google Launcher homescreen, however if you want to further customise it then the following attributes can be added:
app:unselectedDotDiameter and app:selectedDotDiameter to set the diameters of the dots
app:unselectedDotColor and app:selectedDotColor to set the colors of the dots
app:spacingBetweenDots to change the distance between the dots
app:dotTransitionDuration to set the time for animating the change from small to big (and back)
Additionally, the view can be created programatically using:
DotIndicator indicator = new DotIndicator(context);
Methods exist to modify the properties, similar to the attributes. To update the indicator to show a different page as selected, just call method indicator.setSelectedItem(int, true) from inside ViewPager.OnPageChangeListener.onPageSelected(int).
Here's an example of it in use:
If you're interested, the library was actually designed to make intro screens like the one shown in the above gif.
Github source available here: https://github.com/MatthewTamlin/SlidingIntroScreen

Action items from Viewpager initial fragment not being displayed

In the application I am developing I am using a ViewPager with fragments and each fragment constructs its own menu independently of all of the other fragments in the ViewPager.
The issue is that sometimes the fragments that are initialised by the ViewPager by default (i.e in it's initial state) are not having their items populated into the action items menu. What's worse is that this issue only occurs intermittently. If I swipe through the ViewPager enough so that the fragments are forced to re-initialise them selves, when I swipe back, the menu populates correctly.
Activity code:
package net.solarnz.apps.fragmentsample;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.support.v13.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
public class FragmentSampleActivity extends Activity {
private ViewPagerAdapter mViewPagerAdapter;
private ViewPager mViewPager;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (mViewPagerAdapter == null) {
mViewPagerAdapter = new ViewPagerAdapter(getFragmentManager());
}
mViewPager = (ViewPager) findViewById(R.id.log_pager);
mViewPager.setAdapter(mViewPagerAdapter);
mViewPager.setCurrentItem(0);
}
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 8;
}
#Override
public Fragment getItem(int position) {
Fragment f = Fragment1.newInstance(position);
// f.setRetainInstance(true);
f.setHasOptionsMenu(true);
return f;
}
}
}
Fragment code:
package net.solarnz.apps.fragmentsample;
import android.app.Fragment;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
public class Fragment1 extends Fragment {
int mNum;
static Fragment newInstance(int num) {
Fragment1 f = new Fragment1();
// Supply num input as an argument.
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mNum = getArguments() != null ? getArguments().getInt("num") : 0;
}
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_list, menu);
}
}
Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<android.support.v4.view.ViewPager
android:id="#+id/log_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Menu:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/menu_refresh"
android:title="Refresh"
android:icon="#android:drawable/ic_delete"
android:showAsAction="ifRoom|withText" />
</menu>
Action menu being populated:
http://i.stack.imgur.com/QFMDd.png
Action menu not being populated:
http://i.stack.imgur.com/sH5Pp.png
You should read this (by xcolw...)
Through experimentation it seems like the root cause is invalidateOptionsMenu getting called more than one without a break on the main thread to process queued up jobs. A guess - this would matter if some critical part of menu creation was deferred via a post, leaving the action bar in a bad state until it runs.
There are a few spots this can happen that aren't obvious:
calling viewPager.setCurrentItem multiple times for the same item
calling viewPager.setCurrentItem in onCreate of the activity. setCurrentItem causes an option menu invalidate, which is immediately followed by the activity's option menu invalidate
Workarounds I've found for each
Guard the call to viewPager.setCurrentItem
if (viewPager.getCurrentItem() != position)
viewPager.setCurrentItem(position);
Defer the call to viewPager.setCurrentItem in onCreate
public void onCreate(...) {
...
view.post(new Runnable() {
public void run() {
// guarded viewPager.setCurrentItem
}
}
}
After these changes options menu inside the view pager seems to work as expected. I hope someone can shed more light into this.
source http://code.google.com/p/android/issues/detail?id=29472
The simple answer is to not use menus within fragments in the ViewPager.
If you do need to use menus within the fragments, what I suggest is loading the menu's through the onCreateOptionsMenu method in the parent Activity. Obviously you will need to be able to determine which menu to show.
I was able to achieve this by using class reflection.
You will also need to use the invalidateOptionsMenu method each time you switch pages. You will need a OnPageChangeListener to call this when the ViewPager changes pages.
I also had same issue. In my case I have one activity with viewpager that contains two fragments, every fragment inflate its own action menu but fragment actions menu not shown.
View pager adapter code
public class ScreensAdapter extends FragmentPagerAdapter {
public TrackerScreensAdapter(Context context, FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 2;
}
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position){
case 0:
fragment = new Fragment1();
break;
case 1:
fragment = new Fragment2();
break;
}
return fragment;
}
}
Activity on create
screensAdapter = new ScreensAdapter(this, getFragmentManager());
viewPager.setAdapter(screensAdapter);
This way my viewPager has two fragments, every fragment fire its own task in onActivityCreated, obtain data and draw its layout based on obtained data. Also every fragment has onCreateOptionsMenu
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
MyTask task = new MyTask();
task.setTaskListener(this);
task.execute();
}
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_menu, menu);
}
Spent many times to solve this problem and figure out why fragment menu not shows.
All that I was need is
screenAdapter = new ScreenAdapter(this, getFragmentManager());
viewPager.post(new Runnable() {
public void run() {
viewPager.setAdapter(screenAdapter);
}
});
In my case I traced the root cause of the issue to what I believe is a bug in FragmentStatePagerAdapter which is calling Fragment#setMenuVisibility to false and failing to properly set it back to true when it restores it's state.
Workarounds:
Use FragmentPagerAdapter instead of FragmentStatePagerAdapter
If you must use FragmentStatePagerAdapter, in your adapter subclass override setPrimaryItem like so:
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
//This is a workaround for a bug in FragmentStatePagerAdapter
Fragment currentItem = getItem(position);
if (currentItem != null) {
currentItem.setMenuVisibility(true);
currentItem.setUserVisibleHint(true);
}
}
First create a method in the sub class of FragmentPagerAdapter to get the current fragment
public SherlockFragment getFragment() {
return currentFragment;
}
#Override
public void onTabSelected(final Tab tab, FragmentTransaction ft) {
((SherlockFragmentActivity) mContext).invalidateOptionsMenu();
Fragment f = ((SherlockFragmentActivity) mContext)
.getSupportFragmentManager().findFragmentByTag(
makeFragmentName(tab.getPosition()));
currentFragment=(SherlockFragment) f;
}
Now override below methods in Main Actvity
#Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mTabsAdapter.getPositionOfTabSelected() != 0) {
menu.add("Read").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add("Write").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add("Clear").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add("Factory data reset").setShowAsAction(
MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
return super.onCreateOptionsMenu(menu);
}
Now call the onOptionItemSelected from activity to fragment
#Override
public boolean onOptionsItemSelected(MenuItem item) {
mTabsAdapter.getFragment().onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
I solved a very similar issue in which the Action bar icons assigned by the fragment inside of a ViewPager were disappearing onPause(). They would reappear when the Fragment came back into view and the user swiped left or right, but not immediately. The solution was calling notifyDataSetChanged() on the PagerAdapter in the onResume() method of the fragment.
#Override
public void onResume() {
mPagerAdapter.notifyDataSetChanged();
}
I was having this problem with the Action Bar Items while I was using HorizontalScrollView to show the tabs but I changed to PagerTitleStrip and the problem was solved.
Perhaps this information can help someone else.
My solution to this problem was to only inflate fragment menus if the fragment is currently visible. This solution may be too specific for your purposes, but it might help someone.
In the main activity:
boolean isFragmentVisible(int fragmentIndex) { ... }
In onCreateOptionsMenu() in your fragment:
if ( getActivity().isFragmentVisible(HOME_FRAGMENT_POS) ) {
inflater.inflate(R.menu.menu_home_fragment, menu);
}

Categories

Resources