Tracking scrolling tableRows with respect to a LinearLayout in android - android

Inside a RelativeLayout; I have a TableLayout in a ScrollView, and a horizontal LinearLayout. My view is working fine: as the table scrolls in the background, the LinearLayout is static in the foreground. What I now need, is to know exactly where the table and the LinearLayout are intersecting during scrolling.
Since the LinearLayout is static, I know where it is at all time, say y=50pixel. Supposing I have TextViews inside the TableLayout. So as the table scrolls, I want to know which TextView (i.e. tableRow) is intersecting/hidden by the LinearLayout.

Have you tried using View.getLocationOnScreen() on the table rows?

I have a horizontal line at y-offset = 310 pixel. As I scroll my
tableLayout, which is inside a ScrollView, I want to know which row of
the table is intersecting my horizontal line.
You could use the View.getLocationInWindow() method. Initially you'd need to see which row intersects the line and then using a custom ScrollView, follow the scrolling:
// in the onCreate:
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int[] loc = new int[2];
ll.getLocationInWindow(loc);
scrollView.setYIntersection(loc[1]);
ViewGroup table = (ViewGroup) scrollView.getChildAt(0);
// find out which row initially intersects our line so
// we can initialize mCrossedRow and position
for (int i = 0; i < table.getChildCount(); i++) {
final View row = table.getChildAt(i);
row.getLocationInWindow(loc);
if (loc[1] <= scrollView.getYIntersection()
&& loc[1] + row.getHeight() >= scrollView
.getYIntersection()) {
scrollView.setCurrIntersection(row, i);
row.setBackgroundColor(Color.RED);
break;
}
}
scrollView.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
}
});
With the custom ScrollView being:
public class CustomScrollView extends ScrollView {
/**
* This will be the current intersected row.
*/
private View mCrossedRow;
/**
* The row position of the intersected row, mCrossedRow.
*/
private int mRowOrder;
/**
* The y value of the intersecting line.
*/
private int mLine;
/**
* Used as a temporary holder for the location retrieval.
*/
private int[] mLocHelper = new int[2];
public CustomScrollView(Context context) {
super(context);
}
public void setYIntersection(int y) {
mLine = y;
}
public int getYIntersection() {
return mLine;
}
public void setCurrIntersection(View row, int childPosition) {
mCrossedRow = row;
mRowOrder = childPosition;
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
// this will be called every time the user scrolls so we need to
// keep updating the position and see if the crossed row still
// intersects the line otherwise move it to the next row.
mCrossedRow.getLocationInWindow(mLocHelper);
if (mLocHelper[1] <= mLine
&& mLocHelper[1] + mCrossedRow.getHeight() >= mLine) {
// do nothing, we're still in the same row
} else {
if (t - oldt > 0) {
// going down so increase the row position
mRowOrder++;
} else {
// going up so decrease the row position
mRowOrder--;
}
// visual effect, the intersecting row will have a red
// background and all other rows will have a white background.
// You could setup a listener here to get notified that a new
// row intersects the line.
mCrossedRow.setBackgroundColor(Color.WHITE);
mCrossedRow = ((ViewGroup) getChildAt(0)).getChildAt(mRowOrder);
mCrossedRow.setBackgroundColor(Color.RED);
}
}
}

Related

Auto Scroll to HorizontalScrollView

I am doing auto-horizontal scrolling. So i have 15 items. Now i want to access at 12 item so my index is 11. But i am unable to scroll it auto when a index occur.
horizontalScrollView.scrollTo(12, 0);
#Override
public void onPageSelected(int page) {
for(int i = 0; i < holeTitle.length; i++) {
if(i == page) {
title[i].setTextColor(0xffffffff);
horizontalScrollView.scrollTo(12, 0);
}
else {
title[i].setTextColor(0xffe0e0e0);
}
}
}
please expert make a look.
DmRomantsov's answer is the right way to scroll to the 12th button. However, getLeft() and getRight() methods return 0 because the layout is not displayed yet on the screen. It is too early to calculate the width of the layout parent and children. To achieve it, you need to do your auto-scroll inside onWindowFocusChanged.
#Override
public void onWindowFocusChanged(boolean hasFocus){
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
// do smoothScrollTo(...);
}
}
However, inside a Fragment, this method above will not work. I just wrote it to give a clue, to understand the concept. To have the same behaviour in Fragment, you just need to do a Runnable which lets the time to your UI to be displayed. Then, do this with a LinearLayout oriented to horizontal:
// Init variables
HorizontalScrollView mHS;
LinearLayout mLL;
// onCreateView method
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_container, container, false);
// Find your views
mHS = (HorizontalScrollView)view.findViewById(R.id.hscrollview);
mLL = (LinearLayout)view.findViewById(R.id.hscrollview_container);
// Do a Runnable on the inflated view
view.post(new Runnable() {
#Override
public void run() {
Log.v("","Left position of 12th child = "+mLL.getChildAt(11).getLeft());
mHS.smoothScrollTo(mLL.getChildAt(11).getLeft(), 0);
}
});
return view;
}
Middle HorizontalScrollView:
Your question was to auto-scroll until your 12th child. However, in the comments below, you ask me to auto-scroll at the middle of the HorizontalScrollView, I assume on every device. You need to calculate the width of the screen, the total width of the container and how many children are displayed inside the device width. Here is a simple code:
// Auto scroll to the middle (regardless of the width screen)
view.post(new Runnable() {
#Override
public void run() {
// Width of the screen
DisplayMetrics metrics = getActivity().getResources()
.getDisplayMetrics();
int widthScreen = metrics.widthPixels;
Log.v("","Width screen total = " + widthScreen);
// Width of the container (LinearLayout)
int widthContainer = mLL.getWidth();
Log.v("","Width container total = " + widthContainer );
// Width of one child (Button)
int widthChild = mLL.getChildAt(0).getWidth();
Log.v("","Width child = " + widthChild);
// Nb children in screen
int nbChildInScreen = widthScreen / widthChild;
Log.v("","Width screen total / Width child = " + nbChildInScreen);
// Width total of the space outside the screen / 2 (= left position)
int positionLeftWidth = (widthContainer
- (widthChild * nbChildInScreen))/2;
Log.v("","Position left to the middle = " + positionLeftWidth);
// Auto scroll to the middle
mHS.smoothScrollTo(positionLeftWidth, 0);
}
});
/**
* Your value might be resumed by:
*
* int positionLeftWidth =
* ( mLL.getWidth() - ( mLL.getChildAt(0).getWidth() *
* ( metrics.widthPixels / mLL.getChildAt(0).getWidth() ) ) ) / 2;
*
**/
Middle HorizontalScrollView with chosen Value:
I have a bit misunderstand the real request. Actually, you wanted to auto-scroll until a chosen child view, and display this view at the middle of the screen.
Then, I changed the last int positionLeftWidth which refers now to the left position of the chosen view relative to its parent, the number of children contained in one screen, and the half width of the chosen view. So, the code is the same as above, except positionLeftWidth:
// For example the chosen value is 7
// 7th Child position left
int positionChildAt = mLL.getChildAt(6).getLeft();
// Width total of the auto-scroll (positionLeftWidth)
int positionLeftWidth = positionChildAt - // position 7th child from left less
( ( nbChildInScreen // ( how many child contained in screen
* widthChild ) / 2 ) // multiplied by their width ) divide by 2
+ ( widthChild / 2 ); // plus ( the child view divide by 2 )
// Auto-scroll to the 7th child
mHS.smoothScrollTo(positionLeftWidth, 0);
Then, whatever the value in getChildAt() method, and whatever the width screen, you will always have the chosen (in your case) button at the middle of the screen.
Try
horizontalScrollView.smoothScrollTo(horizontalScrollView.getChildAt(11).getRight(),0);
first patameter - X coord, second - Y.
Offset:
public final void smoothScrollBy (int dx, int dy)
Absolute:
public final void smoothScrollTo (int x, int y)
Try horizontalScrollView.smoothScrollBy(12, 0);
Try this is working code. position will be where you want to scroll
final HorizontalScrollView mHorizontalScrollView = (HorizontalScrollView) .findViewById(R.id.horizontalScrollView);
mHorizontalScrollView.postDelayed(new Runnable() {
#Override
public void run() {
mHorizontalScrollView.scrollTo(position, 0);
mHorizontalScrollView.smoothScrollBy(1200, 0);
}
},100);

How to check if a my ListView has scrollable number of items?

How do you know if your ListView has enough number of items so that it can scroll?
For instance, If I have 5 items on my ListView all of it will be displayed on a single screen. But if I have 7 or more, my ListView begins to scroll. How do I know if my List can scroll programmatically?
Diegosan's answer cannot differentiate when the last item is partially visible on the screen. Here is a solution to that problem.
First, the ListView must be rendered on the screen before we can check if its content is scrollable. This can be done with a ViewTreeObserver:
ViewTreeObserver observer = listView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (willMyListScroll()) {
// Do something
}
}
});
And here is willMyListScroll():
boolean willMyListScroll() {
int pos = listView.getLastVisiblePosition();
if (listView.getChildAt(pos).getBottom() > listView.getHeight()) {
return true;
} else {
return false;
}
}
As per my comment on Mike Ortiz' answer, I believe his answer is wrong:
getChildAt() only counts the children that are visible on screen. Meanwhile, getLastVisiblePosition returns the index based on the Adapter. So if the lastVisiblePosition is 8 because it's the 9th item in the list and there are only 4 visible items on screen, you're gonna get a crash. Verify it by calling getChildCount() and see. The indices for getChildAt are not the same as the indices for the data set. Check this out: ListView getChildAt returning null for visible children
Here's my solution:
public boolean isScrollable() {
int last = listView.getChildCount()-1; //last visible listitem view
if (listView.getChildAt(last).getBottom()>listView.getHeight() || listView.getChildAt(0).getTop()<0) { //either the first visible list item is cutoff or the last is cutoff
return true;
}
else{
if (listView.getChildCount()==listView.getCount()) { //all visible listitem views are all the items there are (nowhere to scroll)
return false;
}
else{ //no listitem views are cut off but there are other listitem views to scroll to
return true;
}
}
}
You cannot detect this before android render the screen with the listView. However, you can absolutely detect this post-render.
boolean willMyListScroll() {
if(listView.getLastVisiblePosition() + 1 == listView.getCount()) {
return false;
}
return true;
}
What this does is check if the listView visible window contains ALL your list view items. If it can, then the listView will never scroll and the getLastVisiblePosition() will always be equal to the total number of items in the list's dataAdapter.
This is the code I wrote for showing a picture after the last row of the listview:
public class ShowTheEndListview
{
private ImageView the_end_view;
private TabbedFragRootLayout main_layout;
private ListView listView;
private float pas;
private float the_end_img_height;
private int has_scroll = -1;
public ShowTheEndListview(float height)
{
the_end_img_height = height;
pas = 100 / the_end_img_height;
}
public void setData(ImageView the_end_view, TabbedFragRootLayout main_layout, ListView listView)
{
this.the_end_view = the_end_view;
this.main_layout = main_layout;
this.listView = listView;
}
public void onScroll(int totalItemCount)
{
if(totalItemCount - 1 == listView.getLastVisiblePosition())
{
int pos = totalItemCount - listView.getFirstVisiblePosition() - 1;
View last_item = listView.getChildAt(pos);
if (last_item != null)
{
if(listHasScroll(last_item))
{
// Log.e(TAG, "listHasScroll TRUE");
}
else
{
// Log.e(TAG, "listHasScroll FALSE");
}
}
}
}
private boolean listHasScroll(View last_item)
{
if(-1 == has_scroll)
{
has_scroll = last_item.getBottom() > (main_layout.getBottom() - the_end_img_height - 5) ? 1 : 0;
}
return has_scroll == 1;
}
public void resetHasScroll()
{
has_scroll = -1;
}
}
AbsListView includes this:
/**
* Check if the items in the list can be scrolled in a certain direction.
*
* #param direction Negative to check scrolling up, positive to check scrolling down.
* #return true if the list can be scrolled in the specified direction, false otherwise.
* #see #scrollListBy(int)
*/
public boolean canScrollList(int direction);
You need to override layoutChildren() and use it within that:
#Override
protected void layoutChildren() {
super.layoutChildren();
isAtBottom = !canScrollList(1);
isAtTop = !canScrollList(-1);
}
This is how i used to check
if (listView.getAdapter() != null
&& listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1
&& listView.getChildAt(listView.getChildCount() - 1).getBottom() == listView.getBottom())
if it gives true, then list is at the bottom
Use the setOnScrollListener, by applying the callback to OnScrollListener, you can determine if scrolling is taking place using the pre-defined constants and handle the situation accordingly.
boolean listBiggerThanWindow = appHeight - 50 <= mListView.getHeight();
Toast.makeText(HomeActivity.this, "list view bigger that window? " + listBiggerThanWindow, Toast.LENGTH_LONG)
.show();
if (listBiggerThanWindow) {
// do your thing here...
}
you can get dimensions in onCreate() by calling post(Runnable) on View.

ViewGroup onLayout Problem

I have a problem with my custom ViewGroup. I layout 3 of the children in a row and use a Scroller to scroll to the middle child. Based on the touch input of the user I change the children that should be displayed and request a new layout. Then I layout the children in a new order. But when one child has been displayed in a previous layout run the children lie on top of each other. I checked that the children get layout in the right way and I think the old layout is not deleted and the new children get just drawn on top of the old layout. Is there a way to ensure that the old layout gets cleared or something?
Here is my code:
#Override
public boolean onTouchEvent(MotionEvent event) {
...
case MotionEvent.ACTION_UP:
if(old != current)
this.requestLayout();
else
this.scrollTo(getWidth(), 0);
...
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// show current-1, current, current+1
int count = 0;
for(int i = 1; i >= -1; i--) {
// determine index of child
// the mod does a modulo
int index = mod(current-i, getChildCount());
// position in row from left to right
this.getChildAt(index).layout(count*this.getWidth(), 0, (count+1)*this.getWidth(), height);
count++;
}
// scroll to middle view
this.scrollTo(getWidth(), 0);
...
}
Try to call invalidate() at the end of onLayout().

Is there a way to programmatically scroll a scroll view to a specific edit text?

I have a very long activity with a scrollview. It is a form with various fields that the user must fill in. I have a checkbox half way down my form, and when the user checks it I want to scroll to a specific part of the view. Is there any way to scroll to an EditText object (or any other view object) programmatically?
Also, I know this is possible using X and Y coords but I want to avoid doing this as the form may changed from user to user.
private final void focusOnView(){
yourScrollView.post(new Runnable() {
#Override
public void run() {
yourScrollView.scrollTo(0, yourEditText.getBottom());
}
});
}
The answer of Sherif elKhatib can be greatly improved, if you want to scroll the view to the center of the scroll view. This reusable method smooth scrolls the view to the visible center of a HorizontalScrollView.
private final void focusOnView(final HorizontalScrollView scroll, final View view) {
new Handler().post(new Runnable() {
#Override
public void run() {
int vLeft = view.getLeft();
int vRight = view.getRight();
int sWidth = scroll.getWidth();
scroll.smoothScrollTo(((vLeft + vRight - sWidth) / 2), 0);
}
});
}
For a vertical ScrollView use
...
int vTop = view.getTop();
int vBottom = view.getBottom();
int sHeight = scroll.getBottom();
scroll.smoothScrollTo(0, ((vTop + vBottom - sHeight) / 2));
...
This works well for me :
targetView.getParent().requestChildFocus(targetView,targetView);
public void RequestChildFocus (View child, View focused)
child - The child of this ViewParent that wants focus. This view will contain the focused view. It is not necessarily the view that actually has focus.
focused - The view that is a descendant of child that actually has focus
In my opinion the best way to scroll to a given rectangle is via View.requestRectangleOnScreen(Rect, Boolean). You should call it on a View you want to scroll to and pass a local rectangle you want to be visible on the screen. The second parameter should be false for smooth scrolling and true for immediate scrolling.
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, false);
I made a small utility method based on Answer from WarrenFaith, this code also takes in account if that view is already visible in the scrollview, no need for scroll.
public static void scrollToView(final ScrollView scrollView, final View view) {
// View needs a focus
view.requestFocus();
// Determine if scroll needs to happen
final Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (!view.getLocalVisibleRect(scrollBounds)) {
new Handler().post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, view.getBottom());
}
});
}
}
You should make your TextView request focus:
mTextView.requestFocus();
Another varition would be:
scrollView.postDelayed(new Runnable()
{
#Override
public void run()
{
scrollView.smoothScrollTo(0, img_transparent.getTop());
}
}, 200);
or you can use the post() method.
My EditText was nested several layers inside my ScrollView, which itself isn't the layout's root view. Because getTop() and getBottom() were seeming to report the coordinates within it's containing view, I had it compute the distance from the top of the ScrollView to the top of the EditText by iterating through the parents of the EditText.
// Scroll the view so that the touched editText is near the top of the scroll view
new Thread(new Runnable()
{
#Override
public
void run ()
{
// Make it feel like a two step process
Utils.sleep(333);
// Determine where to set the scroll-to to by measuring the distance from the top of the scroll view
// to the control to focus on by summing the "top" position of each view in the hierarchy.
int yDistanceToControlsView = 0;
View parentView = (View) m_editTextControl.getParent();
while (true)
{
if (parentView.equals(scrollView))
{
break;
}
yDistanceToControlsView += parentView.getTop();
parentView = (View) parentView.getParent();
}
// Compute the final position value for the top and bottom of the control in the scroll view.
final int topInScrollView = yDistanceToControlsView + m_editTextControl.getTop();
final int bottomInScrollView = yDistanceToControlsView + m_editTextControl.getBottom();
// Post the scroll action to happen on the scrollView with the UI thread.
scrollView.post(new Runnable()
{
#Override
public void run()
{
int height =m_editTextControl.getHeight();
scrollView.smoothScrollTo(0, ((topInScrollView + bottomInScrollView) / 2) - height);
m_editTextControl.requestFocus();
}
});
}
}).start();
The above answers will work fine if the ScrollView is the direct parent of the ChildView. If your ChildView is being wrapped in another ViewGroup in the ScrollView, it will cause unexpected behavior because the View.getTop() get the position relative to its parent. In such case, you need to implement this:
public static void scrollToInvalidInputView(ScrollView scrollView, View view) {
int vTop = view.getTop();
while (!(view.getParent() instanceof ScrollView)) {
view = (View) view.getParent();
vTop += view.getTop();
}
final int scrollPosition = vTop;
new Handler().post(() -> scrollView.smoothScrollTo(0, scrollPosition));
}
I know this may be too late for a better answer but a desired perfect solution must be a system like positioner. I mean, when system makes a positioning for an Editor field it places the field just up to the keyboard, so as UI/UX rules it is perfect.
What below code makes is the Android way positioning smoothly. First of all we keep the current scroll point as a reference point. Second thing is to find the best positioning scroll point for an editor, to do this we scroll to top, and then request the editor fields to make the ScrollView component to do the best positioning. Gatcha! We've learned the best position. Now, what we'll do is scroll smoothly from the previous point to the point we've found newly. If you want you may omit smooth scrolling by using scrollTo instead of smoothScrollTo only.
NOTE: The main container ScrollView is a member field named scrollViewSignup, because my example was a signup screen, as you may figure out a lot.
view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(final View view, boolean b) {
if (b) {
scrollViewSignup.post(new Runnable() {
#Override
public void run() {
int scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);
int new_scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, scrollY);
scrollViewSignup.smoothScrollTo(0, new_scrollY);
}
});
}
}
});
If you want to use this block for all EditText instances, and quickly integrate it with your screen code. You can simply make a traverser like below. To do this, I've made the main OnFocusChangeListener a member field named focusChangeListenerToScrollEditor, and call it during onCreate as below.
traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);
And the method implementation is as below.
private void traverseEditTextChildren(ViewGroup viewGroup, View.OnFocusChangeListener focusChangeListenerToScrollEditor) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
if (view instanceof EditText)
{
((EditText) view).setOnFocusChangeListener(focusChangeListenerToScrollEditor);
}
else if (view instanceof ViewGroup)
{
traverseEditTextChildren((ViewGroup) view, focusChangeListenerToScrollEditor);
}
}
}
So, what we've done here is making all EditText instance children to call the listener at focus.
To reach this solution, I've checked it out all the solutions here, and generated a new solution for better UI/UX result.
Many thanks to all other answers inspiring me much.
yourScrollView.smoothScrollTo(0, yourEditText.getTop());
Just Do It ;)
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, myTextView.getTop());
}
});
Answering from my practical project.
I think I have found more elegant and less error prone solution using
ScrollView.requestChildRectangleOnScreen
There is no math involved, and contrary to other proposed solutions, it will handle correctly scrolling both up and down.
/**
* Will scroll the {#code scrollView} to make {#code viewToScroll} visible
*
* #param scrollView parent of {#code scrollableContent}
* #param scrollableContent a child of {#code scrollView} whitch holds the scrollable content (fills the viewport).
* #param viewToScroll a child of {#code scrollableContent} to whitch will scroll the the {#code scrollView}
*/
void scrollToView(ScrollView scrollView, ViewGroup scrollableContent, View viewToScroll) {
Rect viewToScrollRect = new Rect(); //coordinates to scroll to
viewToScroll.getHitRect(viewToScrollRect); //fills viewToScrollRect with coordinates of viewToScroll relative to its parent (LinearLayout)
scrollView.requestChildRectangleOnScreen(scrollableContent, viewToScrollRect, false); //ScrollView will make sure, the given viewToScrollRect is visible
}
It is a good idea to wrap it into postDelayed to make it more reliable, in case the ScrollView is being changed at the moment
/**
* Will scroll the {#code scrollView} to make {#code viewToScroll} visible
*
* #param scrollView parent of {#code scrollableContent}
* #param scrollableContent a child of {#code scrollView} whitch holds the scrollable content (fills the viewport).
* #param viewToScroll a child of {#code scrollableContent} to whitch will scroll the the {#code scrollView}
*/
private void scrollToView(final ScrollView scrollView, final ViewGroup scrollableContent, final View viewToScroll) {
long delay = 100; //delay to let finish with possible modifications to ScrollView
scrollView.postDelayed(new Runnable() {
public void run() {
Rect viewToScrollRect = new Rect(); //coordinates to scroll to
viewToScroll.getHitRect(viewToScrollRect); //fills viewToScrollRect with coordinates of viewToScroll relative to its parent (LinearLayout)
scrollView.requestChildRectangleOnScreen(scrollableContent, viewToScrollRect, false); //ScrollView will make sure, the given viewToScrollRect is visible
}
}, delay);
}
reference : https://stackoverflow.com/a/6438240/2624806
Following worked far better.
mObservableScrollView.post(new Runnable() {
public void run() {
mObservableScrollView.fullScroll([View_FOCUS][1]);
}
});
Examining Android source code, you can find that there already is a member function of ScrollView– scrollToChild(View) – that does exactly what is requested. Unfortunatelly, this function is for some obscure reason marked private. Based on that function I've written following function that finds the first ScrollView above the View specified as a parameter and scrolls it so that it becomes visible within the ScrollView:
private void make_visible(View view)
{
int vt = view.getTop();
int vb = view.getBottom();
View v = view;
for(;;)
{
ViewParent vp = v.getParent();
if(vp == null || !(vp instanceof ViewGroup))
break;
ViewGroup parent = (ViewGroup)vp;
if(parent instanceof ScrollView)
{
ScrollView sv = (ScrollView)parent;
// Code based on ScrollView.computeScrollDeltaToGetChildRectOnScreen(Rect rect) (Android v5.1.1):
int height = sv.getHeight();
int screenTop = sv.getScrollY();
int screenBottom = screenTop + height;
int fadingEdge = sv.getVerticalFadingEdgeLength();
// leave room for top fading edge as long as rect isn't at very top
if(vt > 0)
screenTop += fadingEdge;
// leave room for bottom fading edge as long as rect isn't at very bottom
if(vb < sv.getChildAt(0).getHeight())
screenBottom -= fadingEdge;
int scrollYDelta = 0;
if(vb > screenBottom && vt > screenTop)
{
// need to move down to get it in view: move down just enough so
// that the entire rectangle is in view (or at least the first
// screen size chunk).
if(vb-vt > height) // just enough to get screen size chunk on
scrollYDelta += (vt - screenTop);
else // get entire rect at bottom of screen
scrollYDelta += (vb - screenBottom);
// make sure we aren't scrolling beyond the end of our content
int bottom = sv.getChildAt(0).getBottom();
int distanceToBottom = bottom - screenBottom;
scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
}
else if(vt < screenTop && vb < screenBottom)
{
// need to move up to get it in view: move up just enough so that
// entire rectangle is in view (or at least the first screen
// size chunk of it).
if(vb-vt > height) // screen size chunk
scrollYDelta -= (screenBottom - vb);
else // entire rect at top
scrollYDelta -= (screenTop - vt);
// make sure we aren't scrolling any further than the top our content
scrollYDelta = Math.max(scrollYDelta, -sv.getScrollY());
}
sv.smoothScrollBy(0, scrollYDelta);
break;
}
// Transform coordinates to parent:
int dy = parent.getTop()-parent.getScrollY();
vt += dy;
vb += dy;
v = parent;
}
}
My solution is:
int[] spinnerLocation = {0,0};
spinner.getLocationOnScreen(spinnerLocation);
int[] scrollLocation = {0, 0};
scrollView.getLocationInWindow(scrollLocation);
int y = scrollView.getScrollY();
scrollView.smoothScrollTo(0, y + spinnerLocation[1] - scrollLocation[1]);
Vertical scroll, good for forms. Answer is based on Ahmadalibaloch horizontal scroll.
private final void focusOnView(final HorizontalScrollView scroll, final View view) {
new Handler().post(new Runnable() {
#Override
public void run() {
int top = view.getTop();
int bottom = view.getBottom();
int sHeight = scroll.getHeight();
scroll.smoothScrollTo(0, ((top + bottom - sHeight) / 2));
}
});
}
You can use ObjectAnimator like this:
ObjectAnimator.ofInt(yourScrollView, "scrollY", yourView.getTop()).setDuration(1500).start();
Add postDelayed to the view so that getTop() does not return 0.
binding.scrollViewLogin.postDelayed({
val scrollTo = binding.textInputLayoutFirstName.top
binding.scrollViewLogin.isSmoothScrollingEnabled = true
binding.scrollViewLogin.smoothScrollTo(0, scrollTo)
}, 400
)
Also make sure the view is a direct child of scrollView, otherwise you would get getTop() as zero. Example: getTop() of edittext which is embedded inside TextInputLayout would return 0. So in this case, we have to compute getTop() of TextInputLayout which is a direct child of ScrollView.
<ScrollView>
<TextInputLayout>
<EditText/>
</TextInputLayout>
</ScrollView>
In my case, that's not EditText, that's googleMap.
And it works successfully like this.
private final void focusCenterOnView(final ScrollView scroll, final View view) {
new Handler().post(new Runnable() {
#Override
public void run() {
int centreX=(int) (view.getX() + view.getWidth() / 2);
int centreY= (int) (view.getY() + view.getHeight() / 2);
scrollView.smoothScrollBy(centreX, centreY);
}
});
}
Que:Is there a way to programmatically scroll a scroll view to a specific edittext?
Ans:Nested scroll view in recyclerview last position added record data.
adapter.notifyDataSetChanged();
nested_scroll.setScrollY(more Detail Recycler.getBottom());
Is there a way to programmatically scroll a scroll view to a specific edit text?
The following is what I'm using:
int amountToScroll = viewToShow.getBottom() - scrollView.getHeight() + ((LinearLayout.LayoutParams) viewToShow.getLayoutParams()).bottomMargin;
// Check to see if scrolling is necessary to show the view
if (amountToScroll > 0){
scrollView.smoothScrollTo(0, amountToScroll);
}
This gets the scroll amount necessary to show the bottom of the view, including any margin on the bottom of that view.
Based on Sherif's answer, the following worked best for my use case. Notable changes are getTop() instead of getBottom() and smoothScrollTo() instead of scrollTo().
private void scrollToView(final View view){
final ScrollView scrollView = findViewById(R.id.bookmarksScrollView);
if(scrollView == null) return;
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, view.getTop());
}
});
}
If you want to scroll to a view when a soft keyboard is opened, then it might get a bit tricky.
The best solution I've got so far is to use a combination of inset callbacks and requestRectangleOnScreen method.
First, you need to setup inset callbacks:
fun View.doOnApplyWindowInsetsInRoot(block: (View, WindowInsetsCompat, Rect) -> Unit) {
val initialPadding = recordInitialPaddingForView(this)
val root = getRootForView(this)
ViewCompat.setOnApplyWindowInsetsListener(root) { v, insets ->
block(v, insets, initialPadding)
insets
}
requestApplyInsetsWhenAttached()
}
fun View.requestApplyInsetsWhenAttached() {
if (isAttachedToWindow) {
requestApplyInsets()
} else {
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
v.removeOnAttachStateChangeListener(this)
v.requestApplyInsets()
}
override fun onViewDetachedFromWindow(v: View) = Unit
})
}
}
We are setting a callback on a root view to make sure we get called. Insets could be consumed before our view in question received them, so we have to do additional work here.
Now it's almost easy:
doOnApplyWindowInsetsInRoot { _, _, _ ->
post {
if (viewInQuestion.hasFocus()) {
requestRectangleOnScreen(Rect(0, 0, width, height))
}
}
}
You can get rid of a focus check. It's there to limit number of calls to requestRectangleOnScreen. I use post to run an action after scrollable parent scheduled scroll to a focused view.
If anybody is looking for a Kotlin version you can do this with an extension function
fun ScrollView.scrollToChild(view: View, onScrolled: (() -> Unit)? = null) {
view.requestFocus()
val scrollBounds = Rect()
getHitRect(scrollBounds)
if (!view.getLocalVisibleRect(scrollBounds)) {
findViewTreeLifecycleOwner()?.lifecycleScope?.launch(Dispatchers.Main) {
smoothScrollTo(0, view.bottom - 40)
onScrolled?.invoke()
}
}
}
There is a little callback that lets you do something after the scroll.
If scrlMain is your NestedScrollView, then use the following:
scrlMain.post(new Runnable() {
#Override
public void run() {
scrlMain.fullScroll(View.FOCUS_UP);
}
});
here is another better version for efficient scrolling:
kotlin code to scroll to particular position of view added in scrollview(horizontal)
horizontalScrollView.post {
val targetView = findViewById<View>(R.id.target_view)
val targetX = targetView.left
horizontalScrollView.smoothScrollTo(targetX, 0)
}
for vertical scroll just change targetView.left to targetView.top
for JAVA here is a sample code:
scrollView.postDelayed(new Runnable() {
#Override
public void run() {
int targetViewY = targetView.getTop();
scrollView.smoothScrollTo(0, targetViewY);
}
}, 500);

Scroll a ListView by pixels in Android

I want to scroll the a ListView in Android by number of pixels. For example I want to scroll the list 10 pixels down (so that the first item on the list has its top 10 pixel rows hidden).
I thought the obviously visible scrollBy or scrollTo methods on ListView would do the job, but they don't, instead they scroll the whole list wrongly (In fact, the getScrollY always return zero even though I have scrolled the list using my finger.)
What I'm doing is I'm capturing Trackball events and I want to scroll the listview smoothly according to the motion of the trackball.
The supported way to scroll a ListView widget is:
mListView.smoothScrollToPosition(position);
http://developer.android.com/reference/android/widget/AbsListView.html#smoothScrollToPosition(int)
However since you mentioned specifically that you would like to offset the view vertically, you must call:
mListView.setSelectionFromTop(position, yOffset);
http://developer.android.com/reference/android/widget/ListView.html#setSelectionFromTop(int,%20int)
Note that you can also use smoothScrollByOffset(yOffset). However it is only supported on API >= 11
http://developer.android.com/reference/android/widget/ListView.html#smoothScrollByOffset(int)
If you look at the source for the scrollListBy() method added in api 19 you will see that you can use the package scoped trackMotionScroll method.
public class FutureListView {
private final ListView mView;
public FutureListView(ListView view) {
mView = view;
}
/**
* Scrolls the list items within the view by a specified number of pixels.
*
* #param y the amount of pixels to scroll by vertically
*/
public void scrollListBy(int y) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mView.scrollListBy(y);
} else {
// scrollListBy just calls trackMotionScroll
trackMotionScroll(-y, -y);
}
}
private void trackMotionScroll(int deltaY, int incrementalDeltaY) {
try {
Method method = AbsListView.class.getDeclaredMethod("trackMotionScroll", int.class, int.class);
method.setAccessible(true);
method.invoke(mView, deltaY, incrementalDeltaY);
} catch (Exception ex) {
throw new RuntimeException(ex);
};
}
}
Here is some code from my ListView subclass. It can easily be adapted so it can be used in Activity code.
getListItemsHeight() returns the total pixel height of the list, and fills an array with vertical pixel offsets of each item. While this information is valid, getListScrollY() returns the current vertical pixel scroll position, and scrollListToY() scrolls the list to pixel position.
If the size or the content of the list changes, getListItemsHeight() has to be called again.
private int m_nItemCount;
private int[] m_nItemOffY;
private int getListItemsHeight()
{
ListAdapter adapter = getAdapter();
m_nItemCount = adapter.getCount();
int height = 0;
int i;
m_nItemOffY = new int[m_nItemCount];
for(i = 0; i< m_nItemCount; ++i){
View view = adapter.getView(i, null, this);
view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
m_nItemOffY[i] = height;
height += view.getMeasuredHeight();
}
return height;
}
private int getListScrollY()
{
int pos, nScrollY, nItemY;
View view;
pos = getFirstVisiblePosition();
view = getChildAt(0);
nItemY = view.getTop();
nScrollY = m_nItemOffY[pos] - nItemY;
return nScrollY;
}
private void scrollListToY(int nScrollY)
{
int i, off;
for(i = 0; i < m_nItemCount; ++i){
off = m_nItemOffY[i] - nScrollY;
if(off >= 0){
setSelectionFromTop(i, off);
break;
}
}
}
For now, ListViewCompat is probably a better solution.
android.support.v4.widget.ListViewCompat.scrollListBy(#NonNull ListView listView, int y)
if you want to move by pixels then u can use this
public void scrollBy(ListView l, int px){
l.setSelectionFromTop(l.getFirstVisiblePosition(),l.getChildAt(0).getTop() - px);
}
this works for even ones with massive headers

Categories

Resources