Endless horizontal scroll view in android - android

I am a New Android Application Developer. I would like to know how to create endless horizontal scroll view. For example, there are three buttons (Button1, Button2 and Button3). When user scroll the view, I still want to display Button1 again after Button3. Could you please provide any sample code or any idea?
Thanks.

You could check if your button view is still visible. First check if button one is visible:
private boolean isViewVisible(View view) {
Rect scrollBounds = new Rect();
mScrollView.getDrawingRect(scrollBounds);
float top = view.getY();
float bottom = top + view.getHeight();
if (scrollBounds.top < top && scrollBounds.bottom > bottom) {
return true;
} else {
return false;
}
}
If it is not visible, then add button one again. Call this method in a scroll listener every time user scrolls, to check if the button is not visible. If the button is not visible, then add it again.
If you want to make it endless itself, try this:
public class Test extends ListActivity implements OnScrollListener {
Aleph0 adapter = new Aleph0();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(adapter);
getListView().setOnScrollListener(this);
}
public void onScroll(AbsListView view,
int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
if(loadMore) {
adapter.count += visibleCount; // or any other amount
adapter.notifyDataSetChanged();
}
}
public void onScrollStateChanged(AbsListView v, int s) { }
class Aleph0 extends BaseAdapter {
int count = 40; /* starting amount */
public int getCount() { return count; }
public Object getItem(int pos) { return pos; }
public long getItemId(int pos) { return pos; }
public View getView(int pos, View v, ViewGroup p) {
TextView view = new TextView(Test.this);
view.setText("entry " + pos);
return view;
}
}
}
And take a look at this:
Android Endless List
android:how to make infinite scrollview with endless scrolling
Thats it. Just see when the view is out of bounds when the user is scrolling.
And when it is out of view, just re add it.

Related

Issue In Getting Child Row of the Listview On Swipe

I am trying to retrieve the Child Row of the listview when the event swipe take place.I have created a BaseAdapter which sets a customView following is the GetView Method of BaseAdapter:
public View getView(int position, View convertView, ViewGroup parent) {
ModelView modelView = null;
ModelClass model=modelList.get(position);
if(convertView==null)
{
modelView=new ModelView(context,model);
}
else{
Log.d("convertView","NotNUll");
modelView=(modelView) convertView;
}
modelView.setModel(diet);
return modelView;
}
My ModelView Class Looks Like this:
public class ModelView extends LinearLayout {
Context context;
TextView text1,text2,text3;
ImageView img1;
Model d;
public ModelView(Context context,Model d) {
super(context);
this.context=context;
this.d=d;
this.setTag(d);
HookUp();
}
public void HookUp() {
this.setLayoutParams(new ListView.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT));
this.setOrientation(LinearLayout.VERTICAL);
LayoutInflater inflater = LayoutInflater.from(context);
view = inflater.inflate(R.layout.lay, null);
this.addView(view);
textDietHeadLine=(TextView) view.findViewById(R);
text=(TextView) view.findViewById(R.id.txt1);
text=(TextView) view.findViewById(R.id.txt2);
}
}
In My Main Activity on TouchListener when i am retrieving the Tag it always give me null,i dont know where i m going wrong...any help would be appreciated ,although i searched alot on this issue but it was of no avail,i am a beginner in android,and struck on this for quiet a while now,plss helppp :(
Following is the touchListener Code:
public boolean onTouch(View v, MotionEvent e)
{
if (gestureDetector.onTouchEvent(e)){
// Log.d("Diet d","v.gettag());
return true;
}else{
return false;
}
It a bit complicated, I hope i can do it right, in case you want to get particular row which user swiping on, it need to extracting a method which carry deep in ListView calls findMotionRow, this method declare as an actually private visibility for extender, so we must make that method available in our own ListView and learn how to invoke it from the ListView's source code :
public class YourListView extends ListView {
...Constructors...
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mMotionPosition = findMotionRow(ev.getY());
break;
}
return super.onInterceptTouchEvent(ev);
}
private int mMotionPosition;
public int getMotionPosition() {
return mMotionPosition;
}
/**
* Find which position is motion on.
* Note : this method copy into public from 4.0 source code.
* #param y Y coordinate of the motion event.
* #return Selected index (starting at 0) of the data item.
*/
private int findMotionRow(float y) {
int childCount = getChildCount();
if (childCount > 0) {
if (!isStackFromBottom()) {
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
if (y <= v.getBottom()) {
return getFirstVisiblePosition() + i;
}
}
} else {
for (int i = childCount - 1; i >= 0; i--) {
View v = getChildAt(i);
if (y >= v.getTop()) {
return getFirstVisiblePosition() + i;
}
}
}
}
return INVALID_POSITION;
}
}
In the proper place, i.e Activity.onCreate(), we can listening the onTouch event and able to use mMotionPosition to take the row's View :
mYourListView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int hoverChildIndex =
mYourListView.getMotionPosition() - mYourListView.getFirstVisiblePosition();
ModelView hoveringView = (ModelView) mYourListView.getChildAt(hoverChildIndex);
return false;
}
});

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.

Android scroll up hide view and scroll down show view effect like twitter

How to do scrolling effect like twitter when scroll Up hide viewpager tab (Home, Discover, activity). Or effect like facebook scrolling, while scroll up hide option view(status, photo, checkin) when scroll down show option view. Any example link will do please help.
Easy solution:
public abstract class OnScrollObserver implements AbsListView.OnScrollListener {
public abstract void onScrollUp();
public abstract void onScrollDown();
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
int last = 0;
boolean control = true;
#Override
public void onScroll(AbsListView view, int current, int visibles, int total) {
if (current < last && !control) {
onScrollUp();
control = true;
} else if (current > last && control) {
onScrollDown();
control = false;
}
last = current;
}
Usage:
listView.setOnScrollListener(new OnScrollObserver() {
#Override
public void onScrollUp() {
}
#Override
public void onScrollDown() {
}
});
EDIT: better, you have this library https://github.com/ksoichiro/Android-ObservableScrollView
You can look at this https://github.com/LarsWerkman/QuickReturnListView
Source: https://stackoverflow.com/a/25304575/244702
That's my own implementation:
notice:
View to be hidden should be fixed height
We are not hiding the view by Visiblity.GONE
We are setting the final height to 0px
Here is the code:
//Your view which you would like to animate
final RelativeLayout yourViewToHide = (yourViewToHideativeLayout) findViewById(R.id.topWrapper);
//The initial height of that view
final int initialViewHeight = yourViewToHide.getLayoutParams().height;
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//Try catch block for NullPointerExceptions
try{
//Here is a simple delay. If user scrolls ListView from the top of the screen to the bottom then continue
if(firstVisibleItem % visibleItemCount == 0) {
//Here we initialize the animator, doesn't matter what values You will type in
ValueAnimator animator = ValueAnimator.ofInt(0, 1);
//if Scrolling up
if (fastScrollSB.getProgress() > view.getFirstVisiblePosition()){
//Getting actual yourViewToHide params
ViewGroup.LayoutParams params = yourViewToHide.getLayoutParams();
if (!animator.isRunning()) {
//Setting animation from actual value to the initial yourViewToHide height)
animator.setIntValues(params.height, initialViewHeight);
//Animation duration
animator.setDuration(500);
//In this listener we update the view
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams params = yourViewToHide.getLayoutParams();
params.height = (int) animation.getAnimatedValue();
yourViewToHide.setLayoutParams(params);
}
});
//Starting the animation
animator.start();
}
System.out.println("Scrolling up!");
//If not scrolling
} else if (fastScrollSB.getProgress() == view.getFirstVisiblePosition()) {
System.out.println("Not Scrolling!");
//If scrolling down
} else if (fastScrollSB.getProgress() < view.getFirstVisiblePosition()){
//Getting actual yourViewToHide params
ViewGroup.LayoutParams params = yourViewToHide.getLayoutParams();
if (!animator.isRunning()) {
//Setting animation from actual value to the target value (here 0, because we're hiding the view)
animator.setIntValues(params.height, 0);
//Animation duration
animator.setDuration(500);
//In this listener we update the view
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams params = yourViewToHide.getLayoutParams();
params.height = (int) animation.getAnimatedValue();
yourViewToHide.setLayoutParams(params);
}
});
//Starting the animation
animator.start();
}
System.out.println("Scrolling down!");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
});`
Hope it fits your needs :)
there is no quick example for this. But what you can do is keep track which way you are scrolling and show or hide the view accordingly
For example get first visible position of the ListView keep track of this and if it is smaller then before you know you are scrolling up this way you can show the view. In case it is bigger then hide the view.
This is a simple approach in case you want to have more precise you need to work with onTouchListeners and y coordinates of the movement.
http://developer.android.com/reference/android/widget/ListView.html

Listview - Footer at the bottom of screen

I have a ListView with a footer added with listview.addFooterView(footerView);
All works as expected excepted in one case: when my listview's items doesn't fill the whole screen, I would like the footer to be at the bottom of the screen, instead of being in the middle. Is there a way to do this easily? Or should I change my layout?
Thanks
EDIT: that might help (this is what I want)
If you want it to always be at the bottom of the screen, no matter how long your ListView is, then get rid of listview.addFooterView(footerView); and use a RelativeLayout. Give yourListView` the property
android:layout_alignParentTop="true"
and give the property to your footer
android:layout_alignParentBottom="true"
If this doesn't solve your problem then please be a little more specific about what you want and provide a picture of what you want if possible.
Edit
After reading the comments this might work. There might be an easier way but you could do something like
listView.post(new Runnable()
{
public void run()
{
int numItemsVisible = listView.getLastVisiblePosition() -
listView.getFirstVisiblePosition();
if (itemsAdapter.getCount() - 1 > numItemsVisible)
{
// set your footer on the ListView
}
else
{
footerView.setVisibility(View.VISIBLE);
}
}
footerView would be a custom layout that you would create with the properties I referenced above. This should set that to visible if the items aren't more than can fit on the screen. If they are more than can fit then you apply the footer view on the ListView as you are now. This might not be the best way but its the first thing that comes to mind. You would run this code just before you set the Adapter.
You cannot use ListView footer as footer for the whole layout.
You're better off with RelativeLayout as root element for your layout, and then a direct child of it containing the footer view with the attribute:
android:layout_alignParentBottom="true"
In addition to #codeMagic response, you could add a listener to check when your adapter gets updated and then update the footer
registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
updateSmartFooter();
}
});
where updateSmartFooter is the function he described
private void updateSmartFooter {
listView.post(new Runnable()
{
public void run()
{
int numItemsVisible = listView.getLastVisiblePosition() -
listView.getFirstVisiblePosition();
if (itemsAdapter.getCount() - 1 > numItemsVisible)
{
// set your footer on the ListView
}
else
{
footerView.setVisibility(View.VISIBLE);
}
}
}
}
After spent a lot of time to research, I found the best solution for it.
Please have a look at: https://stackoverflow.com/a/38890559/6166660
and https://github.com/JohnKuper/recyclerview-sticky-footer
For details:
Create a StickyFooterItemDecoration extends RecyclerView.ItemDecoration like the example code below.
After that, set ItemDecoration to your recyclerView:
recyclerListView.addItemDecoration(new StickyFooterItemDecoration());
---------------------------------------
public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {
private static final int OFF_SCREEN_OFFSET = 5000;
#Override
public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
int adapterItemCount = parent.getAdapter().getItemCount();
if (isFooter(parent, view, adapterItemCount)) {
if (view.getHeight() == 0 && state.didStructureChange()) {
hideFooterAndUpdate(outRect, view, parent);
} else {
outRect.set(0, calculateTopOffset(parent, view, adapterItemCount), 0, 0);
}
}
}
private void hideFooterAndUpdate(Rect outRect, final View footerView, final RecyclerView parent) {
outRect.set(0, OFF_SCREEN_OFFSET, 0, 0);
footerView.post(new Runnable() {
#Override
public void run() {
parent.getAdapter().notifyDataSetChanged();
}
});
}
private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
int topOffset = parent.getHeight() - visibleChildsHeightWithFooter(parent, footerView, itemCount);
return topOffset < 0 ? 0 : topOffset;
}
private int visibleChildsHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
int totalHeight = 0;
int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
for (int i = 0; i < onScreenItemCount - 1; i++) {
totalHeight += parent.getChildAt(i).getHeight();
}
return totalHeight + footerView.getHeight();
}
private boolean isFooter(RecyclerView parent, View view, int itemCount) {
return parent.getChildAdapterPosition(view) == itemCount - 1;
}
}

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