PagerAdapter and ViewPager start a new fragment over the current visible view - android

I have a ViewPager with a PagerAdapter that is called from another fragment, a main fragment if you will.
My PagerAdapter is showing the user images and the user can swipe between them using ViewPager.
I've registered an onClick listener for every image and it seems to work as expected, at least it returns the correct position if I debug the position from instantiateItem.
The problem is that this onClick is supposed to start a new fragment, like an overlay over the image that the user clicked on but it doesn't start the fragment over the current image that the user clicked on for some reason.
Example of what I mean:
On page one onClick it starts the fragment at the correct position, (overlaying the current visible view).
On page two onClick it starts the fragment on page 1 (not in the visible view and not where the onClick fired).
On page three onClick it starts the fragment on page 2 (not in the visible view and not where the onClick fired).
Code:
MainFragment.java
public class MainFragment extends Fragment implements AdapterView.OnItemSelectedListener {
ViewPager viewPager;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
viewPager = (ViewPager) getView().findViewById(R.id.viewPager);
swipeAdapter = new SwipeAdapter(getActivity(), getContext(), stringFetchedName, stringFetchedUserID, stringFetchedUsername, stringFetchedRating, stringFetchedDescription, stringFetchedDate);
viewPager.setAdapter(swipeAdapter);
}
}
SwipeAdapter.java
public class SwipeAdapter extends PagerAdapter {
FragmentActivity activity;
Context context;
String names[], userid[], username[], rating[], description[], date[];
LayoutInflater layoutInflater;
ImageView imageView;
public View itemView;
public SwipeAdapter(FragmentActivity activity, Context context, String names[], String userid[], String username[], String rating[], String description[], String date[]) {
this.activity = activity;
this.context = context;
this.names = names;
this.userid = userid;
this.username = username;
this.rating = rating;
this.description = description;
this.date = date;
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return names.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((View) object);
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
final View itemView = layoutInflater.inflate(R.layout.imageswap, container, false);
imageView = itemView.findViewById(R.id.imageView);
container.addView(itemView);
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (itemView.findViewById(R.id.userText).isShown()) {
showImageOverlay(v);
} else {
hideImageOverlay(v);
}
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
public void showImageOverlay(View view) {
Fragment fragment = new ImageOverlayFragment().newInstance("0", "0");
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.imageSwap, fragment, IMAGE_OVERLAY);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
public void hideImageOverlay(View view) {
Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag(IMAGE_OVERLAY);
if(fragment != null) {
activity.getSupportFragmentManager().beginTransaction().remove(fragment).commit();
}
}
}
How can I make sure that the new fragment ImageOverlayFragment I start is started on top of the image that was clicked, like how could I take the position from instantiateItem into account etc? or what should I do?
If I debug Log.d("debug", "Clicked position:" + position); inside the imageView OnClickListener it gives me the correct position but when I call showImageOverlay(v), it seems like it always starts the fragment on the view to the left of my currently visual view?
Note:
I'm passing a FragmentActivity to the SwipeAdapter from the MainFragment which is why I can access getSupportFragmentManager().
Update:
I tried to use FragmentStatePagerAdapter instead of PagerAdapter as suggested by SO and the result is still the same. The onClick event is sending the correct position and I can toggle the visibility of visible elements within the onClick event etc but I still cannot start the new fragment over the view of where the user clicked. The new fragment still starts to the left, and not in the view of where the user clicked. Problem still remains.

fragmentTransaction.replace(R.id.imageSwap, fragment, IMAGE_OVERLAY);
You are using your activity's TransactionManager to start you overlay fragment. In the above code, what I am guessing is that you are giving the ID of the container viewgroup of , parent of ImageView in its layout file. In a ViewAdapter not the currently visible fragment is initialized or view is inflated, but adjacent fragments/views also. That means your view hierarchy from activity root contains more than one layout with the ID you provide for fragment transaction. When activity searches its view hierarchy, it will return the first view it reaches with given ID. In your case, when you are not on the first page of adapter, this first matching view will be inside the leftmost page inside ViewPager. ViewPager - by default - uses one view on left and one view on right of the visible page in created state. That is why you see your fragment is instantiated on the page before the one you want it to be.
As a solution, you can consider using fragments inside Pager instead of plain views. Every fragment has a 'childFragmentManager' that u can use to start your new fragment upon click.
I hope this helps.

I had a similar issue with FragmentStatePagerAdapter, and what worked had much to do with two of the adapter methods:
instantiateItem - "Create the page for the given position."
setPrimaryItem - "Called to inform the adapter of which item is currently considered to be the "primary", that is the one show to the user as the current page."
Because of the way the adapter works, where it instantiates the fragments before they are displayed, the instantiateItem method is called for fragments or slides that are not in view (apart from the first item). If you can override setPrimaryItem, and then leave the UI/view set up in instantiateItem, and then the rest of the logic (listener with the show/hide overlay) in setPrimaryItem, you can make sure the listener and the fragment are set to the currently active view/fragment.
Let me know if that makes sense to you

Related

Viewpager to update only current visible fragment

i update recycleView inside viewpager fragments and call pagerAdapter.notifyDataSetChanged(); to update them. It updates all recycleviews and the app takes long time. Now to overcome this problem i want to update only the current visible fragment and on ViewPage change update other fragments with notifyDataSetChanged(); is there a way to tell which fragment to update?
ViewPagerAdapter
private class MyPageAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragments;
private int[] mResources;
public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
#Override
public int getItemPosition(Object object) {
MyFragment f = (MyFragment) object;
if (f != null) {
f.update();
}
return super.getItemPosition(object);
}
}
this is fragment
public static class MyFragment extends Fragment implements CustomAdapterOdds.OnGameClickListener, Updateable {
public static final String COLUMN_IN_DISPLAY = "column_in_display";
RecyclerView recyclerView, recyclerView2;
HashMap<String, List<OddsFeed>> oddsList1;
HashMap<String, List<OddsFeed>> oddsList2;
int oddsColDisp;
CustomAdapterOdds adapterOdds1, adapterOdds2;
boolean isvisible = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
oddsList1 = new HashMap<>();
oddsList2 = new HashMap<>();
Bundle b = this.getArguments();
oddsColDisp = b.getInt(COLUMN_IN_DISPLAY);
List<GameFeed> leftGames = getArguments().getParcelableArrayList("list");
if (b.getSerializable("hashmap1") != null && b.getSerializable("hashmap2") != null) {
oddsList1 = (HashMap<String, List<OddsFeed>>) b.getSerializable("hashmap1");
oddsList2 = (HashMap<String, List<OddsFeed>>) b.getSerializable("hashmap2");
}
View v = inflater.inflate(R.layout.view_table_viewpager_fragment_layout, container, false);
ArrayList<GameFeed> gameFeedsCol0 = getArguments().getParcelableArrayList("list");
//recycleview1
recyclerView = v.findViewById(R.id.table_view_recycle_view_odds1);
adapterOdds1 = new CustomAdapterOdds(getContext(), leftGames, this, oddsList1, bestSitesList, oddsColDisp);
configRecyclerViewOdds(getContext(), recyclerView, adapterOdds1);
//recycleview2
recyclerView2 = v.findViewById(R.id.table_view_recycle_view_odds2);
adapterOdds2 = new CustomAdapterOdds(getContext(), leftGames, this, oddsList2, bestSitesList, oddsColDisp + 1, true);
configRecyclerViewOdds(getContext(), recyclerView2, adapterOdds2);
return v;
}
#Override
public void setMenuVisibility(final boolean visible) {
super.setMenuVisibility(visible);
if (visible) {
isvisible = true;
}
}
#Override
public void onResume() {
super.onResume();
Toast.makeText(getContext(), "onresume", Toast.LENGTH_SHORT).show();
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
}
#Override
public void OnGameClickListener(View view, int position) {
}
#Override
public void update() {
Toast.makeText(getContext(), "update", Toast.LENGTH_SHORT).show();
if (isVisible()) {
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
updateViewpager++;
int size = bestSitesList.size() / 2;
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
if (updateViewpager == size - 1) {
progressBar.setVisibility(View.INVISIBLE);
updateViewpager = 0;
}
}
}
}
I'm sorry this is a bit of rambling answer but there was not enough code example given to make a functioning answer, so more really trying to explain a concept. Really need details of when and how the data is updated BUT...
As you are using androidx, you might want to consider moving to viewpager2 or with viewpager you can change how the fragment lifecycle states are managed.
If you changed to using BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter#BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT when you constructed the Adapter https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter#FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager,%20int)
Then all the Fragments will only be brought up to "Started" when created or re-created and only the current one on screen with "Resumed" and then "Paused" when moved off screen.
The exact mechanics depends on how the Fragments recyclerviews get their data and what triggers the update but then general idea is for the Fragments onCreateView to create a lightweight "shell" of a layout, I basically have the static buttons/text and create the recyclerview with an empty dataset.
Then in the onResume method of the Fragment which only gets called for the Fragment currently on Screen it calls your update method to replace the two recyclerview's empty datasets with the actual dataset and does a notifyDataSetChanged() on the recyclerviews.
Therefore when the viewpager is initially created X number of Fragments gets created and there static content is laid out plus 1 Fragment (the current one on screen) gets the recyclerview populated with actual data.
You might also then want to put in some optimisation checks in onResume of the Fragment to check the recyclerview views data has actually changed (a simple size check or using Recyclerview's DiffUtils) otherwise as you move between the Fragment's in the viewpager each Fragment will be paused/resumed.
This really only delays the cost of the two recyclerviews in the Fragment until it is really needed (when it is about to be displayed), it's a form of "Lazy loading"
With moving the "dynamic" data to this "lazy loading" is could be possible to remove the need to notifyDataSetChanged on the Fragments BUT the code snippets don't show enough about how and why the recyclerviews content changes.
With this method when the data is drawn is changed and you might not like how it looks.
This is really at a high level an inversion of the Fragments update logic, instead of saying "The data has changed redraw the data in all the Fragments" it is "This fragment is been shown, redraw the data IF it has been changed since the last time I drew it"

How to make button visible only after viewpager page loaded completly?

I am new in view pager.i have 2 pages in view pager..each contain button..i want show button after page fully loaded.Now button is showing half scrolling it self..
class MyPageAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
adding page
pageAdapter = new MyPageAdapter(activity.getSupportFragmentManager(), fragments);
viewpager.setId(position);
viewpager.setAdapter(pageAdapter);
fragment:
public class secondclass extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_secondclass, container, false);
return rootView;
}
please help me..
Just use OnPageChangeListener.
Actually you need to do some tedious tasks for this to be done.
First your activity containing the viewpager should have a way to communicate with the fragments of the viewpager. once you are done with this.
Then set OnPageChangeListener on the viewpager
and one of the overridden callback methods would be the following one
#Override
public void onPageSelected(int position) {
// you know the position now. send the message to the fragment at this position to show the button or do any particular task
}
you are all set to go
In viewpager nearby fragments are loaded even before they are actually visible to the user, this is done so as to provide a smooth scrolling effect.
So onCreateView and onResume methods of the second fragment is already called...even before the user scrolls to the second page.
So to do something exactly when the user loads a page completely we need to user the onPageSelected method and with the help of the position argument we notify the fragment at that position to do the tasks.
In your case show the button...so you need to set visibility of the button INVISIBLE in the layout and make it visible when the page is selected.
For the above thing to work your activity needs to have a reference to the fragment...or you may request the viewpageradapter to provide you the reference by doing a bit caching of the current fragment...apply any logic of yours.
Assume all your fragments are of type MyFragment and have a method doVisibleTask to make the button visible AND getItem of your view pager adapter returns the same instance everytime for a particular position.
you could do the following
#Override
public void onPageSelected(int position) {
// get the reference to the fragment
MyFragment mf = (MyFragment)viewPagerAdapter.getItem(position);
mf.m=doVisibleTask();
// you know the position now. send the message to the fragment at this position to show the button or do any particular task
}

How to display images in Android and change it by swipe right/left?

I want to add an introduction to my Android application, to inform the user about how the app works. This intro will be displayed only, if the preferred settings intro will be false. So in this intro, there will be 3 images and at the end, there will be a page, with some text and two buttons, to enable the user to access the application, by making a login. The change between each image, will be made with a swipe movement, (so right to left +, left to right -). How Can I do ?
This can be done via the use of Fragments and ViewPager and FragmentPagerAdapter. Look at this documentation:
FragmentPagerAdapter: http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html
ViewPager:
http://developer.android.com/reference/android/support/v4/view/ViewPager.html
You can have one fragment that is instantiated based on the id in the ViewPager, and that id will indicate which image to show in your image fragment. So for three images, you instantiate a new fragment that sets the image in the fragment based on the current page in the FragmentPagerAdapter. The second fragment can be one for the login buttons and text you want at the end.
Ex for adapter defined in your FragmentActivity (or AppCompatActivity)
public class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
if(position < 3)
return ImageFragment.newInstance(position);
else
return new LoginFragment();
}
}
Ex for the image fragment for the various images in your introduction:
public static class ImageFragment extends Fragment{
private int mPosition;
public ImageFragment(){
}
public static ImageFragment newInstance(int pos){
ImageFragment frag = new ImageFragment();
Bundle args = new Bundle();
args.putInt("pos", pos);
frag.setArguments(args);
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPosition = getArguments().getInt("pos");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_image, container, false);
ImageView backgroundView = (ImageView) v.findViewById(R.id.background_image);
switch(mPosition){
case 0:
//set background view image 1
case 1:
//set background view image 2
default:
//set background view image 3
}
return v;
}
}
I would recommend using a ViewPager. Check out this tutorial from the Developer Guide
http://developer.android.com/training/animation/screen-slide.html
If you want to add functionality to each of these pages instead of having just images then perhaps you can implement a fragmentStatePagerAdapter and then put all the functionality in each fragment. Here is a tutorial to implement one.
http://www.truiton.com/2013/05/android-fragmentstatepageradapter-example/
I think we can do it by using recycler view itself.
Using PagerSnapHelper layout manager in recycler view, we can implement swipe to change images.
recyclerView.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.HORIZONTAL, false));
// add pager behavior
PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

Showing a fragment with different parameters in tabs of a view pager

I want to show a same fragment for example fragment having activities of a day of week in a viewpager for all days with different data. I will be giving the dayNumber parameter to each fragment being instantiated and showing related activities. The problem is I see same fragment in each tab no matter what parameter I passed. I think the last fragment added or instantiated by a pager overrides all the other tab fragments instance. Because when I open a list item in expendableList View it is opened in all fragments of the pager.
This is how I am using the pager and fragment.
Pager
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// When swiping between pages, select the
// corresponding tab.
if (bar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS)
if (bar.getSelectedNavigationIndex() != position)
bar.setSelectedNavigationItem(position);
// should be changed when some solution comes.
if (tab == 0) {
Fragment ev;
if ((ev = (Fragment) mPagerAdapter.instantiateItem(mPager,
0)) instanceof frTimetable)
((frTimetable) ev).refresh(day.Monday);
} else if (tab == 1) {
Fragment ac;
if ((ac = (Fragment) mPagerAdapter.instantiateItem(mPager,
1)) instanceof frTimetable)
((frTimetable) ac).refresh(day.Tuesday);
}
tab = position;
}
Fragement
{
//class other methods
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fr_timetable, container, false);
ExpandList = (ExpandableListView) view.findViewById(R.id.expActivityView);
//I will change the list items in refresh method of the fragment for a day type
ExpListItems = new ArrayList<Items>(Timetable_Provider.getAllActivites());
ExpAdapter = new ExpandListAdapter(getActivity(), ExpListItems);
ExpandList.setAdapter(ExpAdapter);
return view;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
instantiateItem() is certainly not what you want. That is called by ViewPager, not by a consumer of a ViewPager.
Normally, you would provide the data to the ViewPager as part of setting up the pages, inside of your PagerAdapter. For example, this sample app uses the arguments Bundle to pass the page number of the page to the Fragment that is the implementation of the page.
If the data inside a page needs to be updated, ideally the page itself determines on its own that this is needed and handles it. Or, use an event bus (e.g., LocalBroadcastManager, greenrobot's EventBus, Square's Otto) to publish information that relevant pages can pick up. There is no great way to get at an existing page from outside of the page itself using FragmentPagerAdapter or FragmentStatePagerAdapter (e.g., to have an activity push data into a page), which is one of the reasons I wrote ArrayPagerAdapter.

Android: Using ViewPager with multiple dynamic Fragments

I am using a ViewPager to show 9 fragments. In each of these fragments, I want to just show a different picture. I want to use one single fragment layout, but dynamically add in the picture. Also, would like add a "Continue" button on the last fragment that when pressed will go to another activity.
How do I go about making a fragment layout dynamic?
Main Activity
public class StoryboardPageActivity extends FragmentActivity {
// The number of pages (wizard steps) to show in this demo.
private static final int NUM_PAGES = 9;
// The pager widget, which handles animation and allows swiping horizontally to access previous and next wizard steps.
private ViewPager mPager;
// The pager adapter, which provides the pages to the view pager widget.
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_storyboard_page);
// Instantiate a ViewPager and a PagerAdapter.
mPager = (ViewPager) findViewById(R.id.storyboardPager);
mPagerAdapter = new StoryboardPagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
#Override
public void onBackPressed() {
if (mPager.getCurrentItem() == 0) {
// If the user is currently looking at the first step, allow the system to handle the
// Back button. This calls finish() on this activity and pops the back stack.
super.onBackPressed();
} else {
// Otherwise, select the previous step.
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
}
// A simple pager adapter that represents 5 fragment objects, in sequence.
private class StoryboardPagerAdapter extends FragmentStatePagerAdapter {
public StoryboardPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return StoryboardFragment.newInstance(position);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
}
Fragment
public class StoryboardFragment extends Fragment {
private static final String KEY_POSITION = "position";
static StoryboardFragment newInstance(int position) {
StoryboardFragment frag = new StoryboardFragment();
Bundle args = new Bundle();
args.putInt(KEY_POSITION, position);
frag.setArguments(args);
return(frag);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_storyboard_page, container, false);
ImageView image = (ImageView)rootView.findViewById(R.id.imgStoryboard);
int position = getArguments().getInt(KEY_POSITION, -1);
int[] images = {R.drawable.storyboard1, R.drawable.storyboard2, R.drawable.storyboard3,
R.drawable.storyboard4, R.drawable.storyboard5, R.drawable.storyboard6,
R.drawable.storyboard7, R.drawable.storyboard8, R.drawable.storyboard9};
image.setImageResource(images[position]);
return rootView;
}
}
Fragment XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff" >
<ImageView
android:id="#+id/imgStoryboard"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:contentDescription="#string/storyboardSlide" />
</RelativeLayout>
How do I go about making a fragment layout dynamic?
The same way you make any other "layout dynamic". If you want to put an image in an ImageView, call setImageBitmap() or setImageDrawable() or whatever. For example, the PagerAdapter could supply the position to the fragment (via a factory method), and the fragment could then know what image to load.
This sample project demonstrates populating the hint of an EditText with a custom value based upon the page's position.
With respect to the "Continue" button, either have a separate fragment class for that (and appropriate smarts in your PagerAdapter, or always have the button in your layout, but set to android:visibility="gone" by default, toggling it via setVisibility(View.VISIBLE) for the fragment that needs it.

Categories

Resources