I used Motion Layout for Collapse Layout. Motion layout working perfect as I need but I want to set motion layout animation based on data fit too the screen.
Like if data fit in screen then no need to animation. If data out of the screen then show animation.
Finally I got solution:
motionLogin?.enableTransition(R.id.transitionLogin, false)
constraintLayout.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
// If the scrollView can scroll, disable the accept menu item button
if (constraintLayout.canScrollVertically(1) || constraintLayout.canScrollVertically(
-1
)
) {
motionLogin?.enableTransition(R.id.transitionLogin, true)
}
// Remove itself after onGlobalLayout is first called or else it would be called about a million times per second
constraintLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
For Java version:
public static void enableDisableTransition(MotionLayout motionLayout, RecyclerView recyclerView){
motionLayout.enableTransition(R.id.transition, false);
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (recyclerView.canScrollVertically(1) || recyclerView.canScrollVertically(-1 )
) {
motionLayout.enableTransition(R.id.transition, true);
}
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
Related
In my app, I have a ScrollView that listens to Scroll Changes, thanks to the ViewTreeObserver class.
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
// I want to directly target this scrollview changes
// if specific scrollview { do calculations }
}
});
However, that class listens to global scroll changes, and I have 3 ScrollViews. So, the main ScrollView is activated and performs the calculations and causes everything to go awry.
Is there a way to ignore other ScrollViews and only target the main one?
What you can do to overcome it is store the getScrollX() values (if horizontal scrollview) or the getScrollY() values (if vertical scrollview) in some int and every time an onScrollChanged() is fired, just check if the new scroll Values are different from the ones previously stored.
If they're different, the correct scrollview is being called. If they are the same, the wrong scrollview is being called and therefore ignore it.
int prevScrollX_for1 = -1;
int prevScrollX_for2 = -1;
scrollView1.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
// Assuming horizontal scrollview
if(scrollView1.getScrollX() != prevScrollX_for1)
{
// Do something
}
prevScrollX_for1 = getScrollX();
}
});
scrollView2.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
// Assuming horizontal scrollview
if(scrollView2.getScrollX() != prevScrollX_for2)
{
// Do something
}
prevScrollX_for2 = getScrollX();
}
});
I'm trying to make an horizontal list of sticky images with RecyclerView and I'd like to move them by pixels' offset with scrollToPositionWithOffset. I thought passing 0 as position and the pixels I want to move to right / left as offset.
But it doesn't work, the list remains untouched, unscrolled, it doesn't move. This is my implementation:
final LargeImageAdapter mLargeImageAdapter = new LargeImageAdapter(this);
linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(mLargeImageAdapter);
seekBar = (SeekBar)findViewById(R.id.seekBar);
seekBar.setMax(7000);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int scrollToDX = progress;
((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(0, scrollToDX);
// tried invoking also linearLayoutManager instead getLayoutManager.
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
what am I doing wrong?
Thank you very much.
Regards.
Rafael.
I finally used:
recyclerView.scrollBy(int offsetX, int offsetY); setting offsetY = 0 and it works now.
I don't understand what's the utility of the function scrollToPositionWithOffset.
I had a similar issue. My problem was that my recyclerview wasn't of the same size of its parent layout. I solved it by setting the recycler view width and height to match_parent. I don't know why this happens in this case.
A late answer to your first question, and an addition to your answer:
Your method works better for your personal needs, because scrollToPositionWithOffset is not intended to do what you want.
As the doc says here:
[...]Resolved layout start depends on [...]
getLayoutDirection(android.view.View) [...]
Which means it would offset the scroll target position in the layout direction, vertically in your case.
I don't understand what's the utility of the function
scrollToPositionWithOffset.
it allows to not only scroll to a given item in the list, but also position it at a more "visible" or otherwise convenient place.
recently I encountered this problem too, I invoke scrollToPositionWithOffset when onScrolled() directly, but nothing change, with that I turn to scrollToPosition() even scrollBy() but not help, finally I attempt to delay that so it work, first time I delay 50ms, but two weeks later I found that's not enough, so I increase to 100ms with no approachs in my hands, of course it work, just feel a little unsettled.
val layoutManager = LinearLayoutManager(hostActivity, VERTICAL, false)
fileRv.layoutManager = layoutManager
fileRv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dx == 0 && dy == 0) {
scrollToLastPosition()
}
}
private fun scrollToLastPosition() {
val lastScrollPosition = viewModel.consumeLastScrollPosition()
if (lastScrollPosition > 0) {
Handler().postDelayed({ layoutManager.scrollToPositionWithOffset(lastScrollPosition, 0) }, 100)
}
}
})
override fun onItemClick(position: Int) {
layoutManager.findFirstVisibleItemPosition().let {
if (it >= 0) viewModel.markLastScrollPosition(it)
}
}
fun markLastScrollPosition(position: Int) {
currentFolderListData.value?.lastOrNull()?.lastScrollPosition = position
}
fun consumeLastScrollPosition(): Int {
currentFolderListData.value?.lastOrNull()?.run {
return lastScrollPosition.apply { lastScrollPosition = -1 }
}
return 0
}
I find a solution.
Coz I am the developer of DNA Launcher. When I use RecyclerView to display A-Z App List, I found that the function scrollToPositionWithOffset is not working. I track the problem for almost one day and I figured it out.
When the RecyclerView display again, just let the parent of RecyclerView do requestLayout.
It works for me.
And I know how to make the function scrollToPositionWithOffset not working. You just need to add a view on it and make it gone then.
I'm using basic RecyclerView with GridLayoutManager. I observed that nor smoothScrollToPosition nor scrollToPosition works properly.
a) when using smoothScrollToPosition I often receive error from RecyclerView
"RecyclerView﹕ Passed over target position while smooth scrolling."
and RecyclerView is not scrolled properly (often it misses the targeted row). This is observed mostly when I'm trying to scroll to the 1st item of some row
b) when using scrollToPosition it seems to work quite ok but most of the time I can see only the 1st item of the row and the rest are not displayed.
Can you give me some hints how to make work properly at least one of the methods?
Thanks a lot!
Finally I was able to make it work! LinearLayoutManager.scrollToPositionWithOffset(int, int) did the trick.
I also have same issue, but managed to fix the issue by Customizing SmoothScroller
let Custom LayoutManager as below
public class CustomLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 50f;
private Context mContext;
public CustomLayoutManager(Context context) {
super(context);
mContext = context;
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView,
RecyclerView.State state, final int position) {
LinearSmoothScroller smoothScroller =
new LinearSmoothScroller(mContext) {
//This controls the direction in which smoothScroll looks
//for your view
#Override
public PointF computeScrollVectorForPosition
(int targetPosition) {
return CustomLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
//This returns the milliseconds it takes to
//scroll one pixel.
#Override
protected float calculateSpeedPerPixel
(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH/displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
}
(documentation commented inside the code given above)Please set the above LayoutManager
to the recyerview
CustomLayoutManagerlayoutManager = new CustomLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.smoothScrollToPosition(position);
by using the custom Layout manager
scrollToPosition
also working well in my case u can use
recyclerView.scrollToPosition(position)
also if you want to adjust the speed of smoothScrollToPosition please override the
private static final float MILLISECONDS_PER_INCH = 50f;
in CustomLayoutManager.
So if we put the value as 1f the smoothScrollToPosition will be faster like scrollToPosition.increasing value make delay and decreasing will make the speed of scroll.
Hope this will useful.
In My case,
mRecyclerView.scrollToPosition(10);
also did not work.
But
mRecyclerView.smoothScrollToPosition(10);
works fine for me...
To scroll down smoothly to bottom from any position in the RecyclerView on clicking EditText.
edittext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
rv_commentList.postDelayed(new Runnable() {
#Override
public void run() {
rv_commentList.scrollToPosition(rv_commentList.getAdapter().getItemCount() - 1);
}
}, 1000);
}
});
Another reason why any of the before mentioned solutions may not work is if your RecyclerView is embedded in a NestedScrollView. In this case you have to call the scroll action on the NestedScrollView.
for example:
nestedScrollview.smoothScrollTo(0,0)
This extension is so useful, try please.
fun RecyclerView.smoothSnapToPosition(position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START) {
val smoothScroller = object : LinearSmoothScroller(this.context) {
override fun getVerticalSnapPreference(): Int = snapMode
override fun getHorizontalSnapPreference(): Int = snapMode
}
smoothScroller.targetPosition = position
layoutManager?.startSmoothScroll(smoothScroller)
}
I was facing a weird issue wherein smoothScrollToPosition only worked occasionally.
After putting the smoothScrollToPosition inside Handler Post
Delayed with 1 second delay, it worked fine.
Refer to the following Kotlin example:
Handler().postDelayed({
recyclerViewObject.smoothScrollToPosition(0) // mention the position in place of 0
}, 1000) // 1000 indicates the 1 second delay.
recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView,new RecyclerView.State(),currentPosition);
Try measuring item width or height and call smoothScrollBy(int dx, int dy).
How to perform smooth scrolling and save RecyclerView vertical position after device rotating:
This is the method that works for my case,
public class MainFragment extends Fragment { //OR activity it's //fragment in my case
....
#Override
public void onLoadFinished(#NonNull Loader<List<Report>> loader, List<Report> objects) { // or other method of your choice, in my case it's a Loader
RecyclerView recyclerViewRv = findViewById(........;
.....
recyclerViewRv.setAdapter(.....Your adapter);
recyclerViewRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
recyclerScrollY = recyclerViewRv. computeVerticalScrollOffset();
}
});
//Apply smooth vertical scroll
recyclerViewRv.smoothScrollBy(0,recyclerScrollY);
}
//Save vertical scroll position before rotating devices
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("recyclerScrollY",recyclerScrollY);
}
//BackUp vertical scroll position after rotating devices
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
recyclerScrollY = savedInstanceState.getInt("recyclerScrollY");
}
}
//If you want to perform the same operation for horizontal scrolling just add a variable called recyclerScrollX = recyclerScrollY = recyclerViewRv. computeHorizontalScrollOffset(); then save in bundle
Calling the recyclerView smoothScroll isn't effective, as the recyclerView itself doesn't handle its layout.
What you should do is calling the layout manager scroll method instead.
This should look something like this
mRecyclerView.getLayoutManager().scrollToPosition(desiredPosition);
If you are trying to do a quick scroll to a position at the top of the RecyclerView, just use LinearLayoutManager.scrollToPositionWithOffset with 0 as the offset.
Example:
mLinearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mLinearLayoutManager);
mLinearLayoutManager.scrollToPositionWithOffset(myPosition, 0);
smoothScrollToPosition is very slow. If you want something fast go with scrollToPositionWithOffset.
when you use scrollToPosition it will show it on top of the recycler view.
But if you use smoothScrollToPosition it will scroll till it come in to Window Visible. that's why while smoothScrool to item below, it will show it on bottom
Actually, if you have a RecyclerView inside a NestedScrollView, you must use both of these lines every time you want to go to the beginning of the RecyclerView:
nestedScrollView.smoothScrollTo(0, 0);
layoutManager.scrollToPositionWithOffset(0, 0);
This completely works for me.
this worked for me
Handler().postDelayed({
(recyclerView.getLayoutManager() as LinearLayoutManager).scrollToPositionWithOffset( 0, 0)
}, 100)
None of these answers worked for me. I needed to smoothScrollToPosition but #Ramz answer didn't work. I was finding it would consistently overscroll but only in one direction. I discovered that it seemed to be the item decorators throwing it off. I had a horizontal layout and I wanted to add a space after every item except the last and it didn't like that asymmetry. As soon as I included a space after every item, it worked!
nestedScroll.smoothScrollTo(0, recycler.top)
So i was looking for a solution to get back to the top with a recyclerview inside another layout that has a view on top of it (in my case I had a LinearLayout with a TextView and my recyclerview inside). Because of that the smoothscroll would go only to half the way to the first item.
Here's what I did which works really well (Kotlin solution):
back_to_top.setOnClickListener {
GlobalScope.launch(Dispatchers.Main) {
GlobalScope.launch(Dispatchers.Main) {
recyclerview.layoutManager?.smoothScrollToPosition(recyclerview, RecyclerView.State(), 0)
delay((recyclerview.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() * 100L)
}.join()
recyclerview.scrollToPosition(0)
}
back_to_top.visibility = View.GONE
}
}
Here what I do is I smoothscroll to the first element and delay the scroll by 100ms times the last item visible and then call the scrollToPosition(0) (which goes to the top.correctly)
I'm interested in what is the right and first possible moment to get size of first item of RecyclerView?
I've tried to use:
recyclerView.setLayoutManager(new GridLayoutManager(context, 2));
recyclerView.setAdapter(new MyDymmyGridRecyclerAdapter(context));
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
recyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
View firstRecyclerViewItem = recyclerView.getLayoutManager().findViewByPosition(0);
// firstRecyclerViewItem is null here
}
});
but it returns null at this moment.
If you're using OnGlobalLayoutListener you should remember onGlobalLayout can be called multiple times. Some of those calls can happen even before Layout is ready (and by ready I mean the moment when you can get dimensions of a View by calling view.getHeight() or view.getWidth()). So the proper way of implementing your approach would be:
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int width = recyclerView.getWidth();
int height = recyclerView.getHeight();
if (width > 0 && height > 0) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
recyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
View firstRecyclerViewItem = recyclerView.getLayoutManager().findViewByPosition(0);
}
});
Apart from that you still need to be sure that at the time of findViewByPosition(0) call:
Your RecyclerView's Adapter has at least one data element.
View at position 0 is currently visible in RecyclerView
Tell me if that fixes your issue, if not there is still another way of doing what you need.
In my extended RecyclerView I override onChildAttachedToWindow like this
#Override
public void onChildAttachedToWindow(View child) {
super.onChildAttachedToWindow(child);
if (!mIsChildHeightSet) {
// only need height of one child as they are all the same height
child.measure(0, 0);
// do stuff with child.getMeasuredHeight()
mIsChildHeightSet = true;
}
}
I had this type of problem. I want to perform click by default on the first visible position of the recylerview. I wrote the code for that on onResume but it did not work. I solved my problem by writting the code in onWindowFocusChanged method
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(isCalledForTheFirstTime)
{
LinearLayoutManager manager= (LinearLayoutManager) rcViewHeader.getLayoutManager();
int pos= manager.findFirstCompletelyVisibleItemPosition();
View view=manager.findViewByPosition(pos);
if(view!=null)
{
view.performClick();
}
// change the vaule so that it would not be call in case a pop up appear or disappear
isCalledForTheFirstTime=false;
}
}
As I've a master in MS Paint, I will just upload a picture selfdescripting what I'm trying to achieve.
I've searched, but I'm not really sure what do I've to search. I've found something called Animations. I managed to rotate, fade, etc an element from a View (with this great tutorial http://www.vogella.com/articles/AndroidAnimation/article.html)
But this is a bit limited for what I'm trying to achieve, and now, I'm stuck, because I don't know how is this really called in android development. Tried words like "scrollup layouts" but I didn't get any better results.
Can you give me some tips?
Thank you.
You can see a live example, with this app: https://play.google.com/store/apps/details?id=alexcrusher.just6weeks
Sincerely,
Sergi
Use something like this as your layout (Use Linear, Relative or other layout if you wish):
<LinearLayout
android:id="#+id/lty_parent">
<LinearLayout
android:id="#+id/lyt_first" />
<LinearLayout
android:id="#+id/lyt_second"/>
</LinearLayout>
And then in an onClick method on whatever you want to use to control it, set the Visibility between Visible and Gone.
public void buttonClickListener(){
((Button) findViewById(R.id.your_button))
.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (lyt_second.getVisibility() == View.GONE) {
lyt_second.setVisibility(View.VISIBILE);
}
else {
lyt_second.setVisibility(View.GONE);
}
});
Which is fine if you just want a simple appear/disappear with nothing fancy. Things get a little bit more complicated if you want to animate it, as you need to play around with negative margins in order to make it appear to grow and shrink, like so:
We use the same onClick method that we did before, but this time when we click it starts up a custom SlideAnimation for the hidden/visible view.
#Override
public void onClick(View v) {
SlideAnimation slideAnim = new SlideAnimation(lyt_second, time);
lyt_second.startAnimation(slideAnim);
}
The implementation of the SlideAnimation is based on a general Animation class, which we extend and then Override the transformation.
public SlideAnimation(View view, int duration) {
//Set the duration of the animation to the int we passed in
setDuration(duration);
//Set the view to be animated to the view we passed in
viewToBeAnimated = view;
//Get the Margin Parameters for the view so we can edit them
viewMarginParams = (MarginLayoutParams) view.getLayoutParams();
//If the view is VISIBLE, hide it after. If it's GONE, show it before we start.
hideAfter = (view.getVisibility() == View.VISIBLE);
//First off, start the margin at the bottom margin we've already set.
//You need your layout to have a negative margin for this to work correctly.
marginStart = viewMarginParams.bottomMargin;
//Decide if we're expanding or collapsing
if (marginStart == 0){
marginEnd = 0 - view.getHeight();
}
else {
marginEnd = 0;
}
//Make sure the view is visible for our animation
view.setVisibility(View.VISIBLE);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
// Setting the new bottom margin to the start of the margin
// plus the inbetween bits
viewMarginParams.bottomMargin = marginStart
+ (int) ((marginEnd - marginStart) * interpolatedTime);
// Request the layout as it happens so we can see it redrawing
viewToBeAnimated.requestLayout();
// Make sure we have finished before we mess about with the rest of it
} else if (!alreadyFinished) {
viewMarginParams.bottomMargin = marginEnd;
viewToBeAnimated.requestLayout();
if (hideAfter) {
viewToBeAnimated.setVisibility(View.GONE);
}
alreadyFinished = true;
}
hideAfter = false;
}
}
EDIT: If anyone had used this code before and found that if you click on the button that starts the animation more than once before the animation was finished, it would mess up the animation from then on, causing it to always hide the view after the animation finished. I missed the reset of the hideAfter boolean near the bottom of the code, added it now.
you can do this manually by using setvisibility feature on the event onClick()
or
use this
dynamically adding two views one below other