I have expandable views inside CardView thats parent is NestedScrollView. I'm trying to create smooth scroll to child when expand animation ended. But I found only one solution:
scrollView.requestChildFocus(someView, someView);
This code works fine, but, when call requestChildFocus it scrolls immediately, and that annoying me a little bit. Is it possible to scroll to child smoothly?
The childView, to which I wanted to scroll, has CardView parrent, so childView.getTop() returns the value relative to the CardView not to the ScrollView. So, to get top relative to ScrollView I should get childView.getParent().getParent() then cast it to View and call getTop().
Scroll position calculates like
int scrollTo = ((View) childView.getParent().getParent()).getTop() + childView.getTop();
nestedScrollView.smoothScrollTo(0, scrollTo);
more to the answer from #jerry-sha
fun NestedScrollView.smoothScrollTo(view: View) {
var distance = view.top
var viewParent = view.parent
//traverses 10 times
for (i in 0..9) {
if ((viewParent as View) === this) break
distance += (viewParent as View).top
viewParent = viewParent.getParent()
}
smoothScrollTo(0, distance)
}
I had child views at different levels to the scrollview so made this function based off the accepted answer to calculate the distance and scroll
private int findDistanceToScroll(View view){
int distance = view.getTop();
ViewParent viewParent = view.getParent();
//traverses 10 times
for(int i = 0; i < 10; i++) {
if (((View) viewParent).getId() == R.id.journal_scrollview) {
return distance;
}
distance += ((View) viewParent).getTop();
viewParent = viewParent.getParent();
}
Timber.w("view not found");
return 0;
}
Then scroll using
journal_scrollview.smoothScrollTo(0, distance);
You can use my library ViewPropertyObjectAnimator for that.
Assuming mNestedScrollView is your NestedScrollView and mChildView is the child View you want to scroll to, you can do the following:
ViewPropertyObjectAnimator.animate(mNestedScrollView).scrollY(mChildView.getTop()).start();
Just make sure mChildView.getTop() is not 0 at the moment of calling .animate(...).
Edit:
As I said: make sure your View's top is non-zero when CALL .animate(...). In other words: call .animate(...) only when your child View already has dimensions. How can you determine that? For example like this:
mChildView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int width = mChildView.getWidth();
int height = mChildView.getHeight();
if (width > 0 && height > 0) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
mChildView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mChildView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
ViewPropertyObjectAnimator.animate(mNestedScrollView)
.scrollY(mChildView.getTop())
.start();
}
}
});
Try to read the source code.
svMain.setSmoothScrollingEnabled(true);
Rect rect = new Rect();
rect.top = 0;
rect.left = 0;
rect.right = tv4.getWidth();
rect.bottom =tv4.getHeight();
svMain.requestChildRectangleOnScreen(tv4,rect,false);
rect is the place u want the view to be shown on screen.
The main issue is to calculate the correct x/y position relative to the nestedscrollview. However, most of the answers here try to propose a solution by navigating in the hierarchy and hardcoding the hierarchy-level of the desired view. Imho, this is very error prone, if you change your viewgroup hierarchy.
Therefore, I would suggest to use a much more cleaner approach, which computes the relative position based on a Rect. Then, you can use the nestedscrollview's smoothScrollTo(..) methods to scroll to the desired position.
This should get the work done, where childView is the view you want to scroll to
public static void scrollToView(final NestedScrollView nestedScrollView, final View viewToScrollTo) {
final int[] xYPos = new int[2];
viewToScrollTo.getLocationOnScreen(xYPos);
final int[] scrollxYPos = new int[2];
nestedScrollView.getLocationOnScreen(scrollxYPos);
int yPosition = xYPos[1];
if (yPosition < 0) {
yPosition = 0;
}
nestedScrollView.scrollTo(0, scrollxYPos[1] - yPosition);
}
I find the accepted answer to work, but it is specific to their view structure as of now and it could not be used as a static method to use for all similar scrolls with different view structures. I made a variety of it in a utility class that measures until it find it's ScrollView or NestedScrollView parent. I made it so that the scrollToView(View,View) method should work with both ScrollView and NestedScrollView in case I would update which I use later on or whatever. You could of course call the right "child method" directly.
public static void scrollToView(View scrollView, View scrollToView) {
if (scrollToView == null) {
Log.d(TAG, "scrollToView() failed due to scrollToView == NULL!");
return;
}
if (scrollView instanceof NestedScrollView) {
scrollToInNestedView((NestedScrollView) scrollView, scrollToView);
} else if (scrollView instanceof ScrollView) {
scrollToInScrollView((ScrollView) scrollView, scrollToView);
} else {
Log.d(TAG, "scrollToView() failed due to scrollView not appearing to be any kind of scroll view!");
}
}
public static void scrollToInNestedView(NestedScrollView scrollView, View scrollToView) {
if (scrollView == null || scrollToView == null) {
return;
}
scrollView.post(() -> {
int startY = scrollView.getScrollY();
int requiredY = scrollToView.getTop();
View parent = (View) scrollToView.getParent();
while (parent != null && !(parent instanceof NestedScrollView)) {
requiredY += parent.getTop();
parent = (View) parent.getParent();
}
if (requiredY != startY) {
scrollView.smoothScrollTo(0, requiredY);
}
});
}
public static void scrollToInScrollView(ScrollView scrollView, View scrollToView) {
if (scrollView == null || scrollToView == null) {
return;
}
scrollView.post(() -> {
int startY = scrollView.getScrollY();
int requiredY = scrollToView.getTop();
View parent = (View) scrollToView.getParent();
while (parent != null && !(parent instanceof ScrollView)) {
requiredY += parent.getTop();
parent = (View) parent.getParent();
}
if (requiredY != startY) {
scrollView.smoothScrollTo(0, requiredY);
}
});
}
I want to change position of layout and after 75ms return it to first position to make a movement and that is my code:
for(int i = 0; i < l1.getChildCount(); i++) {
linear = (LinearLayout) findViewById(l1.getChildAt(i).getId());
LayoutParams params = new LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT);
params.bottomMargin = 10;
linear.setLayoutParams(params);
SystemClock.sleep(75);
}
The problem is the app is stop for 750ms and don't do anything. I tried invalidate() , refreshDrawableState(), requestLayout(), postInvalidate(), and try to call onResume(), onRestart(), onPause() .
Maybe you need:
linear.invalidate();
linear.requestLayout();
after making the layout changes.
EDIT:
Run the code on a different thread:
new Thread() {
#Override
public void run() {
<your code here>
}
}.start();
And whenever you need to update the UI from that thread use:
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
<code to change UI>
}
});
After several hours of testing, I found the solution about updating a view if you made operation with these views like adding children, visibility, rotation, etc.
We need to force update the view with the below methods.
linearSliderDots.post {
// here linearSliderDots is a linear layout &
// I made add & remove view option on runtime
linearSliderDots.invalidate()
linearSliderDots.requestLayout()
}
You should try using an ValueAnimator (Or object animator), the below code is in kotlin but same logic would be applied for java:
val childCount = someView.childCount
val animators = mutableListOf<ValueAnimator>()
for (i in 0..childCount) {
val child = (someView.getChildAt(i))
val animator = ValueAnimator.ofInt(0, 75)
animator.addUpdateListener {
val curValue = it.animatedValue as Int
(child.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = curValue
child.requestLayout()
}
animator.duration = 75
animator.startDelay = 75L * i
animators.add(animator)
}
animators.forEach { animator ->
animator.start()
}
Basically you create a bunch of animators that have start delay proportionate to the number of children, so as soon as one animation ends, the new one starts
ActivityName.this.runOnUiThread(new Runnable() {
#Override
public void run() {
<code to change UI>
}
});
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);
I'm trying to add views dynamically to a linearlayout.
I see through getChildCount() that the views are added to the layout, but even calling invalidate() on the layout doesn't give me the childs showed up.
Am I missing something?
A couple of things you can check in your code:
On the View that is being added, check that you call its setLayoutParameter method with an appropriate ViewGroup.LayoutParameter.
When you adding the new Views, make sure you are doing it on the UI thread. To do this, you can use the parent View's post method.
This self contained example adds a TextView after a short delay when it starts:
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ProgrammticView extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT));
setContentView(layout);
// This is just going to programatically add a view after a short delay.
Timer timing = new Timer();
timing.schedule(new TimerTask() {
#Override
public void run() {
final TextView child = new TextView(ProgrammticView.this);
child.setText("Hello World!");
child.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
// When adding another view, make sure you do it on the UI
// thread.
layout.post(new Runnable() {
public void run() {
layout.addView(child);
}
});
}
}, 5000);
}
}
I had the same problem and noticed that my overriden onMeasure() method wasn't called after the invalidate. So I created my own subroutine in the LinearLayout and called it before the invalidate() method.
Here is the code for an vertical LinearLayout:
private void measure() {
if (this.getOrientation() == LinearLayout.VERTICAL) {
int h = 0;
int w = 0;
this.measureChildren(0, 0);
for (int i = 0; i < this.getChildCount(); i++) {
View v = this.getChildAt(i);
h += v.getMeasuredHeight();
w = (w < v.getMeasuredWidth()) ? v.getMeasuredWidth() : w;
}
height = (h < height) ? height : h;
width = (w < width) ? width : w;
}
this.setMeasuredDimension(width, height);
}
I spent a lot of time to solve this problem too. And I found a simple method of refresh LinearLayout in 3 lines of code
You must set transperent color in style.xml
<color name="transparent">#00000000</color>
And in the code just call to set background
LinearLayout ll = (LinearLayout) findViewById(R.id.noteList);
ll.setBackgroundColor(getResources().getColor(R.color.transparent));
ll.invalidate();
If you has drawable background call
ll.setBackgroundResource(R.drawable.your_drawable);