I'm having trouble using a Scroller to scroll a ScrollView programmatically, so no touch gestures are involved in this so far. I want to scroll the ScrollView down at a certain speed, as long as data from a sensor is in a certain range. So basically I want to start scrolling the first time the data enters the valid range and then not disturb the scrolling process until the data is out of the range again. I don't want to connect the onSensorChanged directly to a scrollBy() because it will probably not work right on other devices. Here's what I've got so far:
in my onCreate:
tx = new TextView(ShowLyrics.this);
mainscrollarea = (ScrollView) findViewById (R.id.mainscrollarea);
scroller = new Scroller(getApplicationContext(), new LinearInterpolator());
tx.setScroller(scroller);
in my onSensorChanged:
if(integratedAngle - scrollTolerance > pointzero){ //this is checking for the data range and works so far
if(!scrollingDown){
scrollText("down");
}
}
and the scrollText function:
void scrollText(String direction){
if(direction.matches("down")){
scrollingUp = false;
scrollingDown = true;
scroller.forceFinished(true);
int currY = mainscrollarea.getScrollY();
int endY = tx.getHeight();
int distance = endY - currY;
scroller.startScroll(0, currY, 0, -distance, 5000);
}
if(direction.matches("up")){
//nothing yet
}
}
So for now I've hardcoded 5 seconds for a scroll down, but nothing happens. A Log.d() of the Scroller's getCurrY in the onSensorChanged only spits out 0's. If someone could point me in the right direction, I would be thankful.
I kind of do an automated scrolling like you. Except I rely on user input (when the user is near the edge of the screen with his finger, I start scrolling at a specific speed).
I use a runnable which does the same as the scroller will do.
private final DragScroller mDragScroller;
/** inner class */
private class DragScroller implements Runnable {
private SCROLL_DIRECTION mDirection;
private boolean mIsFinished = true;
DragScroller(Context context) {
}
void start(SCROLL_DIRECTION direction) {
mState = STATE.DRAG_SCROLL;
mIsFinished = false;
mDirection = direction;
post(this);
}
#Override
public void run() {
if (mIsFinished) {
return;
}
if (mDirection.equals(SCROLL_DIRECTION.UP)) {
// check if the touch is still in the correct area...
if (!isOverThreshold(0, mTempY, mDragScrollThreshold)) {
scrollTo(0, ensureScrollBoundaries(getScrollY() - mDragScrollSpeed));
post(this);
} else {
forceFinish();
}
} else {
// check if the touch is still in the correct area...
if (!isOverThreshold(getHeight(), mTempY, mDragScrollThreshold)) {
scrollTo(0, ensureScrollBoundaries(getScrollY() + mDragScrollSpeed));
post(this);
} else {
forceFinish();
}
}
}
public boolean isFinished() {
return mIsFinished;
}
public void forceFinish() {
mIsFinished = true;
}
}
It is simply started by: mDragScroller.start(SCROLL_DIRECTION.UP); and can be stopped by mDragScroller.forceFinish();
edit
Based on your comment, you want to use the duration for the speed. This is kind of problematic because the resulting speed of the scroll depends on the distance you have to scroll in your given time. Short math sample: Scrolling 600px in 1 minute means you scroll 10px per second which is not that bad (depends on what you scroll, text or image...) but if you are near the edge and you need to scroll only 60px, the resulting speed depending on the given duration of 1min means very slow 1px per second.
Given that example you should base your scroll speed not on total duration but on pixel per second.
And yes, there is no need to use a Scroller for programmatically scrolling. Just the runnable which will call itself until it should stop and the speed can be adjusted to what ever you need...
Related
When i call setVisibility on view's child while the (parent) view is animated with ViewCompat.postOnAnimation things get broken. (setVisibility doesn't work + some other things get broken).
Question - is there any method of animation or workaround which allows to call setVisibility on child while the parent is animated?
This is very important request and i think not so unusual, because for example http request is returned in random time, and the view can be animated anytime during that.
Code request edit:
Regarding code, it is bit complicated. I will first explain. It is animation in the custom CoordinatorLayout Behavior, clone of the standard BottomSheetBehavior (sliding of sheet from bottom to up).
Animation is launched by calling this:
ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState));
SettleRunnable is this:
private class SettleRunnable implements Runnable {
private final View mView;
#State
private final int mTargetState;
SettleRunnable(View view, #State int targetState) {
mView = view;
mTargetState = targetState;
}
#Override
public void run() {
if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
ViewCompat.postOnAnimation(mView, this);
} else {
setStateInternal(mTargetState);
}
}
}
So as you can see, all the animation movement is done by mViewDragHelper.continueSettling. Drag helper is standard class ViewDragHelper.
ViewDragHelper.continueSettling looks like this
public boolean continueSettling(boolean deferCallbacks) {
if (mDragState == STATE_SETTLING) {
boolean keepGoing = mScroller.computeScrollOffset();
final int x = mScroller.getCurrX();
final int y = mScroller.getCurrY();
final int dx = x - mCapturedView.getLeft();
final int dy = y - mCapturedView.getTop();
if (dx != 0) {
ViewCompat.offsetLeftAndRight(mCapturedView, dx);
}
if (dy != 0) {
ViewCompat.offsetTopAndBottom(mCapturedView, dy);
}
if (dx != 0 || dy != 0) {
mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
}
if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
// Close enough. The interpolator/scroller might think we're still moving
// but the user sure doesn't.
mScroller.abortAnimation();
keepGoing = false;
}
if (!keepGoing) {
if (deferCallbacks) {
mParentView.post(mSetIdleRunnable);
} else {
setDragState(STATE_IDLE);
}
}
}
return mDragState == STATE_SETTLING;
}
It simply animates the sheet up or down to desired position according the chosen target state.
Pseudo code of problem is:
launchAnimation(); // it takes eg 300 ms
changeVisibilityOfAnimatedViewChildren(); // this is problem
I can wait until the animation finishes, but as i said, in case of http request it is bit problem, i would like to ideally refresh the data right away without waiting.
Animated element is CoordinatorLayout. Affected child by setVisibility is one or more its children.
Judging by this link, android seems to have generally problem with animations and setVisibility.
Possible solutions i am thinking of now:
Maybe if i would change the visibility with another parallel postOnAnimation() task (?)
Or because it are basically just step by step subsequent calls of moving function mViewDragHelper.continueSettling() why don't do it without postOnAnimation()? I could run the task also without it. But i guess that postOnAnimation chooses some correct delay of animation step for concrete device + probably some other things.
You can add AnimatorListenerAdapter to your parent animation, and override onAnimationEnd() method. In this method you can call the child animation. However, I would rather change alpha of view than visibility. You can achieve more smoothly effect in this case.
For example, consider this code:
parentAnimationInstance.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
childView.animate()
.alpha(1.f)
.setDuration(200)
.start();
}
});
I have implemented the method shown in this question and the animation on scroll looks perfectly fine. However, the initial list fill animation just shows all objects appear on the screen at the same time and it just looks like a lag.
While debugging, I can see that the animation method is being called 7 times, but I guess it is so fast that they are all trying to run at basically the same time. Any ideas what I can do? I tried delaying the animation, but I got stuck with how to do that. I asked that question here. Thank you for the help!
Edit: I can post the same code that I put on the other question:
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
//Normal OnBindViewHolder stuff
SetAnimation(vh.ItemView, position);
}
And then the SetAnimation method:
private void SetAnimation(View viewToAnimate, int position)
{
if (position > lastPosition)
{
var animation = AnimationUtils.LoadAnimation(_context, Resource.Animation.up_from_bottom);
//animation.SetAnimationListener(new CheckpointAnimationListener());
viewToAnimate.StartAnimation(animation);
lastPosition = position;
}
}
What I really want here is for the animation to finish before the lastPostion = position line is called.
And the empty AnimationListener, since I am really not sure how to handle the wait.
private class CheckpointAnimationListener : Java.Lang.Object, Animation.IAnimationListener
{
public void OnAnimationEnd(Animation animation)
{
}
public void OnAnimationRepeat(Animation animation)
{
}
public void OnAnimationStart(Animation animation)
{
}
}
I had to do something similar. Basically, the approach I took was whenever I started an animation, I also informed when the minimum start time would be, based on the last time the animation was initiated.
Since you didn't post any code, I'll write a basic outline of what I did:
// member to know when the minimum start time of the next animation will be.
private long mNextAnimationStartTime;
...
public void onBindViewHolder(...) {
// perform binding logic
// sample the current time.
long currentTime = System.currentTimeMillisec();
// if the next animation time is greater than now...
if (mNextAnimationStartTime > currentTime) {
// calculate how much time to wait before showing the next animation.
int delay = mNextAnimationStartTime - currentTime;
// In this example, I use postDelayed to postpone the animation,
// but you could also use start offset of the animation itself.
postDelayed(new Runnable() {
#Override
public void run() {
// start the animation after a delay.
startAnimation(viewToAnimate);
}
}, delay);
} else {
// if the next animation time is in the past, just start the animation.
startAnimation(viewToAnimate);
}
// schedule the next animation start time to now + 100 ms (play with this 100 to fit your needs)
mNextAnimationStartTime = currentTime + 100;
}
Hope this was clear, and helps you get started.
The answer from Gil sent me in the right direction, but since I am working with Xamarin.Android I needed to do a couple of things differently. I used his idea for StartOffset instead, since C# does not use anonymous classes (making runnables quite a bit more difficult). Here is my final animation code (after playing around with the delays).
private void SetAnimation(View viewToAnimate, int position)
{
var animation = AnimationUtils.LoadAnimation(_context, Resource.Animation.up_from_bottom);
_currentTime = JavaSystem.CurrentTimeMillis();
long difference = _currentTime - _timeAtFirstCall;
//this will cause the first items to populate the view to animate in order instead of at the same time.
//_delay is increased each time so each item will start 75ms after the one before it. difference is
//there to make sure later items in the list dont get this delay and animate as they appear.
if (_nextAnimationStartTime > _currentTime && difference < 150)
{
if (position > lastPosition)
{
animation.StartOffset = _delay;
viewToAnimate.StartAnimation(animation);
lastPosition = position;
_delay += 75;
}
}
else
{
if (position > lastPosition)
{
animation.StartOffset = 75;
viewToAnimate.StartAnimation(animation);
lastPosition = position;
}
}
_nextAnimationStartTime = _currentTime + 400;
}
And then I had these variables defined and initialized at the top of the class:
private int lastPosition = -1;
private long _nextAnimationStartTime = 0;
private long _currentTime = 0;
private long _timeAtFirstCall = JavaSystem.CurrentTimeMillis();
private long _delay = 150;
This method is called at the end of OnBindViewHolder. So now, the first time OnBindViewHolder is called it goes through the else statement. I had to set an initial delay here as well because otherwise the animation seemed to start half way on the screen and just looked bad. This delay also ensures the items will animate in sequence on a fast scroll.
The one problem that I still have is if the user scrolls immediately when loading the view. The initial items keep their delay, but the first item that is set to animate when it is scrolled to might start its animation before the first items are finished. I am not sure how to get around this problem...
I want to animate the change of my RecyclerViews GridLayoutManager. I defaulty show a list of items in a grid with 3 columns and the user can select to show more or less columns.
I would like the views in the RecyclerView to move/scale to their new positions, but I have no idea how this could be done.
What I want in the end
allow to scale the grid via an expand/contract touch gesture => I know how to do that
animate the change of the LayoutManager
Does anyone know how I can animate the change of the LayoutManager?
The source of inspiration here would be the Google Photos app,the stock Sony Gallery app
There are basically 2 approaches you can go with:
You modify the spancount of the GridLayoutManager using setSpanCount(int)
You set a very high span count(~100) use the SpanSizeLookUp to change the per item spanSize on the fly.
I have used the Gist provided by Musenkishi,for this answer to provide an animator to animate the changes in grid layout changes
I have used this approach in a sample GitHub project implementing the same.
Caveats:
I have currently used the click listener to keep modifying the the span size look up.This could be changed to a ItemGestureListener to capture pinch zoom events and change accordingly.
You need to determine a way to choose a span count so that all the items in a row occupy the entire screen width (and hence you do not see any empty space)
You call notifyItemRangeChanged using a runnable post delayed since you cannot call the notifyChanged methods from within bindView/createView etc.
After changing the span size,you need to notifyItemRangeChanged with an appropriate range so that all the items currently displayed on the screen are shifted accordingly.I have used (code at the bottom)
This is not a complete solution but a 2 hour solution for the same.You can obviously improve on all the points mentioned :).
I hope to keep updating the sample since this kind of views have always fascinated me.
Do not view this as the final solution but just a particular way of achieving this approach. If you were to use a StaggerredLayoutManager instead,you could easily avoid blank spaces between items.
public int calculateRange() {
int start = ((GridLayoutManager) grv.getLayoutManager()).findFirstVisibleItemPosition();
int end = ((GridLayoutManager) grv.getLayoutManager()).findLastVisibleItemPosition();
if (start < 0)
start = 0;
if (end < 0)
end = getItemCount();
return end - start;
}
I deal with the same problem as you, and so far I have not found a good solution.
Simple change of columns number in GridLayoutManager seems weird so for now I use animation to fade out/in entire layout. Something like this:
private void animateRecyclerLayoutChange(final int layoutSpanCount) {
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new DecelerateInterpolator());
fadeOut.setDuration(400);
fadeOut.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
productsRecyclerLayoutManager.setSpanCount(layoutSpanCount);
productsRecyclerLayoutManager.requestLayout();
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new AccelerateInterpolator());
fadeIn.setDuration(400);
productsRecycler.startAnimation(fadeIn);
}
});
productsRecycler.startAnimation(fadeOut);
}
If you combine fade out/in animation with scaling each visible item, It will be a decent animation for GridLayoutManager changes.
You can do this with "gesture detector"
see the sample tutorial here http://wiki.workassis.com/pinch-zoom-in-recycler-view/ In this tutorial we will fetch images from gallery and show them in a grid layout in recycler view. You will be able to change layout on pinch gesture. Following are the screen shots of different layouts.
mScaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
#Override
public boolean onScale(ScaleGestureDetector detector) {
if (detector.getCurrentSpan() > 200 && detector.getTimeDelta() > 200) {
if (detector.getCurrentSpan() - detector.getPreviousSpan() < -1) {
if (mCurrentLayoutManager == mGridLayoutManager1) {
mCurrentLayoutManager = mGridLayoutManager2;
mRvPhotos.setLayoutManager(mGridLayoutManager2);
return true;
} else if (mCurrentLayoutManager == mGridLayoutManager2) {
mCurrentLayoutManager = mGridLayoutManager3;
mRvPhotos.setLayoutManager(mGridLayoutManager3);
return true;
}
} else if(detector.getCurrentSpan() - detector.getPreviousSpan() > 1) {
if (mCurrentLayoutManager == mGridLayoutManager3) {
mCurrentLayoutManager = mGridLayoutManager2;
mRvPhotos.setLayoutManager(mGridLayoutManager2);
return true;
} else if (mCurrentLayoutManager == mGridLayoutManager2) {
mCurrentLayoutManager = mGridLayoutManager1;
mRvPhotos.setLayoutManager(mGridLayoutManager1);
return true;
}
}
}
return false;
}
});
Background: I have a ScrollView / TextView pair that receives an intermittent stream of text from an external source. It automatically scrolls to the bottom on each update.
I'd like for the user to be able to break out of that auto scroll down mode by manually scrolling to somewhere, however I am unclear how to distinguish the manual scroll from the programatic scroll I am doing myself.
My UI update runs on a timer to buffer redraws:
private Handler outputUpdater = new Handler ();
private static String outputBuffer = "";
private static boolean outputHasChanged = false;
private static final Object lock = new Object ();
private Runnable outputUpdaterTask = new Runnable () {
public void run () {
synchronized (lock) {
// if the output has changed, update the TextView
if (outputHasChanged) {
TextView tv = (TextView) findViewById (R.id.textView);
tv.setText (outputBuffer);
}
// if the output has changed, or the scroll hasn't reached the bottom yet
// then keep scrolling down
if (outputHasChanged || !scrollAtBottom ()) {
ScrollView sv = (ScrollView) findViewById (R.id.scrollView);
sv.fullScroll (View.FOCUS_DOWN);
}
outputHasChanged = false;
}
outputUpdater.postDelayed (this, 100);
}
};
scrollAtBottom gets it's value from a onScrollChanged handler.
This all works fine. It's necessary to call fullScroll even if there was no text update because a single call to fullScroll doesn't always go to the bottom if there are TextView updates going on, or the virtual keypad visibility changed, etc.
I'd like that if the user manually scrolls, that I could know to make a decision to stop calling fullScroll.
Unfortunately It's seems that not enough simply to treat any transition from "at the bottom, automatic mode" to "not at the bottom" as a cue to switch to manual mode because various UI changes seem to move the scroll view off the bottom also (e.g. virtual keypad showing).
Question Restated:
How can I distinguish the user initiated scroll from the programatic scroll?
Have you tried using a boolean with onTouchEvent, something similar to:
boolean userIntercept = false;
#Override
public boolean onTouchEvent(MotionEvent me) {
int action = me.getAction();
if (action == MotionEvent.ACTION_MOVE) {
userIntercept = true;
}
return super.onTouchEvent(me);
}
then in your outputUpdaterTask:
// if the output has changed, or the scroll hasn't reached the bottom yet
// then keep scrolling down
if (outputHasChanged || !scrollAtBottom () && !userIntercept) {
ScrollView sv = (ScrollView) findViewById (R.id.scrollView);
sv.fullScroll (View.FOCUS_DOWN);
}
You would just need to determine a way in which you can return the userIntercept to false, whichever best fits your application.
Goal
Build a Circular ViewPager.
The first element lets you peak to the last element and swipe to it, and vice versa. You should be able to swipe in either direction forever.
Now this has been accomplished before, but these questions do not work for my implementation. Here are a few for reference:
how to create circular viewpager?
ViewPager as a circular queue / wrapping
https://github.com/antonyt/InfiniteViewPager
How I Tried to Solve the Problem
We will use an array of size 7 as an example. The elements are as follows:
[0][1][2][3][4][5][6]
When you are at element 0, ViewPagers do not let you swipe left! How terrible :(. To get around this, I added 1 element to the front and end.
[0][1][2][3][4][5][6] // Original
[0][1][2][3][4][5][6][7][8] // New mapping
When the ViewPageAdapter asks for (instantiateItem()) element 0, we return element 7. When the ViewPageAdapter asks for element 8 we return element 1.
Likewise in the OnPageChangeListener in the ViewPager, when the onPageSelected is called with 0, we setCurrentItem(7), and when it's called with 8 we setCurrentItem(1).
This works.
The Problem
When you swipe to the left from 1 to 0, and we setCurrentItem(7), it will animate all the way to right by 6 full screens. This doesn't give the appearance of a circular ViewPager, it gives the appearence rushing to the last element in the opposite direction the user requested with their swipe motion!
This is very very jarring.
How I Tried to Solve This
My first inclination was to turn off smooth (ie, all) animations. It's a bit better, but it's now choppy when you move from the last element to the first and vice versa.
I then made my own Scroller.
http://developer.android.com/reference/android/widget/Scroller.html
What I found was that there is always 1 call to startScroll() when moving between elements, except when I move from 1 to 7 and 7 to 1.
The first call is the correct animation in direction and amount.
The second call is the animation that moves everything to the right by multiple pages.
This is where things got really tricky.
I thought the solution was to just skip the second animation. So I did. What happens is a smooth animation from 1 to 7 with 0 hiccups. Perfect! However, if you swipe, or even tap the screen, you are suddenly (with no animation) at element 6! If you had swiped from 7 to 1, you'll actually be at element 2. There is no call to setCurrentItem(2) or even a call to the OnPageChangeListener indicating that you arrived at 2 at any point in time.
But you're not actually at element 2, which is kind of good. You are still at element 1, but the view for element 2 will be shown. And then when you swipe to the left, you go to element 1. Even though you were really at element 1 already.. How about some code to help clear things up:
Animation is broken, but no weird side effects
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, duration);
}
Animation works! But everything is strange and scary...
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
if (dx > 480 || dx < -480) {
} else {
super.startScroll(startX, startY, dx, dy, duration);
}
}
The ONLY difference is that when the second animation (bigger than the width of the 480 pixel screen) is called, we ignore it.
After reading through the Android Source code for Scroller, I found that startScroll does not start scrolling anything. It sets up all the data to be scrolled, but doesn't initiate anything.
My Hunch
When you do the circular action (1 to 7 or 7 to 1), there are two calls to startScroll(). I think something in between the two calls is causing an issue.
User scrolls from element 1 to element 7 causing a jump from 0 to 7. This should animate to the left.
startScroll() is called indicating a short animation to the left.
STUFF HAPPENS THAT MAKES ME CRY PROBABLY I THINK
startScroll() is called indicating a long animation to the right.
Long animation to the right occurs.
If I comment out 4, then 5 becomes "Short correct animation to the left, things go crazy"
Summary
My implementation of a Circular ViewPager works, but the animation is broken. Upon trying to fix the animation, it breaks the functionality of the ViewPager. I am currently spinning my wheels trying to figure out how to make it work. Help me! :)
If anything is unclear please comment below and I will clarify. I realize I was not very precise with how things are broken. It's difficult to describe because it's not even clear what I'm seeing on the screen. If my explanation is an issue I can work on it, let me know!
Cheers,
Coltin
Code
This code is slightly modified to make it more readable on its own, though the functionality is identical to my current iteration of the code.
OnPageChangeListener.onPageSelected
#Override
public void onPageSelected(int _position) {
boolean animate = true;
if (_position < 1) {
// Swiping left past the first element, go to element (9 - 2)=7
setCurrentItem(getAdapter().getCount() - 2, animate);
} else if (_position >= getAdapter().getCount() - 1) {
// Swiping right past the last element
setCurrentItem(1, animate);
}
}
CircularScroller.startScroll
#Override
public void startScroll(int _startX, int _startY, int _dx, int _dy, int _duration) {
// 480 is the width of the screen
if (dx > 480 || dx < -480) {
// Doing nothing in this block shows the correct animation,
// but it causes the issues mentioned above
// Uncomment to do the big scroll!
// super.startScroll(_startX, _startY, _dx, _dy, _duration);
// lastDX was to attempt to reset the scroll to be the previous
// correct scroll distance; it had no effect
// super.startScroll(_startX, _startY, lastDx, _dy, _duration);
} else {
lastDx = _dx;
super.startScroll(_startX, _startY, _dx, _dy, _duration);
}
}
CircularViewPageAdapter.CircularViewPageAdapter
private static final int m_Length = 7; // For our example only
private static Context m_Context;
private boolean[] created = null; // Not the best practice..
public CircularViewPageAdapter(Context _context) {
m_Context = _context;
created = new boolean[m_Length];
for (int i = 0; i < m_Length; i++) {
// So that we do not create things multiple times
// I thought this was causing my issues, but it was not
created[i] = false;
}
}
CircularViewPageAdapter.getCount
#Override
public int getCount() {
return m_Length + 2;
}
CircularViewPageAdapter.instantiateItem
#Override
public Object instantiateItem(View _collection, int _position) {
int virtualPosition = getVirtualPosition(_position);
if (created[virtualPosition - 1]) {
return null;
}
TextView tv = new TextView(m_Context);
// The first view is element 1 with label 0! :)
tv.setText("Bonjour, merci! " + (virtualPosition - 1));
tv.setTextColor(Color.WHITE);
tv.setTextSize(30);
((ViewPager) _collection).addView(tv, 0);
return tv;
}
CircularViewPageAdapter.destroyItem
#Override
public void destroyItem(ViewGroup container, int position, Object view) {
ViewPager viewPager = (ViewPager) container;
// If the virtual distance is distance 2 away, it should be destroyed.
// If it's not intuitive why this is the case, please comment below
// and I will clarify
int virtualDistance = getVirtualDistance(viewPager.getCurrentItem(), getVirtualPosition(position));
if ((virtualDistance == 2) || ((m_Length - virtualDistance) == 2)) {
((ViewPager) container).removeView((View) view);
created[getVirtualPosition(position) - 1] = false;
}
}
I think the best doable approach would be instead of using a normal list to have a wrapper to the List that when the get(pos) method is executed to obtain the object to create the view, you make something like this get(pos % numberOfViews) and when it ask for the size of the List you put that the List is Integer.MAX_VALUE and you start your List in the middle of it so you can say that is mostly impossible to have an error, unless they actually swipe to the same side until the reach the end of the List. I will try to post a proof of concept later this weak if the time allows me to do so.
EDIT:
I have tried this piece of code, i know is a simple textbox shown on each view, but the fact is that it works perfectly, it might be slower depending on the total amount of views but the proof of concept is here. What i have done is that the MAX_NUMBER_VIEWS represents what is the maximum numbers of times a user can completely give before he is stopped. and as you can see i started the viewpager at the length of my array so that would be the second time it appears so you have one turn extra to the left and right but you can change it as you need it. I hope i do not get more negative points for a solution that in fact does work.
ACTIVITY:
pager = (ViewPager)findViewById(R.id.viewpager);
String[] articles = {"ARTICLE 1","ARTICLE 2","ARTICLE 3","ARTICLE 4"};
pager.setAdapter(new ViewPagerAdapter(this, articles));
pager.setCurrentItem(articles.length);
ADAPTER:
public class ViewPagerAdapter extends PagerAdapter {
private Context ctx;
private String[] articles;
private final int MAX_NUMBER_VIEWS = 3;
public ViewPagerAdapter(Context ctx, String[] articles) {
this.ctx = ctx;
this.articles = articles.clone();
}
#Override
public int getCount() {
return articles.length * this.MAX_NUMBER_VIEWS;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
TextView view = new TextView(ctx);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
int realPosition = position % articles.length;
view.setText(this.articles[realPosition]);
((ViewPager) container).addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((View) object);
}
#Override
public Parcelable saveState() {
return null;
}
}