How to add different content fragments to sliding tabs - android

I play arround with the SlidingTabsColors example from the android developers site. Where can I add a different content fragment? Now all tabs have the same fragment/layout. I tried to copy all important for the content fragment and renamed it, changed the fragment and the layout. But it dont works this way. Probably the ArrayList is not the best for different content?

I am not sure whether I got the question right but the fact is that there are fragments created in
SlidingTabsColorsFragment.java by the following method:
Fragment createFragment() {
return ContentFragment.newInstance(mTitle, mIndicatorColor, mDividerColor);
}
So I would suggest to simply change that method to something like this:
Fragment createFragment() {
return MyVeryOwnFragment.newInstance(mTitle, mIndicatorColor, mDividerColor);
}
Because there can only be Fragments displayed whose were created beforehand in this method. Then your own Fragment will be displayed in the content area.
You may also want to watch this video on the topic by the android developers youtube channel.
-------- Edit --------
Ok so the question was how to insert multiple different fragments:
Fragment createFragment() {
// Decide based on a class member which Fragment should be created.
Fragment frament;
if (mIndicatorColor == Color.Red) {
fragment = new MyRedFragment(mTitle, mDividerColor);
} else if (mIndicatorColor == Color.Blue) {
fragment = new MyBlueFragment(mTitle, mDividerColor);
}
return fragment;
}
You may want to introduce a different memeber than color. That could be an enum field.

Related

How to save the state of views held in dynamic viewpager

I have an enhanced loop, which will dynamically inflate however many layouts relevant to the number of values held in my array.
This works perfectly however, there is a method being called on each iteration, which also works but there is a big bug that I need help resolving.
Imagine there are 5 items in my array, therefore 5 layouts are inflated, in these layouts there is a little scratchcard type section on the layout.
Now if the user is on page 1, uses the scratchcard, then moves on to page 2, uses the scratchcard etc etc, it works fine.
But if the user is on page 1 and then goes to say, page 5 and then back to page 1 (basically in a random order), the scratchcard doesn't work.
From my understanding, the reason for this is that the method is being called an implemented on each iteration and the view is losing its state if the user scrolls back or scrolls in random orders.
Therefore I need a way to save the created view state in my viewpager.
Is this possible for my scenario? I have tried my best to find a solution, but cannot find something that feels relevant to my question.
Here is a snippet of the code in question. Thanks for any guidance or suggestions!
for (String x : array1) {
//loop out the number of layouts relative to the number of questions held in x
View current_layout = LayoutInflater.from(getActivity()).inflate(R.layout.question_fragment, null);
//use the pageAdapter to add the layout to the users view
pagerAdapter.addView(current_layout);
//call method to add functionality to the scratchcard
isCorrect(current_layout);
}
public void isCorrect(View current_layout) {
ScratchoffController controller1 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view1), current_layout.findViewById(R.id.scratch_view_behind1));
ScratchoffController controller2 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view2), current_layout.findViewById(R.id.scratch_view_behind2));
ScratchoffController controller3 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view3), current_layout.findViewById(R.id.scratch_view_behind3));
ScratchoffController controller4 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view4), current_layout.findViewById(R.id.scratch_view_behind4));
}
I ussually use ViewPager with Fragments and what you mention has happend to me when I try to keep references to the Fragment instances (in my case) outside of the viewpager.
This happens because the viewpager may create new instances of the Fragment it contains when you re-vist the tab in the way you mention. When this happens, the instance reference you hold outside of the viewpager is not anymore what the viewpager is showing.
In your case , according to this question, you have to oveeride instatiateItem and destroyItem. I think you can use these methods to save state restore state, and also you could update any external reference when instantiateItem is called.

DrawerLayout and Multi Pane Layout

My application uses a Multi Pane layout to display a list of assignments. Each Assignment can be put in one AssignmentCategory. I want to use a DrawerLayout to display all the AssignmentCategories so the user can switch easily between the diffirent categories.
I didn't manage to create such a layout. In the official DrawerLayout tutorial the DrawerLayoutActivity replaces a Fragment when a user clicks on a item (in my case an AssignmentCategory). The problem I facing is that a Multi Pane layout requires a FragmentActivity. I don't know how to create a Fragment which contains a Multi Pane layout. Did someone manage to do this?
Combining the two projects shouldn't be too difficult. In the sample code the DrawerLayout example does replace the content fragment but you don't have to do the same, you could simply update the same fragment to show the proper data. You could implement the two projects this way:
start from the multi pane demo project.
update the two activities of the multi pane demo to extends ActionBarActivity(v7), you don't need to extend FragmentActivity
implement the DrawerLayout(the sample code from the drawer project) code in the start list activity(I'm assuming you don't want the DrawerLayout in the details activity, but implementing it shouldn't be a problem if you want it).
the layout of the start list activity will be like this(don't forget that you need to implement the DrawerLayout changes in the activity_item_twopane.xml as well!):
<DrawerLayout>
<fragment android:id="#+id/item_list" .../>
<ListView /> <!-- the list in the DrawerLayout-->
</DrawerLayout>
change the implementation DrawerItemClickListener so when the user clicks the drawer list item you don't create and add a new list fragment, instead you update the single list fragment from the layout:
AssignmentListFragment alf = (AssignmentListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list);
if (alf != null && alf.isInLayout()
&& alf.getCurrentDisplayedCategory() != position) {
alf.updateDataForCategory(position); // the update method
setTitle(DummyContent.CATEGORIES[alf.getCurrentDisplayedCategory()]);
}
the update method would be something like this:
/**
* This method update the fragment's adapter to show the data for the new
* category
*
* #param category
* the index in the DummyContent.CATEGORIES array pointing to the
* new category
*/
public void updateDataForCategory(int category) {
mCurCategory = category;
String categoryName = DummyContent.CATEGORIES[category];
List<DummyContent.Assigment> data = new ArrayList<Assigment>(
DummyContent.ITEM_MAP.get(categoryName));
mAdapter.clear(); // clear the old dsata and add the new one!
for (Assigment item : data) {
mAdapter.add(item);
}
}
public int getCurrentDisplayedCategory() {
return mCurCategory;
}
-various other small changes
I've made a sample project to illustrate the above changes that you can find here.

How to make the ViewPager's items to be destroyed on orientation changes?

I have a ViewPager that I shows more than one item. I used the solution of putting multiple items in one fragment and I created an Adapter that calculates how many items I can put per fragment based in the width of the screen.
The getItem of my FragmentPagerAdapter creates a range of items for each fragment, dividing the quantity of items for each fragment.
For example, I have 12 items and in the portrait orientation, I can put 3 items. The getItem will create fragments with the range of 0-2, 3-5, 6-8 and 9-11. In the landscape orientation, since the width of the screen is bigger, I can put, for example, 5 items. So, the ranges would be 0-4, 5-8 and 9-11.
With this requirement, I need to create new Fragments and destroy the old ones on orientation changes.
I created a solution, but it depends on the method isChangingConfigurations() of the Activity. But this method just exists for API level 11 and above. So I can't use it.
Basically, I I'm not allowing the Fragment to save its state and I'm removing it in the onPause if the configuration is changing. But since I don't have this method in old android versions, I need another solution.
Can anyone help me?
Another solution is:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
if (pagerAdapter != null) {
pagerAdapter.removeAllfragments();
}
super.onSaveInstanceState(savedInstanceState);
}
And the code for adapter:
public void removeAllfragments()
{
if ( mFragmentList != null ) {
for ( Fragment fragment : mFragmentList ) {
mFm.beginTransaction().remove(fragment).commit();
}
mFragmentList.clear();
notifyDataSetChanged();
}
}
mFragmentList should add fragments inside of:
#Override
public Fragment getItem(int position) {}
The easiest solution to your current approach is probably to ensure that getItem(int) returns different values for the landscape and portrait orientations. The id is used to generate the fragment tag, that, after an orientation change, is used to retrieve a detached fragment and reattach it. With different ids for the situation where respectively 3 items and 5 items are next to each other, a fragment with 3 items will never be reattached if there should be 5.
By default, getItemId(int) simply returns the supplied position of the element, like so:
public long getItemId(int position) {
return position;
}
In order to return different ids for your situations, you have various options. A straightforward one would be to do something smart with the indices of all items displayed for the given position. Alternatively, you could do a simple device orientation check and offset the id with the total number of items, or do something smart with a string and a hashcode etc. Just make sure you return the same id for the same fragment in the same orientation.

Selection of fragment View depending on selected item ViewModel type

Target MvvmCross, Android
Objective: A screen (ViewModel/View) where the user can select an animal group (Amphibians, Birds, Fish, Invertebrates, Mammals, Reptiles). When a group has been selected, a Fragment Views will will display information for that animal group. The fields and layout differ per animal group (e.g. fish don't have wings).
Although for this question I have chosen for animal group (which is pretty static), want the list animal groups to be flexible.
Simplified app structure:
MyApp.Core
ViewModels
MainViewModel
IAnimalGroupViewModel
AmphibiansViewModel
BirdsBViewModel
FishViewModel
MyApp.Droid
Layout
MainView
AmphibiansFragment
BirdsFragment
FishFragment
Views
MainView
AmphibiansFragment
BirdsFragment
FishFragment
The MainView.axml layout file will contain (a placeholder for) the fragment of the displayed animal group.
In WPF or WP8 app I could make use of a ContentPresenter and a Style to automatically display the selected ViewModel with its View.
How could I achieve something like that in Droid?
I could use a Switch/Case in the MainView.cs that sets the Fragment according to the type of the selected ViewGroup. But that means I have to modify the MainView every time I add a new View.
Any suggestions/ideas?
Currently MvvmCross doesn't provide any kind of automatic navigation mechanism for Fragments in the same way that it does for Activities.
However, within your use case, if you wanted to use a navigation approach, then you could automatically build a similar type of automated lookup/navigation mechanism.
To do this, the easiest developer root would probably be to use reflection to find a lookup dictionary for all the fragments
var fragments = from type in this.GetType().Assembly.GetTypes()
where typeof(IAnimalGroupView)..sAssignableFrom(type)
where type.Name.EndsWith("Fragment")
select type;
var lookup = fragments.ToDictionary(
x => x.Name.Substring(0, x.Name.Length - "Fragment".Length)
+ "ViewModel",
x => x);
With this in place, you could then create the fragments when they are needed - e.g.
assuming that you convert the Selection event via an ICommand on the ViewModel into a ShowViewModel<TViewModel> call
and assuming that you have a Custom Mvx presenter which intercepts these ShowViewModel requests and passes them to the activity (similar to the Fragment sample) - e.g.
public class CustomPresenter
: MvxAndroidViewPresenter
{
// how this gets set/cleared is up to you - possibly from `OnResume`/`OnPause` calls within your activities.
public IAnimalHostActivity AnimalHost { get; set; }
public override void Show(MvxViewModelRequest request)
{
if (AnimalHost != null && AnimalHost.Show(request))
return;
base.Show(request);
}
}
then your activity could implement Show using something like:
if (!lookup.ContainsKey(request.ViewModelType.Name))
return false;
var fragmentType = lookup[request.ViewModelType.Name];
var fragment = (IMvxFragmentView)Activator.Create(fragmentType);
fragment.LoadViewModelFrom(request);
var t = SupportFragmentManager.BeginTransaction();
t.Replace(Resource.Id.my_selected_fragment_holder, fragment);
t.Commit();
return true;
Notes:
if you aren't using ShowViewModel here then obviously this same approach could be adjusted... but this answer had to propose something...
in a larger multipage app, you would probably look to make this IAnimalHostActivity mechanism much more generic and use it in several places.

FragmentPagerAdapter with ViewPager and two Fragments. Go to the first from the second and update first's text

I'm not familiar with FragmentPagerAdapter, so this is going to be one of those questions that we (you) read the description critically.
Structure: I have a FragmentPagerAdapter (code below), that will hold two fragments at a time. The first displays book excerpts, and the second a list of book titles.
Goal: I want to achieve what is described in the title: the user can navigate to the second fragment in the pager, click on a title, and then I want to move the user back to the first fragment and tell the first fragment to update the text. The first fragment has a triggerRefresh method for that.
Code: I believe my problem happens because of the way FragmentPagerAdapter reuses/creates the Fragments (which I don't understand). This is my class:
static class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
switch(position) {
case 0:
return new ExcerptsFragment();
case 1:
return new BookListFragment();
default:
throw new IllegalArgumentException("not this many fragments: " + position);
}
}
}
This is how I created the relevant members:
ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
MyFragmentPagerAdapter mFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mFragmentPagerAdapter);
And this is what I've tried elsewhere in my Activity, when I receive the callback from the book titles Fragment with the title selected:
mViewPager.setCurrentItem(0); // back to excerpts screen page. It's OK.
// Here's the problem! How to identify the fragment 0
// to ExcerptsFragment and call its triggerRefresh()?!?
Series of problems:
Calling the adapter's getView() won't work because it will return a new instance of ExcerptsFragment, which is not the one currently attached (as expected, throws the exception).
I've seen many people here (example) just storing fragments in the getView(). Is that right? Because by looking at the official examples, seems like an anti-pattern to me (defeat the automatic reference by holding the items). And that is also the opinion here and here (and looks right to me).
Any suggestions? I wouldn't be surprised if I'm not understanding all of this one bit...
Disclaimer: Although this had worked perfectly fine for me before, you should be aware of the classic pitfalls of depending on internal, private behavior. While I wrote tests that would eventually warn me if the internal implementation changed, I have since moved on to greener pastures. And you should, too. As such, the value of this question and its answer is only historical, in my opinion.
Sorry about that question, I think it was the hour.
To solve that problem, I implemented this solution as is. Seems to work just fine. So, I believe it was just a matter of finding the (currently attached) fragment instance by figuring out how its Id is named. The link above explains how it's made.
I opted to answer my own question instead of deleting it because I believe novices like me on these pagers will benefit from a "real case scenario". Most of the answers I've seen talk most about the theory, which is the right way BTW... but without a real example to work on sometimes people like me get lost.
Anyway, here is the last piece of code that I needed (the commented part above):
int n = 0;
mViewPager.setCurrentItem(n); // in the question I had stopped here.
ExcerptsFragment f = (ExcerptsFragment) ContainerActivity.this
.getSupportFragmentManager().findFragmentByTag(getFragmentTag(n));
f.triggerRefresh();
// ... below the helper method: used the solution from the link.
private String getFragmentTag(int pos){
return "android:switcher:"+R.id.pager+":"+pos;
}
So, I'm having a feeling that this is a robust solution, because I'm not holding any references to fragments (thus risking the references being outdated). I kept my own code at a minimum, therefore minimizing the chances of me doing something stupid.
Of course, if you have something to add, to show us, to tell what is wrong in doing it or what can be improved, I'll be glad to hear from you.
I searched for a solution to this problem a while myself. Your approach in principle works, but it will break your code if ever the code of the fragment tag creation in the Android base class implementation changes. This is a quite nasty dependency!
A more elegant approach would be to turn the problem around and keep an instance of your base activity in your fragment. Implement a setter for the tag in your activity and call that inside the fragment upon creation - the tag there is simply available with getTag().
An example implementation can be found here.
I solved this problem by using WeakReferences to the fragments upon creation. See : https://stackoverflow.com/a/23843743/734151
If you find anything wrong with this approach, please comment.

Categories

Resources