Android ScrollView isFinished? - android

Just looking for an easy answer that I can't find on Google. Simply put. Is there a way to tell when ScrollView has stopped scrolling after a fling?

Yes, after fling it comes in ideal state which means that your fling is over. For this you have to put listener on your list view Like list.setOnScrollListener(this). Then you have to check in the listener that if it's in ideal state or not. like this
public void onScrollStateChanged(AbsListView view, int scrollState)
{
if(OnScrollListener.SCROLL_STATE_IDLE)
{
your code;
}
}

Related

Detect which item is currently scrolled to in RecyclerView

I'm trying to implement an autoplay feature for video items in a RecyclerView (linear vertical layout). I can't figure out how to know when a certain item is currently on/off the screen so I can autoplay/pause the video. If I put the code in onBindViewHolder method all videos start playing simultaniously. Couldn't find a solution by googling it either. Help, please!
For Recyclerview you should rely on your layout manager to give you this information.
https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager
or
https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager
Assign a layout to the RecyclerView when you assign the adapter. Then use it to see what is visible.
Let me help a bit more with some Psedo Kotlin code for you to help with the player aspect.
Let's pretend you have some object that is bound into each row that can trigger the playing and has a unique ID. Let's call that an ActiveRowPlayer for this example.
NOTE*
If you are using databinding, it is simple to bind your video player playing content to a property in your model that is populating the row, but that's different story for a different post.
You can make an interface like:
interface IActivePlayerUpdater{
fun onUpdateCurrentPlayer
}
You can make a helper method like:
in your activity, you can implement an interface like :IActivePlayerUpdater and override the methods for it.
override fun onUpdateCurrentPlayer(){
var activeRowPlayer = recyclerView.layoutManager.findFirstCompletelyVisibleItemPosition()
if(activeRowplayer.someID != currentRowPlayer.someID){
currentRowPlayer.stopPlaying
currentRowPlayer = activeRowPlayer
activeRowPlayer.startPlaying
}
}
Then pass it into your adapter and just monitor your onBind method and anytime a new onBind is called that means the content has moved enough to trigger a new row item.
MyAdapterConstructor(IActivePlayerUpdater myCallback)
fun recyclerView.onBindMethod(stuffThatComesHere){
//do normal stuff
myCallback.onUpdateCurrentPlayer()
}
Keep in mind, this is just pseudo to help you on your journey. not intended to be direct copy and paste.
----NOTE* REQUESTED FROM COMMENT TO SUPPLY HOW TO TOUCH VIEWMODEL FROM OUTSIDE OF ADAPTER---
#Goran, this example I had setup a long time ago to avoid
notifyDataSetChanged on selection changed to toggle a checkbox for
each item. I eventually moved to a better option, but you asked how do
you get the viewmodel, here is a sample. rvCameraRoll was my
recyclerView, I was using it to display camera media, but that is not
relevant, just focus on getting the viewModel piece.
The only part you should care about is getting the ViewHolder, I just left the rest there in case it helps you with anything else.
int count = rvCameraRoll.getChildCount();
for (int i = 0; i < count; i++) {
MediaModelGridAdapter.ViewHolder childRow = (MediaModelGridAdapter.ViewHolder)rvCameraRoll.getChildViewHolder(rvCameraRoll.getChildAt(i));
if(isVisible) {
childRow.imgCellSelected.setVisibility(View.VISIBLE);
if(getMediaModelList().get(i).getIsSelected()){
childRow.imgCellSelected.setBackgroundResource(R.drawable.ic_ap_selected_on);
}
}else{
//check that exists, because after fresh delete list may be short while updating cells
if(getMediaModelList().size() > i) {
getMediaModelList().get(i).setIsSelected(false);
}
childRow.imgCellSelected.setBackgroundResource(R.drawable.ic_ap_selected_off);
childRow.imgCellSelected.setVisibility(View.GONE);
}
}
OLD
leaving this for anyone using a ListView
There is a OnScrollListener for ListView
You can override the
onScrollStateChanged(AbsListView view, int scrollState)
and
the
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
this will give you the ListView item that is visible. So by using the onScroll you can detect which items are visible and determine which one to play and stop playing.
if you use with recyclerview
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener
The RecyclerView is only holding the items and the LayoutManager is responsible for displaying the items, so in order to get the ones that are visible to the user, assuming that you use LinearLayoutManager, you should call :
((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
or
((LinearLayoutManager)recyclerView.getLayoutManager()).findLastVisibleItemPosition();

Synchronize scrolling of two ScrollViews inside a RelativeLayout

I need to synchronize the scrolling positions of two ScrollViews.
Both ScrollViews contain their individual RecyclerView (I can't put them inside same ScrollView due to design requirements).
How can I achieve that?
I highly recommend that you CHANGE YOUR DESIGN but there is a way to achieve this
for first scroll view, define two variable x,y and get current scroll position
x = firstScrollView.getScrollX();
y = firstScrollView.getScrollY();
and pass these values to other scrollView "scrollTo" method.
like:
ScrollView secondScrollView = (ScrollView)findViewById(R.id.scrollView);
secondScrollView.scrollTo(x, y);
if didn't work try:
secondScrollView.post(new Runnable() {
#Override
public void run() {
secondScrollView.scrollTo(x, y);
}
});
First of all, use NestedScrollView from Support library since it enables us to listen scroll events easily. Then, set onScrollChange listeners for both NestedScrollView you have. When you receive scroll change for scrollView1 for instance, call scrollTo(...) for scrollView2:
scrollView1.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
scrollView2.setOnScrollChangeListener(null);
scrollView2.scrollTo(scrollX, scrollY);
scrollView2.setOnScrollChangeListener(...); //SET SCROLL LISTENER AGAIN
}
});
Before calling scrollTo(..) for scrollView2, remove its listener, then add it again. Otherwise, scrollTo(..) you call can cause infinite calls in both listeners of NestedScrollViews.
And of course, you need to write similar above code for your scrollView2.
Attach an RecyclerView.OnScrollListener to both RecyclerViews via addOnScrollListener(RecyclerView.OnScrollListener listener).
Update the position of the other one when the event is fired via: scrollTo(int x, int y).
Remember to somehow distinguish between a user-triggered scroll and a programmtic scroll (from the other RecyclerViews event).
One way would be, like Ugurcan Yildirim suggested, to detach the listener of the ScrollView you want to update.
Another option is a boolean flag isUserTriggered. A third way would be to determine which ScrollView currently has the focus to distinguish (see here).

RecyclerView onScrollStateChanged/onScrolled event is not fired when no scrolling occurred

Doesn't sound like a problem, right?) Let me explain the use case. Let say at the end of every scroll event you want to animate/highlight the target item:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
#Override
public void onScrollStateChanged(final RecyclerView recyclerView,final int newState){
super.onScrollStateChanged(recyclerView,newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) { // on scroll stop
blink();
}
}
}
...
recyclerView.scrollToPosition(targetPosition);
Works as expected, as long as current position is different from the target one and there is something to scroll actually. What is also very possible is that the list is already in the right position and calling scrollToPosition() has no effect, onScrollStateChanged() is not triggered, as well as our animation logic. As a workaround I can think about this:
final int firstVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
if (firstVisibleItemPosition == targetPosition){
blink();
} else {
[previous code block]
}
but is it really the best possible solution? It sounds natural to me to have some consistent onScrollStop() callback after every scrollToPosition() call, regardless of the visual list movement, because it's none of my business to predict what is going to happen on the screen (will it move, or not?) and try to hack that. All I know, as an API user is that scrollToPosition() was called and I expect the appropriate callback to be fired.
Fun fact: if you extend RecyclerView.SmoothScroller and override onStop() method then you're actually notified all the time. The issue with this approach is that it's triggered slightly before the list stop scrolling ¯\_(ツ)_/¯.

Programmatically "swipe-to-dismiss" an item in a ListView/RecyclerView?

I need to be able to programmatically dismiss an item inside a RecyclerView without the user actually swiping (instead I want to dismiss the item when they tap a button in the card). A lot of libraries I've seen only seem to support actual swipes.
I've tried using an existing library and just simulate a MotionEvent by creating a swipe on my own programmatically, but this interferes with another horizontal-swipe listener, so I'm wondering how else this could be done, ideally for a RecyclerView but if anyone knows how to for a ListView instead I can try to adapt that.
I've looked at this library as well as others for inspiration but I can't figure out how to trigger the swipes programmatically instead.
Use a ListView or RecyclerView with custom adapter, and call notifyDataSetChanged after removing an item from the datalist:
private void removeListItem(View rowView, final int position) {
Animation anim = AnimationUtils.loadAnimation(this,
android.R.anim.slide_out_right);
anim.setDuration(500);
rowView.startAnimation(anim);
new Handler().postDelayed(new Runnable() {
public void run() {
values.remove(position); //Remove the current content from the array
adapter.notifyDataSetChanged(); //Refresh list
}
}, anim.getDuration());
}
Use one of the libraries that offer the swipe to dissmis funcionality ad extract the animation part, if im not mistaken its at the action_up at the onTouch(). Then call it from your onClick of the button.

workarounds for GridView.scrollTo()?

As mentioned here, Android's GridView.scrollTo() doesn't work. The method the solution mentioned, setSelectedPosition, doesn't seem to exist in GridView
smoothScrollToPosition does work, but I really don't want the animation.
For context, I have a CursorAdapter-backed GridView, and I want the view to "reset", i.e. scroll to the top, when I change the cursor.
I've been using setSelection(int position) for this, and it seems to be working just fine. To scroll to the top, just use 0 for position.
From the docs:
If in touch mode, the item will not be selected but it will still be positioned appropriately.
Edit:
Added code to post setSelection as a Runnable:
albumsView.post(new Runnable() {
#Override
public void run() {
albumsView.setSelection(0);
}
});
I have found that in order to reliably use gridView.setSelection(index), I have to first call gridView.setAdapter() (even if the adapter has already been set and has not changed). Like so:
gridView.setAdapter(gridAdapter);
gridView.setSelection(index);

Categories

Resources