I have a ViewPager (using a FragmentStatePagerAdapter to page fragments). The user can use left/right swiping to page from one fragment to the next; this works fine. I've also added < and > buttons for paging. The onClick listeners for these are implemented using:
mViewPager.arrowScroll(View.FOCUS_LEFT);
and
mViewPager.arrowScroll(View.FOCUS_RIGHT);
This works most of the time: when the user taps < or >, the view scrolls left or right as expected.
But on the first tap of the > button, nothing happens. I do hear a "click" confirming that the tap occurred. And I have log statements showing that the onClick listener was called, and mViewPager.arrowScroll(View.FOCUS_RIGHT) was called. I even check the result return by arrowScroll(); it's true! Yet no paging happens.
After that, all tapping on the < and > buttons works fine.
Why would the first call to arrowScroll(View.FOCUS_RIGHT) have no effect, and how can I fix it?
I guess I can try calling it twice the first time, but since I don't know why the documented behavior isn't happening, I don't know whether that approach will cause a double paging on some phones or Android versions.
Update: some logging
// Before any button taps.
QuestionAdapter: getItem(0)
QuestionAdapter: instantiateItem: position 0
QuestionAdapter: getItem(1)
QuestionAdapter: instantiateItem: position 1
QuestionAdapter: setPrimaryItem: position 0
QuestionAdapter: setPrimaryItem: position 0
// The screen is displaying page 0.
// Now I tap the > button:
QuestionAdapter: setPrimaryItem: position 0
// The screen is still displaying page 0.
// Now I tap the > button again:
QuestionAdapter: getItem(2)
QuestionAdapter: instantiateItem: position 2
QuestionAdapter: setPrimaryItem: position 1
QuestionAdapter: setPrimaryItem: position 1
QuestionAdapter: setPrimaryItem: position 1
// Now the screen displays page 1.
OK, I think I'm starting to figure out the problem. When I trace ViewPager.arrowScroll(FOCUS_RIGHT) (source code here) it looks like what it's doing is trying first to move the focus to the right (hmm, maybe that explains why the argument says FOCUS_RIGHT!).
So depending on what currently has focus, and whether there's a nextFocus view that's to the right of it, it will just move focus there, consider its job done, and return true to signal success.
Only if there's not a nextFocus view to the right will it actually pageRight() as I want it to do.
In my case, when I first press >, currentFocus = this.findFocus() returns null. Then it calls nextFocused = FocusFinder.findNextFocus() and comes up with a ListView that's on the currently displayed page. Since currentFocus is null and nextFocus is not (among other conditions), arrowScroll() is satisfied with setting focus to the ListView.
The second time I tap >, currentFocus = this.findFocus() returns the ListView, and nextFocused = FocusFinder.findNextFocus() yields null. Because nextFocused is null, it doesn't try to nextFocused.requestFocus() but instead calls pageRight(), which is what I wanted in the first place.
That being the case, what is the solution that fits the design of the ViewPager? It sounds like arrowScroll() is not intended for just paging left/right, like a left/right button would be expected to do. Instead it's meant to do what a keyboard arrow key should do; hence the name.
So then what method should be used to just page left/right, without regard for what currently has or can get focus?
I could try to work around arrowScroll's behavior by setting focus to the right view before calling arrowScroll, but that seems like a kludge. There are the pageRight and pageLeft methods, which look like they do exactly what I need, but they're not public, nor documented!
Well, pageRight and pageLeft call setCurrentItem(mCurItem + or - 1) to do their work, a method that is public and documented. So I guess I can just copy the code for pageRight and pageLeft into my own code.
This API design seems strange to me, given how common it is to have left/right buttons on a pager screen that are expected to page left/right regardless of focus. It's also frustrating that the documentation for arrowScroll(), and for paging the ViewPager left/right, is so vague.
But in any case I think I've found a decent solution.
Related
I am trying to scroll and request focus to an off screen element from a recycler view. I have a list of 15 languages. When app starts the user can select one language from the recycler. Then if the app starts again the recycler scrolls and request focusto that item.
But imagine the user selects the last language from the list which is not showed in the recycler when the app starts. How to scroll and therefore reqeust focus to that element which is not currently showed in the recycler?
I expected to do something like recycler.scrollToPosition(14) and then scrollToPosition(14) , but the index is outof bunds... I guess thats because the element is not created yet. Any idea?
I had similar scenario and below code worked for me:
(rv_your_recycler_view.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, offset)
Just set offset to 0 and position to 14, this will get you last item.
P.S. If it doesn't work check if your recyclerview's bottom constraint tied up to parent's bottom.
app:layout_constraintBottom_toBottomOf="parent"
If you try to scroll and set the focus at the same time then it may fail. While the scrolling will proceed, the request for the focus will fail if the item is not yet present in the RecyclerView. (As you state.)
The solution is to separate the two activities. Scroll to the position as you do now, but set the focus when the item is bound to the view holder in the adapter. This will require you to keep track of which item has the focus so that it can be referenced in the adapter.
I have created a custom TextView with an X button, whose visibility are set to GONE when the button is clicked. Now I want to get the number of visible TextViews in the LinearLayout. Currently, I am getting the count of total TextViews inserted rather than the visible ones.
Example:
When I have 2 TextViews, getChildCount() gives 2
but if I delete one TextView by clicking the X button, it still gives me 2. Why is this happening?
I have created something like this:
The X here is a button whose onClick() will set the visibility of both TextView and the Button to GONE.
how can I get the count of the visible children?
Well for that you need to iterate over the children of the view/layout and check the visibility. It is a simple loop:
// untested/pseudocode
int visibleChildren = 0;
for (int i = 0; i < layout.getChildCount(); i++) {
if (layout.getChildAt(i).getVisibility() == View.VISIBLE) {
visibleChildren++;
}
}
Rather late to problem posted. Don't think the answers addressed the problem correctly. A few years since the problem of "getChildCount under reporting visible items displayed" was reported, yet my research found no one had fingered out what was causing this problem surfacing in some circumstances.
I had the same problem recently which led me to investigate further. Here's my discovery.
Say if 9 items on RecyclerView are visible. Call getChildCount(). It should return 9, maybe 1 or 2 less/more depending on partially visible items at the top and bottom. This will be the result most of the time ... until the soft keyboard shows for some TextEdit input. If you call getChildCount() about 500 msecs after the method call that led to showing the keyboard, the result will be less than 9. It will be the count of items unobstructed by the keyboard view. Weird, even though user didn't change the displayed contents of the RecyclerView. What's weird too is that even after the keyboard is dismissed, and all 9 items are again visible, calling getChildCount() will still return the incorrect under count! This happened with Android 11, and presumably with pre-11s (didn't test with post-11s).
A clue to solving this is the method RecyclerView.postInvalidateDelayed. If you really need to have the correct getChildCount number to work with, do something like this (after the keyboard is dismissed):
myRecyclerView.invalidate();
myRecyclerView.postDelayed(myRunnable, 200);
Subtle problem!
I have a tablet app with a Master / Detail layout, with a TableView in the Master panel that changes the content of the Detail pane. I'd like to have the master's row stay highlighted when clicked like it does in the android OS settings.
Right now the best I'm able to do is set the backgroundColor to a new color in response to a 'click' event. However, when I do a quick tap, the row highlights, then blinks back to normal before highlighting again. I'm guessing this is the delay between when I lift my finger off and when the backgroundColor gets changed.
tableview.addEventListener('click', function(e) {
...
else if (e.rowData.id == 3) { // scan history
e.row.backgroundColor = 'blue';
This appears to be the way others have done it: http://developer.appcelerator.com/question/124359/android---tableviews-deselectrow
And if I was just using android and not Titanium: How can I highlight the table row on click ?
Try this instead. The touch start is called immediately, while the click usually is not. Note that according to the docs a scroll event and a touch event cant happen concurrently for iOS and Android, so you probably want to handle the touchcancel event also if you have a `touchstart`` listener.
tableview.addEventListener('touchstart', function(e) {
...
// Get the source row, and then get its id
else if (e.source.id == 3) { // scan history
e.row.backgroundColor = 'blue';
});
EDIT:
Maybe a better way to do this would be to handle the touchend event. This would handle bothe the quick taps, the user sliding to a different row, and the scrolling edge cases.
When creating your row, use:
selectionStyle : "NONE"
This prevents it from changing color on click/touch
And roll your own event listeners to govern exactly when your row is highlighted.
The iOS blue row selection color is something like: "#006EF1"
I am using Viewflipper in my application where I am getting some(x) number of items in the Viewflipper.
Now what is happening is that it showing the first item in the continuous scroll after the last item.
I need to stop it as the scrolling needs to get stopped in any particular direction after the last item is reached and vice a versa.
Thanks,
David
If you are using timed interval flipping, you can call stopFlipping() when you hit the last view.
If you are doing it manually via showNext() and showPrevious(), then you can simply set a flags in your code to check for the first and last views and handle the swipe actions. For example:
if(lastFlag==true)
{
}
else
{
vf.showNext();
}
Here's a good tutorial on using ViewFlipper:
http://www.techrepublic.com/blog/app-builder/a-dog-limps-into-a-saloon-a-tutorial-on-androids-viewflipper-widget/634
I have an android-app with a listview in an activity. The listview has, if I call it so, three data states.
no data loaded from inet -> only one dummy item is visible, saying that data is loading;
data is loaded and shown in list;
one listitem is clicked and now shows more information for this listitem (so it is increased in its height).
On every state change (1 -> 2, 2 -> 3), I call notifyDataSetChanged() on this ListAdapater.
This causes the listview to scroll down to the last item. This is ugly in the first transition and even more ugly in the second because the clicked list item is now out of focus.
As I can see, this happens with a google g1 with android 1.6. An htc touch with the same sdk acts like desired (I will try to figure it out with some more devices).
To avoid this, I tried to read out getScrollY() and set this value back. but this returns 0. The reason for this return value I already found on stackoverflow in other questions.
Has anyone else seen this problem? Why does the listview scroll to the last item? It was mentioned that listview keeps track of the scroll position. but it seems that it does not in my case.
Or maybe I am calling the wrong refresh method? Is notifyDataSetChanged the correct one?
I believe you want:
listview.setTranscriptMode(ListView.TRANSCRIPT_MODE_DISABLED);