Android ScrollView jumps up after changing child element visibility - java

I have a ScrollView that contains elements with visibility set to View.GONE by default. The problem is that whenever this visibility changes from View.GONE to View.VISIBLE, scroll jumps up as it is shown on second picture:
https://i.imgur.com/WmRc4M2.png
The red box represents current scroll position, blue rectangle is an element which visibility is changed, and green rectangle is some element (for example button) that I can refer to. I want my ScrollView to work as it is shown on third picture. If green button is on the screen, it should also be visible on screen after displaying new element (the blue one) above it. Any ideas how can I achieve it?

well, it behaves exacly as it should - scroll position is on e.g. 200px from top and this doesn't change when new item changes its visibility
you should scroll by yourself to proper (new) point, use scrollTo method - measure height of this new View and call scrollView.scrollTo(0, currentScrollY + heightOfNewView)
be aware that when you change visibility then you can't call above scrollTo just one line below, you have to wait for this View being measured and drawn. so you should call scrollTo in e.g.
visibilityChangedView.setVisibility(View.VISIBLE);
visibilityChangedView.post(new Runnable() {
// this will be called when visibilityChangedView won't have
// any pending tasks to do, but we just changed visibility,
// so at first view will be measured and drawn, after that
// below run() method will be called
#Override
public void run() {
int heightOfNewView = visibilityChangedView.getMeasuredHeight();
int currentScrollY = scrollView.getScrollY();
scrollView.scrollTo(0, currentScrollY + heightOfNewView);
}
);
there is a chance that this automatic scroll will be visible to user as quick jump - at first whole view will be drawn as #2 and after split second/one frame it will redraw at new position like #3. I don't know how your content is built (rest of childs in Scrollview), but I suspect that this should be RecyclerView or at least ListView with proper Adapter, not ScrollView with fixed layout

You can use NestedScrollView. It did the job.

Put your scrollView inside constraint layout and set all constraints to parent.

Related

Hide view on scroll in NestedScrollView and take it's place

I would like the yellow LinearLayout to hide as I'm scrolling the NestScrollView. Preferably with the parallax effect. What I was able to achieve already was increase alpha and set visibility to gone on appBar scroll. But I want the recyclerView below it to take it's space as I'm scrolling. Thanks
How I got it to work perfectly.
1. Be above RecyclerView
Added as a row with different layout, used getItemViewType() to specify different view for that first row.
2. Disappear on RecyclerView scroll with parallax effect
Had to override AppBar's addOnOffsetChangedListener() which provided me with verticallOffset variable which I used to translate just the first row in recyclerView
recyclerView.getChildAt(0).setTranslationY(verticallOffset / 2);
3. RecyclerView take its place when it dissapears
Still using the verticallOffset:
if (alpha < -200) {
readingsRecyclerAdapter.hideFirstRow();
}
and then inside the readingsRecyclerAdapter I removed the row
public void hideFirstRow() {
if (headerShowing) {
appsList.remove(0);
notifyItemRemoved(0);
headerShowing = false;
}
}
notifyItemRemoved(0); is important here as it causes the recyclerview to take the first rows empty space.

EditText loses focus on Scroll in RecyclerView

I have a RecyclerView which have EditText as its list items. When i scroll the RecyclerView, the EditText loses focus when the item goes off screen. So the focus does not remain on the EditText when it comes back on the screen on scroll.
I want the focus to remain on the same item. For that i also tried storing position of the item on focus and reassigning the focus in onBindViewHolder. But that slow downs the scrolling.
I also tried this with ListView but there is another kind of focus related problem. Focus jumps there.
Have searched for this lot on SO and Googled a lot. but always found answers like android:focusableInTouchMode="true" and android:descendantFocusability="afterDescendants" which does not work.
Any help would be highly appreciated.
The whole point of a RecyclerView is to reuse the views so that the phone doesn't have to keep all the rows in memory at once and doesn't have to be constantly destroying and creating views. When your EditText leaves the screen, the phone takes that view, resets its contents and moves it to the bottom of the incoming view stack.
This means that once your EditText leaves the screen it no longer exists. It cannot keep focus because it is removed from the layout.
The only way around this would be what you mentioned, which is storing the position, checking when that position comes onscreen, and manually restoring focus.
As Elan Hamburger wrote, we should store the position and restore it when an item appears on screen after scrolling.
A problem is we don't know when the item appears and disappears. When we have several ViewHolders, onBindViewHolder can even not be invoked again. In this case we should use onViewAttachedToWindow and onViewDetachedFromWindow to learn when the item appears and disappears on screen.
private var focusPosition: Int = -1
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
// Choose right ViewHolder and set focus event.
holder.edit_text.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
focusPosition = position
}
}
override fun onViewAttachedToWindow(holder: ViewHolder) {
super.onViewAttachedToWindow(holder)
// Your condition, for instance, compare stored position or item id, or text value.
if (holder.adapterPosition == focusPosition && holder is SomeViewHolder) {
holder.edit_text.requestFocus()
}
}
//override fun onViewDetachedFromWindow(holder: UinBillViewHolder) { //
// super.onViewDetachedFromWindow(holder)
//}
Try use the next:
LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
It helped me when using GridLayoutManager with Vertical orientation.
This option scrolls the list a little on its own so that when scrolling, the edge of the next items in the RecyclerView becomes is visible, so the focus is not lost.
But this does not help if you need to scroll too quickly (if the column did not have time to load, and you need to scroll further).
I found the answer here Scrolling recyclerview from the middle item

Pass click event through levels of ScrollViews and Layouts

I have many ScrollViews with RelativeLayouts inside in one Activity all of them cover the whole screen. I got buttons in some of them but the problem is the top layout is blocking the click events for the other layouts.
I want it so if no button is hit at the first layout it passes the click event to next etc.
You may wonder why i have many layouts that covers the whole screen and that is because i got scrollviews in them to create a 3D paralax effect scrolling background with different layers.
On the top layer also have setOnDragListener if that is important.
Here i will try to show you how my setup is:
HorizontalScrollView1--Layout1--Images/Buttons
HorizontalScrollView2--Layout2--Images/Buttons
HorizontalScrollView3--Layout3--Images/Buttons
So right now ScrollView3 is taking all clicks because its in the front/top. I can't disable clicks on the Scroll cut it needs to be scrollable and there is also buttons at the top layer that needs to be clickable.
overriding this in the relativelayout and returning true makes the above routine redundant:
RelativeLayout slideLayout=new RelativeLayout(mContext){
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mIsMuteTouches) return true;
return super.onInterceptTouchEvent(ev);
}
};

Horizontal Scrollview with snap effect

I need a widget that functions as a horizontal scrollview, but snaps to the nearest value when scrolled (it should snap from value to value, and not be possible to stop the scrolling in between values). Theoretically I would solve this with a viewpager with negative margins - so that all the views are simultaneously visible, but it also "snaps". But I have a problem - if I use a viewpager then only the "foreground" (i.e. currentView) view is "clickable", and I need all onscreen visible views to be clickable at any given time. So - is there any way of having a "snapping" horizontal scroll widget where all visible views are clickable?
For example, the screen might look like this:
A B C D E
where scrolling right reveals F, G, H..
initial state should allow clicking on any of A,B,C,D,E and not just on A (if it was the "currentview")
You can use Snap ScrollView library. It supports both horizontal and vertical snap scrolling. You can add views directly. Click on this SnapScrollView library
And regarding click, you can implement onClick listener for each view and call correspondingly.

ListView: Scroll to an item at the top of the ListView (and keep it there)?

I have a ListView with a significant number of items on it. I would like to figure out a way to get it to scroll to a certain position, but have that position be at the very top of the view.
The other issue is that the ListView gets invalidated and re-drawn at some point after this happens and I need it to return to this position when it is re-drawn.
I have tried smoothScrollToPosition, but this only brings the item into the view, and does not place it at the top. I have tried scrollTo(x,y) but I don't know the exact y-position of the item in the view. I have tried setSelection and setSelectionFromTop, but the selection gets reset when the view is invalidated. I have also tried all of these things within a post(Runnable) call, with no luck.
Here is the solution I have at the moment, which is awful.
listView.post(new Runnable() {
#Override
public void run() {
listView.smoothScrollToPosition(10000);
listView.smoothScrollToPosition(pos);
}
});
This forces it to scroll down to the bottom and scroll back up until the item at the specified position comes into the view, which has the effect of placing it at the top. Fortunately, the animation only goes so far as to scroll from the top to the end position, so it doesn't actually play out scrolling to the bottom and then back up. However, this solution is slow, hacky, and just generally awful. Does anyone have a better one?

Categories