RecyclerView custom LayoutManager remove unwanted views - android-recyclerview

I have a custom LayoutManager (inherited from LinearLayoutManager) that needs to calculate the item width of each child and remove all children from RecyclerView that has no space for them to appear.
Sample code (edited V2):
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
super.onLayoutChildren(recycler, state)
// skip if orientation is vertical, for now we only horizontal custom menu
if (orientation == RecyclerView.VERTICAL) return
// skip if adapter has no items
if (itemCount == 0) return
var totalItemWidth = 0
var totalItemsCanFit = 0
// calculate menu item width and figure out how many items can fit in the screen
for (i in 0 until childCount) {
getChildAt(i)?.let { childView ->
totalItemWidth += getDecoratedMeasuredWidth(childView)
}
if (screenWidth > totalItemWidth) {
totalItemsCanFit++
}
}
// if all items can fit, do nothing and show the whole menu
if (childCount > totalItemsCanFit) {
// remove child views that have no space on screen
for (i in childCount - 1 downTo totalItemsCanFit) {
removeAndRecycleViewAt(i, recycler)
}
}
}
I have 2 questions:
Is the sample code above the correct way to approach this problem?
How can I add a 3-dot icon at the end after seeing that not all items could fit?
EDIT:
To clarify, what I am trying to achieve is a popup menu backed by a RecyclerView. The menu has no item limit, instead it should calculate each item width and remove all items that have no space. Also, add a 3-dot menu item as a more option at the end.

Regarding your first question:
See if addDisappearingView(View child) could help you,
according to the documentation:
To be called only during onLayoutChildren(Recycler, State) to add a
view to the layout that is known to be going away, either because it
has been removed or because it is actually not in the visible portion
of the container but is being laid out in order to inform RecyclerView
in how to animate the item out of view.
As for the second question - you simply need to implement a 'load more' feature to your recyclerView. How you'll implement this is up to your needs/design (if you want a button or auto-scroll...).
There are many tutorials for that, for example: https://androidride.com/android-recyclerview-load-more-on-scroll-example/ .

Related

Custom CollapsingTopAppBar Jetpack Compose

The essence of the problem is that I want to write my own version of the AppBar that would include content as another Compose function. After looking at the source code of the current CollapsingTopAppBar implementation, I saw the following lines:
#Composable
private fun TwoRowsTopAppBar(
...
scrollBehavior: TopAppBarScrollBehavior?
) {
...
val pinnedHeightPx: Float = 64.dp
val maxHeightPx: Float = 152.dp
LocalDensity.current.run {
pinnedHeightPx = pinnedHeight.toPx()
maxHeightPx = maxHeight.toPx()
}
// Sets the app bar's height offset limit to hide just the bottom title area and keep top title
// visible when collapsed.
SideEffect {
if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
}
}
...
Surface(...) {
Column {
TopAppBarLayout(
...
heightPx = pinnedHeightPx
...
)
TopAppBarLayout(
...
heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
?: 0f),
...
)
}
}
}
As I understand it, scrollBehavior is used to handle the collapse and expansion behavior. In the current implementation, just constant values are put in heightOffsetLimit. And since I need my appbar implementation to be able to contain content of any size, I need to somehow know the size of this content in advance and put this value in heightOffsetLimit.
I have already written the code for my AppBar, so that it also contains content. But since I can't pass the height value of the content to scrollBehavior, the AppBar doesn't collapse to the end.
you need to calculate the height that the appbar will have before drawing it into the screen. I have followed this issue and solved my problem with the last solution. hope it helps:
Get height of element Jetpack Compose
use the content you can put (ex. an image or a huge text) as the MainContent
use your appbar as the DependentContent and use the size given in lambda to give the height to your appbar
finally set placeMainContent false as I believe you don't need to draw the image (or any other composable) directly in a box
and you will good to go

Why did the border radius disappear for an RecyclerView item with a custom background?

I try to figure out Android development, and sometimes I have beginner question. For now I faced with issue creating the RecyclerView items with different background color. I created a simple
RecyclerView items list First I set one background (light green) for all items.
Then I decided to set a separate background for each item. Here's how I did it in adapter file:
override fun onBindViewHolder(holder: ChapterListAdapter.ViewHolder, position: Int) {
holder.chapter_item.text = chapter_titles[position]
holder.chapter_details.text = chapter_descrs[position]
holder.chapter_image.setImageResource(chapter_images[position])
when(position){
0 -> holder.chapter_card.setBackgroundColor(Color.parseColor("#ff5668"))
1 -> holder.chapter_card.setBackgroundColor(Color.parseColor("#41d5e2"))
2 -> holder.chapter_card.setBackgroundColor(Color.parseColor("#4d53e0"))
3 -> holder.chapter_card.setBackgroundColor(Color.parseColor("#ff8e36"))
}
}
And it works, it may not be the right way to do it, but it works. However, there is one problem. In the screenshot, you can see that the last item has a border radius. I set the cardCornerRadius value for the element in card_layout.xml And for some reason, when I assign a custom color for an item, this value disappears. This can be seen in the screenshot. The last element with a light green background has a border radius (I did not assign a custom background color value to this element) and the first four elements that have a custom color assigned do not have a border radius.
Please tell me why this is happening and how to fix it. I need to keep the border radius for all elements.
I really glad I fond the error by myself. For setting the item background color I used setBackgroundColor but it's not correct in my case. In my case I have to use setCardBackgroundColor because I setting background for the CardView. Now my code looks:
override fun onBindViewHolder(holder: ChapterListAdapter.ViewHolder, position: Int) {
holder.chapter_item.text = chapter_titles[position]
holder.chapter_details.text = chapter_descrs[position]
holder.chapter_image.setImageResource(chapter_images[position])
when(position){
0 -> {
holder.chapter_card.setCardBackgroundColor(Color.parseColor("#ff5668"))
}
1 -> {
holder.chapter_card.setCardBackgroundColor(Color.parseColor("#41d5e2"))
}
2 -> {
holder.chapter_card.setCardBackgroundColor(Color.parseColor("#4d53e0"))
}
3 -> {
holder.chapter_card.setCardBackgroundColor(Color.parseColor("#ff8e36"))
}
}
}
And it works well.

offset set with getItemOffsets disappears after scroll

I have a horizontal RecyclerView which works with a FlexLayoutManager. I also have some decorations set with RecyclerView.ItemDecoration.
My getItemOffsets method looks something like this:
override fun getItemOffsets(outRect: Rect, view: View, recyclerView: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, recyclerView, state)
val position: Int = recyclerView.getChildAdapterPosition(view)
if(position meets some rules){
outRect.top = some values here
}
if(viewType == CERTAIN_VIEW_TYPE){
outRect.bottom = some value
}
}
..onDrawOver(){
//I draw the decorations here
}
This works, the views that I set as decorations are shown and they are at the right position.
The problem that I have is that AFTER I SCROLL TO RIGHT AND THEN BACK TO LEFT, the offset set by outRect.top is set to 0 and my decorations overlap my items.
The curious stuff is that offset set by outRect.bottom doesn't disappear or cause any issues.
I just specify that the offset set by outRect.top is set only for certain positions.
Also my decorations don't disappear, just the margin set initially by outRect.top is not there anymore
Can you please help me with this issue?
Thank you
EDIT:
This can be the result of view recycling I guess, because I see that after scroll other items now have top offset even though I did not intend to set it for them
I had the same problem with item offsets when scrolling.
My case was ->
I had a RecyclerView with a GridLayoutManager, orientation was horizontal and span count set to 2
I've added a custom item decoration to the RecyclerView related to the requirements that I had, by just adding some space around the items and making calculations.
Used the same overridden function as you and set the value to top, bottom, left and right.
Anyway the problem that I had, was at how I was getting the position of the view that I wanted to make my changes.
val viewHolder = recyclerView.findContainingViewHolder(view) ?: return
val adapterPosition = viewHolder.absoluteAdapterPosition
val childPosition = parent.indexOfChild(view)
val isEvenPosition = adapterPosition % 2 == 0
Previously I was using childPosition but when scrolling I was having the problem, when I've used adapterPosition the views were getting the correct amount of space.

Visibility of expanding last view in the expandable RecyclerView

I want the things in the last expandable item to be fully visible when it is clicked,Now what is happening means when I click on last item it expands down, but I manually need to scroll up again to see the the things inside the expanded item.How can the last expandable item be fully visible.
I am using Recyclerview.
I have found the solution which I wanted, I used
recyclerView.smoothScrollToPosition(selectedPosition);
after setting the adapter. So now the things in the last expandable item is fully visible when it is clicked.
Just a supplementary:
If you use h6ah4i/advrecyclerview, you can use the following code snippet:
#Override
public boolean onHookGroupExpand(int groupPosition, boolean fromUser) {
// NOTE21: collapse all other groups when one item expand.
mExpMgr.collapseAll();
// NOTE21: Visibility of expanding last view in the expandable recyclerview
if (groupPosition == getGroupCount() - 1) {
mRecyclerView.smoothScrollToPosition(groupPosition + getChildCount(groupPosition));
}
return true;
}

How to toggle visibility of NSSplitView subView + hide Pane Splitter divider?

We have a parent Split view (NSSplitView), and two subviews, Content and SideBar (the sidebar is on the right).
What would be the optimal Cocoa-friendly way to toggle the SideBar view?
I would really love it, if the suggested solution includes animation
I really don't need any suggestions related to external plugins, etc (e.g. BWToolkit)
HINT : I've been trying to do that, but still I had issues hiding the divider of the NSSplitView as well. How could I do it, while hiding it at the same time?
Here's a pretty decent tutorial that shows how to do this: Unraveling the Mysteries of NSSplitView.
Hiding the divider is done in NSSplitView's delegate method splitView:shouldHideDividerAtIndex:.
You will have to animate the frame size change yourself if you don't like the way NSSplitView does it.
Easiest way to do it is as follows - and it's animated: [SWIFT 5]
splitViewItems[1].animator().isCollapsed = true // Show side pane
splitViewItems[1].animator().isCollapsed = false // hide side pane
I wrote a Swift version of the content in the link from #Nathan's answer that works for me. In the context of my example splitView is set elsewhere, probably as an instance property on an encompassing class:
func toggleSidebar () {
if splitView.isSubviewCollapsed(splitView.subviews[1] as NSView) {
openSidebar()
} else {
closeSidebar()
}
}
func closeSidebar () {
let mainView = splitView.subviews[0] as NSView
let sidepanel = splitView.subviews[1] as NSView
sidepanel.hidden = true
let viewFrame = splitView.frame
mainView.frame.size = NSMakeSize(viewFrame.size.width, viewFrame.size.height)
splitView.display()
}
func openSidebar () {
let sidepanel = splitView.subviews[1] as NSView
sidepanel.hidden = false
let viewFrame = splitView.frame
sidepanel.frame.size = NSMakeSize(viewFrame.size.width, 200)
splitView.display()
}
These functions will probably methods in a class, they are for me. If your splitView can be nil you obviously have to check for that. This also assumes you have two subviews and the one at index 1, here as sidePanel is the one you want to collapse.
In Xcode 9.0 with Storyboards open Application Scene select View->Menu->Show sidebar. CTRL-click Show Sidebar, in sent actions delete the provided one, click on x. From the circle CTRL drag to First Responder in application scene and select toggleSideBar to connect to. Open storyboard and select the first split view item and in attributes inspector change behaviour from default to sidebar. Run and try with view menu item show/hide. All done in interface builder no code. toggleSideBar handles the first split view item. https://github.com/Dis3buted/SplitViewController
I got some artifacts with the code above, likely because it was out of context. I am sure it works where it was meant to. Anyway, here is a very streamlined implementation:
// this is the declaration of a left vertical subview of
// 'splitViewController', which is the name of the split view's outlet
var leftView: NSView {
return self.splitViewController.subviews[0] as NSView
}
// here is the action of a button that toggles the left vertical subview
// the left subview is always restored to 100 pixels here
#IBAction func someButton(sender: AnyObject) {
if splitViewController.isSubviewCollapsed(leftView) {
splitViewController.setPosition(100, ofDividerAtIndex: 0)
leftView.hidden = false
} else {
splitViewController.setPosition(0, ofDividerAtIndex: 0)
leftView.hidden = true
}
}
To see a good example using animations, control-click to download this file.
If your NSSplitView control is part of a NSSplitViewController object, then you can simply use this:
splitViewController.toggleSidebar(nil)