SwipeListView only one item opened at a time - android

This question refers to the SwipeListView component found here: https://github.com/47deg/android-swipelistview
After trying out several implementations and fixes I found on the web I decided to modify the sources a little.
I will post this here since i know it's a known issue and all the versions I found proved to have some issues eventually.
SwipeListViewTouchListener.java has suffered the following changes:
...
/**
* Create reveal animation
*
* #param view affected view
* #param swap If will change state. If "false" returns to the original
* position
* #param swapRight If swap is true, this parameter tells if movement is toward
* right or left
* #param position list position
*/
private void generateRevealAnimate(final View view, final boolean swap, final boolean swapRight, final int position) {
int moveTo = 0;
if (opened.get(position)) {
if (!swap) {
moveTo = openedRight.get(position) ? (int) (viewWidth - rightOffset) : (int) (-viewWidth + leftOffset);
}
} else {
if (swap) {
moveTo = swapRight ? (int) (viewWidth - rightOffset) : (int) (-viewWidth + leftOffset);
}
}
final boolean aux = !opened.get(position);
if(swap) {
opened.set(position, aux);
openedRight.set(position, swapRight);
}
animate(view).translationX(moveTo).setDuration(animationTime).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
swipeListView.resetScrolling();
if (swap) {
if (aux) {
swipeListView.onOpened(position, swapRight);
} else {
swipeListView.onClosed(position, openedRight.get(position));
}
}
// if (aux || !swap) {
// resetCell();
// }
}
});
}
...
/**
* Close all opened items
*/
void closeOtherOpenedItems() {
if (opened != null && downPosition != SwipeListView.INVALID_POSITION) {
int start = swipeListView.getFirstVisiblePosition();
int end = swipeListView.getLastVisiblePosition();
for (int i = start; i <= end; i++) {
if (opened.get(i) && i != downPosition) {
closeAnimate(swipeListView.getChildAt(i - start).findViewById(swipeFrontView), i);
}
}
}
}
...
/**
* #see View.OnTouchListener#onTouch(android.view.View,
* android.view.MotionEvent)
*/
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
...
closeOtherOpenedItems();
view.onTouchEvent(motionEvent);
return true;
}
The rest of the code not mentioned is the same.
Any comments highly appreciated, this changes prevent you from having to implement the SwipeListViewOnTouchListener in the activity which inflates the list.

Cons: doesn't close the row opened by openAnimate()
BaseSwipeListViewListener swipeListViewListener = new BaseSwipeListViewListener() {
int openItem = -1;
#Override
public void onStartOpen(int position, int action, boolean right) {
super.onStartOpen(position, action, right);
if (openItem > -1)
swipeListView.closeAnimate(openItem);
openItem = position;
}
}
Or better way:
#Override
public void onStartOpen(int position, int action, boolean right) {
super.onStartOpen(position, action, right);
swipeListView.closeOpenedItems();
}
And set the listener to the listView:
swipeListView.setSwipeListViewListener(swipeListViewListener);

Your fix worked, but there is a way to do it without affecting the original code:
swipeListView.setSwipeListViewListener(new BaseSwipeListViewListener() {
int openItem = -1;
int lastOpenedItem = -1;
int lastClosedItem = -1;
#Override
public void onOpened(int position, boolean toRight) {
lastOpenedItem = position;
if (openItem > -1 && lastOpenedItem != lastClosedItem) {
swipeListView.closeAnimate(openItem);
}
openItem = position;
}
#Override
public void onStartClose(int position, boolean right) {
Log.d("swipe", String.format("onStartClose %d", position));
lastClosedItem = position;
}
}
You should however, send a pull request to apply your code as that would fix the bug.
Source: https://github.com/47deg/android-swipelistview/issues/46

If you're going to modify the swipelistview library itself I have a simpler solution.
Add the following if block to SwipeListViewTouchListener.java in the onTouch method right at the beginning of case MotionEvent.ACTION_DOWN:
if(lastOpenedPosition != downPosition && opened.get(lastOpenedPosition)) {
closeAnimate(lastOpenedPosition);
return false;
}
Create an int lastOpenedPosition field and initialize it to 0, and in the generateRevealAnimate method inside the if (aux) block add:
lastOpenedPosition = position;
I would also add config variable (in res/values/swipelistview_attrs.xml) to SwipeListView and add it to the onTouch if block, to add the ability to turn this feature off and on.
This basically results in if the list is touched while a row is open, than the row will close. Which, imho, is better functionality than the row closing only after you finished opening another row.

swipeListView.setSwipeListViewListener(new BaseSwipeListViewListener() {
//...
#Override
public void onClickBackView(int position) {
//DELETE ITEM
adapter.notifyDataSetChanged();
swipeListView.closeOpenedItems();
}
//...
});

Yeah, the SwipeListView of the original codes can open many items at the same time. Your code segment here can open one item at one time? Or when open another item, the opened items will be closed?

Related

popup a headerview/layout whenever keyboard showed in edittext

Am stuck with a pretty simple issue in myapp . i have a custom dialog which has EditText and whenever softkeyboard opensup i want to show header/a another layout on dialog layout(see picture with three textviews ). if he clicks on done. hidethesoftkeyboard along with header.
ettagmsg = (EditText) dialog.findViewById(R.id.etFlyTagName);
popup header
LinearLayout layheader = (LinearLayout)findViewById(R.layout.header_buttons);
you might want to add this listener!
ettagmsg.setOnFocusChangeListener(new View.OnFocusChangeListener(){
#Override
public void onFocusChange(View v, boolean hasFocus) {
if(v.hasFocus()){
layheader.setVisibility(View.VISIBLE);
}else{
layheader.setVisibility(View.GONE);
//hide soft input here
}
}
}
Hope i was of use!
Haven't really tested this out but here's a nice snippet that should work: http://felhr85.net/2014/05/04/catch-soft-keyboard-showhidden-events-in-android/
tl;dr: since popping up the soft keyboard requires that some views get flattened (height becomes smaller), you can use that to check if the soft keyboard is hidden/shown.
keyboards are pretty annoying on Android. you feel free to use this class I done before:
you instantiate it with a Listener (your dialog), and attach and detach it from view during onStart/onStop or similar callbacks. Remember you want to attach it to the Dialog view.
also, you might need to adjust the DP_KEYBOARD_THRESHOLD value
public class KeyboardObserver implements ViewTreeObserver.OnGlobalLayoutListener, ViewTreeObserver.OnPreDrawListener {
private static final int DP_KEYBOARD_THRESHOLD = 60;
private int keyboardThreshold;
private int currentHeight;
private View view;
private final KeyboardListener listener;
private boolean isKeyboardShown = false;
public KeyboardObserver(KeyboardListener listener) {
this.listener = listener;
}
public void attachToView(View view) {
keyboardThreshold = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, DP_KEYBOARD_THRESHOLD, view.getResources().getDisplayMetrics());
this.view = view;
currentHeight = view.getHeight();
view.getViewTreeObserver().addOnGlobalLayoutListener(this);
if (currentHeight <= 0) {
view.getViewTreeObserver().addOnPreDrawListener(this);
}
}
public void detachFromView() {
if (view != null) view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
#Override
public void onGlobalLayout() {
int newHeight = view.getHeight();
if (currentHeight > 0) {
int diff = newHeight - currentHeight;
if (diff < -keyboardThreshold) {
Log.d(this, "onGlobalLayout. keyboard is show. height diff = " + -diff);
// keyboard is show
isKeyboardShown = true;
if (listener != null)
listener.onKeyboardShow(-diff);
} else if (diff > keyboardThreshold) {
Log.d(this, "onGlobalLayout.keyboard is hide. height diff = " + diff);
// keyboard is hide
isKeyboardShown = false;
if (listener != null)
listener.onKeyboardHide(diff);
} else {
Log.v(this, "onGlobalLayout. height diff = " + diff);
}
}
currentHeight = newHeight;
}
public boolean isKeyboardShown() {
return isKeyboardShown;
}
#Override
public boolean onPreDraw() {
currentHeight = view.getHeight();
view.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
public interface KeyboardListener {
public void onKeyboardShow(int height);
public void onKeyboardHide(int height);
}
}

ListView scroll position not maintained after loading content using CWAC Endless Adapter

update
Basically, bbrakenhoff has answered my question but there is just one more thing left to fix. How can I update the contents of my EndlessFeedAdapter (mEndlsFidAdptr)? I need to clear the item and then reload. I'm using the CWAC EndlessAdapater. Is there a trick to clear the contents or would it be easier to just program a method? After this is done the scroll position should be maintaind.
I am getting data from a server and updating my EndlessFeedAdapter when content changes. Each time I am updating my adapter and reloading content. The problem is that after reloading my list jumps right back to the top as my scroll position is not maintained. I have tried setSelection and setSelectionFromTop extensively, but without positive results.
How do I maintain scroll position after the adapter has been updated?
I have been going through the forums searching for an answer but nothing seems to be working.
I have tried all these: Maintain/Save/Restore scroll position when returning to a ListView
This didn't work:
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// restore index and position
mList.setSelectionFromTop(index, top);
Nor this:
// Save ListView state
Parcelable state = listView.onSaveInstanceState();
// Set new items
listView.setAdapter(adapter);
// Restore previous state (including selected item index and scroll position)
listView.onRestoreInstanceState(state);
Nor the other solutions such as setting up a runnable or setting the scrollPositionY. Setting notifyDataChanged didn't work as I am loading from different lists.
My code:
private void showFeed() {
if (mFeedActivity.mInFeed) {
mQuickReturnView.setVisibility(View.VISIBLE);
} else {
mQuickReturnView.setVisibility(View.GONE);
}
Activity actvt= getActivity();
if (actvt == null || mFeedListView == null) return;
actvt.invalidateOptionsMenu();
mFeedListView.setVisibility(View.VISIBLE);
//updated with help from response
if (mAdapter == null){
mAdapter = new FeedAdapter(actvt, 0, mFeed.getItems().getFeedItemList(), this);
} else {
mAdapter.clear();
mAdapter.addAll(mFeed.getItems().getFeedItemList());
mAdapter.notifyDataSetChanged();
}
mEndlsFidAdptr = new EndlessFeedAdapter(actvt, mAdapter, R.layout.progress_row, mFeed.isShowMoreBar(),
mEndlsFidAdptr.setRunInBackground(false);
//Parcelable state = mFeedListView.onSaveInstanceState();
mFeedListView.setAdapter(mEndlsFidAdptr);
//mFeedListView.onRestoreInstanceState(state);
mFeedListView.setSelectionFromTop(mFirstVisibleItem, mVisibleItemOffset);
if(!(mFeedScope.equalsIgnoreCase(FeedScope.BOOKMARKS.xmlValue()) ||
mFeedScope.equalsIgnoreCase(FeedScope.DOCUMENT.xmlValue()) ||
mFeedScope.equalsIgnoreCase(FeedScope.NOTIFICATIONS.xmlValue()) ||
mFeedScope.equalsIgnoreCase(FeedScope.RECEIVED_TASKS.xmlValue()) ||
mFeedScope.equalsIgnoreCase(FeedScope.SEND_TASKS.xmlValue()))) {
mFeedListView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
mFeedListView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCanShowHide = scrollState == SCROLL_STATE_FLING;
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
View v = mFeedListView.getChildAt(0);
//View v = null;
if(!mFeedActivity.mInFeed || v == null)
return;
int top = v.getTop();
if(mIsAnimating) {
mVisibleItemOffset = top;
mFirstVisibleItem = firstVisibleItem;
return;
}
boolean hide = false;
boolean show = false;
float stickyHeight = getResources().getDimension(R.dimen.sticky_height);
if(firstVisibleItem == mFirstVisibleItem) {
if((top + stickyHeight) < mVisibleItemOffset) {
// Content scrolled down
// if shown then hide quickactionview
if(mQuickReturnShown) {
hide = true;
}
} else if (top > mVisibleItemOffset) {
// Content scrolled up
// if hidden then show quickactionview
if(!mQuickReturnShown) {
show = true;
}
}
} else if(firstVisibleItem > mFirstVisibleItem) {
// Content scrolled down
// if shown then hide quickactionview
if(mQuickReturnShown) {
hide = true;
}
} else if (firstVisibleItem < mFirstVisibleItem) {
// Content scrolled up
// if hidden then show quickactionview
if(!mQuickReturnShown) {
show = true;
}
}
if((show && mCanShowHide) || (top == 0 && !mQuickReturnShown)) {
mTranslateAnimation = new TranslateAnimation(0, 0, -mQuickReturnHeight, 0);
mTranslateAnimation.setDuration(DURATION_MILLIS);
mTranslateAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
mIsAnimating = true;
}
#Override
public void onAnimationEnd(Animation animation) {
mIsAnimating = false;
mQuickReturnShown = true;
mQuickReturnView.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
mQuickReturnView.startAnimation(mTranslateAnimation);
}
if(hide) {
mTranslateAnimation = new TranslateAnimation(0, 0, 0, -mQuickReturnHeight);
mTranslateAnimation.setDuration(DURATION_MILLIS);
mTranslateAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
mIsAnimating = true;
}
#Override
public void onAnimationEnd(Animation animation) {
mIsAnimating = false;
mQuickReturnShown = false;
mQuickReturnView.setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) { }
});
mQuickReturnView.startAnimation(mTranslateAnimation);
}
mVisibleItemOffset = top;
mFirstVisibleItem = firstVisibleItem;
}
});
} else {
mFeedListView.setOnScrollListener(null);
}
mFeedListView.setSelectionFromTop(mFirstVisibleItem, mVisibleItemOffset);
//mFeedListView.scrollTo(mCurrentX,mCurrentY);
if(mFeed.getItems().getFeedItemList().size() == 0) {
mEmptyFeedView.setVisibility(View.VISIBLE);
}
}
Are you calling the method showFeed() everytime you received new data? If yes, then maybe you could try to refill the adapter instead of assigning a new one every time.
I don't know exactly what you are doing in your adapter, so I'll show you how I did it in one of my apps.
In my Activity/Fragment I do this when I want to update the list with new items:
private void refreshCalendar(ArrayList<CalendarDay> newCalendar) {
if (mAdapter == null) {
mAdapter = new CalendarAdapter(getActivity(), newCalendar);
mExpandableListView.setAdapter(mAdapter);
}
else {
mAdapter.refill(newCalendar);
}
restoreInstanceState();
}
And in the adapter:
public void refill(ArrayList<CalendarDay> newCalendar) {
mCalendar.clear();
mCalendar.addAll(newCalendar);
notifyDataSetChanged();
}
Maybe you could try and remove this line?
mFeedListView.setSelectionFromTop(mFirstVisibleItem, mVisibleItemOffset);
Edit: You are using this line twice in your code.
Edit: In the code in your question you are only refreshing mAdapter and not mEndlsFidAdptr . That one is still assigned a new one. Everytime you assign a new adapter to you ListView it scrolls back to the top.
So the solution wasn't the logical place where I was looking. Thanks a lot to bbrakenhoff for pointing me in the right direction!
My class showFeed() was called from another class called downloadFeed() my scroll position wasn't maintained because downloadFeed() was only loading 5 items when it refreshed, hence even if I maintained the correct scroll position it was not visible.
Although it may be a long shot if someone else has this problem - to fix simply create a variable to hold the total size of your scrollable list when the user performs an onClick event. Then when downloadFeed() is called again, there are more items to download instead the default 5. Then the scroll position is able to be maintained as the visible items are now present.
I ended up using mFeedListView.setSelectionFromTop(firstVisibleItem, positionOffset)

Custom Animation on ListView items doesn't trigger 1st time

I have some code here (which I did not write) which I need to fix. Here's the required flow:
User clicks on an item in a ListView
The item expands to show a footer which is otherwise hidden
If another list item is expanded, it is shrunk back to normal size (so that only 1 item is expanded at a time).
My problem: When tapping an item which is not expanded, nothing happens. The 2nd time, the item expands, tapping again shrinks it, then once again the 1st tap does nothing and so on.
Of course, I'm trying to eliminate the 1st redundant tap which does nothing.
Another interesting side-effect: When I tap an item the 1st time, nothing happens, then I will tap a DIFFERENT item once, and both the items will expand together.
I've been over the code for quite a while now and I can't see what's causing this.
Here's the code:
Setting the listener on the ListView:
productsListView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> list, View view,int position, long id)
{
if (lastSelectedPosition == -1) {
lastSelectedPosition = position;
} else if (lastSelectedPosition == position) {
lastSelectedPosition = -1;
} else {
lastSelectedPosition = position;
}
View child;
ProductItemView tag;
for (int i = 0; i < productsListView.getChildCount(); i++) {
child = productsListView.getChildAt(i);
tag = (ProductItemView) child.getTag();
tag.onSomeListItemClicked(position);
productsListView.smoothScrollToPosition(position);
}
}
});
The list view's adapter:
public class ProductsCursorAdapter extends CursorAdapter {
public ProductsCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
ProductItemView item = null;
int pos = cursor.getPosition();
Log.d("BookListFragment", "BookListFragment: Position is: " + pos);
item = new ProductItemView(getActivity(), cursor.getPosition(), view, new ProductDAO(cursor));
view.setTag(item);
item.setContainer(BookListFragment.this, BookListFragment.this);
if (lastSelectedPosition == cursor.getPosition()) {
item.openedFooter();
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
View view = (View) getActivity().getLayoutInflater().inflate(R.layout.course_list_item, null);
return view;
}
}
Relevant code inside ProductItemView:
public void onSomeListItemClicked(int position)
{
if (m_position == position)
{
Log.i("ProductItemView", "Animate footer for position: " + m_position);
animateFooter(position);
}
else
{
Log.i("ProductItemView", "Hide footer for position: " + m_position);
hideFooter(position);
}
}
public void showFooter(int position) {
if (!isFooterVisible())
{
animateFooter(position);
}
}
public void hideFooter(int position)
{
Log.i("ProductItemView", "Hide called for position: " + m_position);
if (isFooterVisible() && position != m_position)
{
animateFooter(position);
}
}
public void animateFooter(final int position)
{
if (footer != null && (m_footerExpandAnim == null || m_footerExpandAnim.hasEnded()))
{
Log.i("ProductItemView", "Animating footer for position: " + m_position);
isFooterVisible=!isFooterVisible;
m_footerExpandAnim = new ExpandAnimation(footer, 200, animationDelegate, position);
footer.startAnimation(m_footerExpandAnim);
}
}
ExpandAnimation:
public ExpandAnimation(View view, int duration, AnimationDelegate delegate, int position) {
this.position = position;
this.delegate = delegate;
setDuration(duration);
mAnimatedView = view;
mViewLayoutParams = (LayoutParams) view.getLayoutParams();
// decide to show or hide the view
mIsVisibleAfter = (view.getVisibility() == View.VISIBLE);
mMarginStart = mViewLayoutParams.bottomMargin;
mMarginEnd = (mMarginStart == 0 ? (-view.getHeight()) : 0);
mAnimatedView.clearAnimation();
Log.i("ExpandAnimation", "Margin Start = " + mMarginStart + ", Margin End = " + mMarginEnd);
//view.setVisibility(View.VISIBLE);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
Log.i("ExpandAnimation", "InterpolatedTime: " + interpolatedTime);
if (interpolatedTime < 1.0f) {
// Calculating the new bottom margin, and setting it
mViewLayoutParams.bottomMargin = mMarginStart
+ (int) ((float)(mMarginEnd - mMarginStart) * interpolatedTime);
mAnimatedView.setLayoutParams(mViewLayoutParams);
// Invalidating the layout, making us seeing the changes we made
mAnimatedView.requestLayout();
mAnimatedView.postInvalidate();
// Making sure we didn't run the ending before (it happens!)
} else if (!mWasEndedAlready) {
mViewLayoutParams.bottomMargin = mMarginEnd;
mAnimatedView.setLayoutParams(mViewLayoutParams);
mAnimatedView.requestLayout();
mAnimatedView.postInvalidate();
if (mIsVisibleAfter) {
//mAnimatedView.setVisibility(View.GONE);
}
mWasEndedAlready = true;
}
if(delegate!=null){
delegate.animationDidEnd(position);
}
}
Some things I've noticed:
The 1st time the item is clicked, the ExpandAnimation's constructor is indeed called, but the logs from the applyTransformation method aren't printed.
The 2nd time the item is clicked, the ExpandAnimation's constructor is called, but the mMarginStart value is not what it should be (randomly between -60 to -80 instead of -100), but then the logs in the applyTransformation are printed properly.
If you need any more code, let me know. Any ideas would help.
As I mentioned, this is not my code - I'm trying to edit code which a developer who has since left wrote. If it were up to me, this entire thing would'v been written very differently. I require a solution which involves minimal changes to the code structure.
Okay, I found the problem.
The clue was that I noticed that after the 1st click which "did nothing", if I scrolled the list slightly, the item I clicked would suddenly expand. This told me that the ListView was, for some reason, preventing its child views from performing UI operations.
I added a postInvalidate call on the list on the OnItemClick listener, and everything works as expected.
Interesting.

How to make a ViewPager loop?

I have a ViewPager with some views. I'd like to go to the first one after right swiping on the last one.
I tried
#Override
public Fragment getItem(int arg0) {
int i = arg0 % fragmentList.size();
return fragmentList.get(i);
}
#Override
public int getCount() {
return fragmentList.size()+1;
}
But I got an error
E/AndroidRuntime(22912): java.lang.IllegalStateException: Fragment already added: RubricFragment{4136cd80 #1 id=0x7f06000c android:switcher:2131099660:0}
One possibility is setting up the screens like this:
C' A B C A'
C' looks just like C, but when you scroll to there, it switches you to the real C.
A' looks just like A, but when you scroll to there, it switches you to the real A.
I would do this by implementing onPageScrollStateChanged like so:
#Override
public void onPageScrollStateChanged (int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
int curr = viewPager.getCurrentItem();
int lastReal = viewPager.getAdapter().getCount() - 2;
if (curr == 0) {
viewPager.setCurrentItem(lastReal, false);
} else if (curr > lastReal) {
viewPager.setCurrentItem(1, false);
}
}
}
Note that this calls the alternate form of setCurrentItem and passes false to cause the jump to happen instantly rather than as a smooth scroll.
There are two main drawbacks I see to this. Firstly, upon reaching either end the user has to let the scrolling settle before they can go further. Secondly, it means having a second copy of all of the views in your first and last page. Depending on how resource-heavy your screens are, that may rule out this technique as a possible solution.
Note also that since the view pager doesn't let clicks go through to underlying controls until after the scrolling has settled, it's probably fine to not set up clicklisteners and the like for the A' and C' fragments.
Edit: Having now implemented this myself, there's another pretty major drawback. When it switches from A' to A or C' to C, the screen flickers for a moment, at least on my current test device.
I would create a dummy page at the end of the ViewPager.
Then I use this code to go to the first page when the user scroll to the dummy page. I know it's far from perfect :D
#Override
public void onPageScrolled(int position, float arg1, int arg2) {
if (position >= NUM_PAGE-1) {
mViewPager.setCurrentItem(0, true);
}
}
My solution is based on benkc, but first and last page scroll animation are disabled, and when pages "scrolled" to real page, scroll animation is enable again, this scheme can solve the first drawback.
but my ViewPager.setCurrentItem(position, false) result is still have scroll animation, so i implements animation which is too fast to seen.
the fast scrolling animation like this, don't mind the comment, just my code didn't use these method:
public class FixedSpeedScroller extends Scroller {
private int mDuration = 0;
public FixedSpeedScroller(Context context) {
super(context);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, mDuration);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy) {
super.startScroll(startX, startY, dx, dy, mDuration);
}
}
and use this method to viewpager's activity
private Scroller scroller;
private void setViewPagerScroll(boolean instant) {
try {
Field mScroller = null;
mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
if (scroller == null) {
scroller = (Scroller) mScroller.get(mViewPager);
}
FixedSpeedScroller fss = new FixedSpeedScroller(mViewPager.getContext());
mScroller.set(mViewPager, instant ? fss : scroller);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
and modify onPageScrollStateChanged like this, only first page or last page (i have 5 pages) would change animation to fast scrolling, otherwise has normal scrolling:
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
if (position == 0) {
setViewPagerScroll(true);
mViewPager.setCurrentItem(3);
} else if (position == 4) {
setViewPagerScroll(true);
mViewPager.setCurrentItem(1);
} else {
setViewPagerScroll(false);
}
}
}
FixedSpeedScroller references is here: http://blog.csdn.net/ekeuy/article/details/12841409
Kotlin Version:
Initailize the variables
private var mCurrentPosition = 0
private var mScrollState = 0
private lateinit var mImageViewPager: ViewPager
onCreate:
mImageViewPager = findViewById<View>(R.id.pager) as ViewPager
mImageViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
handleScrollState(state)
mScrollState = state
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
mCurrentPosition = position
}
})
functions outside onCreate:
private fun handleScrollState(state: Int) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
if (!isScrollStateSettling()){
val lastPosition: Int = mImageViewPager.getAdapter()?.getCount()!! - 1
if (mCurrentPosition == 0) {
mImageViewPager.setCurrentItem(lastPosition , false)
} else if (mCurrentPosition == lastPosition) {
mImageViewPager.setCurrentItem(0 , false)
}
}
}
}
private fun isScrollStateSettling(): Boolean {
return mScrollState == ViewPager.SCROLL_STATE_SETTLING
}
this should do the job without dummy pages:
private boolean isFirstOrLastPage;
private int currentPageIndex = 0;
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
if(currentPageIndex!=arg0){
isFirstOrLastPage = false;
return;
}
if((arg0==0 || arg0==PAGES.size()-1) && arg1 == 0 && arg2 == 0){
if(isFirstOrLastPage){
//DO SOMETHING
}else{
isFirstOrLastPage = true;
}
}
}
#Override
public void onPageSelected(int arg0) {
currentPageIndex = arg0;
}
this works, the accepted answer no good because there is a lag when the loop happens:
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public CharSequence getPageTitle(int position) {
String title = mTitleList.get(position % mActualTitleListSize);
return title;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
int virtualPosition = position % mActualTitleListSize;
return super.instantiateItem(container, virtualPosition);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
int virtualPosition = position % mActualTitleListSize;
super.destroyItem(container, virtualPosition, object);
}
answer taken from here : ViewPager as a circular queue / wrapping

Android listView find the amount of pixels scrolled

I have a listView. When I scroll and stops in a particular place.
How can I get the amount of pixels I scrolled(from top)?
I have tried using get listView.getScrollY(), but it returns 0.
I had the same problem.
I cannot use View.getScrollY() because it always returns 0 and I cannot use OnScrollListener.onScroll(...) because it works with positions not with pixels. I cannot subclass ListView and override onScrollChanged(...) because its parameter values are always 0. Meh.
All I want to know is the amount the children (i.e. content of listview) got scrolled up or down. So I came up with a solution. I track one of the children (or you can say one of the "rows") and follow its vertical position change.
Here is the code:
public class ObservableListView extends ListView {
public static interface ListViewObserver {
public void onScroll(float deltaY);
}
private ListViewObserver mObserver;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
public ObservableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mTrackedChild == null) {
if (getChildCount() > 0) {
mTrackedChild = getChildInTheMiddle();
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = getPositionForView(mTrackedChild);
}
} else {
boolean childIsSafeToTrack = mTrackedChild.getParent() == this && getPositionForView(mTrackedChild) == mTrackedChildPrevPosition;
if (childIsSafeToTrack) {
int top = mTrackedChild.getTop();
if (mObserver != null) {
float deltaY = top - mTrackedChildPrevTop;
mObserver.onScroll(deltaY);
}
mTrackedChildPrevTop = top;
} else {
mTrackedChild = null;
}
}
}
private View getChildInTheMiddle() {
return getChildAt(getChildCount() / 2);
}
public void setObserver(ListViewObserver observer) {
mObserver = observer;
}
}
Couple of notes:
we override onScrollChanged(...) because it gets called when the listview is scrolled (just its parameters are useless)
then we choose a child (row) from the middle (doesn't have to be precisely the child in the middle)
every time scrolling happens we calculate vertical movement based on previous position (getTop()) of tracked child
we stop tracking a child when it is not safe to be tracked (e.g. in cases where it might got reused)
You cant get pixels from top of list (because then you need to layout all views from top of list - there can be a lot of items). But you can get pixels of first visible item: int pixels = listView.getChildAt(0).getTop(); it generally will be zero or negative number - shows difference between top of listView and top of first view in list
edit:
I've improved in this class to avoid some moments that the track was losing due to views being too big and not properly getting a getTop()
This new solution uses 4 tracking points:
first child, bottom
middle child, top
middle child, bottom
last child, top
that makes sure we always have a isSafeToTrack equals to true
import android.view.View;
import android.widget.AbsListView;
/**
* Created by budius on 16.05.14.
* This improves on Zsolt Safrany answer on stack-overflow (see link)
* by making it a detector that can be attached to any AbsListView.
* http://stackoverflow.com/questions/8471075/android-listview-find-the-amount-of-pixels-scrolled
*/
public class PixelScrollDetector implements AbsListView.OnScrollListener {
private final PixelScrollListener listener;
private TrackElement[] trackElements = {
new TrackElement(0), // top view, bottom Y
new TrackElement(1), // mid view, bottom Y
new TrackElement(2), // mid view, top Y
new TrackElement(3)};// bottom view, top Y
public PixelScrollDetector(PixelScrollListener listener) {
this.listener = listener;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// init the values every time the list is moving
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL ||
scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
for (TrackElement t : trackElements)
t.syncState(view);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
boolean wasTracked = false;
for (TrackElement t : trackElements) {
if (!wasTracked) {
if (t.isSafeToTrack(view)) {
wasTracked = true;
if (listener != null)
listener.onScroll(view, t.getDeltaY());
t.syncState(view);
} else {
t.reset();
}
} else {
t.syncState(view);
}
}
}
public static interface PixelScrollListener {
public void onScroll(AbsListView view, float deltaY);
}
private static class TrackElement {
private final int position;
private TrackElement(int position) {
this.position = position;
}
void syncState(AbsListView view) {
if (view.getChildCount() > 0) {
trackedChild = getChild(view);
trackedChildPrevTop = getY();
trackedChildPrevPosition = view.getPositionForView(trackedChild);
}
}
void reset() {
trackedChild = null;
}
boolean isSafeToTrack(AbsListView view) {
return (trackedChild != null) &&
(trackedChild.getParent() == view) && (view.getPositionForView(trackedChild) == trackedChildPrevPosition);
}
int getDeltaY() {
return getY() - trackedChildPrevTop;
}
private View getChild(AbsListView view) {
switch (position) {
case 0:
return view.getChildAt(0);
case 1:
case 2:
return view.getChildAt(view.getChildCount() / 2);
case 3:
return view.getChildAt(view.getChildCount() - 1);
default:
return null;
}
}
private int getY() {
if (position <= 1) {
return trackedChild.getBottom();
} else {
return trackedChild.getTop();
}
}
View trackedChild;
int trackedChildPrevPosition;
int trackedChildPrevTop;
}
}
original answer:
First I want to thank #zsolt-safrany for his answer, that was great stuff, total kudos for him.
But then I want to present my improvement on his answer (still is pretty much his answer, just a few improvements)
Improvements:
It's a separate "gesture detector" type of class that can be added to any class that extends AbsListView by calling .setOnScrollListener(), so it's a more flexible approach.
It's using the change in scroll state to pre-allocate the tracked child, so it doesn't "waste" one onScroll pass to allocate its position.
It re-calculate the tracked child on every onScroll pass to avoiding missing random onScroll pass to recalculate child. (this could be make more efficient by caching some heights and only re-calculate after certain amount of scroll).
hope it helps
import android.view.View;
import android.widget.AbsListView;
/**
* Created by budius on 16.05.14.
* This improves on Zsolt Safrany answer on stack-overflow (see link)
* by making it a detector that can be attached to any AbsListView.
* http://stackoverflow.com/questions/8471075/android-listview-find-the-amount-of-pixels-scrolled
*/
public class PixelScrollDetector implements AbsListView.OnScrollListener {
private final PixelScrollListener listener;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
public PixelScrollDetector(PixelScrollListener listener) {
this.listener = listener;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// init the values every time the list is moving
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL ||
scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
if (mTrackedChild == null) {
syncState(view);
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mTrackedChild == null) {
// case we don't have any reference yet, try again here
syncState(view);
} else {
boolean childIsSafeToTrack = (mTrackedChild.getParent() == view) && (view.getPositionForView(mTrackedChild) == mTrackedChildPrevPosition);
if (childIsSafeToTrack) {
int top = mTrackedChild.getTop();
if (listener != null) {
float deltaY = top - mTrackedChildPrevTop;
listener.onScroll(view, deltaY);
}
// re-syncing the state make the tracked child change as the list scrolls,
// and that gives a much higher true state for `childIsSafeToTrack`
syncState(view);
} else {
mTrackedChild = null;
}
}
}
private void syncState(AbsListView view) {
if (view.getChildCount() > 0) {
mTrackedChild = getChildInTheMiddle(view);
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = view.getPositionForView(mTrackedChild);
}
}
private View getChildInTheMiddle(AbsListView view) {
return view.getChildAt(view.getChildCount() / 2);
}
public static interface PixelScrollListener {
public void onScroll(AbsListView view, float deltaY);
}
}
Try to implement OnScrollListener:
list.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
int last = view.getLastVisiblePosition();
break;
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});

Categories

Resources