I am writing a photo picker for Facebook inside my app, i have a recyclerview with a grid layout and i want to prevent for scrolling up, i was able to do this by using scrollToPosition and this works but not the way i want
Problem
When i click in a photo on the 2 row that row jumps to the top and becomes the number 1 visible row, if i click the 3 row the samething happens.
I don't want the recycler to move if the view is visible it should remain the same, so if i click on a a photo that is on the last visible row i want the scroll to stay the same, i don't want it to make the last row the first.
Tries to solve it
I tried several things to fix this, i tried calling setNestedScrollingEnabled i followed this How to disable RecyclerView scrolling?
public static void onItemClick(int position){
//picker.setNestedScrollingEnabled(false);
for(int k = 0; k<photoBag.size();k++) {
if(k == position)
photoBag.set(position, new PhotoBag(photoBag.get(position).getPhoto(), true)); //Here im marking the photo to selected
else
photoBag.set(k, new PhotoBag(photoBag.get(k).getPhoto(), false));//Here im setting unselecting all the other photos
}
picker.setAdapter(adapter);
adapter.notifyDataSetChanged();
picker.scrollToPosition(position);
//Log.d("FacebookPicker", "position " + grid.findFirstCompletelyVisibleItemPosition());
//picker.setNestedScrollingEnabled(true);
}
I thought that maybe disabling the scroll would lock the recyclerview on the corrent position but it didn't jumps right up.
I also tried getting the Vertical offset and set it after calling notifyDataSetChange but i can't find a way to set the offset programmatically
EDIT
Adapter
class PickerAdapter extends RecyclerView.Adapter<PickerAdapter.PickerAdapterHolder> {
public final String TAG = "PickerAdapter";
private ArrayList<PhotoBag> photoBag;
private Context context;
private OnClickListener onClickListener;
class PickerAdapterHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView photo;
ImageView imageBorder;
PickerAdapterHolder(View view) {
super(view);
photo = (ImageView) view.findViewById(R.id.photoItem);
photo.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.photoItem:
FacebookPhotoPicker.onItemClick(getAdapterPosition()); //i know that there are better ways to get the clicked item from other class but since im still debuging i don't need to worry about performace i just need it to work
break;
}
}
}
PickerAdapter(Context context, ArrayList<PhotoBag> itemList) {
this.photoBag = itemList;
this.context = context;
}
#Override
public PickerAdapterHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.facebook_picker_item, null);
return new PickerAdapterHolder(layoutView);
}
#Override
public void onBindViewHolder(final PickerAdapterHolder holder, final int position) {
if(photoBag.get(position).isSelected()){
int border = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, context.getResources().getDisplayMetrics()));
Bitmap photo = photoBag.get(position).getPhoto();
photo = Bitmap.createScaledBitmap(photo,photo.getWidth() - (border*2), photo.getHeight() - (border*2), false);
photo = addWhiteBorder(photo,border);
holder.photo.setImageBitmap(photo);
}else {
holder.photo.setImageBitmap(photoBag.get(position).getPhoto());
}
}
#Override
public int getItemCount() {
return this.photoBag.size();
}
private Bitmap addWhiteBorder(Bitmap bmp, int borderSize) {
Bitmap bmpWithBorder = Bitmap.createBitmap(bmp.getWidth() + borderSize * 2, bmp.getHeight() + borderSize * 2, bmp.getConfig());
Canvas canvas = new Canvas(bmpWithBorder);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bmp, borderSize, borderSize, null);
return bmpWithBorder;
}
remove those 2 lines from onItemClick
picker.setAdapter(adapter);
picker.scrollToPosition(position);
every time you setAdapter it resets position, and now you don't need to set a new position again.
this should work. If it doesn't, check this answer of mine (and their comments) about providing ID How to remain at a scroll position in RecyclerView after adding items at its first index and call notifydatasetchange
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 don't understand why invalidate(Rect) is invalidating the entire region.
The region is divided up into 5 sections and a line graph is being drawn in each one. Each section contains 100 points. As the data arrives for times tn to tn+100 I call invalidate(new Rect(left, top, right bottom)) where top is the top of the screen in height (but a lower numerical value than bottom). This invokes a call to the onDraw() method. The region from tn to tn+100 is drawn, but the previously drawn segment in region tn-100 to tn is erased. It continues that way forever. Each invalidate draws in only that region (since that is the only data I have) which IS correct, but all the previously drawn data is erased. So I get a marching segment!
In other words, I get identical behavior if I call invalidate() or invalidate(Rect).
I am assuming that the parameters of the Rect() are pixels and are getting the values based upon the height and width of the AlertDialog window in which this is being drawn.
The hope is eventually to reduce the region of 'Rect()' so I can simulate real time drawing and only invalidate time step t to t+1 instead of a region.
I must be doing something stupid.
I hope that the fact it is being done in an AlertDialog is not the issue.
This part is for trying to help 'android developer' help a noob like me get this right.
First the sequence of events:
1. Data is received via Bluetooth in a callback
2. If it is the right type of data, a BroadcastReceiver in the main activity (UI thread) is signaled and from there a routine is called that sets the parameters of a WaveFormView extends View class and then ShowDialog(id) is called which calls the onCreateDialog(id) callback in the main activity.
3. Then I call invalidate().
4. The dialog pops up and then the graph is drawn.
5. All subsequent calls to ShowDialog(id) bypass the onCreateDialog(id)
That works but the entire region is always invalidated regardless of the parameters. There are also no user events here. From your example the best I could come up with is the following where I place invalidate in the onShow() instead of calling myself after the showDialog(id)
#Override
protected Dialog onCreateDialog(int id)
{
Log.d(TAG, "Alert Dialog 'onCreateDialog' method has been called with id " + id);
Builder bldr = new AlertDialog.Builder(this);
AlertDialog alert = bldr.setView(waveForm).setNegativeButton("Dismiss " + id,
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
dialog.cancel();
}
}).create();
// I tried adding this and invalidating here worked only first pass
alert.setOnShowListener(
new DialogInterface.OnShowListener()
{
#Override
public void onShow(DialogInterface dialog)
{
// call to invalidate
waveForm.drawArea();
}
});
//alert.getWindow().setLayout(alert.getWindow().getAttributes().width, alert.getWindow().getAttributes().height / 2);
alert.getWindow().setLayout(waveForm.getCurrentWidth(), waveForm.getCurrentHeight());
return alert;
}
However the onShow() does not get called.
The method in the main activity that calls the showDialog is
private void displayRtsa(int[] rtsaReceived)
{
// rtsaReceived[0] has the agentsink hash code
int agent = rtsaReceived[0];
// rtsaReceived[1] has the index of the RtsaData object updated
int index = rtsaReceived[1];
TreeMap<Integer, RtsaData[]> agentRtsa = BluetoothPanService.getAgentRtsaMap();
RtsaData[] rtsaDataValues = agentRtsa.get(agent);
int dialogId = 0;
synchronized(dialogIds)
{
int i = 0;
if(dialogIds.containsKey(agent + index) == false)
{
for(i = 0; i < dialogIds.size(); i++)
{
if(dialogIds.containsValue(i) == false)
{
break;
}
}
dialogIds.put(agent + index, i);
}
dialogId = dialogIds.get(agent + index);
}
final int id = dialogId;
Log.d(TAG, "Dialog id being shown = " + dialogId);
waveForm.setPeriod(rtsaDataValues[index].getPeriod());
waveForm.setMaxMin(rtsaDataValues[index].getMinValue(), rtsaDataValues[index].getMaxValue());
waveForm.setColor(Color.argb(255, 255, 200, 0));
waveForm.setData(rtsaDataValues[index].getData());
waveForm.setTitle(rtsaDataValues[index].getType());
showDialog(id);
// invalidate
// waveForm.drawArea(); (try to do in onCreateDialog callback)
}
This is probably a completely wrong approach. Probably openGl is the only way.
By the way, thanks for putting up with me!
i think it depends on what exactly you do the invalidation on . if the view you are calling the invalidation on didn't handle a rectangular invalidation , the default invalidation takes place.
in any case , if you wish to change the behavior , you can change it yourself. for the "onDraw" method , use the next code in order to fetch the invalidated rectangle :
public class InvalidateTestActivity extends Activity
{
static class CustomView extends ImageView
{
public CustomView(Context context)
{
super(context);
}
#Override
protected void onDraw(Canvas canvas)
{
final Rect r = canvas.getClipBounds();
Log.d("DEBUG", "rectangle of invalidation:" + r);
super.onDraw(canvas);
}
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
CustomView customView = new CustomView(this);
customView.setLayoutParams(new FrameLayout.LayoutParams(200, 200));
customView.setBackgroundColor(0xffff0000);
customView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v)
{
v.invalidate(new Rect(0, 0, 49, 49));
}
});
setContentView(customView);
}
}
If you have hardware acceleration enabled it looks like it just invalidates the whole view...
ViewGroup:
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
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);