I have a childView inside ScrollView which expands onClick. I used the below code to bring the expanded view fully in the screen.
scrollView.requestChildFocus(childView, focussedView);
But the scroll is not smooth. What can I do to make the scroll smooth?
You could use the smooth scroll methods provided by ScrollView. Here is the documentation.
Try these methods:
public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled)
// Set whether arrow scrolling will animate its transition.
public final void smoothScrollTo(int x, int y)
// Like scrollTo(int, int), but scroll smoothly instead of immediately.
public final void smoothScrollBy(int dx, int dy)
// Like View.scrollBy(int, int), but scroll smoothly instead of immediately.
Related
I want to animate appearance of only last item of a recyclerview. Every time scroll is done to end, view should be shown with animation.
Applying Item Animator animates when item is removed, added, deleted. But i did not get option to animate in on scroll.
I applied animation in onBindView, but onBindView is not always called and animation is not started. Moreover, in cases onBindView is called and user is performing slow scroll operation, animation had already started when view is actually visible to user.
What can be suitable way to apply this animation?
Thanks
Vibhor
I have tested this solution and It's working like a charm.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager layoutManager = LinearLayoutManager.class.cast(recyclerView.getLayoutManager());
int lastItem=adapter.getItemCount()-1;
tryAnimation(layoutManager.findViewByPosition(lastItem));
}
});
public void tryAnimation(View view) {
Animation animation = AnimationUtils.loadAnimation(this, android.R.anim.slide_in_left);
if (view != null)
view.startAnimation(animation);
}
this solution can be optimized by taking advantage of dy
According to the Building a RecyclerView LayoutManager article I have created own custom layout manager for RecyclerView, but due to a few documentation available i can't find a way to force recycle view rebuild animation inside from layout manager ( just like animations when notifyItemInserted or notifyItemDeleted is used). Such animations are controlled by recyclerView and its item animators, developer only can control position of items.
So i have a method fill, which performs placing child views according to current scroll position and state of layout manager. Such method is called from two places,
in onLayoutChildren ( this method is called when layout manager performs initialization placing of items or when data set was changed)
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler);
fill(recycler);
}
in scrollVerticallyBy
/** calculate offset of views while scrolling, layout items on new places*/
#Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
dy = scrollVerticallyInternal(dy);
offsetChildrenVertical(-dy);
if (condition) {
//todo refactor it, blinking now without animation.
detachAndScrapAttachedViews(recycler);
}
fill(recycler);
return dy;
}
And i want in case of some condition perform a bit more complex transition for some views and with animation.
It may be reached if somehow initiate layouting childrens flow, when onLayoutChildren method will be invoked by RecyclerView.
I'm able to do this with detachAndScrapAttachedViews(recycler) and that initiates onLayoutChildren and running default fill process, but such transition will be performed instantly without any animation.
Is it possible to force recyclerView (or layout manager) inside from layout manager to run its animations?
When we have implemented a custom LayoutManager, we also made our own animation for ExpandLayoutManager. In particular, we have used ValueAnimator for animating changes of the LayoutManager look. If you’re curious about our ExpandLayoutManager, it’s available on GitHub.
Here you can also find some valuable details for creating custom LayoutManagers: http://cases.azoft.com/create-custom-layoutmanager-android/
I've investigated the source of RecyclerView and found that onLayoutChildren is called when RecyclerView performs own layouting process. So calling requestLayout should be an option, instead of detachAndScrapAttachedViews. And with combination of requestSimpleAnimationsInNextLayout it should helps. But NOT.
Those operations would work only if been performed inside postOnAnimation runnable. So, at least, with my completed scrollVerticalBy my layout manager've been running animations successfully:
#Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
dy = scrollVerticallyInternal(dy);
offsetChildrenVertical(-dy);
postOnAnimation(new Runnable() {
#Override
public void run() {
requestLayout();
requestSimpleAnimationsInNextLayout();
}
});
fill(recycler);
return dy;
}
I have a ListView Object and use:
Timer timer=new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#SuppressLint("NewApi")
#Override
public void run() {
lv1.scrollBy(0, counter_automatic);
counter_automatic++;
}
});
}
}, 0, 100);
code work correctly and don't get any Error! But I want Scrolling to go till end of listView!
My listView has 286 item! but it goes only for 3 items and after 3 items scrolling continue but items don't appear!
What's the problem?
The scrollBy() method is a member of the View class, and isn't going to work as you're expecting for a ListView because of how it handles its child Views. Depending on what exactly the desired behavior is, you probably want to use smoothScrollToPosition(int position) or smoothScrollBy(int distance, int duration).
The scrollBy method takes pixels as parameters. I suppose you mix up of number of items with amount of pixels.
If you want to smooth scroll to a certain item use smoothScrollToPosition instead. See http://developer.android.com/reference/android/widget/AbsListView.html#smoothScrollToPosition(int)
scrollBy() scrolls by an amount of pixel, and not from item to item. The documentation of scrollBy() says:
public void scrollBy (int x, int y)
Move the scrolled position of your view. This will cause a call to onScrollChanged(int, int, int,
int) and the view will be invalidated.
Parameters
x - the amount of pixels to scroll by horizontally
y - the amount of pixels to scroll by vertically
So what you want to do is something like this:
listView.scrollTo(0, listView.getHeight());
Using scrollTo() instead of scrollBy() should be more reliable for your use-case.
But I guess the method you are actually looking for is smoothScrollToPosition()! Try something like this:
listView.smoothScrollToPosition(adapter.getCount() - 1);
Instead of using scrollBy, try using setSelectionFromTop(int position, int pixelsFromTop). In your case, use counter_automatic as position and let the pixelsFromTop variable not change.
I have a form within a ScrollView. When I tap into an EditText the soft keyboard appears and the ScrollView scrolls the now focused EditText so that it just comes into view.
However, I have hint information just below the EditText that I also would like to show, so the scrolling should go just a bit further up, like this:
The EditText is embedded in a form element and actually I'd like to scroll to the bottom of that. I've checked the source code of ScrollView and it will just scroll to the bottom of the currently focused view. Maybe there's a way to tell the ScrollView that the form element is the currently focused element?
Of course I could write my own ScrollView sub class and override the scroll behavior, but I wonder if there's a more elegant way of doing this.
Any other suggestions (with adjust scrolling with a fixed offset or so) are also appreciated.
I have not really found any way to configure the scrolling behavior of the ScrollView from the outside. So I ended up to define my own sub class of ScrollView:`
/**
* {#link ScrollView} extension that allows to configure scroll offset.
*/
public class ConfigurableScrollView extends ScrollView {
private int scrollOffset = 0;
public ConfigurableScrollView (final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public void setScrollOffset (final int scrollOffset) {
this.scrollOffset = scrollOffset;
}
#Override
protected int computeScrollDeltaToGetChildRectOnScreen (final Rect rect) {
// adjust by scroll offset
int scrollDelta = super.computeScrollDeltaToGetChildRectOnScreen(rect);
int newScrollDelta = (int) Math.signum(scrollDelta) * (scrollDelta + this.scrollOffset);
return newScrollDelta;
}
}
computeScrollDelta(...) is the only protected method that can be targeted for overriding, apart from onSizeChanged(...).
The signum function in the example above ensures that scrolling is only increased, if the ScrollView really thinks that scrolling is necessary (e.g. when keyboard pops up).
I can now set the extra scroll offset once from the outside, as calculated from the height of the hint.
It's not hard to use the extended ConfigurableScrollView instead of the standard ScrollView, I only had to replace the ScrollView XML tag with the FQN of the new class.
Considering that you are using ScrollView you have the possibility to use the method ScrollTo as follow:
scrollView.post(new Runnable() {
#Override
public void run() {
sv.scrollTo(x-value, y-value);
}
});
where the first argument is the scroll value for X, while the second argument is the scroll value for Y. So you just have to set your scrollView offset when the keyboard is displayed.
Hope it helps;)
I have a LinearLayout containing both a ListView and an EditText. When the On-Screen keyboard is launched by touching the EditText, the ListView resizes so that only the top few elements remain visible.
The context that the ListView is being used in has the bottom few elements being more visually relevant than the top, and so I'd like for it to resize so that the bottom remains visible, rather than the top. Any pointers?
(Incidentally, the current fix I'm using involves using smoothScrollToPosition, but the laggy scroll behaviour makes this undesirable)
I just solved a similar issue this morning, and thought I'd post my result here for the benefit of future searchers. My issue was that scrolling to the bottom wasn't helping since I was calling it before the view actually changed size. The solution? Wait until it does change size by using a GlobalLayoutListener
Steps:
1) implement the following method in the activity holding the listview
public void scrollToBottom(){
//I think this is supposed to run on the UI thread
listView.setSelection(mAdapter.getCount() - 1);
}
2) create the following class
public class OnGlobalLayoutListenerWithScrollToBottom implements OnGlobalLayoutListener{
private boolean scroll;
private OnScrollToBottomListener listener;
public interface OnScrollToBottomListener{
public void scrollToBottom();
}
public OnGlobalLayoutListenerWithScrollToBottom(OnScrollToBottomListener listener){
this.listener = listener;
}
#Override
public void onGlobalLayout() {
if(scroll){
listener.scrollToBottom();
scroll = false;
}
}
/**
* Lets the listener know to request a scroll to bottom the next time it is layed out
*/
public void scrollToBottomAtNextOpportunity(){
scroll = true;
}
};
3) In your activity, implement the interface from this class. Then, in your activity, create an instance of this OnGlobalLayoutListener and set it as the listener for your listView
//make sure your class implements OnGlobalLayoutListenerWithScrollToBottom.OnScrollToBottomListener
listViewLayoutListener = new OnGlobalLayoutListenerWithScrollToBottom(this);
listView.getViewTreeObserver().addOnGlobalLayoutListener(listViewLayoutListener);
4) In your activity, before you make changes that will affect the size of list view, such as showing and hiding other views or adding stuff to the listview, simply let the layout listener know to scroll at the next opportunity
listViewLayoutListener.scrollToBottomAtNextOpportunity();
You might achieve this with setSelectionFromTop() and by overriding onSizeChanged() in a custom listview. In your layout, you should have a RelativeLayout has a parent container and place the listview above the edittext.
By creating your own listview and overriding onSizeChange(), you will be able to get the last visible item's position before the listview resizing and get its new height, in order to finally set the "previous" position of the list with an offset of its height.
How it works: Your list will place the previous last visible item at its top and you will add the pixels to scroll it at its bottom, just above your edittext.
To override the method and display it with an offset, do as follows:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// get last visible item's position before resizing
int lastPosition = super.getLastVisiblePosition();
// call the super method to resize the listview
super.onSizeChanged(w, h, oldw, oldh);
// after resizing, show the last visible item at the bottom of new listview's height
super.setSelectionFromTop(lastPosition, (h - lastItemHeight));
}
lastItemHeight is a little workaround, because I didn't find how to get the last item's height before that onSizeChanged is called. Then, in the case of your listview contains many types of items (without the same height), I prefer to get the selected height when an event occurs, just before the SoftKeyboard opens up.
So in the custom listview, you have this global variable:
int lastItemHeight = 0;
And in the activity (or fragment, whatever), you update this value in OnClickListener:
edittext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// set a new Thread
listview.post(new Runnable() {
#Override
public void run() {
// get last visible position of items
int lastPosition = listview.getLastVisiblePosition() - 1;
// if the view's last child can be retrieved
if ( listview.getChildAt(lastPosition) != null ) {
// update the height of the last child in custom listview
listview.lastItemHeight = listview.getChildAt(lastPosition).getHeight();
}
}
});
}
});
Note: there is another possible solution but you have to set android:stackFromBottom="true" on the listview, which stackes its content from the bottom. Instead, this solution here can display a specific item without forcing the content to start from the bottom, with the default listview's behaviour.
Second note: (just in case) don't forget to add android:windowSoftInputMode="adjustResize" in the manifest.
You should add this attribute in the manifest for your activity and pick the correct value for it (probably you will pick "adjustResize"):
<activity android:name="package.yourActivity" android:windowSoftInputMode="adjustResize"/>