So I have a scroll view, and I need to adjust the height of my scroll view to make sure it stays above a modal pop-up view. I can't use a constraint layout because this modal pop-up view is not a child of the same view parent. So I'm trying to dynamically update my scroll views layout params so its height is small enough to not get hidden behind the modal pop-up.
The pop-up view height can change at points so I have a callback that returns the new height of the modal view anytime it changes. In that callback I adjust the scroll views height like so:
someModalView.onHeightChanged = { newViewHeight ->
Log.d("TESTHEIGHT", "PreHeight = ${scrollView.height}")
scrollView.layoutParams = FrameLayout.LayoutParams(scrollView.width, scrollView.height - newViewHeight)
scrollView.requestLayout()
Log.d("TESTHEIGHT", "PostHeight = ${scrollView.height}")
}
Unfortunately the above code seems to do nothing and in my logs I can see that the PreHeight prints the same height as the PostHeight. Any reason the views height isn't getting changed?
Also, I did debug it and make sure that newViewHeight is not 0, and it isn't, it's ~800
Ended up making it work by adding padding to the view rather than changing its height like so:
someModalView.onHeightChanged = { newViewHeight ->
scrollView.setPadding(0, 0, 0, newViewHeight)
}
This works exactly how i needed it to, however it doesn't really answer the question so I will just leave it in the answer for anyone else who it might help. But it would still be nice to know why changing the layout params wouldn't update the views height.
try to see it works for you
val params = scrollView.layoutParams;
params.height = scrollView.height - newViewHeight
scrollView.layoutParams = params
Once I needed to get the height of the softKeyboard to update my view:
public class MainActivity extends AppCompatActivity {
private ScrollView sView;
private int heightDiff;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sView = findViewById(R.id.scrollView);
//Here we get the height of soft keyboard by observing changes of softKeyboard height.
sView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
heightDiff = sView.getRootView().getHeight() - sView.getHeight();
}
});
final EditText email = findViewById(R.id.eemail);
EditText firstName = findViewById(R.id.efirstname);
firstName.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!isVisibleWhileSoftKeyboardShowing(email) && hasFocus) {
sView.postDelayed(new Runnable() {
#Override
public void run() {
sView.smoothScrollBy(0, 200);
}
}, 500);
}
}
});
}
/**
* check if a view is currently visible in the screen or not
*
* #param view
* #return
*/
public boolean isVisibleWhileSoftKeyboardShowing(final View view) {
if (view == null) {
return false;
}
if (!view.isShown()) {
return false;
}
final Rect actualPosition = new Rect();
view.getGlobalVisibleRect(actualPosition);
final Rect screen = new Rect(0, 0, getScreenWidth(), getScreenHeight() - heightDiff);
return actualPosition.intersect(screen);
}
/**
* to get screen width
*
* #return
*/
public static int getScreenWidth() {
return Resources.getSystem().getDisplayMetrics().widthPixels;
}
/**
* to get screen height
*
* #return
*/
public static int getScreenHeight() {
return Resources.getSystem().getDisplayMetrics().heightPixels;
}
}
heightDiff is the height of softKeyboard. There was 2 edit texts. I wanted to scroll if softKeyboard hided the lower one. Hope this is similar to your case.
Actually I don't know how it properly called - overlay, parallax or slideUP, whatever, I have an Activity called "cafe details" which presents a Container(LinearLayout) with header information (name, min price, delivery time min e.t.c) and other container (ViewPager) which contains a ExpandableListView with something information (menus&dishes) and all I want to do is slide up my Viewpager when scrolls listview to scpecific Y position to cover(or overlay) header information.
A similar effect (but with parallax that I don't need to use) looks like this
I can detect when user scrolling listview down or up but how I can move container with ViewPager to overlay other container? Please give me ideas, regards.
UPD
I have tried a huge number of ways how to implement it and all of them unfortunately are not suitable. So now I have come to next variant - add scroll listener to ListView, calculate scrollY position of view and then based on that move the viewpager on y axis by calling setTranslationY();
Here is some code
1) ViewPager's fragment
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
if (getActivity() != null) {
((MainActivity) getActivity()).resizePagerContainer(absListView);
}
}
});
2) MainActivity
//object fields
int previousPos;
private float minTranslation;
private float minHeight;
<--------somewhere in onCreate
minTranslation = - (llVendorDescHeaders.getMeasuredHeight()+llVendorDescNote.getMeasuredHeight());
//llVendorDescHeaders is linearLayout with headers that should be hidden
//lVendorDescNote is a textView on green background;
minHeight = llVendorDescriptionPagerContainer.getMeasuredHeight();
//llVendorDescriptionPagerContainer is a container which contains ViewPager
--------->
public void resizePagerContainer(AbsListView absListView){
final int scrollY = getScrollY(absListView);
if (scrollY != previousPos) {
final float translationY = Math.max(-scrollY, minTranslation);
llVendorDescriptionPagerContainer.setTranslationY(translationY);
previousPos = scrollY;
}
}
private int getScrollY(AbsListView view) {
View child = view.getChildAt(0);
if (child == null) {
return 0;
}
int firstVisiblePosition = view.getFirstVisiblePosition();
int top = child.getTop();
return -top + firstVisiblePosition * child.getHeight() ;
}
This simple solution unfortunately has a problem - it is blinking and twitching (I don't know how to call it right) when scrolls slowly. So instead setTranslationY() I've used an objectAnimator:
public void resizePagerContainer(AbsListView absListView){
.............
ObjectAnimator moveAnim = ObjectAnimator.ofFloat(llVendorDescriptionPagerContainer, "translationY", translationY);
moveAnim.start();
..............
}
I don't like this solution because 1) anyway it does resize viewpager with delay, not instantly 2) I don't think that is good idea to create many ObjectAnimator's objects every time when I scroll my listView.
Need your help and fresh ideas. Regards.
I'm assuming that you are scrolling the top header (the ImageView is a child of the header) based on the scrollY of the ListView/ScrollView, as shown below:
float translationY = Math.max(-scrollY, mMinHeaderTranslation);
mHeader.setTranslationY(translationY);
mTopImage.setTranslationY(-translationY / 3); // For parallax effect
If you want to stick the header/image to a certain dimension and continue the scrolling without moving it anymore, then you can change the value of mMinHeaderTranslation to achieve that effect.
//change this value to increase the dimension of header stuck on the top
int tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height);
mHeaderHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
mMinHeaderTranslation = -mHeaderHeight + tabHeight;
The code snippets above are in reference to my demo but I think it's still general enough for you.
If you're interested you can check out my demo
https://github.com/boxme/ParallaxHeaderViewPager
Have you tried CoordinatorLayout from this new android's design support library? It looks like it's what you need. Check this video from 3:40 or this blog post.
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);
I'm implementing an Animation for one menu in my project.
The animation itself is ok, the menu enters and exit just as I wanted: Slide from left to right and right to left, however...
If the entire view is OUT of the screen, then it NEVER comes back egain! If, at least one pixel is still inside the screen, then it comes back normally.
I belive that Android is disposing the layout, and not caring about it after out of the screen bounds. I tried to place a setVisibility(VISIBLE) but it also didn't worked.
Here is the code:
public class ChwaziMenuAnimation extends Animation{
float posStart = 0;
float posTarget = 100;
int getCurrentPosition(){
RelativeLayout.LayoutParams rootParam =
(RelativeLayout.LayoutParams) rootView.getLayoutParams();
return rootParam.leftMargin;
}
public void setTarget(float target){
// Save current position
posStart = getCurrentPosition();
posTarget = target;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
RelativeLayout.LayoutParams rootParam =
(RelativeLayout.LayoutParams) rootView.getLayoutParams();
// Calculate current position
rootParam.leftMargin = (int) ((posTarget - posStart) * interpolatedTime + posStart);
rootView.setLayoutParams(rootParam);
}
/*
* Since we will be animating the margin, the bounds will always change
*/
public boolean willChangeBounds() {
return true;
};
};
And how I initialize the animation:
public void appear(){
Log.i(TAG, "appear");
menuAnimation.setTarget(0);
menuAnimation.setDuration(750);
rootView.clearAnimation();
rootView.startAnimation(menuAnimation);
}
public void disapear(){
Log.i(TAG, "disapear");
menuAnimation.setTarget(-400);
menuAnimation.setDuration(750);
rootView.startAnimation(menuAnimation);
}
I encountered the same problem, my workaround so far is to extend the view bounds to at least one pixel on the displayable area, and make that part transparent. Ugly, but for me it seems to work.
To make it even more weird: the view did not disappear, when shifted out the right side of the screen, only when shifted out the left side of the screen. But that might be device dependent.
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);