My app has a ListView on startup. The user can either manually select an item in the ListView to go to a details screen or swipe using a ViewPager between the different details screens. The ViewPager's fragments are setup like this:
Listing
Detail 1
Detail 2
Detail 3
Detail 4
...
It's my understanding, when the Listing fragment is loaded, the ViewPager will execute Detail 1's code, for performance. The same when Detail 1 is loaded, Detail 2's code will execute.
The problem I'm running into is that I'm setting the title of each detail fragment in onActivityCreated, however, when the Listing fragment is loaded, it is displaying Detail 1's title. So I moved the code to onPageSelected of the ViewPager, which works if the user is swiping, but if the user manually selects an item in the ListView the title is never set.
I'm not sure if there is an event that is only fired when a user manually selects an item in the ListView and not when they are swiping or if I need to rethink my apps' setup. For example, instead of using this code in the Listing fragment's onListItemClick event:
final Intent listing = new Intent(getActivity().getApplicationContext(), Details.class);
startActivity(listing);
I need to somehow use the ViewPager.
mViewPager = (ViewPager)findViewById(R.id.viewpager);
mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mMyFragmentPagerAdapter);
mViewPager.setSaveEnabled(false);
mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
String title = GetTitle(position);
getSupportActionBar().setTitle(title);
}
#Override
public void onPageScrolled(int position, float offset, int offsetPixel) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
You'll probably want to override the method getPageTitle(int) in your MyFragmentPagerAdapter class. The documentation states:
This method may be called by the ViewPager to obtain a title string to
describe the specified page. This method may return null indicating no
title for this page. The default implementation returns null.
So rather than returning null, make sure you return the actual page title. You get passed in the position/index of the page the title is requested for, so a simple switch-case statement should suffice. Alternatively, you could set up an interface for your pages and query the relevant page for its title.
Related
I am trying to use ViewPager and TabLayout in different ways than the usual.
I have 5 tabs, and each tabs have ViewPager which has two fragment pages (A Fragment and B Fragment)each.
For each tab, ViewPager needs to display same fragment pages with different information. ( Such as Tab's title is displayed in A Fragment, and Tab 1's title is "Android", and Tab 2's title is "IOS" )
Since FragmentPagerAdapter sets the Fragments in getItem(int position) method like the below, I tried to send these information in that method (information about the tab).
I found the problem,when i launch the ViewPager and set the adapter to it, the adapter is only stetted once So, when Tab 1's ViewPager already set to A Fragment the title of "Android", it won't change to "IOS" when I turn it to second tab Tab2.
Is there any way sending different informations each tab to the ViewPager to receive the information differently?
I kind of wrote it too broad maybe? if you need more specific informations, or do not get the question please tell me I will explain you more.
Thanks for helping
My FragmentPagerAdapter
public class ManagerPagerAdapter extends FragmentPagerAdapter{
....
#Override
public Fragment getItem(int position){
...
/* I received informations about the tab, and setted to bundle.
So thought each tab gives different bundle data to AFragment.
But since the Adapter is setted only once, this method is not
called more than once*/
Bundle bundle = new Bundle();
switch(position){
case 0:
AFragment fragment_a = new AFragment();
fragment_a.setArguments(bundle);
return fragment_a;
case 1: //Not really matters
BFragment fragment_b = new BFragment();
return fragment_b;
}
}
}
My TabLayout code is not so different from other examples.
Thank you again!!!
If the Activity needs to be able to listen for changes to the page selected or other events surrounding the ViewPager, then we just need to hook into the ViewPager.OnPageChangeListener on the ViewPager to handle the events:
pager.addOnPageChangeListener(new OnPageChangeListener() {
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// This method will be invoked when the current page is scrolled
}
public void onPageSelected(int position) {
// set your title here for specic fagment
//This method will be invoked when a new page becomes selected.
}
public void onPageScrollStateChanged(int state) {
// Called when the scroll state changes:
// SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING, SCROLL_STATE_SETTLING
}
});
I have an application running on an embedded system with 4 tabs in a SectionsPagerAdapter and in the Fragment of each tab I have code that updates the display every 0.5 seconds via a timer.
I find that when I create 4 tabs, the first 2 are created and 3 and 4 are only created if I scroll to the right which is expected.
The issue for me is that after the view for each tab is created and the timer is started, the timer fires and the calls to update the views gets processed even if the tab is not visible.
I am trying to find a way to know if the tab is visible or not and if it's hidden, I simply skip the re-drawing of the views. Only when the tab is visible do I do the updates.
I have tried using isShown() and hasWindowFocus() from the the view that getView() returns but they always return true;
I've also tried to use onPause and onResume for each fragment but they only get called when I move to the 2rd tab from it. Eg, on tab1, move to tab2, not called, moved to tab3, onPause() called.
For now I have used onTabSelected() to store the current tab in my singleton class that all the fragments have access to the system data from. When I create the fragment, I pass in the tab position.
BUT , how can I check for this view being visible in a more elegant way?
You can check if the Fragment you want to update is selected in an OnPageListener attached to your ViewPager. In the listener you can check if the page you want to update your timer on is selected. Here is an example:
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int page) {
/*If the page is the page you want to update your timer on, enable it.*/
if ( page == myTimerPageId ){
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment timerFragment = fragmentManager.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + myTimerPageId);
timerFragment.startTimer();
}
/*If it is not the page you update your timer on, disable it.*/
else{
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment timerFragment = fragmentManager.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + myTimerPageId);
timerFragment.stopTimer();
}
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
#Override
public void onPageScrollStateChanged(int arg0) {}
});
You have to get the Fragment through the FragmentManager and then do whatever you want to that Fragment, implement startTimer() or something. myTimerPageId must be the identifier of the page where you are incrementing your timer. The findFragmentByTag part is stolen from this answer. The OnPageChangeListener part is grabbed from here.
As a side note, the ViewPager has an setOffscreenPageLimit that sets how many pages beside the active one that is kept in memory. This can be minimum 1, though, so it is not possible to use this together with Fragment.onPause to achieve what is asked here. If this could be of use in another sitatuation, take a look here.
I have a view pager with 2 scrolling pages in my app. at first I populate it with two fragments.
In first fragment I have a button. clicking the button new adapter is created and view pager is populated with two different fragments. at the moment when I press back I exit from the app instead I want to restore previous state of the view pager. please help
For the first time:
ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(),nBank);
mViewpager.invalidate();
mViewpager.setAdapter(pagerAdapter);
Second time:
public void onListItemPressed(Currency objectCurrency) {
// TODO Auto-generated method stub
DetailPagerAdapter detaluriadapteri = new DetailPagerAdapter(getSupportFragmentManager());
mViewpager.setAdapter(detaluriadapteri);
}
One solution could be to implement onBackPressed:
#Override public void onBackPressed() {
if (mViewpager != null && mViewpager.getAdapter() instanceof DetailPagerAdapter) {
ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(),nBank);
mViewpager.invalidate();
mViewpager.setAdapter(pagerAdapter);
} else {
super.onBackPressed();
}
}
Though, I think it would be better to start a new activity using DetailPagerAdapter when onListItemPressed is called. This way the default behavior of android would be to navigate back to your main activity on backpress, currently your main activity could be getting too much responsibility. Thus, having a self contained activity handling the details part would also be easier to maintain as it might need different tabs, actionbar menu items, etc..
Could also be a Fragment containing a the details viewpager but I have had trouble implementing this myself. Should be possible though and my trouble might be caused by some of the libraries I use.
I have a Viewpager that uses a FragmentStatePagerAdapter. Each page is a Fragment. My pages contains an linearlayout (llTags) which is default visible.
If the user clicks on a page (main layout of the fragment), the linearlayout must be invisible (works) but the other linearlayouts (llTags in other pages) needs to be changed too.
If i click, the visibility of the linearlayouts changed off all pages exept the previous and next.
This is because the getItem from the adapter isn't called for the next/previous item again.
How can i notify these pages.
ps: i have a newInstance method and a public void setTagslayoutVisible(boolean) for changing the visibility from the adapter.
Next and previous are already loaded and the adapter is not going to call creation.
Take a quick try at this, in your adapter get function if your fragment is already loaded it returns those objects. So just add something like this
if(myMap.containsKey(position))
{
LiveStreamFullScreenFragment lfsf = (LiveStreamFullScreenFragment) myMap.get(position));
lfsf.setShowTags(mShowTags);
return lfsf;
} else
{
and in your fragment class
public void setShowTags(boolean showtags)
{
mShowTags = showtags;
if(!mShowTags)
{
llLiveStreamTags.setVisibility(View.GONE);
}
}
Hope this helps and enjoy your work.
I am having trouble implementing a feature in my Android app.
Here's the setup:
ItemPagerActivity: An activity that contains a fragment that displays a pager.
ItemPagerFragment: The fragment containing a pager that loads other fragments. A cursor is used to load the fragments.
ItemFragment: The fragment in the pager, which performs an asynchronous task to load its data.
What I want is the following:
as a I swipe pages, the data in the currently displayed ItemFragment is communicated to the ItemPagerActivity (specifically, the name of the item will be used as the activity's title).
I've defined a listener in ItemFragment that notifies when the data is loaded:
public class ItemFragment ... {
public interface OnItemLoadedListener {
public void onItemLoaded(Item item);
}
private Collection<OnItemLoadListener> listeners;
private class LoadItemTask extends AsyncTask<...> {
...
public void onPostExecute(Item item) {
notifyItemLoaded(item);
...
}
}
}
If this fragment was wrapped by an Activity, then I could set the activity's title simply by doing the following:
public class ItemActivity {
public void onCreate(...) {
...
ItemFragment fragment = new ItemFragment();
fragment.registerItemLoadedListener(new ItemLoadedListener() {
public void onItemLoaded(Item item) {
setTitle("Item: " + item.getName());
}
});
...
}
}
So that's easy enough, and works as expected: when the activity starts, it creates the fragment, which loads the item, which notifies the activity, and the title is updated correctly.
But with ItemPagerFragment, the fragments are loaded pre-emptively: swiping to Fragment 3 may mean that Fragment 4 and Fragment 5 are created. Receiving notifications from the ItemFragment class when items are loaded is not correct here because the fragment displayed may not match the fragment that performed the last load.
Now the ViewPager class has a OnPageChangeListener which could be a solution: when I swipe, this listener is invoked with the current page number. From that page number, I need to (somehow) get the fragment representing that page from the adapter, get the Item data out of the fragment, and notify listeners that the Item is now loaded:
public class ItemPagerFragment ... {
private Collection<OnItemLoadedListener> listeners;
public View onCreateView(...) {
...
ViewPager pager = (ViewPager) view.findViewById(R.id.pager):
pager.setOnPageChangeListener(new OnPageChangeListener() {
public void onPageChange(int pageNumber) {
ItemFragment fragment = getItemFragment(pageNumber);
Item item = fragment.getLoadedItem();
notifyItemLoaded(item);
}
});
...
}
}
The ItemPagerActivity class would then register as a listener on the ItemPagerFragment class as follows:
class ItemPagerActivity ... {
public void onCreate(...) {
...
ItemPagerFragment fragment = new ItemPagerFragment();
fragment.registerOnItemLoadedListener(new OnItemLoadedListener() {
public void onItemLoaded(Item item) {
setTitle("Item: " + item.getName());
}
});
...
}
}
This looks good, but there are a number of problems:
The OnPageChangeListener may be invoked before a fragment has loaded its data (i.e., the fragment is swiped into view before the item has asynchronously loaded). So the call to fragment.getLoadedItem() may return null.
The OnPageChangeListener is not invoked for the initial page (only when a page changes, e.g. after a swipe action) so the activity title will be incorrect for the initial page.
The ViewPager class allows for only one OnPageChangeListener. This is a problem because I am also using the ViewPageIndicator library, which wants to assign a listener to the ViewPager.
I'm assuming that this pattern (notifying the activity of the data in a fragment that has been swiped into view) might be common, so I am wondering if there are any good solutions for this pattern, and to the three specific problems that I have identified above.
...so I am wondering if there are any good solutions for this pattern,
and to the three specific problems that I have identified above.
I don't know if I would call it a pattern but the OnPageChangeListener is the way to go.
The OnPageChangeListener may be invoked before a fragment has loaded
its data (i.e., the fragment is swiped into view before the item has
asynchronously loaded). So the call to fragment.getLoadedItem() may
return null.
First, your code should handle the "no data available situation" from the start. Your AsyncTasks will have the job of loading the data and also update the title only if the fragment for which they are working is the visible one(a position field in the ItemFragment tested against the ViewPager's getCurrentItem() method). The OnPageChangeListener will handle the update of the title after the data was loaded, as the user switches between pages and the data is available(it will return null if no data is available). To get the ItemFragment for a certain position you could use the code below:
ItemFragment itf = getSupportFragmentManager()
.findFragmentByTag(
"android:switcher:" + R.id.theIdOfTheViewPager + ":"
+ position);
if (itf != null) {
Item item = fragment.getLoadedItem();
notifyItemLoaded(item);
}
The OnPageChangeListener is not invoked for the initial page (only
when a page changes, e.g. after a swipe action) so the activity title
will be incorrect for the initial page.
See above.
The ViewPager class allows for only one OnPageChangeListener. This is
a problem because I am also using the ViewPageIndicator library, which
wants to assign a listener to the ViewPager
I admit I don't have much knowledge on the ViewPagerIndicator library but at a quick look on its site I saw:
(Optional) If you use an OnPageChangeListener with your view pager you
should set it in the indicator rather than on the pager directly.
titleIndicator.setOnPageChangeListener(mPageChangeListener);
I don't see where is the limitation.
For my purposes, it worked to use ViewPager.OnPageChangeListener.onPageSelected() in conjunction with Fragment.onActivityCreated() to perform an action when the Fragment is visible. Fragment.getUserVisibleHint() helps too.