I am using recylerview in my application and whenever new element is added to recyclerview, it scrolls to last element by using
recyclerView.scrollToPosition(adapter.getCount());
But, whenever keyboard opens(because of editTextView), it resizes the view and recyclerview gets smaller, but couldn't scroll to last element.
android:windowSoftInputMode="adjustResize"
I even tried to used the following code to scroll to last element, but it didn't work
editTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus){
recyclerView.scrollToPosition(chatAdapter.getItemCount());
}
}
});
I can try adjustPan to shift the pan up, but it is hiding my toolbar.
Please suggest any way to rectify the issue.
You can catch keyboard up changes using recyclerview.addOnLayoutChangeListener().
If bottom is smaller than oldBottom then keyboard is in up state.
if ( bottom < oldBottom) {
recyclerview.postDelayed(new Runnable() {
#Override
public void run() {
recyclerview.smoothScrollToPosition(bottomPosition);
}
}, 100);
}
Add this your activity or fragment:
if (Build.VERSION.SDK_INT >= 11) {
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v,
int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (bottom < oldBottom) {
recyclerView.postDelayed(new Runnable() {
#Override
public void run() {
recyclerView.smoothScrollToPosition(
recyclerView.getAdapter().getItemCount() - 1);
}
}, 100);
}
}
});
}
It works for me
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(layoutManager);
I found the postDelayed to be unnecessary and using adapter positions doesn't account for when the recycler is scrolled to somewhere in the middle of an item. I achieved the look I wanted with this:
recycler.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (bottom < oldBottom) {
messageRecycler.scrollBy(0, oldBottom - bottom);
}
})
Although this an old question, I experienced this problem today and I found out that none of of the above method works. This is my solution
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v,
int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (bottom < oldBottom) {
final int lastAdapterItem = mAdapter.getItemCount() - 1;
recyclerView.post(new Runnable() {
#Override
public void run() {
int recyclerViewPositionOffset = -1000000;
View bottomView = linearLayoutManager.findViewByPosition(lastAdapterItem);
if (bottomView != null) {
recyclerViewPositionOffset = 0 - bottomView.getHeight();
}
linearLayoutManager.scrollToPositionWithOffset(lastAdapterItem, recyclerViewPositionOffset);
}
});
}
});
}
This works.
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private LinearLayoutManager mManager;
...
mManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mManager);
(initialize and set adapter.)
mRecyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (bottom < oldBottom) scrollToBottom();
}
});
private void scrollToBottom() {
mManager.smoothScrollToPosition(mRecyclerView, null, mAdapter.getItemCount());
}
recyclerView.smoothScrollToPosition(recyclerView.getAdapter().getItemCount());
It works properly in support library version 27.0.1
There is nothing to set in the manifest.
val currentScrollPosition = 0
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
currentScrollPosition = recyclerView.computeVerticalScrollOffset() + recyclerView.computeVerticalScrollExtent()
}
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) { }
})
storyList.addOnLayoutChangeListener { view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
if (bottom < oldBottom) {
if (currentScrollPosition >= recyclerView.computeVerticalScrollRange()) {
recyclerVIew.post {
recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_NEVER
recyclerView.smoothScrollBy(0, recyclerView.computeVerticalScrollRange() - recyclerView.computeVerticalScrollOffset() + recyclerView.computeVerticalScrollExtent())
}
}
} else {
recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_ALWAYS
}
}
layoutManager.scrollToPositionWithOffset(chatMessageAdapter.getItemCount() - 1, 0);
You can use addOnItemTouchListener to see the scrolling change:
private Boolean scrollListerActivate = true;
mRecyclerViewTop.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(scrollListerActivate == true) {
scrollListTopManager();
}
}
});
To scroll for last (or other) position you should use scrollListTopManager method:
public void scrollListTopManager() {
int position = 0;
int itemCount = mRecyclerViewTop.getAdapter().getItemCount();
position = itemCount - 1;
mRecyclerViewTop.scrollToPosition(position);
}
You should also use addOnItemTouchListener to stop using scrollListTopManager method, if you want to scroll manually:
mRecyclerViewTop.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
#Override
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent
motionEvent) {
scrollListerActivate = false;
return false;
}
#Override
public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent)
{}
#Override
public void onRequestDisallowInterceptTouchEvent(boolean b) {}
});
When you open chat you will get last message at the end of recycle view:
layoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(layoutManager);
Below code will make adjustments according to keyboard!
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if ( bottom <= oldBottom) {
recyclerview.postDelayed(new Runnable() {
#Override
public void run() {
recyclerview.smoothScrollToPosition(bottom);
}
}, 100);
}
}
});
Bind the RecyclerView inside NestedScrollView
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.widget.NestedScrollView>
Related
I am using Room data base to fetch the data from the data base. Recyclerview I want to scroll to a particular position in recyclerview.
I have already tried list.setNestedScrollingEnabled(true)
StationListRepository stationListRepositoryOnlyActive = new StationListRepository(getApplicationContext());
stationListRepositoryOnlyActive.getActiveStationList().observe(this, new Observer>() {
#Override
public void onChanged(List OnlyActivestations) {
Log.d("OnlyActivestations", "" + OnlyActivestations.size());
assert OnlyActivestations != null;
if (OnlyActivestations.size() > 0 || FinalList.size() > 0) {
if (FinalList != null && FinalList.size() > 0) {
OnlyActivestations = sortList(OnlyActivestations);
FinalList.addAll(OnlyActivestations);
} else {
OnlyActivestations = sortList(OnlyActivestations);
FinalList = OnlyActivestations;
}
Log.d("FinalListSizeActive",""+FinalList.size());
detailsAdapter = new db_stationListAdapter(context, FinalList);
//recyclerView.setNestedScrollingEnabled(true);
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
staggeredGridLayoutManager.scrollToPosition(10);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
recyclerView.setAdapter(detailsAdapter);
detailsAdapter.notifyDataSetChanged();
runOnUiThread(new Runnable() {
#Override
public void run() {
// stopLoading();
stopAnimation();
}
});
}
}
});
I except the recycler view to auto scroll to the position 10
Try using the code below after you set the data to the adapter
recyclerView.smoothScrollToPosition(10);
or
You can add OnLayoutChangeListener to your RecyclerView.
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener(){
#Override
public void onLayoutChange(View v, int left, int top, int
right, int bottom, int oldLeft, int oldTop, int oldRight,
int oldBottom) {
if (bottom < oldBottom) {
recyclerView.postDelayed(new Runnable() {
#Override
public void run() {
recyclerView.smoothScrollToPosition(10);
}
}, 100);
}
}
});
I'm working on a chat application. Now, while fetching messages from the back-end for the first time, i want my Linear Layout pointer to move to the last message in the list, so that the user always sees the latest messages on opening the chat fragment.
This is my code:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(homeScreenActivity);
linearLayoutManager.setStackFromEnd(true);
linearLayoutManager.setReverseLayout(true);
chatRecycler.setLayoutManager(linearLayoutManager);
Thanks in advance!
If you dont know the when data is entered, you can make addOnLayoutChangeListener on your recycler view.
And in addOnLayoutChangeListener
chatRecycler.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v,
int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (bottom < oldBottom) {
chatRecycler.postDelayed(new Runnable() {
#Override
public void run() {
recycler_discussion.scrollToPosition(
recycler_discussion.getAdapter().getItemCount() - 1);
}
}, 50);
}
}
});
I'm trying to implement a ScrollView in Android that doesn't scroll when adding an item above the current scroll position.
The default implementation of ScrollView behaves as following:
Adding an item above the current scroll position:
Adding an item below the current scroll position:
How can I "lock" the ScrollView prior to adding an item above the current scroll position?
This is my layout file, I've currently overridden both the ScrollView and LinearLayout, but haven't made any alterations yet.
<LinearLayout
android:layout_height="fill_parent"
android:orientation="vertical"
android:layout_width="fill_parent">
<LinearLayout
android:id="#+id/LinearLayout02"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_alignParentBottom="true">
<Button
android:id="#+id/Button02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" android:text="Add To Top"
android:onClick="addToStart">
</Button>
<Button
android:id="#+id/Button03"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Add to End"
android:onClick="addToEnd">
</Button>
</LinearLayout>
<com.poc.scroller.locable.lockablescrollerpoc.LockedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:verticalScrollbarPosition="right"
android:fadeScrollbars="false"
android:background="#color/scrollColor">
<com.poc.scroller.locable.lockablescrollerpoc.LockedLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/Container">
</com.poc.scroller.locable.lockablescrollerpoc.LockedLinearLayout>
</com.poc.scroller.locable.lockablescrollerpoc.LockedScrollView>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
Example Source Code:
https://github.com/Amaros90/android-lockable-scroller-poc
Thank you!
You can easily get the opposite behavior by using addOnLayoutChangeListener of your LinearLayout and reset the ScrollView's ScrollY. Here is the implementation in your ScrollViewActivity.onCreate
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scrollview);
_linearLayout = (LockedLinearLayout)findViewById(R.id.Container);
_scrollView = (LockedScrollView)findViewById(R.id.ScrollView);
_layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
_linearLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
_scrollView.scrollTo(0, _scrollView.getScrollY() + (bottom - oldBottom ));
}
});
addExampleImage(10, _linearLayout);
}
Then you can easily flag the addToEnd function or check where the child was added to avoid changing scroll when some child is added to the bottom.
You can try to use RecyclerView and implement onDataSetChanged(). Then detect whether add to TOP or add to END button was pressed. Then use scrollToPositionWithOffset(int, int) to manage the scroll.
For example:
//Scroll item 3 to 20 pixels from the top
linearLayoutManager.scrollToPositionWithOffset(3, 20);
For restoring the scroll position of a RecyclerView, this is how to save the scroll positions (the two arguments for the method scrollToPositionWithOffset(int, int)):
int index = linearLayoutManager.findFirstVisibleItemPosition();
View v = linearLayoutManager.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop())
Implementing this worked for me. You can check out the example app I added in the original question.
public class LockableScrollView extends ScrollView {
private boolean _enabled = true;
public LockableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setFillViewport(true);
}
#Override
public void addView(View layout, ViewGroup.LayoutParams params) {
super.addView(layout, params);
((ViewGroup)layout).setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
#Override
public void onChildViewAdded(View layout, View item) {
if (_enabled) {
item.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View item, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
item.removeOnLayoutChangeListener(this);
int scrollViewY = ((LockableScrollView)item.getParent().getParent()).getScrollY();
int layoutPosition = ((View)item.getParent()).getTop();
boolean shouldScroll = item.getTop() + layoutPosition <= scrollViewY || item.getBottom() + getTop() <= scrollViewY;
if (shouldScroll) {
final int childViewHeight = item.getHeight();
((View)item.getParent()).addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View layout, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
layout.removeOnLayoutChangeListener(this);
LockableScrollView scrollView = ((LockableScrollView)layout.getParent());
scrollView.scrollTo(scrollView.getScrollX(), scrollView.getScrollY() + childViewHeight);
}
});
}
}
});
}
}
#Override
public void onChildViewRemoved(View layout, View item) {
if (_enabled) {
int scrollViewY = ((LockableScrollView)layout.getParent()).getScrollY();
int layoutPosition = layout.getTop();
boolean shouldScroll = item.getTop() + layoutPosition <= scrollViewY || item.getBottom() + getTop() <= scrollViewY;
if (shouldScroll) {
final int childViewHeight = item.getHeight();
layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View layout, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
layout.removeOnLayoutChangeListener(this);
LockableScrollView scrollView = ((LockableScrollView)layout.getParent());
scrollView.scrollTo(scrollView.getScrollX(), scrollView.getScrollY() - childViewHeight);
}
});
}
}
}
});
}
}
you can do it easily just get it..
first of all in onclick event store the int value by
using getFirstVisiblePosition()
example.
in onclick of add element button do this---
int currentpos = recycleview.getFirstVisiblePosition();
now after you insert elment to list/recyclerview---
recyclerview.scrolltoposition(currentpos);
thats it try if you want to do it without error..
Good Luck :)
I want to animate View right after it was added to parent (something like DrawerLayout). The problem is that View has varying size, and animation target position depends on that size. Simplified sample code:
AnimatingView extends View {
public int offsetX;
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
final int screenHeight = MeasureSpec.getSize(heightMeasureSpec);
offsetX = calculateOffset(screenWidth);
...
}
}
Code similar to this triggers the animation:
#Override
public void onClick(View v) {
AnimatingView animatingView = new AnimatingView(getContext());
parentLayout.addView(animatingView);
animatingView.animate().x(animatingView.offsetX).setDuration(500).start();
}
In this case onMeasure() happens after animate(), so animation fails. What is the correct way of doing stuff which depends on view measuring?
The simple & stupid way would be something like animateOnceAfterMeasuring() based on isInitialized flag, but I don't think it the correct way of doing this.
this should work:
AnimatingView animatingView = new AnimatingView(getContext());
parentLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
v.removeOnLayoutChangeListener(this);
animatingView.animate().x(animatingView.offsetX).setDuration(500).start();
}
});
You can use the ViewTreeObserver for this
#Override
public void onClick(View v) {
final AnimatingView animatingView = new AnimatingView(getContext());
parentLayout.addView(animatingView);
animatingView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < 16) {
animatingView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
animatingView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
animatingView.animate().x(animatingView.offsetX).setDuration(500).start();
}
});
}
Is there a way to identify if listview is being scroll up or down?
OnScrollListener doens't help me in this case.
Thanks in advance!
this is a simple implementation:
lv.setOnScrollListener(new OnScrollListener() {
private int mLastFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState){}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(mLastFirstVisibleItem < firstVisibleItem){
// Scrolling down
}
if(mLastFirstVisibleItem > firstVisibleItem){
// scrolling up
}
mLastFirstVisibleItem = firstVisibleItem;
}
});
There is a method in ScrollViews that reports the shifting of scrolls. It is called onScrollChanged(). However, it is declared protected, so you must create a wrapper class to gain access. For the usage of the method, please check android docs.
First, create an interface to expose the protected method
public interface OnScrollListener {
void onScrollChanged(int x, int y, int oldx, int oldy);
}
Then, create your wrapper and extend ScrollView
public class ReportingScrollView extends ScrollView {
private OnScrollListener onScrollListener = null;
public ReportingScrollView(Context context) {
super(context);
}
public ReportingScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ReportingScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (onScrollListener != null) {
onScrollListener.onScrollChanged(x, y, oldx, oldy);
}
}
}
Finally, include it in your XML layout like a custom view, and attach listeners like usual.
<your.package.ReportingScrollView />
scrollingandtrolling.setOnScrollListener(new OnScrollListener() {...});
for whom ever still looking for an answer for kotlin, this works for it
MainListView.setOnScrollListener(object :AbsListView.OnScrollListener {
// var VisibleItem: Int = 0
override fun onScroll(p0: AbsListView?, FirstVisibleItem: Int, i2: Int, i3: Int) {
/*
if(VisibleItem < FirstVisibleItem)
{
Toast.makeText(applicationContext,"Scrolling Down",Toast.LENGTH_SHORT).show()
fab.hide()
}
if(VisibleItem >FirstVisibleItem)
{
fab.show()
Toast.makeText(applicationContext,"Scrolling Up",Toast.LENGTH_SHORT).show()
}
VisibleItem=FirstVisibleItem;
*/
}
override fun onScrollStateChanged(p0: AbsListView?, p1: Int) {
if (p1 == 1) {
fab.hide()
} else {
fab.show()
}
}
})
}
Uncomment to use an alternative method.
An easy and working solution.
private int lastPosition = -1;
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if(lastPosition == firstVisibleItem)
{
return;
}
if(firstVisibleItem > lastPosition)
{
Logger.print("Going Down");
}
else
{
Logger.print("Going Up");
}
lastPosition = firstVisibleItem;
}
The solution given by Some Noob Student is good: you have to create a wrapper to use the protected onScrollChanged(int x, int y, int oldx, int oldy) method, which is triggered more often then the standard AbsListView.OnScrollListener.onScrollStateChanged(AbsListView view, int scrollState).
Unfortunately the onScrollChanged() always returns 0 as parameters, no matter how I scroll, and seems to detect only when the scroll ends, reaching the top or the bottom of the list.
So I used the following code to detect the right direction of the scrolling: it uses the getTop() of the first visible item but uses also its position to understand when the first item is no more visible; during this item transition, the scroll direction is given by the item position itself.
#Override
public void onScrollChanged(int x, int y, int oldx, int oldy) {
if(listView.getChildAt(0) != null) {
int scrollingDir = NO_SCROLLING;
int firstChildTop = listView.getChildAt(0).getTop();
if (listView.getFirstVisiblePosition() == mLastFirstVisiblePosition) {
if (mLastFirstChildTop < firstChildTop) {
//Scrolling DOWN
scrollingDir = SCROLLING_DOWN;
} else if (mLastFirstChildTop > firstChildTop) {
//Scrolling UP
scrollingDir = SCROLLING_UP;
}
} else if(listView.getFirstVisiblePosition() > mLastFirstVisiblePosition) {
//Scrolling UP
scrollingDir = SCROLLING_UP;
} else {
//Scrolling DOWN
scrollingDir = SCROLLING_DOWN;
}
mLastFirstVisiblePosition = listView.getFirstVisiblePosition();
mLastFirstChildTop = firstChildTop;
switch(scrollingDir) {
case SCROLLING_DOWN:
//Your DOWN scrolling code here
break;
case SCROLLING_UP:
//Your UP scrolling code here
break;
default:
case NO_SCROLLING:
break;
}
}
}
list.setOnScrollListener(new OnScrollListener() {
int last_item;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(last_item<firstVisibleItem+visibleItemCount-1){
System.out.println("List is scrolling upwards");
}
else if(last_item>firstVisibleItem+visibleItemCount-1){
System.out.println("List is scrolling downwards");
}
last_item = firstVisibleItem+visibleItemCount-1;
}
});
Here based on the last visible item i decide whether the list is going up or down