How to reuse the fragments inside view pager ???
I have view pager and inside that Having 3 fragments.
I'm creating new instance of fragment to all time. So It's take more space in memory.
In this 3 fragments having perform same functionality. While Im creating new instance every time. It's having more problems like.
1) It take times to keyboard open and close.
2) Scrollview take times to scroll.
3) Edit text take time to focus on and off.
https://prnt.sc/or86ec
Here screen that I have implemented viewpager with 3 fragment. In swipe it's look like same.
I have created Pager adapter:
public class ProposalDetailPagerAdapter : FragmentStatePagerAdapter, CardAdapter
{
private static int NUM_ITEMS = 3;
private float mBaseElevation;
public ProposalDetailPagerAdapter(Android.Support.V4.App.FragmentManager fm, float baseElevation) : base(fm)
{
mBaseElevation = baseElevation;
}
public override int Count => NUM_ITEMS;
public override int GetItemPosition(Java.Lang.Object #object)
{
if (#object is ProposalDetailFragment)
{
ProposalDetailFragment fragment = (ProposalDetailFragment)#object;
if (fragment != null)
{
fragment.UpdateFragmentAfterCreate();
}
}
return base.GetItemPosition(#object);
}
public int getCount()
{
return NUM_ITEMS;
}
public override Android.Support.V4.App.Fragment GetItem(int position)
{
switch (position)
{
case 0:
return OpenFragment(0);
case 1:
return OpenFragment(1);
case 2:
return OpenFragment(2);
default:
return null;
}
}
private Android.Support.V4.App.Fragment OpenFragment(int position)
{
ProposalDetailFragment createdFragment = new ProposalDetailFragment();
Bundle bundle = new Bundle();
bundle.PutInt("position", position);
createdFragment.Arguments = bundle;
return createdFragment;
}
public override Android.Support.V4.App.Fragment GetItem(int position)
{
switch (position)
{
case 0:
return OpenFragment(0);
case 1:
return OpenFragment(1);
case 2:
return OpenFragment(2);
default:
return null;
}
}
}
In this adapter, created 3 fragments. But It's having lot's of memory problem. Because lot's of condition having in fragments. and created 3 instance of memory. So How to resolve this issue ???
Here is a solution :
In ProposalDetailPagerAdapter , can override DestroyItem method.And comment out the method of calling the parent class to avoid repeated creation of the fragment.
As follow:
public class ProposalDetailPagerAdapter : FragmentStatePagerAdapter, CardAdapter
{
...
public override void DestroyItem(ViewGroup container, int position, Java.Lang.Object #object)
{
//base.DestroyItem(container, position, #object);
// Comment out the calling parent class
}
...
}
Related
So I have a viewPager with 4 view, which I can access by swiping left or right. My Adapter -
public class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(#NonNull FragmentManager fm, int behavior) {
super(fm, behavior);
}
#Nullable
#Override
public Fragment getItem(int i) {
switch (i){
case 0:
return new FirstFrag();
case 1:
return new SecondFrag();
case 2:
return new ThridFrag();
case 3:
return new FourthFrag();
default:
return null;
}
}
#Override
public int getCount() {
return 3;
}
}
Since getCount() return 3 hence I can only see only three views which is fine and it works perfectly but I go to the fourth view maybe programatically, beyond the fixed count.
Why I am doing this? I am trying to make a view accessible when user click a button on the third view but can't access it by swiping. I have tried launching an new activity but the problem was my livedata viewmodel data couldn't talk to the new activity because two activities can share viewmodel hence this approach.
My solution was, the approach I have mentioned above.
Your error is that you are indicating that there are only 3 views, ignoring a fourth, remember that in programming the 0 does count.
Your solution is
public class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(#NonNull FragmentManager fm, int behavior) {
super(fm, behavior);
}
#Nullable
#Override
public Fragment getItem(int i) {
switch (i){
case 0:
return new FirstFrag(); //View 1
case 1:
return new SecondFrag(); //View 2
case 2:
return new ThridFrag(); //View 3
//By indicating that getCount () return 3, this is all 3 views
case 3:
return new FourthFrag(); //View 4
default:
return null;
}
}
#Override
public int getCount () {
return 4;
}
}
I want to make a quiz application. So far I have 3 activities - home, quiz, score. Since the quiz activity contains multiple equivalent views ( image header, question and 4 answer buttons ), I did some reading and decided that
ViewPager with FragmentStatePagerAdapter show do the trick. So I made an xml template and inflated couple of test views and it was all looking good, until I started handling the user interaction.
I want to simulate a toggle button and there is only one correct answer to each question, so selecting one button should deselect the previous one ( if any ). When the button is pressed I change my Question model, then I find all 4 buttons with findViewById and reset their color filter. Then I set that filter back on my selected button. To determine which question model to update I use the current fragment position, which I have set ( using setTag, in fragment's onCreate ) in my template root view.
This is how I call my fragmets:
public Fragment getItem(int position) {
Question question = Repository.findById(position);
int correctAnswerBtnId;
switch (question.getCorrectAnswerIndex()) {
case 0: correctAnswerBtnId = R.id.quiz_answer_0_btn; break;
case 1: correctAnswerBtnId = R.id.quiz_answer_1_btn; break;
case 2: correctAnswerBtnId = R.id.quiz_answer_2_btn; break;
case 3: correctAnswerBtnId = R.id.quiz_answer_3_btn; break;
this.ACTIVITY_ROOT.setTag(question.getID());
Fragment fragment = new QuestionFragment();
Bundle args = new Bundle();
args.putSerializable(QuestionFragment.QUESTION, question);
fragment.setArguments(args);
return fragment;
}
My QuestionFragment onCreateView is as per documentation:
public View onCreateView(
LayoutInflater inflater,
ViewGroup container,
Bundle questionData) {
this.rootView = inflater.inflate(
R.layout.layout_question_template,
container,
false);
Bundle args = getArguments();
this.question = (Question) args.getSerializable(QuestionFragment.QUESTION);
populateInflatable();
rootView.findViewById(R.id.layout_question_template_root).setTag(this.question.getID());
return rootView;
}
In populateInflatable I use this.rootView to fintViewById and populate it with my question data. Then I change the color of a button, if there is selected one from the Question.
On button click I call selectAnserButton :
public void selectAnswerButton(View selectedButton) {
int questionId =
(int) this.activityRoot.findViewById(
R.id.layout_question_template_root).getTag(); //??
unSelectAllButtons();
changeColor(selectedButton);
Repository.findById(questionId).selectAnswer(selectedButton.getId());
}
Where unSelectAllButtons represents buttonToUnSelect.getBackground().clearColorFilter(); on the four buttons. and Repository is just a static class with example question data.
It all goes terribly wrong, when I have more then one view. On each fragment I inflate the same xml with same View IDs, as I have defined them. And as I now understand calling findViewById retrieves not one, but all views with that Id from my current, but also from my previous and next fragment as well. So every time I want to select my current fragment's view, I also modify the same view in the previous and next fragments as well. You can imagine how this is problematic. This makes me feel I have a fundamental mistake, because I don't think there is supposed to be more then one View with same ID.
I really don't understand how I should do this using ViewPager. At this point it feels like I'm trying to make a wood carving, but instead I am hacking the framework to pieces. There must be a better way to do this with ViewPager.
RESOLVED: Thanks to Soo Chun Jung for pointing me to the answer. In short what got it working for me was:
Passing my Question model id to each fragment with Bundle.
Storing each fragment in inside an ArrayMap with fragment position as key and fragment as value.
Getting each individual fragment from my selectAnswer function is now easy: first get the current fragment's position with myViewPager.getCurrentItem, then calling getter function which returns a fragment on the current position.
Now that I have the fragment I can easily change its button's because they are kept as private fields, assigned in the 'onCreateView` method.
Hope it's helpful~
adapter
class CustomAdapter extends FragmentPagerAdapter {
private final String[] TITLES = {"A", "B"};
private final String TAG = CustomAdapter.class.getSimpleName();
private final ArrayList<Fragment> mFragments;
private final FragmentManager fm;
public CustomAdapter(FragmentManager fm) {
super(fm);
mFragments = new ArrayList<>(getCount());
this.fm = fm;
}
#Override
public CharSequence getPageTitle(int position) {
return TITLES[position];
}
#Override
public int getCount() {
return TITLES.length;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem position = " + position);
mFragments.remove(object);
super.destroyItem(container, position, object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
mFragments.add((Fragment) object);
return object;
}
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem position = " + position);
if (position == 0) {
return MyFragmentA.newInstance();
} else if (position == 1) {
return MyFragmentB.newInstance();
}
return null;
}
public MyFragmentA getMyFragmentA() {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentA) {
return (MyFragmentA) f;
}
}
}
return null;
}
public MyFragmentB getMyFragmentB() {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentB) {
return (MyFragmentB) f;
}
}
}
return null;
}
}
Fragment class
public class MyFragmentB extends Fragment {
...
public updateYourUI(){
//update something
}
}
Usage
mPager = (CustomViewPager) findViewById(R.id.pager);
mAdapter = new CustomAdapter(getChildFragmentManager());
mPager.setAdapter(mAdapter);
mAdapter.getMyFragmentB().updateYourUI();
for your comment below If you only have one kind Fragment. You can modify some function like this.
public static MyFragmentB newInstance(int ID) {
MyFragmentB fragment = new MyFragmentB();
Bundle bundle = new Bundle();
bundle.putInt("ID", ID);
fragment.setArguments(bundle);
return fragment;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
myID = getArguments().getInt("ID");
....
}
public int getMyID() {
return myID;
}
public MyFragmentB getMyFragmentByID(String id) {
synchronized (mFragments) {
for (Fragment f : mFragments) {
if (f instanceof MyFragmentB) {
MyFragmentB temp = (MyFragmentB)f;
if(temp.getID.equals(id){
return (MyFragmentB) f;
}
}
}
}
return null;
}
This is the PagingAdapter I am using:
public class PagingAdapter extends FragmentStatePagerAdapter {
Context context;
public PagingAdapter(FragmentManager fm, Context con) {
super(fm);
context = con;
}
#Override
public Fragment getItem(int index) {
try {
switch (index) {
case 0:
return Fragment.instantiate(context, ActivityFragment.class.getName());
case 1:
return Fragment.instantiate(context, GroupFragment.class.getName());
case 2:
return Fragment.instantiate(context, MessageFragment.class.getName());
case 3:
return Fragment.instantiate(context, NotificationFragment.class.getName());
}
return null;
}
catch (Exception ex)
{
ex.printStackTrace();
return null;
}
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 4;
}
}
This creates same view for first two tabs. I want separate content for all tabs.
I am placing WebView in all 4 tabs.
In Activity, I am doing this :
mViewPager.setOffscreenPageLimit(3);
Use SparseArray<Fragment> in your adapter, and in your getItem use new instantiate your fragments like
return new ActivityFragment();
If you want to add bundle info than first create your fragment than add bundle in your
fragment.setArgument(bundle);
And use these line in your deateyItem before calling super
if(0<=sparseArray.indexOfKey(position))
sparseArray.remove(position);
First of all your method instantiate is returning a Fragment right?
Does it add the param implicitly in that method using setArguements if not you may want to change to it. On the other hand FragmentManager will call default constructor of each fragment when needed. So you have to keep them (do not overload constructor) and in destroyItem try removing the fragment for real.
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
getFragmentManager().beginTransaction().remove((Fragment)(object)).commit();
}
What do you mean by separate view? are you sure that you are not inflating the wrong layout in.
GroupClassFragment.java class
Do check int the method.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View myCorrectView = inflater.inflate(R.layout.my_correct_view,container,false);
return myCorrectView;
}
I've been trying to update a listview on my second position of a viewpager when checking a "favourite" checkbox on the listview using an interface. It's working as expected but when I go to the 3rd Fragment and then swipe back again the favourites doesn't update even though everything seems like it should work as before doesn't work until i rotate or restart the app.
I've checked the fragmentmanager and it does contain only three Fragments all the time and the methods do get called (register as if the listview should be changed like it does before i swipe to the 3rd tab and back), so I'm really scratching my head here.
FragmentStatePageAdapter:
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
/* Some irrelevant code */
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
return FragmentList.newInstance();
case 1:
return FragmentFavourites.newInstance();
case 2:
return FragmentThree.newInstance();
default:
return FragmentList.newInstance();
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
#Override
public int getCount() {
return 3;
}
Activity (callback):
#Override
public void favouritesChangedFromMainMenu() {
FragmentFavourites favouritesList = (FragmentFavourites) mSectionsPagerAdapter.getRegisteredFragment(1);
if(null != favouritesList) {
favouritesList.favouritesChangedFromMainMenu();
}
}
When you move to the third fragment, the first one is probably killed (with a call to onSaveInstanceState).
Solution 1, yourViewPager.setOffscreenPageLimit(2);
Solution 2, check if your first fragment is killed... and re-created when you swipe and re-create correct link that you do in onCreate.
I have been searching for a while for a way to refresh a gridview within a viewpager and I am yet to find an answer. More specificaly my problem is that I have a view pager with three gridviews. Each gridview is populated by an arraylist and if the arraylist is changed the gridview stays the same. How can I make it update? I have tried calling notifydatasetchange on the adapter and that doesnt help.
here is my code for the view pager adapter
public class FavoritesViewPager extends FragmentStatePagerAdapter {
public FavoritesViewPager(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
UserDatabaseHandler UDH = new UserDatabaseHandler(getActivity());
switch (i) {
case 0:
ArrayList<Exercise> strengthList = UDH.getFavoriteExercises("strengthexercises");
UDH.close();
return FavoriteExercises.newInstance(strengthList);
case 1:
ArrayList<Exercise> stretchList = UDH.getFavoriteExercises("stretchingexercises");
UDH.close();
return FavoriteExercises.newInstance(stretchList);
case 2:
ArrayList<Exercise> warmupList = UDH.getFavoriteExercises("warmupexercises");
UDH.close();
return FavoriteExercises.newInstance(warmupList);
default:
ArrayList<Exercise> temp = UDH
.getFavoriteExercises("warmupexercises");
UDH.close();
return FavoriteExercises.newInstance(temp);
}
}
#Override
public int getCount() {
return 3;
}
}
If your pager has only two pages, you should use FragmentPagerAdapter. You can get the fragment by findFragmentByTag then call update method of this fragment.
String fragmentName = makeFragmentName(_viewPager.getId(), i);
Fragment frag = _fragmentManager.findFragmentByTag(fragmentName);
private static String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}