Android Fragments - Multiple tabs/pageflows, custom backstack - android

I'm running into memory issues with fragments and I could use some help as to the appropriate path to take. I cannot use a simple backstack because the application needs to retain several paths that the user takes within the application (and the user can jump back and forth). The navigation handles fragments in this way:
transaction.hide(currentFragment).show(newFragment).commit();
What I think would help my situation is having the view of the fragment temporarily destroyed and then recreated when the fragment gets placed back in view (instead of simply hiding the UI from the user's view). From reading the API, it doesn't sound like the hide method does this. Does anyone know if there are some built-in methods to the FragmentTransaction/FragmentManager/Fragment class that will allow me to do this?
Another option I'm considering is creating my own lifecycle for each fragment. I've explored using tabhost as well, but it doesn't appear it's going to solve the memory issues. If you have another idea, I'm open to it.
Thanks guys, I appreciate your help with this.

I came up with a solution to achieve a better memory managed solution (adapter-based) for having multiple user journeys saved at the same time (independent tabs with something that represents backstacks). I scrapped using the FragmentManager as it was not conducive to this behavior. I will leave my answer here in case anyone else runs into a similar problem of needing this idea of multiple backstacks using ViewPagers with FragmentStatePagerAdapter - one viewpager for each tab in their app (this can still be cleaned up more but you can get the jist):
Primary Activity:
public class MultiTabbedActivity extends FragmentActivity implements NavigationFragment.OnNavigationSelectedListener
{
private Page1Fragment mPage1;
private Page2Fragment mPage2;
private CustomFragment mCurrent;
private String mNav;
private List<CustomFragment> mPage1BackStack;
private List<CustomFragment> mPage2BackStack;
private CustomViewPager mPagerPage1;
private CustomViewPager mPagerPage2;
private boolean mUpdating = false;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// initialize the backstacks
mPage1BackStack = new ArrayList<CustomFragment>();
mPage2BackStack = new ArrayList<CustomFragment>();
mPage1 = new Page1Fragment();
mPage2 = new Page2Fragment();
mPage1BackStack.add(mPage1);
mPage2BackStack.add(mPage2);
mPagerPage1 = (CustomViewPager) findViewById(R.id.page1_fragment);
mPagerPage2 = (CustomViewPager) findViewById(R.id.page2_fragment);
mPagerPage1.setOffscreenPageLimit(3); //Customizable to determine how many
mPagerPage2.setOffscreenPageLimit(3); //fragments you want to hold in memory
mPagerPage1.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage1BackStack));
mPagerPage2.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage2BackStack));
displayPager(mPagerPage1);
mCurrent = mPage1;
mNav = GlobalConstants.PAGE_PAGE1;
}
private void displayPager(ViewPager pager)
{
mPagerPage1.setVisibility(ViewPager.GONE);
mPagerPage2.setVisibility(ViewPager.GONE);
pager.setCurrentItem(pager.getAdapter().getCount() - 1);
pager.getAdapter().notifyDataSetChanged();
pager.setVisibility(ViewPager.VISIBLE);
}
#Override
public void onNavSelected(String page)
{
if (page != null && page != "" && !mUpdating)
{
// Determine the Fragment selected
if (page.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
{
mNav = GlobalConstants.PAGE_PAGE1;
displayPager(mPagerPage1);
mCurrent = mPage1BackStack.get(mPage1BackStack.size() - 1);
}
else if (page.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
{
mNav = GlobalConstants.PAGE_PAGE2;
displayPager(mPagerPage2);
mCurrent = mPage2BackStack.get(mPage2BackStack.size() - 1);
}
}
}
#Override
public void onBackPressed()
{
PageFragment navFrag = null;
// Update the Navigation Menu to indicate the currently visible
// Fragment
try
{
mUpdating = true;
if (mPage1BackStack.size() > 1 && mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
{
mPagerPage1.setCurrentItem(mPage1BackStack.size() - 2);
mPage1BackStack.remove(mPage1BackStack.size() - 1);
navFrag = mPage1BackStack.get(mPage1BackStack.size() - 1);
mPagerPage1.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage1BackStack));
}
else if (mPage2BackStack.size() > 1 && mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
{
mPagerPage2.setCurrentItem(mPage2BackStack.size() - 2);
mPage2BackStack.remove(mPage2BackStack.size() - 1);
navFrag = mPage2BackStack.get(mPage2BackStack.size() - 1);
mPagerPage2.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage2BackStack));
}
else
{
super.onBackPressed();
}
if (navFrag != null)
mCurrent = navFrag;
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
mUpdating = false;
}
}
public void updateFragment(PageFragment newFragment)
{
if (mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
{
mPage1BackStack.add(newFragment);
displayPager(mPagerPage1);
}
else if (mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
{
mPage2BackStack.add(newFragment);
displayPager(mPagerPage2);
}
mCurrent = newFragment;
}
}
CustomViewPager:
public class CustomViewPager extends ViewPager {
private boolean enabled;
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.enabled = false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (this.enabled) {
return super.onTouchEvent(event);
}
return false;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (this.enabled) {
return super.onInterceptTouchEvent(event);
}
return false;
}
public void setPagingEnabled(boolean enabled) {
this.enabled = enabled;
}
}
CustomAdapter:
public class CustomFragmentAdapter extends FragmentStatePagerAdapter
{
List<CustomFragment> mFragments;
public CustomFragmentAdapter(FragmentManager fm, List<CustomFragment> fragments)
{
super(fm);
mFragments = fragments;
}
public List<PageFragment> getmFragments()
{
return mFragments;
}
public void setmFragments(List<CustomFragment> mFragments)
{
this.mFragments = mFragments;
}
#Override
public int getCount()
{
return mFragments.size();
}
#Override
public Fragment getItem(int position)
{
return (Fragment) (mFragments.get(position));
}
}
UI (wrapped inside a linear layout):
<fragment
android:id="#+id/navigation_fragment"
android:name="your.package.here.NavigationFragment"
android:layout_width="70dp"
android:layout_height="match_parent" />
<FrameLayout
android:id="#+id/page_fragment"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="2" >
<your.package.here.CustomViewPager
android:id="#+id/page1_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<your.package.here.CustomViewPager
android:id="#+id/page2_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>

Related

Replacing fragment in ViewPager after selecting option in Drawer

As many applications I have MainActivity with ViewPager.
When I select option in NavigationDrawer I want to change one fragment in ViewPager. So to achieve that I created something like this:
Option click listener:
#Override
public void onItemClick(View view, int pos) {
mainActivity.changeFragment(pos);
}
MainActivity method:
public void changeFragment(final int key) {
mDrawerLayout.closeDrawers();
new Handler(Looper.getMainLooper()).post(new Runnable(){
#Override
public void run() {
mainPagerAdapter.setKey(key);
}
});
}
And MainPagerAdapter:
public class MainPagerAdapter extends FragmentStatePagerAdapter {
public final static int NUM_OF_PAGES = 2;
private int mCurrentFragmentKey = 0;
public MainPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if (position == HOME) {
if (mCurrentFragmentKey == 0)
return new Fragment1();
else
return new Fragment2();
} else
return new SecondViewPagerFragment();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public int getCount() {
return NUM_OF_PAGES;
}
public void setKey(int key) {
mCurrentFragmentKey = key;
notifyDataSetChanged();
}
Question:
Unfortunately it's not working properly because when I select option whole view stutters giving bad performance. How can I fix that?
Edit
I changed notifying pager that data set changed into this:
new Handler().postDelayed(new Runnable(){
#Override
public void run() {
mainPagerAdapter.setKey(key);
}
}, 500);
And now everything works fine, but I'm not sure that is best solution. Could someone help me with this?
What about if you have various adapter that extends FragmentStatePagerAdapter and when you need to change the contents in the viewpager, you simple call pager.setAdapter(anotherAdapter)?
If setAdapter supply the views to be shown, changing the adapter probably will make the content of your viewpager change.

SlidingMenu with ViewPager conflict

I'm trying to get SlidingMenu to work alongside ViewPager and I have followed the examples inside the library to the letter. The source for my Activity can be found bellow.
The problem is that despite the fact that the SlidingMenu and ViewPager work fine together there is this problem that after opening the SlidingMenu for the first time and closing it afterwards the ViewPager starts acting weird.
By weird I mean when you swipe to right and left the UI gets stuck on one page and doesn't change and I mean it's only the UI as I can see in the logger that the pages actually change.
I have searched the web extensively and although there has been a lot of similar problems with implementing SlidingMenu alongside ViewPager, my problem seems to be unique.
Any help is appreciated.
public class MainTabActivity extends SlidingFragmentActivity {
private static final String TAG = "MainTabActivity";
#InjectView(R.id.tabHostPager)
ViewPager tabHostPager;
#InjectView(R.id.tabs)
PagerSlidingTabStrip tabStrip;
FragmentPagerAdapter pagerAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_tab);
setBehindContentView(R.layout.sliding_menu_layout);
loadSlidingMenu();
ButterKnife.inject(this);
pagerAdapter = new MainTabAdapter(getSupportFragmentManager());
tabHostPager.setAdapter(pagerAdapter);
//Assign the TabStrip it's ViewPager
tabStrip.setViewPager(tabHostPager);
tabStrip.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
switch (position) {
case 0:
getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
break;
default:
getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN);
break;
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
tabHostPager.setCurrentItem(0);
}
private void loadSlidingMenu() {
// configure the SlidingMenu
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
getSlidingMenu().setMode(SlidingMenu.LEFT);
getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
getSlidingMenu().setShadowWidth(100);
getSlidingMenu().setFadeDegree(0.35f);
getSlidingMenu().setBehindWidth(width / 100 * 90);
}
private class MainTabAdapter extends FragmentPagerAdapter {
public MainTabAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 4;
}
#Override
public Fragment getItem(int position) {
return Friends.newInstance();
}
#Override
public CharSequence getPageTitle(int position) {
return "Home";
}
}
}
Finally I managed to solve it. It had something to with manageLayers method from the SlidingMenu class.
Commenting out the method body was all it took:
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void manageLayers(float percentOpen) {
/*if (Build.VERSION.SDK_INT < 11) return;
boolean layer = percentOpen > 0.0f && percentOpen < 1.0f;
final int layerType = layer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;
if (layerType != getContent().getLayerType()) {
getHandler().post(new Runnable() {
public void run() {
Log.v(TAG, "changing layerType. hardware? " + (layerType == View.LAYER_TYPE_HARDWARE));
getContent().setLayerType(layerType, null);
getMenu().setLayerType(layerType, null);
if (getSecondaryMenu() != null) {
getSecondaryMenu().setLayerType(layerType, null);
}
}
});
}*/
}

How to detect when a fragment appears on the screen?

How could some part of my code be aware of Fragment instance become visible on a screen?
The following snippet will explain my question.
public class MyApp extends Application {
public static final String TAG = MyApp.class.getSimpleName();
#Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
...
#Override
public void onActivityResumed(Activity activity) {
Log.d(TAG, activity.getClass().getSimpleName() + " is on screen");
}
#Override
public void onActivityStopped(Activity activity) {
Log.d(TAG, activity.getClass().getSimpleName() + " is NOT on screen");
}
...
});
}
Here i can track when any activity within my app appears on the screen. Is there any way to extend this approach on Fragments?
Something like
Activity.getFragmentManager().registerFragmentLifecycleCallbacks();
UPD. I know nothing about activities implementations, do they use fragments at all and how do they use them (injection via xml, ViewPager etc.) The only thing I have within my class is an application context. Let's assume Activity and Fragment implementations are black boxes and i am not able to make any changes.
In your fragment, override onHiddenChanged(...) method:
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
Log.d(TAG, ((Object) this).getClass().getSimpleName() + " is NOT on screen");
}
else
{
Log.d(TAG, ((Object) this).getClass().getSimpleName() + " is on screen");
}
}
Hope this work for you!
Without touching the Activity or Fragment code and assuming you don't know the tag or layout it is placed in, there is very little that you can do. The best that I can see is that you could get the FragmentManager in ActivityResumed and ActivityStopped callbacks (because here you have an Activity reference) and apply a BackstackChangedListener. This assumes that you use the backstack when changing between fragments.
The issue with what you are asking is that you want lifecycle callbacks for Fragments on the Application level when you have no control over the middle men, the Activities which are already starved for Fragment callbacks. They do most everything through their FragmentManager, and propagate their own lifecycle callbacks down to the Fragments so that the fragments will behave appropriately. The onResume and onPause callbacks in fragments only occur when they are first created or when the Activity experiences those callbacks. There is only one lifecycle callback for Fragments in Activities, onAttachFragment, which if you could override, would give you references to the Fragments that are attached to the Activity. But you said you can't change the Activity or the Fragment, and you want to know when the Fragments are shown.
So if you don't use the backstack, I don't think there's a way to do what you want.
For putting Fragments inside Activity i use SlidingTabLayout which Google uses. Inside it you have ViewPager and some Adapter to populate many Fragments. First of all you have to put this and this files in your project. Then here there is good tutorial for how you can implement SlidingTabLayout.
1) After you have implemented SlidingTabLayout in your Activity, you can detect when and which Fragment becomes visible from Activity:
mSlidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//Do nothing
}
#Override
public void onPageSelected(int position) {
if (position == 0) {
//Whenever first fragment is visible, do something
} else if (position == 1) {
//Whenever second fragment is visible, do something
} else if (position == 2) {
//Whenever third fragment is visible, do something
} else if (position == 3) {
//Whenever fourth fragment is visible, do something
}
}
#Override
public void onPageScrollStateChanged(int state) {
//Do nothing
}
});
2) You can detect if Fragment is visible from Fragment itself as i answered here, however this may get called before onCreateView() of Fragment, so check answer in the link:
#Override
public void setUserVisibleHint(boolean visible){
super.setUserVisibleHint(visible);
if (visible){
//when this Fragment is active, do something
}
}
3) You can change also change colors of indicators of each Tab like this from Activity:
mSlidingTabLayout.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
#Override
public int getIndicatorColor(int position) {
if (position == 0) {
return getResources().getColor(R.color.orange);
} else if (position == 1) {
return getResources().getColor(R.color.redDimmed);
} else if (position == 2) {
return getResources().getColor(R.color.yellow);
} else if (position == 3) {
return getResources().getColor(R.color.green);
} else {
return getResources().getColor(R.color.redLight);
}
}
#Override
public int getDividerColor(int position) {
return getResources().getColor(R.color.defaultActionBarBg);
}
});
Use same way as activity
set flag in application class to check visiblity of fragment, use below code in fragment
#Override
public void onStart() {
super.onStart();
Log.e( "Fragment is visible", "Fragment is visible");
Application Class.isFragmentShow = true;
}
#Override
public void onPause() {
super.onPause();
Log.e("Fragment is not visible", "Fragment is not visible");
Application Class.isFragmentShow = false;
}
to communicate with fragment you have to call that activity in which fragment added then use below code
MainFragment fragment = (MainFragment) fragmentManager.findFragmentByTag("MainFragment");
fragment.setFilter();
Don't exist a default way to do, but you can make your own Callbacks, I made this and works fine, first need have a BaseFragment class where we'll handle all fragment events.
public class BaseFragment extends Fragment {
private String fragmentName;
private FragmentLifecycleCallbacks listener;
public void registerCallBacks(String fragmentName){
// handle the listener that implement 'MyApp' class
try{
listener = (FragmentLifecycleCallbacks) getActivity().getApplication();
} catch (ClassCastException e) {
throw new ClassCastException("Application class must implement FragmentLifecycleCallbacks");
}
// set the current fragment Name for the log
this.fragmentName = fragmentName;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if(listener!=null) {
listener.onAttachFragment(fragmentName);
}
}
#Override
public void onResume() {
super.onResume();
if(listener!=null) {
listener.onResumeFragment(fragmentName);
}
}
#Override
public void onStop() {
super.onStop();
if(listener!=null) {
listener.onStopFragment(fragmentName);
}
}
// 'MyApp' class needs implement this interface to handle all the fragments events
public interface FragmentLifecycleCallbacks{
void onStopFragment(String fragmentName);
void onResumeFragment(String fragmentName);
void onAttachFragment(String fragmentName);
}}
On 'MyApp' class implement the interface of BaseFragment
public class MyApp extends Application implements BaseFragment.FragmentLifecycleCallbacks{
public static final String TAG = MyApp.class.getSimpleName();
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onStopFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is NOT on screen");
}
#Override
public void onResumeFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is on screen");
}
#Override
public void onAttachFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is attached to screen");
}}
And now each Fragment that you have need extends 'BaseFragment' and register to the global listener
public class FragmentA extends BaseFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_simple, container, false);
// here register to the global listener
registerCallBacks(FragmentA.class.getName());
return rootView;
}}
Hope this helps!
Intercept onWindowFocusChanged() in the activity and propagate that to the interested fragment.
Try this
private Boolean isFragmentVisible()
{
if(getFragmentManager().findFragmentByTag("TAG") != null && getFragmentManager().findFragmentByTag("TAG").isVisible())
{
//The fragment is visible
return true;
}
return false;
}
Alternative way
private Boolean isFragmentVisible()
{
return getFragmentManager().findFragmentByTag("TAG") != null && getFragmentManager().findFragmentByTag("TAG").isVisible();
}
You can know the following with the built in method called "onActivityCreated(Bundle)" this method tells that the fragment has been created thus you get to know that the fragment appears on the screen Click here for reference
Hope it helps
I've looked through what's available without using a base Fragment or Activity class but couldn't find any. I've made an implementation that provides basic (onAdded / onRemoved) functionality for all fragments in your application. It is certainly possible to extend it to report the current state of the fragment (onAttach, onResume, onPause, onDetach, ...).
You can find the code along with a sample here: https://github.com/Nillerr/FragmentLifecycleCallbacks
It works both for non-support library Fragments and support library Fragments through different implementations. The support library class is safer to use and should perform better, because the non-support one uses Reflection to access the fragments, while the support library FragmentManager includes a getFragments() method.
If you are setting a Fragment to your View, you probably have a container where it will be shown. Given that this container is, say, a FrameLayout with id R.id.container, you can do that:
Fragment f = fragmentManager.findFragmentById(R.id.container);
if (f instanceof YourFragment) {
// TODO something when YourFragment is ready
}
Does this interface provide anything helpful to you?
https://github.com/soarcn/AndroidLifecyle/blob/master/lifecycle/src/main/java/com/cocosw/lifecycle/FragmentLifecycleCallbacks.java
It sounds like your best bet if you can't override the Fragment's own onResume() method is to create your own interface that extends ActivityLifecycleCallbacks, then put your logging code in the onFragmentResumed(Fragment yourFragment) method.
You can get a pointer to the Fragment by doing something like this:
int yourFragmentId = 0; //assign your fragment's ID to this variable; Fragment yourFragment.getId();
FragmentManager fm = activity.getFragmentManager();
Fragment f = fm.findFragmentById(yourFragmentId);
whereever u want to check if fragment is visible or not.. just check isMenuVisible() value.
this is fragment's method which i used to check visible fragment when i have to fire some http request from viewpager selected Item.
hope this helps.
in my case i was using this method in onActivityCreated().
In you fragment override method setMenuVisibility If you are using ViewPager and are swiping from left and right, this method is called when the visivility of the fragment gets changed.
Here is a sample from my project
public abstract class DemosCommonFragment extends Fragment {
protected boolean isVisible;
public DemosCommonFragment() {
}
#Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
isVisible = menuVisible;
// !!! Do Something Here !!!
}
}
Animation listener
I have NOT checked all use cases and there is an unhandled exception. You can play around with it to fit your use case. Please feel free to comment your opinions or use cases it did not solve.
NOTE: You can add fragmentWillDisappear and fragmentDidDisappear by handling for enter in onCreateAnimation.
Parent Fragment:
public class BaseFragment extends Fragment {
private Animation.AnimationListener animationListener;
private void setAnimationListener(Animation.AnimationListener animationListener) {
this.animationListener = animationListener;
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
fragmentWillAppear(animation);
}
#Override
public void onAnimationEnd(Animation animation) {
fragmentDidAppear(animation);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
AnimationSet animSet = new AnimationSet(true);
Animation anim = null;
try {
anim = AnimationUtils.loadAnimation(getActivity(), nextAnim);
} catch (Exception error) {
}
if (anim != null) {
anim.setAnimationListener(animationListener);
animSet.addAnimation(anim);
}
return animSet;
}
public void fragmentDidAppear(Animation animation) {
}
public void fragmentWillAppear(Animation animation) {
}
}
Child Fragment:
class ChildFragment extends BaseFragment {
#Override
public void fragmentDidAppear(Animation animation) {
super.fragmentDidAppear(animation);
}
#Override
public void fragmentWillAppear(Animation animation) {
super.fragmentWillAppear(animation);
}
}

Endless adapter concept for view pager

Can any one please explain how to make endless adapter concept for view pager
I am currently using view pager to see my datas. On every 10th swipe of the view pager I need to hit the server and take dynamic response and need to update the viewpager. Obviously we need to use the endless adapter concept. But I was confused with the exact concept. Anyone please do the needful...
Thanks in advance...
I’ve implemented an endless ViewPager. I think it suits you needs. The request is simulated with a time delay ​​in the AsyncTask thread.
//ViewPagerActivity
public class ViewPagerActivity extends FragmentActivity {
private ViewPager vp_endless;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_endless_view_pager);
vp_endless = (ViewPager) findViewById(R.id.vp_endless);
vp_endless.setAdapter(new FragmentViewPagerAdapter(getSupportFragmentManager()));
}
}
//FragmentViewPagerAdapter
public class FragmentViewPagerAdapter extends FragmentStatePagerAdapter {
private List<CustomObject> _customObjects;
private volatile boolean isRequesting;
private static final int ITEMS_PER_REQUEST = 10;
public FragmentViewPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
_customObjects = HandlerCustomObject.INSTANCE._customObjects;
}
#Override
public Fragment getItem(int position) {
CustomFragment fragment = new CustomFragment();
fragment.setPositionInViewPager(position);
if (position == _customObjects.size() && !isRequesting)
new AsyncRequestItems().execute("www.test.com");
return fragment;
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
public class AsyncRequestItems extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... urls) {
isRequesting = true;
//Fake request lag
try {Thread.sleep(2500);}
catch (InterruptedException e) {e.printStackTrace();}
for (int i = 0; i < ITEMS_PER_REQUEST; i++) {
_customObjects.add(new CustomObject());
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
isRequesting = false;
}
}
}
//CustomFragment
public class CustomFragment extends Fragment {
private CustomObject _customObject;
private TextView tv_position;
private ProgressBar pb_loading;
private View root;
private int _positionInViewPager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
root = inflater.inflate(R.layout.frament_endless_view_pager, container, false);
pb_loading = (ProgressBar) root.findViewById(R.id.pb_loading);
tv_position = (TextView) root.findViewById(R.id.tv_position);
_customObject = retrieveDataSafety();
if(_customObject != null) bindData();
else createCountDownToListenerForUpdates();
return root;
}
public void createCountDownToListenerForUpdates() {
new CountDownTimer(10000, 250) {
public void onTick(long millisUntilFinished) {
_customObject = retrieveDataSafety();
if(_customObject != null) {
bindData();
cancel();
}
}
public void onFinish() {}
}.start();
}
private CustomObject retrieveDataSafety() {
List<CustomObject> customObjects = HandlerCustomObject.INSTANCE._customObjects;
if(customObjects.size() > _positionInViewPager)
return customObjects.get(_positionInViewPager);
else
return null;
}
private void bindData() {
pb_loading.setVisibility(View.GONE);
String feedback = "Position: " + _positionInViewPager;
feedback += System.getProperty("line.separator");
feedback += "Created At: " + _customObject._createdAt;
tv_position.setText(feedback);
}
public void setPositionInViewPager(int positionAtViewPager) {
_positionInViewPager = positionAtViewPager;
}
}
//CustomObject
public class CustomObject {
public String _createdAt;
public CustomObject() {
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
_createdAt = dateFormat.format(new Date());
}
}
//HandlerCustomObject
public enum HandlerCustomObject {
INSTANCE;
public List<CustomObject> _customObjects = new ArrayList<CustomObject>();
}
Well, let's start from the beginning.
If you would like to have 'endless' number of pages you need to use some trick. E.g. you can't store endless number of pages in memory. Probably Android will destroy PageView everytime, when it isn't visible. To avoid destroying and recreating those views all the time you can consider recycling mechanism, which are used e.g. ListView. Here you can check and analyse idea how to implement recycling mechanism for pager adapter.
Moreover to make your UI fluid, try to make request and download new data before user gets to X0th page (10, 20, 30, 40...). You can start downloading data e.g when user is at X5th (5, 15, 25...) page. Store data from requests to model (it could be e.g. sqlite db), and user proper data based on page number.
It's just a brief of solution, but it's interesting problem to solve as well;)
Edit
I've started looking for inspiration and just found standalone view recycler implemented by Jake Wharton and called Salvage. Maybe it will be good start to create solution for your problem.

How to change View of the Activity from within the ViewPager's Fragment?

I need to change a TextView's text that is placed in the layout of my FragmentActivity that has a ViewPager and a ViewPagerAdapter. Basically I have created an action bar in my application that presents the name of the current visible Fragment.
So I need this TextView to be changed on each change of the visible fragment in the ViewPager (either with the swipe motion or selected using the TabHost). From my search I found that this method should help me achieve my goal:
#Override
public void setUserVisibleHint(boolean isVisibleToUser)
{
Log.d(TAG, "setUserVisibleHint invoked!");
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser)
{
if (odTab != null && getActivity() != null)
{
Log.d(TAG, "Currently visable tab: "+odTab.getTabTitle());
Toast.makeText(getActivity(), "Currently visable tab: "+odTab.getTabTitle(), Toast.LENGTH_LONG).show();
if (application.getCurrentDataSource() == DataSource.SSRS)
{
if (rsTab.getTabTitle().equals("") || rsTab.getTabTitle() == null)
{
tabName.setVisibility(View.GONE);
}
else
{
tabName.setText(rsTab.getTabTitle());
}
}
else if (application.getCurrentDataSource() == DataSource.SGRDL)
{
Log.d(TAG, "found Title textView from fragmentActivity");
if (odTab.getTabTitle().equals("") || odTab.getTabTitle() == null)
{
((TabsViewPagerFragmentActivity)getActivity()).findViewById(R.id.tvTabName).setVisibility(View.GONE);
}
else
{
Log.d(TAG, "set Title textView to: "+odTab.getTabTitle());
((TextView) ((TabsViewPagerFragmentActivity)getActivity()).findViewById(R.id.tvTabName)).setText(odTab.getTabTitle());
}
}
}
}
}
I tried to implement it in my fragment but for some reason this line:
((TextView) ((TabsViewPagerFragmentActivity)getActivity()).findViewById(R.id.tvTabName)).setText(odTab.getTabTitle());
doesn't do anything although this line do get presented in the log for each change of fragment in the viewPager:
Log.d(TAG, "set Title textView to: "+odTab.getTabTitle());
UPDATE:
My adapter:
public class ViewPagerAdapter extends FragmentPagerAdapter
{
private List<Fragment> fragments;
/**
* #param fm
* #param fragments
*/
public ViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
/* (non-Javadoc)
* #see android.support.v4.app.FragmentPagerAdapter#getItem(int)
*/
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
/* (non-Javadoc)
* #see android.support.v4.view.PagerAdapter#getCount()
*/
#Override
public int getCount() {
return this.fragments.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
Am I doing something wrong? is there another way to do this operation?
In your activity you can do:
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int pos) {
// Change your textview
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
});
Although I can't pinpoint the problem, I would suggest to simplify your code:
avoid cast within cast. You use TabsViewPagerFragmentActivity both in if and else. assign this cast to a parameter and use this parameter. The processing overhead worth it.
instead of using findViewById, save R.id.tvTabName as a local parameter in TabsViewPagerFragmentActivity and create a method setTitle(String title) in TabsViewPagerFragmentActivity. It will also help you to debug this call at the activity itself.
I agree with #mromer - try to access the fragment from the activity. To achieve this, make your FragmentPagerAdapter inherit the following class:
public abstract class SmartFragmentPagerAdapter extends FragmentPagerAdapter {
private SparseArray<String> mFragmentTags;
private FragmentManager mFragmentManager;
public SmartFragmentPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mFragmentTags = new SparseArray<String>();
}
#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;
}
// use this method in onPageSelected
public Fragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return mFragmentManager.findFragmentByTag(tag);
}
public FragmentManager getFragmentManager() {
return mFragmentManager;
}
}
The way I finally managed to deal with this issue is combined with a few things:
1. First I declared an interface in my fragment:
public interface OnFragmetVisibleListener{
public void fragmetVisible(boolean visible);
}
2. Next I added this function as well to the fragment to know when it's attached to the ViewPager:
#Override
public void onAttach(Activity activity){
super.onAttach(activity);
try{
onFragmetVisibleListener = (OnFragmetVisibleListener) activity;
isAttached = true; //flag for whether this framgnet is attache to pager
} catch (ClassCastException e){
throw new ClassCastException(activity.toString() + " must implement interface OnFragmetVisibleListener");
}
}
3. I added the following method as well to know when the fragment is visible and set it's data object in the Application class:
#Override
public void setUserVisibleHint(boolean isVisibleToUser)
{
Log.d(TAG, "setUserVisibleHint invoked!");
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser)
{
if (odTab != null && getActivity() != null)
{
Log.d(TAG, "Currently visable tab: "+odTab.getTabTitle());
application.currentVisibleTab = odTab;
}
if (isAttached)
{
onFragmetVisibleListener.fragmetVisible(true);
}
}
}
4. In my ViewPager's activity I let it implement the defined interface in step 1:
public class TabsViewPagerFragmentActivity extends FragmentActivity implements GridFragment.OnFragmetVisibleListenerr
5. Next I added the unimplemented method:
#Override
public void fragmetVisible(boolean visible)
{
if(visible)
{
setCurrentTabTitle(application.currentVisibleTab.getTabTitle());
}
}
6. when setCurrentTabTitle is:
public void setCurrentTabTitle(String title)
{
tvTabTitle.setText(title); //my TextView that's needs to be changed.
Log.d(TAG, "set tab title from activity: "+title);
}
And that is it, this is how I changed a view of the activity depending on the visible fragment of the ViewPager.

Categories

Resources