Issue
I'm using a ViewPager with fragments.
Every fragment has a button in its layout which, when clicked, should run a piece of code and then disappear.
If I click a button, it runs the code and disappears as programmed.
But if I swap away from that fragment of 2 tabs, and then I get back to it, it looks like the ViewPager killed the cache for that fragment and restored it with its default layout: so the button appears again.
How can I always keep saved the status of each fragment? I don't want the ViewPager to keep saved just the two fragments asides the one I'm looking at.
Images
Adapter
Here's my adapter:
public class MineAdapter extends PagerAdapter {
private Context mContext;
private LayoutInflater mLayoutInflater;
public MineAdapter(Context context) {
mContext = context;
mLayoutInflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
//Fill Data directly from Repository
return CenterRepository.getSingletonInstance().getListOfLavels().size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((FrameLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, final int position) {
System.out.println("Code executed");
final View itemView = mLayoutInflater.inflate(R.layout.carousal_page, container,
false);
switch (position) {
case 0:
itemView.setBackgroundResource(R.color.iron);
break;
case 1:
itemView.setBackgroundResource(R.color.coal);
break;
case 2:
itemView.setBackgroundResource(R.color.gold);
break;
}
//Mine Name
((TextView) itemView.findViewById(R.id.mineName)).setText(
CenterRepository.getSingletonInstance().getListOfLavels().get(position).getmName());
//Mine Cost
((TextView) itemView.findViewById(R.id.mineCost)).setText("" +
CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost());
//Mine Cost
((TextView) itemView.findViewById(R.id.mineDropRate)).setText("" +
CenterRepository.getSingletonInstance().getListOfLavels().get(position).getDropRate());
//Mineral Name
((TextView) itemView.findViewById(R.id.mineMineral)).setText(
CenterRepository.getSingletonInstance().getListOfLavels().get(position).getMineral().getName());
//Mineral Drop Rate
((TextView) itemView.findViewById(R.id.mineDropRate)).setText("" +
CenterRepository.getSingletonInstance().getListOfLavels().get(position).getMineral().getValue());
// Unlock Button
itemView.findViewById(R.id.unlockButton).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (CenterRepository.getSingletonInstance().getCurrentUser().getGold() >=
CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost()) {
//If User has more gold than cost to unlock remove lock image and buy it
CenterRepository.getSingletonInstance().getCurrentUser().setGold(
CenterRepository.getSingletonInstance().getCurrentUser().getGold()
- CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost()); // Update user's gold
itemView.findViewById(R.id.unlockButton).setVisibility(View.GONE); // Remove lock button
Toast.makeText(mContext,
"Reduced " + CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost() +
"\n Updated Gold " + CenterRepository.getSingletonInstance()
.getCurrentUser().getGold(), Toast.LENGTH_LONG).show();
} else {
// Not enough money
Toast.makeText(mContext, "Not enough money to purchase You need " +
(CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost()
- CenterRepository.getSingletonInstance().getCurrentUser().getGold()) + "More", Toast.LENGTH_SHORT).show();
}
}
});
container.addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((FrameLayout) object);
}
}
public void setOffscreenPageLimit(int limit)
is what you need in for ViewPager view.
Android developer link for ViewPager
Save the state of the button's visibility in onSaveInstanceState and handle it accordingly in onViewCreated.
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
btnTest = (Button) view.findViewById(R.id.btnTest);
btnTest.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
btnTest.setVisibility(View.GONE);
}
});
if(savedInstanceState != null){
btnTest.setVisibility(savedInstanceState.getInt("button_visibility",View.VISIBLE));
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("button_visibility",btnTest.getVisibility());
super.onSaveInstanceState(outState);
}
Related
In my activity I have a OnPageChangeListener and view Pager implemented.
On onPageSelectedin function I call function name viewPagerHandler, when I have all the logic for single viewPager Page (Buttons, actions depended of current page etc.)
public class ActivityClass extends Activity implements ViewPager.OnPageChangeListener, View.OnClickListener{
protected void onCreate(Bundle savedInstanceState) {
viewPager=(ViewPager)findViewById(R.id.Pager);
adapter= new
ViewPageAdapter(ActivityClass.this,list,imagesList);
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(0);
viewPager.addOnPageChangeListener(ActivityClass.this);
}
#Override
public void onClick(View v) {
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
this.position=position;
viewPagerHandler(viewPager.findViewWithTag(position),position);
}
#Override
public void onPageScrollStateChanged(int state) {
}
public View viewPagerHandler(final View view, final int position){
//ALL LOGIC BUTTONS ACTIONS ETC.
view.invalidate();
return view;
}
}
public class ViewPageAdapter extends PagerAdapter{
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
final View itemView = LayoutInflater.from(mContext).inflate(R.layout.art_work_item, container, false);
itemView.setTag(position);
container.add(itemView); // edited after suggested
if(mContext instanceof ActivityClass && position==0) //The page listener in ActivityClass is trigger only when page is changed so i tried to hack it when the viewPager is instantiate for the first time
return ((ActivityClass) mContext).viewPagerHandler(itemView,position);
return itemView;
}
}
EDIT: The logic works perfectly on the first and the last item. For rest of them the view is not refreshing (however its still working, because i have buttons which changing color after clicked, when I click on them and then slide to next item and back to the previous one the color is changed so its trigger)
You are missing the line in the instantiateItem() method,
public Object instantiateItem(final ViewGroup container, final int position) {
final View itemView = LayoutInflater.from(mContext).inflate(R.layout.art_work_item, container, false);
itemView.setTag(position);
container.addView(itemView);
if(mContext instanceof ActivityClass && position==0) //The page listener in ActivityClass //is trigger only when page is changed so i tried to hack it when the viewPager is //instantiate for the first time
return ((ActivityClass) mContext).viewPagerHandler(itemView,position);
return itemView;
}
I have been trying to retrieve a value from a viewpager fragment but I can't seem to get it right.
The FragmentA consist of a listview, and when an item is clicked it should send the position as an int to FragmentB which suppose to display it in as a toast. Other code are working perfectly but the toast is not displaying anything.
Fragment A:
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> adapter, View v, int position, long id) {
//This check whether the song is playing
if (MusicPlaying.mp.isPlaying()) {
post = position;
MusicPlaying.mp.stop();
start();
} else {
start();
}
}
});
public void start() {
ViewPager vp = (ViewPager)getActivity().findViewById(R.id.slide);
vp.setCurrentItem(vp.getCurrentItem() + 1, true);
}
public static int rInt(){
return post;
}
FragmentB
public class FragmentB extends Fragment{
int position;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View v = inflater.inflate(R.layout.album, container, false);
position = ListMusic.rInt();
Toast.makeText(getActivity(), "" + position, Toast.LENGTH_LONG).show();
return v;
}
}
Direct Communication between two fragments should be avoided whenever possible, but it should be done with the help of an Activity that is holding the two fragments , in your case, it is the activity in which view pager is inflated. I think you are trying to implement a master-detail layout in android, in which, if you do something in master fragment(your Listview fragment A) then it should pass data into detail fragment(your fragment B) . There is a tutorial for this on the android developer website . Link is given below
https://developer.android.com/training/basics/fragments/communicating.html
There is also a very good video by Slidenerd explaining the InterFragment Communication. This is the easiest to understand and best one on the web.
https://www.youtube.com/watch?v=VyyGP_d0Ia8
Don't try to use static fields to exchange information between activities and fragments. They have a lifecycle and, if the user rotates the screen or sends the app to background and the process is destroyed, your static variable value will no longer be available. This is the way to share information between fragments:
In FragmentA:
FragmentB fragmentB = new FragmentB();
Bundle args = new Bundle();
args.putInt("position", position);
fragmentB.setArguments(args);
// Do your FragmentTransaction here.
In FragmentB:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View v = inflater.inflate(R.layout.album, container, false);
position = getArguments().getInt("position", 0);
Toast.makeText(getActivity(), "" + position, Toast.LENGTH_LONG).show();
return v;
}
The better way is to have a interface implemented in your activity. And on each fragment onAttach function, get the interface. That way you have the temporal data in the activity and get it in the other fragment:
Interface :
public interface MyFragmentListener {
int getSavedInt();
void saveInt(int a);
}
Your Activity:
public class BaseActivity extends AppCompatActivity implements MyFragmentListener{
private int mInt;
#Override public int getSavedInt(){return mInt;};
#Override public void saveInt(int a){this.mInt = a;};
}
Your Fragment:
public class BaseFragment extends Fragment{
private MyFragmentListener mListener;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (mListener==null)
if (context instanceof MyFragmentListener) {
mListener = (MyFragmentListener) context;
} else {
throw new RuntimeException(context.toString() + " must implement MyFragmentListener");
}
}
public void saveInt(int a){
if (mListener!=null)mListener.saveInt(a);
}
public int getInt(){
if (mListener!=null)return mListener.getSavedInt();
else return -1;
}
}
I have used viewpager which displays view with an image and radiobutton below. Each swipe screen is not a fragment, they are layouts which just swipe the same view with different imageview. The problem is that, I am unable to deselect the radio button which is in left and right side of the current view after i select the radio button of the current screen. If I return POSITION_NONE in getItemPosition(), it refresh the screen and radio button also deselected but the view is flickered. If i am able to call instantiateItem(), my problem is solved . But this method is called only when view is destroyed based on the setOffsetScreenPageLimit(). How can I achieved such requirements, if view pager cannot help?
Fragment:
public class AnswerImageDialogFragment extends DialogFragment implements ViewPager.OnPageChangeListener {
//member declaration
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String strQuestion = null;
View rootView = inflater.inflate(R.layout.activity_viewpager_demo, container,
false);
getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
intro_images = (ViewPager) rootView.findViewById(R.id.pager_introduction);
pager_indicator = (LinearLayout) rootView.findViewById(R.id.viewPagerCountDots);
//do some stuff
//set the adapter
mAdapter = new ViewPagerAdapter(getContext(), map);
intro_images.setAdapter(mAdapter);
intro_images.setCurrentItem(clickPosition);
intro_images.setOnPageChangeListener(this);
setUiPageViewController();
bus = EventBus.getDefault();
bus.register(this);
return rootView;
}
//other method
}
PagerAdapter:
public class ViewPagerAdapter extends PagerAdapter {
// member declaration
public ViewPagerAdapter(Context mContext, HashMap<String, Object> map) {
//other initialization
}
#Override
public int getCount() {
return optionImages.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((LinearLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, final int position) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.pager_item, container, false);
final ImageView ivOption = (ImageView) itemView.findViewById(R.id.answerId); // this is used as radio button in this case
/*The code snippet is to select the answer option based on whether answer is choosen or not.
If choosen, select as default in the pop up answer.
*/
if (null != ans) {
Iterator iterator = ans.iterator();
if (ans.size() > 0) {
int value = (int) iterator.next();
if (value == position) {
ivOption.setImageResource(R.drawable.ic_radio_button_tick);
} else {
ivOption.setImageResource(R.drawable.ic_radio_button_nontick);
}
}
}
p = position;
/*The code snippet is to change the answer value based on the user selection.
This means if user choosen another answer then clear the earlier choosen answer
*/
ivOption.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//do stuff
}
});
txtQuestion.setText(question);
txtOption.setText(optionsList.get(position));
imageView.setParseFile(optionImages.get(position));
imageView.loadInBackground();
container.addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
LinearLayout ll = (LinearLayout) object;
/*RelativeLayout ivOption = (RelativeLayout) ll.getChildAt(2);
ImageView iv = (ImageView) ivOption.getChildAt(1);
iv.setImageResource(R.drawable.ic_radio_button_tick);*/
container.removeView(ll);
}
#Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
public void refresh(ArrayList object) {
this.object = new ArrayList();
this.object.addAll(object);
this.notifyDataSetChanged();
}
I am having strange issue in FragmentStatePagerAdapter.when i swipe front ,it works gud,when i swipe back it skip 2 fragments.how to resolve this?
is there any way to get current item no?
NavigationPagerAdapter
public static class NavigationPagerAdapter extends FragmentStatePagerAdapter {
public NavigationPagerAdapter(android.support.v4.app.FragmentManager fm ) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new NaviagtionFragment();
Bundle args = new Bundle();
args.putInt("position", i); // Our object is just an integer :-P
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
// For this contrived example, we have a 100-object collection.
return 100;
}
#Override
public CharSequence getPageTitle(int position) {
return "OBJECT " + (position );
}
in fragment
public static class NaviagtionFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.item_pager, container, false);
Bundle args = getArguments();
int m= args.getInt("position");
Toast.makeText(c, ""+m, Toast.LENGTH_SHORT).show();//here i tracked the position of fragments when i swipe front ,its increasing 1,2,3,4,5,6,7 but when i swipe back it will go directly 7 -5 th position..
}}}
Toast is very slow and it is not good solution when you are swiping views, because it remains on the screen longer and can not reach to show all messages, so its better to check position in LogCat. Try this for items position and see position with Log:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNum = getArguments() != null ? getArguments().getInt("position") : 1;
Log.i("tag", "Item clicked: " + mNum);
}
I would like to implement a ViewPager which uses Fragments and can be swiped in a curcular motion e.g. Page (A<-->B<-->C<-->A).
I have read a couple of posts on how this is done, e.g. returning a fake count of how many elements there are and setting the position at the start in the middle.
how to create circular viewpager?
These all seem to be based of a PagerAdapter. When I try to do a similar thing while extending FragmentPagerAdapter, as soon as I return a fakeCount of pages I get an exception when I Swipe through my Fragments, I only have 2 Fragments.
Exception: java.lang.IllegalStateException: Can't change tag of fragment.
I think this is caused as the FragmentManager thinks I am in position 2 but position 2 points to the fragment at position 0. Does anyone know how I can avoid this? I am thinking I should experiment with extending Fragmentmanager. Any examples or help with this would be greatly appreciated.
I know it is a bit late but this is how it worked for me:
I needed a circular swipe between 3 fragments, so I made those 3 and two more virtual to help me implement the page looping:
public static class FirstViewFragment extends Fragment {
// Empty Constructor
public FirstViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_1, container, false);
}
}
public static class SecondViewFragment extends Fragment {
// Empty Constructor
public SecondViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_2, container, false);
}
}
public static class ThirdViewFragment extends Fragment {
// Empty Constructor
public ThirdViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_3, container, false);
}
}
And two more virtual fragments that enabled me to swipe left from the first and right from the last. The first virtual inflates the same layout as the last actual and the last virtual the same layout as the first actual:
public static class StartVirtualFragment extends Fragment {
public StartVirtualFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_3, container, false);
}
}
public static class EndVirtualFragment extends Fragment {
public EndVirtualFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_1, container, false);
}
}
My Adapter:
private class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
switch (i) {
case 0:
return new StartVirtualFragment();
case 1:
if (firstViewFragment == null) {
firstViewFragment = new FirstViewFragment();
}
return firstViewFragment;
case 2:
if (secondViewFragment == null) {
secondViewFragment = new SecondViewFragment();
}
return secondViewFragment;
case 3:
if (thirdViewFragment == null) {
thirdViewFragment = new ThirdViewFragment();
}
return thirdViewFragment;
case 4:
return new EndVirtualFragment();
}
return null;
}
#Override
public int getCount() {
return 5;
}
}
And my page listener I used the onPageScrollStateChanged to set the correct page and implement the loop:
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
int pageCount = viewPager.getChildCount();
int currentItem = viewPager.getCurrentItem();
if (currentItem == 0) {
viewPager.setCurrentItem(pageCount - 2, false);
} else if (currentItem == pageCount - 1) {
viewPager.setCurrentItem(1, false);
}
}
}
});
And in the end:
viewPager.setCurrentItem(1);
Hope I helped
I have a project in the GitHub with some widgets I've created. Here it its:
https://github.com/CyberEagle/AndroidWidgets
In the following package, there are the adapters to be used with the CircularViewPager:
https://github.com/CyberEagle/AndroidWidgets/tree/master/src/main/java/br/com/cybereagle/androidwidgets/adapter
First, you will use CircularViewPager instead of ViewPager in your layout. The CircularViewPager is here: https://github.com/CyberEagle/AndroidWidgets/blob/master/src/main/java/br/com/cybereagle/androidwidgets/view/CircularViewPager.java
This ViewPager expects a WrapperCircularPagerAdapter, instead of a PagerAdapter. This wrapper is used to trick the ViewPager, making it to think there are a lot of items in the ViewPager, but it actually repeat your items to make the circular effect. So, instead of implementing either PagerAdapter, FragmentPagerAdapter or FragmentStatePagerAdapter, you will implement either CircularFragmentPagerAdapter, CircularFragmentStatePagerAdapter or CircularPagerAdapter. Then, you will wrap your adapter with the WrapperCircularPagerAdapter and set the wrapper in the CircularViewPager, instead of your adapter. Also, when it's time to notify dataset changed, you will call the notifyDatasetChanged() in the wrapper.
When implementing one of the circular adapter, you will notice that instead of implementing instantiateItem, you will have to implement instantiateVirtualItem. For the fragment's pager adapter, you will implement getVirtualItem instead of getItem. That is because I've created the concept of virtual items.
To make it clear, imagine a view pager with 4 items, giving that each item represents a music. When you go all the way to left, you will see the 4th item in the left of the first. Actually, it's a whole new item, but it's linked to the virtual item that represents the 4th music.
Another example: imagine there's only one music now. You will see the same music on the left and on the right. There're 3 items at a time, but only one virtual item.
So, as explained, the Wrapper is tricking the ViewPager, making it think that there are a lot of items. To make it more difficult for the user to reach one of the ends of the ViewPager (it'd take a long time anyway), everytime a change happens to the dataset, the ViewPager goes to the same virtual item, but to one of the real items near the middle.
One more important thing is that the CircularViewPager has the method setCurrentVirtualItem. This method calculates which real item is the nearest desired virtual item and then it uses the setCurrentItem to set it. You have also the option to use the getCurrentVirtualItem, that will return the index of the current virtual item. Notice that if you use getCurrentItem, you'll get a large index.
Well, this is it. I'm sorry for the lack of documentation of the project. I'm planning document it soon. I'm also planning to remove the need for the wrapper. Feel free to copy the code (respecting the Apache 2.0 license), to fork or even contribute to it.
**If you want to make 3 views visible at same time and make it circular**
public abstract class CircularPagerAdapter extends PagerAdapter{
private int count;
int[] pagePositionArray;
public static final int EXTRA_ITEM_EACH_SIDE = 2;
private ViewPager.OnPageChangeListener pageChangeListener;
private ViewPager viewPager;
public CircularPagerAdapter(final ViewPager pager, int originalCount ) {
super();
this.viewPager = pager;
count = originalCount + 2*EXTRA_ITEM_EACH_SIDE;
pager.setOffscreenPageLimit(count-2);
pagePositionArray = new int[count];
for (int i = 0; i < originalCount; i++) {
pagePositionArray[i + EXTRA_ITEM_EACH_SIDE] = i;
}
pagePositionArray[0] = originalCount - 2;
pagePositionArray[1] = originalCount -1;
pagePositionArray[count - 2] = 0;
pagePositionArray[count - 1] = 1;
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
public void onPageSelected(final int position) {
if(pageChangeListener != null)
{
pageChangeListener.onPageSelected(pagePositionArray[position]);
}
pager.post(new Runnable() {
#Override
public void run() {
if (position == 1){
pager.setCurrentItem(count-3,false);
} else if (position == count-2){
pager.setCurrentItem(2,false);
}
}
});
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(pageChangeListener != null)
{
pageChangeListener.onPageScrolled(pagePositionArray[position],positionOffset,positionOffsetPixels);
}
}
public void onPageScrollStateChanged(int state) {
if(pageChangeListener != null)
{
pageChangeListener.onPageScrollStateChanged(state);
}
}
});
}
#Override
public int getCount() {
return count;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return false;
}
public abstract Object customInstantiateItem(ViewGroup container, int position);
public void setPageChangeListener(ViewPager.OnPageChangeListener pageChangeListener)
{
this.pageChangeListener = pageChangeListener;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
int pageId = pagePositionArray[position];
return customInstantiateItem(container,pageId);
}
#Override
public void destroyItem(View container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
public void setFirstItem()
{
viewPager.setCurrentItem(EXTRA_ITEM_EACH_SIDE - 1);
}
}