Why RecyclerView Adapter doesn't instantly initialize its items on manual scroll? - android

I am using 2 RecyclerViews inside a Dialogue. One of the RecyclerView further uses a nested list inside it. Everything's alright till now (no height issues).
However, from the other RecyclerView, I drag an image/view and inside another RecyclerView i wish to drop it.
This functionality works fine but problem occurs here:
I usually highlight a particular Text area when the dragged image enters that area using DragListener's available Drag events (see image below):
So if I drag the image till "Customer Voice 5" Text header, it'll highlight.
When I drag it to the 5th item, it scrolls down (as coded inside DragListener using smoothScrollBy()) and 6th item with the header "Customer Voice 6" Text header is also visible, BUT it doesn't get highlighted. Neither it scrolls down further to show 7th completely, no code works on this item, as if RecyclerView hasn't yet updated/recognized this item inside it's onBindViewHolder(...) method.
Now I drop the dragged image elsewhere (no action) and again drag image to "Customer Header 6", it highlights! (and scrolls too) That means RecyclerView has updated it after I had dropped my image on the first run.
So while I drag, the Drag Listener's event ACTION_DRAG_LOCATION runs continuously and Recycler View seems to not able to take the control while this happens. So how to get both things DragListener and RecyclerViewAdapter, work together? [I am aware how RV uses the items from holder and re-uses it when invisible from to dirty view list]
I tried notifyDataSetChanged(); but doesn't help. Can't find any other thing on this. Maybe LinearLayoutManager might help but can't link it with my problem. Below are some code blocks from my RV adapter.
#Override
public void onBindViewHolder(MyHolder holder, int position) {
Log.d("Log","OnBindViewHolder"+position);
holder.customerVoiceTitle.setText(hashMap.get(position)); //Setting Header Text - "Customer Voice [position+1]"
holder.customerVoiceTitle.setId(position + 100); // Setting id to be able to detect each header while performing drag actions
holder.customerVoiceTitle.setOnDragListener(new MyDragListener());
customerVoiceMainHeaderText.setId(50); //Neglect this for the time being, this is also a part of the issue.
customerVoiceMainHeaderText.setOnDragListener(new MyDragListener());
holder.totalWorkLayoutListView.setId(position + 150); //ListView also is utilized to perform drag actions
holder.totalWorkLayoutListView.setOnDragListener(new MyDragListener());
UtilityClass.setListViewHeightBasedOnChildren(holder.totalWorkLayoutListView, "", ""); //To avoid height inflation issues
/* if(allFlag) {
customerVoiceWorkListCustomAdapter = new CustomerVoiceWorkListCustomAdapter(context, 5, true);
}
else if(!allFlag) {
customerVoiceWorkListCustomAdapter = new CustomerVoiceWorkListCustomAdapter(context, 5, false);
}*/
holder.totalWorkLayoutListView.setAdapter(customerVoiceWorkListCustomAdapter);
}
class MyDragListener implements View.OnDragListener {
Drawable normalShape = context.getResources().getDrawable(R.drawable.bg_fragment_header_with_border);
Drawable targetShape = context.getResources().getDrawable(R.drawable.target_shape);
#Override
public boolean onDrag(View v, DragEvent event) {
// Handles each of the expected events
switch (event.getAction()) {
//signal for the start of a drag and drop operation.
case DragEvent.ACTION_DRAG_STARTED:
// do nothing
break;
//the drag point has entered the bounding box of the View
case DragEvent.ACTION_DRAG_ENTERED:
if(v.getId() >= 100 && v.getId() < 150) //For Header Text only
v.setBackground(targetShape); //change the shape of the view
break;
//the user has moved the drag shadow outside the bounding box of the View
case DragEvent.ACTION_DRAG_EXITED:
if(v.getId() >= 100 && v.getId() < 150) //For Header Text only
v.setBackground(normalShape); //change the shape of the view back to normal
break;
//drag shadow has been released,the drag point is within the bounding box of the View
case DragEvent.ACTION_DROP:
Log.d("Log", "" + v.getId());
// if the view is the bottomlinear, we accept the drag item
if (v.getId() >= 100 && v.getId() < 150) { //For Header Text only. If dragged image is dropped some changes take place inside the list. Not IMP now
TextView workListHeaderText = (TextView) v;
LinearLayout containerLayout = (LinearLayout) workListHeaderText.getParent();
ListView workLayoutList = (ListView) containerLayout.getChildAt(1);
customerVoiceWorkListCustomAdapter = new CustomerVoiceWorkListCustomAdapter(context, 5, true);
workLayoutList.setAdapter(customerVoiceWorkListCustomAdapter);
} else if (v.getId() == 50) {
allFlag = true;
TextView workListRootHeaderText = (TextView) v;
LinearLayout layout = (LinearLayout) workListRootHeaderText.getParent();
RecyclerView technicianAllotment = (RecyclerView) layout.getChildAt(1);
int childCount = technicianAllotment.getChildCount();
for (int listCount = 0; listCount < childCount; listCount++) {
LinearLayout linearLayout = (LinearLayout) technicianAllotment.getChildAt(listCount);
ListView workLayoutList = (ListView) linearLayout.getChildAt(1);
customerVoiceWorkListCustomAdapter = new CustomerVoiceWorkListCustomAdapter(context, 5, true);
workLayoutList.setAdapter(customerVoiceWorkListCustomAdapter);
UtilityClass.setListViewHeightBasedOnChildren(workLayoutList, "", "");
}
}
break;
case DragEvent.ACTION_DRAG_LOCATION: //This is used to scroll when dragged image is dragged to the bottom of screen
LinearLayout dropZoneView = (LinearLayout) v.getParent();
int topOfDropZone = dropZoneView.getTop();
int bottomOfDropZone = dropZoneView.getBottom();
int scrollY = recyclerView.getScrollY();
int scrollViewHeight = recyclerView.getMeasuredHeight();
Log.d("Log", "location: Scroll Y: " + scrollY + " Scroll Y+Height: " + (scrollY + scrollViewHeight));
Log.d("Log", " top: " + topOfDropZone + " bottom: " + bottomOfDropZone);
if (bottomOfDropZone > (scrollY + scrollViewHeight - 100)) { //Manual scrolling down when image is dragged to bottom RV item
notifyDataSetChanged();
recyclerView.smoothScrollBy(0, 300);
}
if (topOfDropZone < (scrollY + 50)) //Manual scrolling up
recyclerView.smoothScrollBy(0, -50);
break;
//the drag and drop operation has concluded.
case DragEvent.ACTION_DRAG_ENDED:
if(v.getId() >= 100 && v.getId() < 150)
v.setBackground(normalShape); //go back to normal shape
default:
break;
}
return true;
}
}
EDIT
https://stackoverflow.com/a/14458111/4452231
This so far has worked. Got better solutions? Kindly suggest.

Related

Img(ImageView) moving randomly within screen with onTouch onDrag listener

EDIT: Cliffnote of what i want accomplished
straight to the point,
My XML set up is has 2 linearview (top, bottom)
1. I want my img to stay within screen when moving
2. Moving continuously randomly
3. Have it clickable/draggable
4. Have the img get its new position and move whereever it is dropped (top or bottom)
Thank you.
Hello making an app (Android Studio) for autistic children (inspired by my autistic son). In my Activity i have 2 linear layout: Top and Bottom. The top is where 3/4 of my screen that has 4 images. The bottom is the targeted drop point where the "Answer" or img should be dropped.
I have an 4 img randomly created in a (via databaseFP iterated) but now i want those img to be moving randomly within screen (top screen) and drag them into a target drag box (bottom screen). Only 1 of four img is the correct "answer" so if its "incorrect" i want this img to return to its position MOVING all the while within screen until the "correct" img is chosen to the targeted drop box. That is all...Please Please help me i have read many and not like this specifically where it involves onTouch/onDrag listener and the img goes back to position if dragged.
So far all i have done is make the img move (it retains the on click and drag but when its incorrect it goes off screen) Pls see code below
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int width = displaymetrics.widthPixels;
int height = displaymetrics.heightPixels;
Random r = new Random();
int distance = 100; //the distance to move in pixels
int duration = 500; //the duration of the animation in ms
double direction = Math.random() * 2 * Math.PI;
int translationX = (int) (Math.cos(direction) * distance);
int translationY = (int) (Math.sin(direction) * distance);
int id2 = getResources().getIdentifier(myShuffledArray[0], "drawable", getPackageName());
image.setImageResource(id2);
image.animate().translationX(translationX - width).translationY(translationY - height).setDuration(duration).start();
I am a newbie but this would help my autistic son and this is a Thesis project for my college.
EDIT this is my ontouch/drag
public boolean onDrag(View v, DragEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==DragEvent.ACTION_DROP){
//we want to make sure it is dropped only to left and right parent view
View view = (View)event.getLocalState();
//if(v.getId() == R.id.left_view || v.getId() == R.id.right_view){
if(view.getId() == R.id.box_view1){
ViewGroup source = (ViewGroup) view.getParent();
source.removeView(view);
LinearLayout target = (LinearLayout) v;
target.addView(view);
}
//make view visible as we set visibility to invisible while starting drag
view.setVisibility(View.VISIBLE);
}
return true;
}
#Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_DOWN){
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
}
return false;
}
I have found the solution posted on this website
https://blahti.wordpress.com/2011/01/17/moving-views-part-2/
Check it out for those who wants learn more on Views and Drag and Drop

Multi ViewPager change padding

I'm implementing ViewPager on Android TV to show 3 items like this (background green/purple are debug background to depict whole ViewPager's page):
I archieved that using:
<android.support.v4.view.ViewPager
...
android:paddingLeft="300px"
android:paddingRight="300px"
android:clipToPadding="false"
...
/>
When loosing focus on ViewPager I wanted central item to scale down and pages to come closer together. To achieve that plugged animation:
ValueAnimator animator = ValueAnimator.ofInt(300, 400);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator){
Integer paddingHorizontal = (Integer) valueAnimator.getAnimatedValue();
vpScreenshots.setPadding(paddingHorizontal, 0, paddingHorizontal, 0);
}
});
animator.setDuration(300);
animator.start();
The animation works perfect for 1st element:
However when moving to next pages (both left and right) the padding animation starts to behave strange. When moving e.g. to right padding animation doesn't take action on left item but rather central and right ones. The effects goes deeper the more I move to right. The results are:
So switching pages in ViewPager spoils items alignment when padding animation occurs.
Why does it happen? How ti fix it ?
I managed to solve the issue. The only way which seems to work is to manipulate left and right items' "x" parameter.
Here is the code for focus listener:
if (hasFocus) {
Map<Integer, View> neighbors = vpScreenshots.findNeighbors();
View leftNeighbor = neighbors.get(0);
View rightNeighbor = neighbors.get(1);
if (!firstFocus) {
leftNeighbor.animate().xBy(-100).setDuration(FOCUS_ANIMATION_DURATION).start();
rightNeighbor.animate().xBy(100).setDuration(FOCUS_ANIMATION_DURATION).start();
}
firstFocus = false;
} else {
Map<Integer, View> neighbors = vpScreenshots.findNeighbors();
View leftNeighbor = neighbors.get(0);
View rightNeighbor = neighbors.get(1);
leftNeighbor.animate().xBy(100).setDuration(FOCUS_ANIMATION_DURATION).start();
rightNeighbor.animate().xBy(-100).setDuration(FOCUS_ANIMATION_DURATION).start();
}
Looking for neighbor items is attached to MyViewPager class:
public Map<Integer, View> findNeighbors() {
neighbors = new HashMap<>();
int currentPosition = getCurrentItem();
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childPosition = (int) childView.getTag();
if ((currentPosition - childPosition) == 1) {
neighbors.put(0, childView);
}
if ((currentPosition - childPosition) == -1) {
neighbors.put(1, childView);
}
}
return neighbors;
}

setSelected to listview item at coordinates after scrolling

I have a listview that has a fixed top & bottom padding. What I want to to is
When scrolling is stopped, check the coordinates at the middle of the
listview and assign selected state to true.
What I have tried to do is using
AbsListView.OnScrollListener to check if the scroll stateis SCROLL_STATE_IDLE, then do the assigning.
pointToPosition to get the item index, this is the adapter position
Use getChildAt(index) and setSelected(true)
The problem here is when I do getChildAt, at some point, the app will be broken, maybe because the getChildAt only works with visible items. I have tried following approaches too:
Used getView's getAdapter then assigning, but no luck.
Doing calculation with getFirstVisiblePosition, then using getChildAt but the issue here is the number of visible items which are not constant.
OnScrollListener:
public class fromListScrollListener implements AbsListView.OnScrollListener {
#Override
public void onScrollStateChanged(final AbsListView view, int scrollState) {
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
index = view.pointToPosition(view.getWidth()/2,view.getHeight()/2);
view.smoothScrollToPositionFromTop(index, 0, 500);
if (actionUp == 1){
view.postDelayed(new Runnable() {
#Override
public void run() {
index = view.pointToPosition(view.getWidth() / 2, view.getHeight() / 2);
view.getChildAt(index).setSelected(true);
}
}, 600);
actionUp = 0;
}
break;
default:
break;
}
}
}
Found a solution while doing another project. I did not specify what type of View I should be applying selected state to. Following is my solution.
index = view.pointToPosition(view.getWidth() / 2, view.getHeight() / 2);
LinearLayout ll = (LinearLayout)toList.getChildAt(index - toList.getFirstVisiblePosition());
ll.setSelected(true);

WebView does not load in proper position

I have an app that loads HTML content in three WebViews. For simplicity, let's call them top, middle, and bottom.
The user is always viewing the middle WebView. When the user reaches the top of the page and swipes down, the layout of the three WebViews change so that the top view is visible. Conversely, when the user reaches the bottom of the page and swipes up, the bottom page comes into view.
Imagine a 100x100 screen. Coordinate 0,0 is the top left of the screen and 100,100 is the bottom right of the screen. The top view will have a layout with the top at -105 so that it is not viewable, the middle view will occupy the screen, and the bottom view will have a layout with the top at 105 so that it is not viewable, as well.
topLayout.topMargin = -105;
middleLayout.topMargin = 0;
bottomLayout.topMargin = 105;
top.setLayoutParams(topLayout);
middle.setLayoutParams(middleLayout);
bottom.setLayoutParams(bottomLayout);
The content are books, so when the user changes pages, the content should flow logically. When scrolling backwards (up), the bottom of the previous page should be shown. When scrolling forwards (down), the top of the next page should be shown. This is accomplished through setting the WebView's scroll positions using scrollTo(x,y). The code looks like this, where a represents the bottom of the content and b represents the top of the content:
top.scrollTo(0, a);
middle.scrollTo(0, {a,b}); // a for previous page; b for next page
bottom.scrollTo(0, b);
When the user swipes to the previous page, the top WebView's layout changes to have a top of 0 to occupy the screen; the middle changes to 105, and the bottom changes to -105 and loads different content so the app will be prepared for a future previous swipe.
Now we actually come to my question. This works exactly as intended except in Android 4.4 (KitKat). In KitKat, it works for two swipes in either direction, but then on the third and subsequent swipe in the same direction, the content is loaded in the wrong position. When scrolling backwards, the content starts to load showing the top. When scrolling forwards, the content starts to load showing the bottom.
I have stepped through the debugger and noticed that the layouts are set properly, followed by the scroll positions being set correctly. Then, after those values are set, but before the content is actually drawn, something happens in the stack that changes the scroll position.
This is where I'm totally lost. Why are the scroll values getting set correctly, then magically changing before the screen is drawn?
I already tried using an onLayoutCompleteListener, it didn't work. I will update the list of things attempted as I receive answers and try the suggestions. Thank you in advance for any and all help.
Here's a summary of what I'm doing to change pages:
public class MyWebView extends WebView {
// Assume this is instantiated; it is by the time it is needed
private List<MyWebView> viewArray = new List<MyWebView>(3);
private class CustomGestureListener extends
GestureDetector.SimpleOnGestureListener {
private static final String DEBUG_TAG = "Gestures";
private int scrollYOnTouch;
private int scrollYOnRelease;
#Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(DEBUG_TAG, "onFling: " + event1.toString()
+ event2.toString());
Log.i(DEBUG_TAG, "onFling: vX[" + velocityX
+ "], vY[" + velocityY + "]");
scrollYOnRelease = getScrollY();
int bottomOfPage = scrollYOnTouch + getMeasuredHeight();
int endOfContent = (int) Math.floor(getContentHeight() * getScale());
int proximity = endOfContent - bottomOfPage;
boolean atBottom = proximity <= 1;
Log.i(DEBUG_TAG, "atBottom = (" + proximity + " <= 1)");
if ((velocityY > VELOCITY_THRESHOLD)
&& (scrollYOnRelease <= 0) && (scrollYOnTouch == 0)) {
// User flung down while at the top of the page.
// Go to the previous page.
changePages(PREVIOUS_PAGE);
} else if ((velocityY < -VELOCITY_THRESHOLD)
&& (scrollYOnRelease >= scrollYOnTouch) && atBottom) {
// User flung up while at the bottom of the page.
// Go to the next page.
changePages(NEXT_PAGE);
}
return true;
}
} // end of CustomGestureListener
#Override
public boolean onTouchEvent(MotionEvent event) {
// Send the event to our gesture detector
// If it is implemented, there will be a return value
this.mDetector.onTouchEvent(event);
// If the detected gesture is unimplemented, send it to the superclass
return super.onTouchEvent(event);
}
#Override
public void changePages(int direction) {
int screenWidth = getScreenWidth();
int screenHeight = getScreenHeight();
VerticalPagingWebView previous;
VerticalPagingWebView current;
VerticalPagingWebView next;
if (direction == NEXT_PAGE) {
// Rearrange elements in webview array
// Next page becomes current page,
// current becomes previous,
// previous becomes next.
Collections.swap(viewArray, 0, 1);
Collections.swap(viewArray, 1, 2);
previous = viewArray.get(0);
current = viewArray.get(1);
next = viewArray.get(2);
// Prepare the next page
next.loadData(htmlContent, "text/html", null);
} else if (direction == PREVIOUS_PAGE) {
// Rearrange elements in webview array
// Previous page becomes current page,
// current becomes next,
// next becomes previous.
Collections.swap(viewArray, 1, 2);
Collections.swap(viewArray, 0, 1);
previous = viewArray.get(0);
current = viewArray.get(1);
next = viewArray.get(2);
// Prepare the previous page
previous.loadData(htmlContent, "text/html", null);
}
LayoutParams previousLayout = (LayoutParams) previous.getLayoutParams();
previousLayout.leftMargin = LEFT_MARGIN;
previousLayout.topMargin = -screenHeight - TOP_MARGIN;
previous.setLayoutParams(previousLayout);
LayoutParams currentLayout = (LayoutParams) current.getLayoutParams();
currentLayout.leftMargin = LEFT_MARGIN;
currentLayout.topMargin = 0;
current.setLayoutParams(currentLayout);
LayoutParams nextLayout = (LayoutParams) next.getLayoutParams();
nextLayout.leftMargin = LEFT_MARGIN;
nextLayout.topMargin = screenHeight + TOP_MARGIN;
next.setLayoutParams(nextLayout);
previous.scrollToBottom();
next.scrollToTop();
// I'm unsure if this is needed, but it works on everything but KitKat
if (direction == NEXT_PAGE) {
current.scrollToTop();
} else {
current.scrollToBottom();
}
} // end of changePages
public void scrollToPageStart() {
scrollTo(0,0);
}
public void scrollToPageBottom() {
// I know getScale() is deprecated; I take care of it.
// This method works fine.
int endOfContent = (int) Math.floor(getContentHeight() * getScale());
int webViewHeight = getMeasuredHeight();
scrollTo(0, endOfContent - webViewHeight);
}
}
You're probably scrolling the WebView before it had finished loading the contents. The problem is that at some point during the page load the WebView resets the scroll (this is intentional, when you navigate between pages you don't want the scroll offset to persist) and sometimes this reset happens after you call scrollTo.
To fix this you have two options:
scroll from JavaScript (in window.onload or something), this ensures the scroll happens after the WebView has finished loading the contents,
wait for the contents to load before scrolling. This is harder since the WebView doesn't have reliable callbacks (onPageFinished will not work reliably). One option would be to poll for when the webview.getContentHeight() method is returning the height of your content before doing the scroll.

Android ListView - stop scrolling at 'whole' row position

Sorry for the confusing title, I cannot express the problem very concisely...
I have an Android app with a ListView that uses a circular / "infinite" adapter, which basically means I can scroll it up or down as much as I want and the items will wrap around when it reaches the top or bottom, making it seem to the user as if he is spinning an infinitely long list of (~100) repeating items.
The point of this setup is to let the user select a random item, simply by spinning / flinging the listview and waiting to see where it stops. I decreased the friction of the Listview so it flings a bit faster and longer and this seems to work really nice. Finally I placed a partially transparent image on top of the ListView to block out the top and bottom items (with a transition from transparent to black), making it seem as if the user is "selecting" the item in the middle, as if they were on a rotating "wheel" that they control by flinging.
There is one obvious problem: after flinging the ListView does not stop at a particular item, but it can stop hovering between two items (where the first visible item is then only partially shown). I want to avoid this because in that case it is not obvious which item has been "randomly selected".
Long story short: after the ListView has finished scrolling after flinging, I want it to stop on a "whole" row, instead of on a partially visible row.
Right now I implemented this behavior by checking when the scrolling has stopped, and then selecting the first visible item, as such:
lv = this.getListView();
lv.setFriction(0.005f);
lv.setOnScrollListener(new OnScrollListener() {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE)
{
if (isAutoScrolling) return;
isAutoScrolling = true;
int pos = lv.getFirstVisiblePosition();
lv.setSelection(pos);
isAutoScrolling = false;
}
}
});
This works reasonably well, apart from one glaringly obvious problem... The first visible item might only be visible for a pixel or two. In that case, I want the ListView to jump "up" for those two pixels so that the second visible item is selected. Instead, of course, the first visible item is selected which means the ListView jumps "down" almost an entire row (minus those two pixels).
In short, instead of jumping to the first visible item, I want it to jump to the item that is visible the most. If the first visible item is less than half visible, I want it to jump to the second visible item.
Here's an illustration that hopefully conveys my point. The left most ListView (of each pair) shows the state after flinging has stopped (where it comes to a halt), and the right ListView shows how it looks after it made the "jump" by selecting the first visible item. On the left I show the current (wrong) situation: Item B is only barely visible, but it is still the first visible item so the listView jumps to select that item - which is not logical because it has to scroll almost an entire item height to get there. It would be much more logical to scroll to Item C (which is depicted on the right) because that is "closer".
(source: nickthissen.nl)
How can I achieve this behavior? The only way I can think of is to somehow measure how much of the first visible item is visible. If that is more than 50%, then I jump to that position. If it is less than 50%, I jump to that position + 1. However I have no clue how to measure that...
Any idea's?
You can get the visible dimensions of a child using the getChildVisibleRect method. When you have that, and you get the total height of the child, you can scroll to the appropriate child.
In the example below I check whether at least half of the child is visible:
View child = lv.getChildAt (0); // first visible child
Rect r = new Rect (0, 0, child.getWidth(), child.getHeight()); // set this initially, as required by the docs
double height = child.getHeight () * 1.0;
lv.getChildVisibleRect (child, r, null);
if (Math.abs (r.height ()) < height / 2.0) {
// show next child
}
else {
// show this child
}
Here's my final code inspired by Shade's answer.
I forgot to add "if(Math.abs(r.height())!=height)" at first. Then it just scrolls twice after it scroll to correct position because it's always greater than height/2 of childView.
Hope it helps.
listView.setOnScrollListener(new AbsListView.OnScrollListener(){
#Override
public void onScrollStateChanged(AbsListView view,int scrollState) {
if (scrollState == SCROLL_STATE_IDLE){
View child = listView.getChildAt (0); // first visible child
Rect r = new Rect (0, 0, child.getWidth(), child.getHeight()); // set this initially, as required by the docs
double height = child.getHeight () * 1.0;
listView.getChildVisibleRect (child, r, null);
if(Math.abs(r.height())!=height){//only smooth scroll when not scroll to correct position
if (Math.abs (r.height ()) < height / 2.0) {
listView.smoothScrollToPosition(listView.getLastVisiblePosition());
}
else if(Math.abs (r.height ()) > height / 2.0){
listView.smoothScrollToPosition(listView.getFirstVisiblePosition());
}
else{
listView.smoothScrollToPosition(listView.getFirstVisiblePosition());
}
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {
}});
Follow these 3 steps, then you can get exactly what you want!!!!
1.Initialize the two variable for scrolling up and down:
int scrollingUp=0,scrollingDown=0;
2.Then increment the value of the variable based on scrolling:
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(mLastFirstVisibleItem<firstVisibleItem)
{
scrollingDown=1;
}
if(mLastFirstVisibleItem>firstVisibleItem)
{
scrollingUp=1;
}
mLastFirstVisibleItem=firstVisibleItem;
}
3.Then do the changes in the onScrollStateChanged():
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_IDLE:
if(scrollingUp==1)
{
mainListView.post(new Runnable() {
public void run() {
View child = mainListView.getChildAt (0); // first visible child
Rect r = new Rect (0, 0, child.getWidth(), child.getHeight()); // set this initially, as required by the docs
double height = child.getHeight () * 1.0;
mainListView.getChildVisibleRect (child, r, null);
int dpDistance=Math.abs (r.height());
double minusDistance=dpDistance-height;
if (Math.abs (r.height()) < height/2)
{
mainListView.smoothScrollBy(dpDistance, 1500);
}
else
{
mainListView.smoothScrollBy((int)minusDistance, 1500);
}
scrollingUp=0;
}
});
}
if(scrollingDown==1)
{
mainListView.post(new Runnable() {
public void run() {
View child = mainListView.getChildAt (0); // first visible child
Rect r = new Rect (0, 0, child.getWidth(), child.getHeight()); // set this initially, as required by the docs
double height = child.getHeight () * 1.0;
mainListView.getChildVisibleRect (child, r, null);
int dpDistance=Math.abs (r.height());
double minusDistance=dpDistance-height;
if (Math.abs (r.height()) < height/2)
{
mainListView.smoothScrollBy(dpDistance, 1500);
}
else
{
mainListView.smoothScrollBy((int)minusDistance, 1500);
}
scrollingDown=0;
}
});
}
break;
case SCROLL_STATE_TOUCH_SCROLL:
break;
}
}
You probably solved this problem but I think that this solution should work
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
View firstChild = lv.getChildAt(0);
int pos = lv.getFirstVisiblePosition();
//if first visible item is higher than the half of its height
if (-firstChild.getTop() > firstChild.getHeight()/2) {
pos++;
}
lv.setSelection(pos);
}
getTop() for first item view always return nonpositive value so I don't use Math.abs(firstChild.getTop()) but just -firstChild.getTop(). Even if this value will be >0 then this condition is still working.
If you want to make this smoother then you can try to use lv.smoothScrollToPosition(pos) and enclose all above piece of code in
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
post(new Runnable() {
#Override
public void run() {
//put above code here
//and change lv.setSelection(pos) to lv.smoothScrollToPosition(pos)
}
});
}
Once you know the first visible position, you should be able to use View.getLocationinWindow() or View.getLocationOnScreen() on the next position's view to get the visible height of the first. Compare that to the View's height, and scroll to the next position if appropriate.
You may need to tweak it to account for padding, depending on what your rows look like.
I haven't tried the above, but it seems like it should work. If it doesn't, here's another, probably less robust idea:
getLastVisiblePosition(). If you take the difference between last and first, you can see how many positions are visible on the screen. Compare that to how many positions were visible when the list was first populated(scroll position 0).
If the same number of positions are visible, simply scroll to the first visible position as you are doing. If there is one more visible, scroll to "first + 1" position.
If you can get the position of the row that needs to be scrolled to, you can use the method:
smoothScrollToPosition
So something like:
int pos = lv.getFirstVisiblePosition();
lv.smoothScrollToPosition(pos);
Edit
Try this, sorry I don't have time to test, I'm out and about.
ImageView iv = //Code to find the image view
Rect rect = new Rect(iv.getLeft(), iv.getTop(), iv.getRight(), iv.getBottom());
lv.requestChildRectangleOnScreen(lv, rect, false);
My Solution:
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if (swipeLayout.isRefreshing()) {
swipeLayout.setRefreshing(false);
} else {
int pos = firstVisibleItem;
if (pos == 0 && lv_post_list.getAdapter().getCount()>0) {
int topOfNext = lv_post_list.getChildAt(pos + 1).getTop();
int heightOfFirst = lv_post_list.getChildAt(pos).getHeight();
if (topOfNext > heightOfFirst) {
swipeLayout.setEnabled(true);
} else {
swipeLayout.setEnabled(false);
}
}
else
swipeLayout.setEnabled(false);
}
}

Categories

Resources