I'm implementing an Android TV app and I'm using HorizontalGridView from the Leanback library. I have a custom layout.
I have to scroll the HorizontalGridView to specific position, after activity is created, but unfortunately the scrollToPositio(position) method is not working on this layout at all. It just do nothing. I found, that when I specifically set the layout manager to LinearLayoutManager it works. But the problem is, that when I'm not using leanback default HorizontalGridView LayoutManager, there is a problem with focusing next items using D-pad.
Basically if I use normal RecyclerView, the control with D-pad is not working as expected, so I decided to go with leanback implementation, where this problem is solved, but so far I cannot make it work with scrollToPosition method.
Any ideas?
Snippet of my code:
Layout:
<android.support.v17.leanback.widget.HorizontalGridView
android:id="#+id/photo_gallery_recycler"
android:layout_width="match_parent"
android:layout_height="#dimen/gallery_image_size"
android:clipChildren="false"
app:itemView="#{viewModel.photoItemView}"
app:items="#{viewModel.photosUrl}"/>
Code [Kotlin]:
binding.photoGalleryRecycler.scrollToPosition(position)
binding.photoGalleryRecycler.getChildAt(position)?.requestFocus()
And I also tried some hacks like this:
// save default leanback layout manager
var defaultLayoutManager = binding.photoGalleryRecycler.layoutManager
// set LinearLayoutManager
binding.photoGalleryRecycler.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
// scroll to position and request focus
binding.photoGalleryRecycler.scrollToPosition(position)
binding.photoGalleryRecycler.getChildAt(position)?.requestFocus()
// set default layout manager back to the view
binding.photoGalleryRecycler.layoutManager = defaultLayoutManager
you need to be using setSelectedPosition(position)
If animation is required you can try setSelectedPositionSmooth(position)
setSelectedPosition Developer docs.
Related
I am trying to convert the usual use of findViewById to a databinding. It seems pretty straight forward but the complexity is that the view is inside a GridLayout and each view inside has been tagged so I can find them. The current code is :
val layout: GridLayout =
binding.quickCommandGridLayout
val view: CardView = layout.findViewWithTag(name.toString())
val tileLayout: CardView = view.findViewById(R.id.tileLayout)
So I am using the name to get the proper CardView I am looking for and then be able to access the element inside this CardView it's working fine the way it is but almost the entire app is using databinding so I would like consistency in the app transform my findViewById to databinding instead.
Any idea?
Thanks
So I've spent the last few hours trying to create a horizontal scrolling Recyclerview.
I'm pretty sure that I'm following the correct techniques for setting the LayoutManager for the Recyclerview, but it just won't work. on the preview it works, but once I push it to the Emulator, it still renders the list in vertical mode instead of horizontal.
I haven't made any references/changes to the Recyclerview programmatically (I tried setting the LayoutManager before, but it also didn't work).
Screenshot showing the issue:
try adding this code to your java class in the onCreate Method
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));//set your recyclerview layout to hoizontal
also try posting your java or kotlin code next time to get better feedback.
You should call Layoutmanager from fragment/activity. If you are using Kotlin, do this
XML
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Fragment/Activity
val layout = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recycler.layoutManager = layout
*Change this with requireContext() if in fragment
I am using WearableRecyclerView to create a curved layout,but the default scrollbar is still vertical.Is there a way to create a curved scrollbar like android wear 2.0 launcher?
Actually, the scrollbars are circular for any scrollable View that takes up the whole screen. It's a framework feature for Wear 2.0.
If the scrollbars are still vertical, make sure your View really does fill up the whole screen - set it to match_parent and as a top level root View.
use boxinsetlayout
//
android.support.wearable.view.BoxInsetLayout
app:layout_box="left|bottom|right"
...Your list View and other contents
android.support.wearable.view.BoxInsetLayout>
and if you are using wearableRecyclerView
do CircularChildLayoutManager mChildLayoutManager = new CircularChildLayoutManager(mContext);
and set this as layout manager for your recycler view.
mRecyclerView.setLayoutManager(mChildLayoutManager);
This may solve for you.
The API was renamed to CurvedChildLayoutManager
So use
val layoutManager = CurvedChildLayoutManager(this)
recyclerView.layoutManager = layoutManager
PS: as for topic question, you dont need app:layout_box just use android:scrollbars="vertical" on your WearableRecyclerView
https://developer.android.com/reference/android/support/wearable/view/CurvedChildLayoutManager.html
We've all been advised against nesting views that contain a scrolling mechanism. However, in the latest Android release (5.0), the Phone app caught my attention with what seems to be a ListView inside of a ScrollView.
What really intrigued me was that the scrolling mechanism switched from the ScrollView to the ListView seamlessly.
Notice the content above the tabs is pushed out of view before the actual ListView begins scrolling.
I've tried duplicating this myself, but ended up unsuccessful. Here is the basic approach I was taking...
With a single, continuous touch event (no lifting of the finger) ...
As user scrolls, the ListView slowly covers up the ImageView. Once the ImageView is 100% covered and the ListView takes up the entire screen, the ListView begins to scroll.
I'm currently listening to touch events on the ListView and if the top has been reached, call requestDisallowInterceptTouchEvent on the ListView, i.e.
#Override
public boolean onTouch(View v, MotionEvent event) {
if (listViewAtTop) {
v.requestDisallowInterceptTouchEvent(true);
} else {
v.requestDisallowInterceptTouchEvent(false);
}
return false;
}
The switching scrolling context works, only if you lift your finger and continue scrolling.
Is there a different approach that will achieve the desired effect?
Android 5.0 Lollipop (API 21) added nested scrolling support.
From what I can tell, both ListView (AbsListView) and ScrollView support this now (if running on API 21), but it must be enabled on the scrolling views.
There are two ways, by calling
setNestedScrollingEnabled(true) or with the layout attribute android:nestedScrollingEnabled="true" (which is undocumented)
To learn about how it works, or to support this for a custom widget, the key methods are these:
onStartNestedScroll
onNestedScrollAccepted
onNestedPreScroll
onNestedScroll
onStopNestedScroll
Unfortunately, there is no guide or training which explains how this works other than the JavaDoc itself which is rather light and there are no examples other than ScrollView.
Add latest support package 'com.android.support:support-v4:22.1.1' to your project. And try this:
<android.support.v4.widget.NestedScrollView
android:id="#+id/nScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<FrameLayout ...>
<ListView ... />
</FrameLayout >
</android.support.v4.widget.NestedScrollView>
By default nested scrolling is Enabled.
While trying to figure out how to solve this issue myself, I found this question first; however, the answer didn't really go into too much detail. I did find a lot of good resources, so if anyone else finds themselves looking for this, I'll link them below. A term for this effect is "Sticky Scrolling".
An article talking about "Synchronized Scrolling".
http://www.pushing-pixels.org/2011/07/18/android-tips-and-tricks-synchronized-scrolling.html
A good video showcasing some Android scrolling tricks, "Quick Return" and "Sticky Scrolling".
https://www.youtube.com/watch?v=PL9s0IJ9oiI
Code:
https://code.google.com/p/romannurik-code/source/browse/misc/scrolltricks
And lastly, here is another one showcasing the same effect using a listView instead of a ScrollView.
https://www.youtube.com/watch?v=rk-tLisxSgM
Code:
https://github.com/jatago/list_sticky_scroll_trick
I found an alternative 'trick' which is quite simple... Use only a ListView with an added transparent header.
I have been wanting to achieve the same effect as well. I came up finding a relevant library called ObservableScrollView in GitHub and it requires more work on the back-end via a TouchInterceptFramework but at least it did the job even for pre-lollipop devices. It also supports not only child scrollviews and listviews but also recyclerviews. Here's the link:
https://github.com/ksoichiro/Android-ObservableScrollView
I hope they consider nested scrolling for both lollipop and pre-lollipop devices as a part of their design standard soon. This is a good sign.
This is classic example of dummy layouts. Something not entirely obvious at first look. Basically the scenario is something like this.
Grey Area->FrameLayout
Followed by a listview that fills up the entire framelayout and followed by a imageview that overlaps the top half of a listview. The listview's first item is a dummy item and has a height identical to that of the imageview.
(Note: The actual data starts from the second element)
Next Step is easy
Translate the Imageview as per the scroll of the listview.
I suppose this is the best way to do that whilst avoiding nested scrolling
You can use the following combination of attributes on your ListView to achieve this:
<ImageView ... /> <!-- must be before ListView -->
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="..." <!-- height of imageView -->
android:clipToPadding="false"
...
/>
You don't have to manage any scrolling in your code at all, and it requires no header/dummy views in your list adapter.
I am using something like this and it works ok I think
scrollView.onScroll { x, y ->
Timber.d("ScrollView offset: ($x, $y)")
val height = dashboardChart.measuredHeight
val recyclerView = viewPager.findViewById<RecyclerView>(R.id.recyclerView)
if(y >= height) {
Timber.d("ScrollView enable nested scrolling!")
recyclerView.isNestedScrollingEnabled = true
} else {
Timber.d("ScrollView disable nested scrolling!")
recyclerView.isNestedScrollingEnabled = false
}
}
Where scrollView is parent I am listening onScroll event (it is extension underneath it is viewTreeObserver.addOnScrollListener). Then depending whether I've scrolled initial offset or not I am enabling/disabling child recyclerView (similary ListView or other scrollView) scrolling.
I have a nested layout like the following:
<LinearLayout> <!----Parent layout--->
<LinearLayout> <!-----child 1--->
...
</LinearLayout> <!----child 1 ended--->
<LinearLayout> <!-----child 2--->
...
</LinearLayout> <!----child 2 ended--->
</LinearLayout> <!----Parent endded--->
The problem I am having now is that since all my data items are within child 1 or child 2 Linearlayout, if I add or delete a item the child linearlayout will animated with the effect of animateLayoutChanges but the parent layout will not do any animation. (I have android:animateLayoutChanges set to true for all linear layouts). Especially when I delete an item within child 1 the animation effect becomes weird (basically child 2 will jump up while child 1 is still doing its animation).
Does anybody have any idea how to solve this?
Thanks
UPDATE
Shortly after I posted this question, I found this on android developer's site in the LayoutTransition API.
Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the interrelationship of the various levels of layout.
So does anyone have any work around suggestions for this issue?
The animateLayoutChanges property makes use of LayoutTransitions, which animate both the layout's children and, from Android 4.0 onward, ancestors in the layout hierarchy all the way to the top of the tree. In Honeycomb, only the layout's children will be animated. See this Android Developers Blog post for details.
Unfortunately, it seems that there's currently no simple way to have the siblings of a layout react to its LayoutTransitions. You could try using a TransitionListener to get notified when the layout's bounds are being changed, and move the sibling views accordingly using Animators. See Chet Haase's second answer in this Google+ post.
EDIT - Turns out there is a way. In Android 4.1+ (API level 16+) you can use a layout transition type CHANGING, which is disabled by default. To enable it in code:
ViewGroup layout = (ViewGroup) findViewById(R.id.yourLayout);
LayoutTransition layoutTransition = layout.getLayoutTransition();
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
So, in your example, to have child 2 layout animated, you'd need to enable the CHANGING layout transformation for it. The transformation would then be applied when there is a change in the bounds of its parent.
See this DevBytes video for more details.
Ok, after digesting the first answer, I make it simple here, for those who don't get proper animation result when using android:animateLayoutChanges="true" in NESTED layout:
Make sure you add android:animateLayoutChanges="true" to the will-be-resized ViewGroup (LinearLayout/RelativeLayout/FrameLayout/CoordinatorLayout).
Use setVisibility() to control the visibility of your target View.
Listen carefully from here, add android:animateLayoutChanges="true" to the outer ViewGroup of your will-be-resized ViewGroup, this outer ViewGroup must be the one who wraps all the position-changing View affected by the animation.
Add following code in your Activity before the setVisibility(), here the rootLinearLayout is the outer ViewGroup I mentioned above:
LayoutTransition layoutTransition = rootLinearLayout.getLayoutTransition();
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
Before:
After:
Reminder: If you miss the 3rd step, you will get null pointer exception.
Good luck!
As a Kotlin Extension
fun ViewGroup.forceLayoutChanges() {
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
}
Usage
someContainer.forceLayoutChanges()
Notes:
In my experience, this happens when the container is a deep nested layout. For some reason android:animateLayoutChanges="true" just doesn't work, but using this function will force it to work.
We had added the android:animateLayoutChanges attribute to our LinearLayout but the change didn’t trigger an animation. To fix that, use this code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
((ViewGroup) findViewById(R.id.llRoot)).getLayoutTransition()
.enableTransitionType(LayoutTransition.CHANGING);
}
More details.
It seems that a delayed transition on the parent also works for animating. At least for me the following code gives a proper expand/collapse animation.
expandTrigger.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition(parentLayout);
expanded = !expanded;
child1.setVisibility(expanded ? View.VISIBLE : View.GONE);
}
});
For deeply nested layouts you sometimes might need to use a parent higher up in the hierarchy in the call to the TransitionManager.
I had a similar issue:
I was using a animatelayoutchanges in one of my activity with a recycler view, I also added some custom layout transition because I wanted to increase speed of the animation while an item disappears in the list. It was working fine when it was not in a nested layout.
I had used the same adapter for another recyclerview which was in a nested layout. It was not working and I tried all the above solutions, None worked for me.
The real reason was, I forgot to set
mTicketsListAdapter.setHasStableIds(true);
in the nested layout activity. And after setting setHasStableIds to true, the animations was working perfectly in the nested layout.