ViewPager for potentially infinite number of pages - android

I am currently using a ViewPager object to create a gallery of dynamically loaded photos (from a remote server). I noticed that a ViewPager object relies heavily on knowing the number of pages (or pictures in my case) in advance, while in my situation I don't know.
Am I right? And if I am, is there an alternative that will make sliding between pictures very easy (that's what I like about ViewPager)?
Thanks in advance

Am I right?
Well, you need to have a getCount() method on your PagerAdapter that returns something. If you wish to define that as "relies heavily", then, use, it "relies heavily".
And if I am, is there an alternative that will make sliding between pictures very easy (that's what I like about ViewPager)?
Use ViewPager.
While your images may be loaded dynamically, probably the count of images is knowable in advance. In that case, retrieve that value, then use that for getCount().
Otherwise, have getCount() return 1000000 or something similarly huge. If you find you reach the end of your available pictures, call notifyDataSetChanged() and have your PagerAdapter return the right value from getCount() (since you know it now). Or, have the remaining pages show some placeholder.

For Infinite Scrolling, I figured out one solution to it.
Here's how I am doing it:
No looping is required in this case. No matter what your getCount() is.
After setting the pager adapter, I am setting a setOnPageChangeListener to the view pager like this:
myPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
//Define the focused page before your onCreate()..private int focusedPage = 0;
focusedPage = position;
}
//We don't have to do anything here.
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
//Here's where the magic is. After you reach the end of the page, you can scroll again and it will move your view to position you desire to put. In my case it's 0.
#Override
public void onPageScrollStateChanged(int arg0) {
myPager.setCurrentItem(0,false);
}
});
I know it's too late to answer this question but this was the solution I found out. Maybe helpful for other users.
It's working perfect for me...Any suggestions to make this answer better will really help me too.

Related

Get ViewPager height and width in pixels only once inside Fragment

I know there are a lot of similar questions, but suggested solutions don't work in my case.
What is my problem.
I have fragment nested inside activity.
Fragment layout includes ViewPager.
What is my idea ? As far as my application won't support landscape (this can cause some additional changes), my ViewPager is loading images from the network.Images can be quietly large and heavy, so I have written server side API which helps to convert image to the desired size on the server and return resized image url.
This will help to get rid of OutOfMemory errors.
But the problem is that I need to send ViewPager's height and width in my request.
I am finding it by id in onViewCreated
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mBannerSlider = (AutoScrollViewPager) view.findViewById(R.id.banner_slider);
mBannerSlider.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener(){
#Override
public void onGlobalLayout() {
mSliderHeight = mBannerSlider.getHeight();
mSliderWidth = mBannerSlider.getWidth();
Toast.makeText(getActivity(),"View " + mSliderHeight + " " + mSliderWidth,Toast.LENGTH_SHORT).show();
}
requestBannerImages(mSliderHeight........);
});
The problem here is that this method is called on every page change.
Of course I can use some helper variable to determine if it is first time or not.
Also there is way to send callback from activity to the fragment using this activitiy's method.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
// TODO Auto-generated method stub
super.onWindowFocusChanged(hasFocus);
//Here you can get the size!
}
What is the best solution in this way, maybe I can do this better in another way.
Thanks in advance.
EDIT
I have a plenty of view where I need to get dimensions, so I need to add listener to every view and that after first measurement remove it ?
I would do it with the tree observer. To avoid repeated calls I would remove the listeners once i get the value. This assures you that you will get the dimensions and it wont get call anymore.
if (Build.VERSION.SDK_INT < 16) {
v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
} else {
v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
I took the code from: https://stackoverflow.com/a/16190337/2051397
I understand your dilemma, mainly because I have implemented a multimedia app. However setting dimensions in every view or many UI elements related to icons/images is time consuming. If not so, then you end up with lots of code doing the same stuff.
In my opinion, you don't have to set dimensions for every view, for the sake of looks. You may set max height of a view/UI or/and set setAdjustViewBound to true, let the UI framework handle the images. There is a cost of performance though if the image is large, in your case probably not.
You may refer to Google notes on setMaxHeight of ImageView. Let me know of your view on this, interested to know.

workarounds for GridView.scrollTo()?

As mentioned here, Android's GridView.scrollTo() doesn't work. The method the solution mentioned, setSelectedPosition, doesn't seem to exist in GridView
smoothScrollToPosition does work, but I really don't want the animation.
For context, I have a CursorAdapter-backed GridView, and I want the view to "reset", i.e. scroll to the top, when I change the cursor.
I've been using setSelection(int position) for this, and it seems to be working just fine. To scroll to the top, just use 0 for position.
From the docs:
If in touch mode, the item will not be selected but it will still be positioned appropriately.
Edit:
Added code to post setSelection as a Runnable:
albumsView.post(new Runnable() {
#Override
public void run() {
albumsView.setSelection(0);
}
});
I have found that in order to reliably use gridView.setSelection(index), I have to first call gridView.setAdapter() (even if the adapter has already been set and has not changed). Like so:
gridView.setAdapter(gridAdapter);
gridView.setSelection(index);

visual indication of over scroll in android

I am trying to add some visual indication, that there are no more pages in the desired fling direction in the ViewPager. However I am struggling to find a place, where to put relevant code.
I have tried extending ViewPager class with following code, but the Toast is not displaying (ev.getOrientation() returns always 0). I have also tried the same with history points, but ev.getHistorySize() returns also 0.
What am I missing?
Class example:
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* #see android.support.v4.view.ViewPager#onTouchEvent(android.view.MotionEvent)
*/
#Override
public boolean onTouchEvent(MotionEvent ev) {
boolean result = super.onTouchEvent(ev);
switch (ev.getAction() & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_MOVE:
if (ev.getOrientation() > 0) {
Toast.makeText(getContext(), "left", 0).show();
}
}
return result;
}
}
If you look at the v4 support library you will see there's a class used by ViewPager called EdgeEffectCompat (this provides the glow effect when you reach the beginning or end of a view pager in ICS+) If you look at the implementation in the compat library you will see that it has an if-statement to see if the build version is 14+ (ICS) or not. If it is, then it ends up eventually (if you trace long enough) using the normal EdgeEffect class that was inroduced in ICS. Otherwise it uses BaseEdgeEffectImpl which basically has nothing in it.
If you want, you can make your own custom ViewPager that uses EdgeEffect of your own. You can look at the android source code to see how they implemented EdgeEffect here which you can pretty much copy (just make sure to copy the overscroll_edge and overscroll_glow drawables in the AOSP /res/drawable directories to your own project since they are internal to android) or go ahead and create your own version.
Good luck.
(By the way, that's how they create the cool looking edge tilt effect in the launcher menu on ICS... so you can pretty much be as creative as you want with this ;)
I was trying to get the exact same effect that was asked in this question. I struggle with it and then I read #wnafee answer (I couldn't do it with out it).
But then I struggle to implement what was sound pretty simple from the answer.
I had so much trouble with implementing it, that I might didn't understand the answer correctly, but there were too many issues of inaccessible APIs since I wasn't working in the same package of the Compatibility library.
After I tried some approaches (none of them succeeded, and they were pretty complicated) I went to a slightly different direction, and now it works like a charm. I used some reflection, for the ones who never used it, don't worry it is really the basic of reflection.
I'm not sure if it's the best solution out there, but it worked for me, so if you would like to use it you are welcome. Please read Wnafee example since it explains some of the stuff that I did.
In order to accomplish this task you should just follow my three parts solution. (Will take you between 3-10 minutes)
Part I:
As Wnafee said I just made my own EdgeEffect class by copy paste the source code from here,
(just make sure to copy the overscroll_edge and overscroll_glow
drawables in the AOSP /res/drawable directories to your own project
since they are internal to android)
I only did 2 really small changes:
I declare that the class extends EdgeEffectCompat (I called my class EdgeEffectForEarlyVersions). public class EdgeEffectForEarlyVersions extends EdgeEffectCompat. The reason for doing this change is that the mLeftEdge and mRightEdge are of the type EdgeEffectCompat.
At the first line of the constructor of "my" new class I added a call to the parent constructor super(context);. Since there is no default constructor to EdgeEffectCompat you have to Explicitly call the constructor.
Part II
Besides that I wrote the another function. The purpose of the function is that in case of an early version (before ICS) we would like to use the EdgeEffectForEarlyVersions that we just copied. In order to get that purpose I used reflection.
This is the function:
private static void changeEdgeEffectCompactOnEarlyVersions(ViewPager viewPager, Context context)
{
/* In case that the version is earlier than 14 there is only empty implementation for the edge effect, therefore we change it.
* for more information look on the following links:
* 1. http://stackoverflow.com/questions/10773565/visual-indication-of-over-scroll-in-android
* 2. http://grepcode.com/file/repo1.maven.org/maven2/com.google.android/support-v4/r7/android/support/v4/view/ViewPager.java#ViewPager.0mLeftEdge
* 3. http://grepcode.com/file/repo1.maven.org/maven2/com.google.android/support-v4/r7/android/support/v4/widget/EdgeEffectCompat.java#EdgeEffectCompat
*/
if (Build.VERSION.SDK_INT < 14)
{
try
{
Class<ViewPager> viewPagerClass = ViewPager.class;
//Get the left edge field, since it is private we used getDeclaredField and not getDeclared
Field leftEdge = viewPagerClass.getDeclaredField("mLeftEdge");
leftEdge.setAccessible(true);
//Get the right edge field, since it is private we used getDeclaredField and not getDeclared
Field rightEdge = viewPagerClass.getDeclaredField("mRightEdge");
rightEdge.setAccessible(true);
EdgeEffectForEarlyVersions leftEdgeEffect = new EdgeEffectForEarlyVersions(context);
EdgeEffectForEarlyVersions rightEdgeEffect = new EdgeEffectForEarlyVersions(context);
//Set the mLeftEdge memeber of viewPager not to be the default one, but to be "our" edgeEffect
leftEdge.set(viewPager, leftEdgeEffect);
//Set the mRightEdge memeber of viewPager not to be the default one, but to be "our" edgeEffect
rightEdge.set(viewPager, rightEdgeEffect);
}
catch (Exception ex)
{
Log.e("refelection", ex.getMessage());
}
}
}
Part III
Now all there is left to do, is to call that function after you have the ViewPager Instance and nothing more.
I Hope it will help someone.
wnafee explained the solution well but for the lazy among us, i made an actual working implementation quite some time ago.
https://github.com/inovex/ViewPager3D
And if you just want overscroll take a look here:
https://github.com/inovex/ViewPager3D/issues/1
You have a lot of options, you can show a Toast, display a Dialog, make a TextView or image to appear over your UI, etc. Or because you know the amount of View items in the ViewPager, you could add different View at positions 0 and/or n + 1 with the message and make it bounce to the last View that actually contains your data.
You could implement:
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
public void onPageSelected(int position) {
//TODO If position is the 0 or n item, add a view at 0 or at n+1 to indicate there is no more pages with data.
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// TODO Show a Toast, View or do anything you want when position = your first/last item;
}
public void onPageScrollStateChanged(int state) {
}
});
just to complement #goBeepit dev answer when you create your own edgeffect class and you extend from EdgeEffectCompat some methods requires to be boolean. you can change those methods to boolean type and make then return true in any case, this way everything works fine
You can overload the setUserVisibleHint(boolean) function in your fragments. Pseudo code:
void setUserVisibleHint(boolean isVisibleToUser) {
// If this fragment is becoming visible
if (isVisibleToUser == true) {
// Check if it is the last fragment in the viewpager
if (indexOfThis == getActivity().indexOfLast) {
// Display right limit reached
Toast(..., "No more Frags to right",...)
}
// Check if it is the first fragment in the viewpager
else if (indexOfThis == getActivity().indexOfFirst) {
// Display Left Limit reached
Toast(..., "No more Frags to left",...)
}
}
}
I have not used this function for this purpose, but have used it for other reasons and it does fire appropriately. Hope this helps...
I've implemented a bounce back effect based on Renard's ViewPager3D: https://stackoverflow.com/a/17425468/973379
Usually with ViewPager, one uses a PagerAdapter such as FragmentPagerAdapter or FragmentStatePagerAdapter to flood the ViewPager with contents(your content are going to be views).
Now, when you use a PagerAdapter, you have one method called getCount(), http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html#getCount%28%29 ,which will give you the size of the content.
Since you now, know the size you can easily display a message with an if control statement.
Try this code : http://developer.android.com/reference/android/support/v4/view/ViewPager.html
Note: I dont think you need a custom ViewPager. You will also need to understand Fragments for ViewPager. Look at samples in ApiDemos. Its a great source.

How to use Android Spinner like a drop-down list

It's taken me quite a while to get my head around the Android Spinner. After several failed implementation attempts, and after reading many questions partially similar to my own but without satisfactory answers, and some without any answers at all, e.g. here and here, I finally get that a "spinner" in Android isn't meant to be the same thing as a "drop-down list" from desktop apps, or a select in HTML. However, what my app (and I'm guessing the apps of all the other posters whose questions are similar) needs is something that works like a drop-down box, not like a spinner.
My two problems are with what I first considered to be idiosynchrasies the OnItemSelectedListener (I've seen these as separate questions on this site but not as one):
An initial selection of the first list item is triggered automatically without the user's interaction.
When the item that was already selected is selected again by the user, it is ignored.
Now I realise that, when you think about it, it makes sense for this to happen on a spinner - it has to start with a default value selected, and you spin it only to change that value, not to "re-select" a value - the documentation actually says: "This callback is invoked only when the newly selected position is different from the previously selected position". And I've seen answers suggesting that you set up a flag to ignore the first automatic selection - I guess I could live with that if there's no other way.
But since what I really want is a drop-down list which behaves as a drop-down list should (and as users can and should expect), what I need is something like a Spinner that behaves like a drop-down, like a combo-box. I don't care about any automatic pre-selection (that should happen without triggering my listener), and I want to know about every selection, even if it's the same one as previously (after all, the user selected the same item again).
So... is there something in Android that can do that, or some workaround to make a Spinner behave like a drop-down list? If there is a question like this one on this site that I haven't found, and which has a satisfactory answer, please let me know (in which case I sincerely apologise for repeating the question).
+1 to David's answer. However, here's an implementation suggestion that does not involve copy-pasting code from the source (which, by the way, looks exactly the same as David posted in 2.3 as well):
#Override
void setSelectionInt(int position, boolean animate) {
mOldSelectedPosition = INVALID_POSITION;
super.setSelectionInt(position, animate);
}
This way you'll trick the parent method into thinking it's a new position every time.
Alternatively, you could try setting the position to invalid when the spinner is clicked and setting it back in onNothingSelected. This is not as nice, because the user will not see what item is selected while the dialog is up.
Ok, I think I've come up with a solution for my own situation with the help of both David's and Felix' answer (I believe David's helped Felix', which in turn helped mine). I thought I'd post it here together with a code sample in case someone else finds this approach useful as well. It also solves both of my problems (both the unwanted automatic selection and the desired re-selection trigger).
What I've done is added a "please select" dummy item as the first item in my list (initially just to get around the automatic selection problem so that I could ignore when it was selected without user interaction), and then, when another item is selected and I've handled the selection, I simply reset the spinner to the dummy item (which gets ignored). Come to think of it, I should've thought of this long ago before deciding to post my question on this site, but things are always more obvious in hindsight... and I found that writing my question actually helped me to think about what I wanted to achieve.
Obviously, if having a dummy item doesn't fit your situation, this might not be the ideal solution for you, but since what I wanted was to trigger an action when the user selected a value (and having the value remain selected is not required in my specific case), this works just fine. I'll try to add a simplified code example (may not compile as is, I've ripped out a few bits from my working code and renamed things before pasting, but hopefully you'll get the idea) below.
First, the list activity (in my case) containing the spinner, let's call it MyListActivity:
public class MyListActivity extends ListActivity {
private Spinner mySpinner;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: other code as required...
mySpinner = (Spinner) findViewById(R.id.mySpinner);
mySpinner.setAdapter(new MySpinnerAdapter(this));
mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> aParentView,
View aView, int aPosition, long anId) {
if (aPosition == 0) {
Log.d(getClass().getName(), "Ignoring selection of dummy list item...");
} else {
Log.d(getClass().getName(), "Handling selection of actual list item...");
// TODO: insert code to handle selection
resetSelection();
}
}
#Override
public void onNothingSelected(AdapterView<?> anAdapterView) {
// do nothing
}
});
}
/**
* Reset the filter spinner selection to 0 - which is ignored in
* onItemSelected() - so that a subsequent selection of another item is
* triggered, regardless of whether it's the same item that was selected
* previously.
*/
protected void resetSelection() {
Log.d(getClass().getName(), "Resetting selection to 0 (i.e. 'please select' item).");
mySpinner.setSelection(0);
}
}
And the spinner adapter code could look something like this (could in fact be an inner class in the above list activity if you prefer):
public class MySpinnerAdapter extends BaseAdapter implements SpinnerAdapter {
private List<MyListItem> items; // replace MyListItem with your model object type
private Context context;
public MySpinnerAdapter(Context aContext) {
context = aContext;
items = new ArrayList<MyListItem>();
items.add(null); // add first dummy item - selection of this will be ignored
// TODO: add other items;
}
#Override
public int getCount() {
return items.size();
}
#Override
public Object getItem(int aPosition) {
return items.get(aPosition);
}
#Override
public long getItemId(int aPosition) {
return aPosition;
}
#Override
public View getView(int aPosition, View aView, ViewGroup aParent) {
TextView text = new TextView(context);
if (aPosition == 0) {
text.setText("-- Please select --"); // text for first dummy item
} else {
text.setText(items.get(aPosition).toString());
// or use whatever model attribute you'd like displayed instead of toString()
}
return text;
}
}
I guess (haven't tried this) the same effect could be achieved using setSelected(false) instead of setSelection(0), but re-setting to "please select" suits my purposes fine. And, "look, Ma, no flag!" (Although I guess ignoring 0 selections is not that dissimilar.)
Hopefully, this can help someone else out there with a similar use case. :-) For other use cases, Felix' answer may be more suitable (thanks Felix!).
Look. I don't know if this will help you, but since you seem tired of looking for an answer without much success, this idea may help you, who knows...
The Spinner class is derived from AbsSpinner. Inside this, there is this method:
void setSelectionInt(int position, boolean animate) {
if (position != mOldSelectedPosition) {
mBlockLayoutRequests = true;
int delta = position - mSelectedPosition;
setNextSelectedPositionInt(position);
layout(delta, animate);
mBlockLayoutRequests = false;
}
}
This is AFAIK taken from 1.5 source. Perhaps you could check that source, see how Spinner/AbsSpinner works, and maybe extend that class just enough to catch the proper method and not check if position != mOldSelectedPosition.
I mean... that's a huge "maybe" with a lot of "ifs" (android versioning comes to mind etc.), but since you seem frustrated (and I've been there with Android many times), maybe this can give you some "light". And I assume that there are no other obvious answers by looking at your previous research.
I wish you good luck!
Here is an alternative solution to differentiate between any (intended or unintended) programmatic and user-initiated changes:
Create your listener for the spinner as both an OnTouchListener and OnItemSelectedListener
public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {
boolean userSelect = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
userSelect = true;
return false;
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (userSelect) {
// Your selection handling code here
userSelect = false;
}
}
}
Add the listener to the spinner registering for both event types
SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
This wouldn't handle the case in which the re-selection of the same item by the user doesn't trigger the onItemSelected method (which I have not observed), but I guess that could be handled by adding some code to the onTouch method.
Anyway, the problems Amos pointed out were driving me crazy before thinking of this solution, so I thought I'd share as widely as possible. There are many threads that discuss this, but I've only seen one other solution so far that is similar to this: https://stackoverflow.com/a/25070696/4556980.
Modifying the Spinner is useful if you want to have multiple selections simultaneously in the same activity.
If you only desire the user to have a hierarchical selection, for example:
What do you want to eat?
Fruit
Apples
Bananas
Oranges
Fast Food
Burgers
Fries
Hot dogs,
then the ExpandableListView might be better for you. It allows the user to navigate a hierarchy of different groups and choose a child element. This would be similar to having several Spinners for the user to choose from - if you do not desire a simultaneous selection, that is.
I worked through several of the issues mentioned in this thread before I realized that the PopupMenu widget is what I really wanted. That was easy to implement without the hacks and workarounds needed to change the functionality of a Spinner. PopupMenu was relatively new when this thread was started in 2011, but I hope this helps someone searching for similar functionality now.

How do I get the number of visible Views for a Gallery?

I have a Gallery and want to know how many items are visible on the screen. Each item takes up the full screen, so I'm really interested in knowing when two items are visible as a result of being mid-scroll. Thanks.
OK here we go.! Extend your Gallery Get access to the
protected boolean getChildStaticTransformation(View child, Transformation t) {}
There you have access to each view displayed at your gallery.
From now on 2 things you can do.
Count all the unique childs (getChildStaticTransformation can be called multiple times eg on touch events etc. that's why checking for unique childs is must).
Notice this method returns the childs that are populated by the gallery. Gallery populated top-1 and bottom+1 childs for performance issues. Sometimes is +2 -2 or +n -n (depends of the child width , cause is horizontal scrolling widget). So that way maybe not what you need cause is not always accurate on the childs displayed to the gallery.
For each child check if the .getLeft and .getRight is inside the gallery bounds.So if the getLeft of a child is inside the gallery and the getRight of another child is in gallery too you are at mid-scroll .This is prolly what you need.
If you have fill_parent to the child (display only one at a time) then you can see if the gallery is scrolling flinging.Then you are at the mid-scroll until the gallery "launch" an setOnItemSelectedListener(). For this to work you have to setCallbackDuringFling(true);
The best I can think of is to set an AnimationListener on your Gallery like this:
gallery.setLayoutAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
//schedule timer to execute halfway through animation here
}
#Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub
}
});
And set the animation duration with gallery.setAnimationDuration(durationInMilliseconds);
Now you know when the animation starts and you know how long it will take. With that information you can schedule a timer to execute your code halfway through the animation, which should be when your gallery has two views showing at the same time.
This seems like a very difficult way to do it though, so give someone else time to give a better suggestion.

Categories

Resources