I am working with Navigation menu view and I am scrolling it programmatically and then get a Y position of its item.
But the code that get the item positions is running immediately after the list (using the layout manager) starts to scroll and when it haven't finished to scroll and change the positions yet.
How can I do the rest of code after "scrollToPositionWithOffset" wait the end of the scroll action to run?
Here's my code:
RecyclerView recyclerView = (RecyclerView) navigationView.getChildAt(0);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
layoutManager.scrollToPositionWithOffset(4, 0);
//it should wait the line above run completely to can run too
mImage.animate().y(navView.getChildAt(4).getY());
You should use a scroll listener that listens for the Scrolling state to change to idle.. See below.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
mImage.animate().y(navView.getChildAt(4).getY());
recyclerView.removeOnScrollListener(this);
}
}
}
layoutManager.scrollToPositionWithOffset(4, 0);
On researching i found this :
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false) {
#Override
public void onLayoutChildren(final Recycler recycler, final State state) {
super.onLayoutChildren(recycler, state);
//TODO if the items are filtered, considered hiding the fast scroller here
final int firstVisibleItemPosition = findFirstVisibleItemPosition();
if (firstVisibleItemPosition != 0) {
// this avoids trying to handle un-needed calls
if (firstVisibleItemPosition == -1)
//not initialized, or no items shown, so hide fast-scroller
mFastScroller.setVisibility(View.GONE);
return;
}
final int lastVisibleItemPosition = findLastVisibleItemPosition();
int itemsShown = lastVisibleItemPosition - firstVisibleItemPosition + 1;
//if all items are shown, hide the fast-scroller
mFastScroller.setVisibility(mAdapter.getItemCount() > itemsShown ? View.VISIBLE : View.GONE);
}
};
The good thing here is that it works well and will handle even keyboard being shown/hidden.
The bad thing is that it gets called on cases that aren't interesting (meaning it has false positives), but it's not as often as scrolling events, so it's good enough for me.
Related
When a user will scroll Recyclerview from right to left at the same time the first view will disappear with fade-out animation according to scrolling and background view also be parallax like google play store app.
It will animated recylerview, "not the normal horizontal recyclerview". You can see this in google play, not every time, it appears occasionally
According to what you have written, you basically wanted a horizontal recycler view which when scrolled has a fade animation to the header.
I had encountered the same problem and I solved it the following was. I used kotlin, during my development.
Firstly you need to add a scroll listener to your recycler view, i have made an extension function for scroll
inline fun RecyclerView.scroll(
crossinline onScrolled: (RecyclerView, Int, Int) -> Unit = { it, dx, dy -> },
crossinline onScrolledChanged: (RecyclerView, Int) -> Unit = { it, newState -> }
) {
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onScrolled(recyclerView, dx, dy)
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
onScrolledChanged(recyclerView, newState)
}
})
}
Now create a divider item decorator for adding the padding to the first item
class DividerDecorator(val paddingStart: Float) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
val position = parent.getChildAdapterPosition(view)
if (position == 0 || position == 1) {
outRect.left = paddingStart.toInt()
}
val lp = view.layoutParams as StaggeredGridLayoutManager.LayoutParams
val spanIndex = lp.spanIndex
if (position >= 0) {
if(spanIndex==0) {
outRect.top = 13.px
outRect.bottom = 5.px
}else{
outRect.top = 5.px
outRect.bottom = 13.px
}
}
}
}
Now in your XML place add the image that you want as background and place the recycler view on top of that image.
Once you have done that you just need to add scroll extension to your recyclerview
private var overallScroll = 0;
recycler.scroll(onScrolled = { _, dx, _ ->
overallScroll += dx
backgroundView.animate()
.alpha(overallScroll.remap(padding))
.setInterpolator(LinearInterpolator())
.setDuration(0)
.start()
})
remap function i have maded to calculate the proper alpha corresponding to scroll displacement.
fun Int.remap(offset:Float):Float{
return 1-(this/offset)
}
and yes don't forget to make the recycler view horizontal.
You can use RecyclerView with following layout manager
LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
I currently have a CoordinatorLayout with both a ConstraintLayout and a NestedScrollView in it. The NestedScrollView has a peek height of 50dp which is just a title. I want to be able to pull up the NestedScrollView when the device is connected, however when it is not connected I only want to be able to see the peek and not be able to drag up the rest of the view.
if(deviceConnected) {
mBottomSheetText.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
});
} else {
//Want to prevent it form being draggable
}
The DragCallback interface allows to choose whether the sibling scrolling view should be controlled by scrolls onto the AppBarLayout.
You can do that like below:
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return false;
}
});
By always returning false, your scrolling view will not be controlled by the appbarLayout any longer.
Note: before calling this you should check that ViewCompat.isLaidOut(appBarLayout), otherwise params.getBehavior() will return null.
Check this link.
I would like to check when smoothScrollToPosition has finished scrolling back to the first item of recyclerview. I tried doing it like this which only works while smoothScrollToPosition is still scrolling:
recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView,new RecyclerView.State(), 0);
if (!recyclerView.getLayoutManager().isSmoothScrolling()) {
Log.d(TAG, "Scrolling has ended.");
}
I use onScrollStateChanged(RecyclerView recyclerView, int newState) method for tracking scrolling state. The method to initiate scroll looks like this:
private void scrollToPosition(int position){
recyclerView.removeOnScrollListener(onScrollListener);
recyclerView.addOnScrollListener(onScrollListener);
recyclerView.smoothScrollToPosition(position);
}
And here is the listener:
RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (newState) {
case SCROLL_STATE_IDLE:
//we reached the target position
recyclerView.removeOnScrollListener(this);
break;
}
}
};
So, when recyclerView reaches SCROLL_STATE_IDLE, that means it has finished scrolling. Don't forget to remove listener in this state, so it won't trigger on the next scroll.
There is a listener for it:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener
void onScrolled (RecyclerView recyclerView,
int dx,
int dy)
Callback method to be invoked when the RecyclerView has been scrolled. This will be called after the scroll has completed.
Can I detect the direction of scroll when I add the on scroll listener to my recycler view?
The code is as follows:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (recyclerView.getScrollState()) {
case RecyclerView.SCROLL_STATE_DRAGGING :
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) relativeBaseLayout.getLayoutParams();
if(layoutParams.weight < 1.0f) {
SlideAnimation slideAnimation = new SlideAnimation(layoutParams.weight, 1.0f, relativeBaseLayout);
slideAnimation.setDuration(250);
relativeBaseLayout.startAnimation(slideAnimation);
}
break;
default:
break;
}
}
});
I want this function to work only if I scroll down.
Currently it'll work on both scroll up and down. What changes do I make?
I do not want to implement the onScrolled() method since that will be called after the scroll has finished. I want to detect it during the scroll.
What am I missing here?
I am trying to implement a drag and drop in a ListView in android(Ice Cream Sandwich). So when the dragged object reaches the edge of the ListView, I am scrolling the ListView in the relevant direction. The problem is that when we scroll, sometimes the adapter creates new Views as necessary and these 'new' Views did not receive the ACTION_DRAG_STARTED event earlier and hence do not receive the DragEvent updates. Is there any way I can send the events to these views as well?
An easiest way to implement drag and drop in listview is you use this great library.
https://github.com/commonsguy/cwac-touchlist
it's worth trying.
Looking at the source for View, I see:
static final int DRAG_CAN_ACCEPT = 0x00000001;
int mPrivateFlags2;
boolean canAcceptDrag() {
return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
}
mPrivateFlags2 is package-private and not exposed by the SDK. However, you should be able to change it in a subclass by doing:
try {
Field mPrivateFlags2 = this.getClass().getField("mPrivateFlags2");
int currentValue = mPrivateFlags2.getInt(this);
mPrivateFlags2.setInt(this, currentValue | 0x00000001);
} catch (Exception e) {
}
I have the same problem. I did not solved this recycling problem, but I found a possible workaround still using the Drag & Drop framework. The idea is to change of perspective: instead of using a OnDragListener on each View in the list, it can be used on the ListView directly.
Then the idea is to find on top of which item the finger is while doing the Drag & Drop, and to write the related display code in the ListAdapter of the ListView. The trick is then to find on top of which item view we are, and where the drop is done.
In order to do that, I set as an id to each view created by the adapter its ListView position - with View.setId(), so I can find it later using a combination of ListView.pointToPosition() and ListView.findViewById().
As a drag listener example (which is, I remind you, applied on the ListView), it can be something like that:
// Initalize your ListView
private ListView _myListView = new ListView(getContext());
// Start drag when long click on a ListView item
_myListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, _myListView.getItemAtPosition(position), 0);
return true;
}
});
// Set the adapter and drag listener
_myListView.setOnDragListener(new MyListViewDragListener());
_myListView.setAdapter(new MyViewAdapter(getActivity()));
// Classes used above
private class MyViewAdapter extends ArrayAdapter<Object> {
public MyViewAdapter (Context context, List<TimedElement> objects) {
super(context, 0, objects);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View myView = convertView;
if (myView == null) {
// Instanciate your view
}
// Associates view and position in ListAdapter, needed for drag and drop
myView.setId(position);
return myView;
}
}
private class MyListViewDragListener implements View.OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
final int action = event.getAction();
switch(action) {
case DragEvent.ACTION_DRAG_STARTED:
return true;
case DragEvent.ACTION_DRAG_DROP:
// We drag the item on top of the one which is at itemPosition
int itemPosition = _myListView.pointToPosition((int)event.getX(), (int)event.getY());
// We can even get the view at itemPosition thanks to get/setid
View itemView = _myListView.findViewById(itemPosition );
/* If you try the same thing in ACTION_DRAG_LOCATION, itemView
* is sometimes null; if you need this view, just return if null.
* As the same event is then fired later, only process the event
* when itemView is not null.
* It can be more problematic in ACTION_DRAG_DROP but for now
* I never had itemView null in this event. */
// Handle the drop as you like
return true;
}
}
}
Now if you need to have a visual feedback when doing a drag and drop, there are several strategies. You can for instance have 2 instance variables in your activity named:
private boolean ongoingDrag = false; // To know if we are in a drag&drop state
private int dragPosition = 0; // You put the itemPosition variable here
When doing the drag and drop in MyListViewDragListener you modify these variables, and you use their state in MyViewAdapter. Of course do not forget to update the UI (in the event thread of course, use a Handler) with something like _myListView.getAdapter()).notifyDataSetChanged() or maybe _myListView.invalidate() method.
The problem is because listView.getPositionForView(view) returns -1 if the view is not visible when it is called. So relying on that will fail when you scroll the list. So, instead of setting a view.setOnLongClickListener() you can set a listView.setOnItemLongClickListener() on the list item which calls startDrag() on the item. onItemLongClick() gives you the position which you can pass to in the myLocalState parameter of startDrag(). Then you recover that in onDrag() using event.getLocalState() and casting it to an Integer. Like this...
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
position -= listView.getHeaderViewsCount();
DragShadowBuilder dragShadow = new View.DragShadowBuilder(view);
view.startDrag(null, dragShadow, position, 0);
return true;
}
});
Then in your OnDragListener...
#Override
public boolean onDrag(View eventView, DragEvent event) {
Integer dragViewPos = ((Integer) event.getLocalState());
int eventViewPos = listView.getPositionForView(eventView) - listView.getHeaderViewsCount();
...
}