I need some help with my GridLayoutManager.
If I have an item it should be displayed as normal in the middle.
For two items these should be next to one another.
Example with 2 Items
From three to four items it should be displayed like a square.
Example with 3 Items
Example with 4 Items
If I'm right, this should be the case with spanCount 2. But that with the 5 items (so three in a row) is then problematic
From five items (and up) should each be 3 in a row
Example with 5 Items
It would be especially good if the items in the bottom row were also in the middle
I hope someone can help me and thanks in advance
You can set your span dynamically by subclassing GridLayoutManager like this :
class CustomGridLayoutManager(context: Context) : GridLayoutManager(context, 2) {
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
updateSpanCount()
super.onLayoutChildren(recycler, state)
}
private fun updateSpanCount() {
val colCount = if (childCount <= 4) {
2
} else {
3
}
this.spanCount = colCount
}
}
If you want to center your elements in the last row, it would a little convoluted to do it through GridLayoutManager. You might want to check FlexboxLayout for that purpose. It's a library made by Google itself and has flex box model found commonly in HTML5 / CSS world.
Related
I was designing an Instagram story type template. I am stuck at a very weird problem. I have used recycler view in the main activity.
MainActivity: (This is just the layout shown. I have change the orientation to Horizontal).
My layout:
Then I have designed a custom adapter and in the layout I have used a linearLayout. When clicked on each view It opens a new Activity which shows the whole story content.
Just like in Instagram when a user opens any story, the user can click on the right side of the screen to get to the next story, or left of the screen to get to the previous one. I tried to implement this same functionality. Opening the story was implemented successfully. The problem comes when I added the functionality of right and left click on the screen. I added two button; one to the right and one to the left. The problem is like, if there are currently 3 views visible, then I can navigate in between these stories only and not to the stories which are not visible in the screen because of the recycler view.
The below code is for right and left clicks
leftArrow = findViewById(R.id.leftArrow);
leftArrow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(myAdapter.currentPosition - 1 >= 0) {
int firstVis = MainActivity.linearLayoutManager.findFirstVisibleItemPosition();
MainActivity.linearLayoutManager.scrollToPosition(firstVis - 1);
rightOrLeftClicks(myAdapter.currentPosition - 1);
}
}
});
rightArrow = findViewById(R.id.rightArrow);
rightArrow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(myAdapter.currentPosition + 1 < myAdapter.localDataSet.length) {
int lastVis = MainActivity.linearLayoutManager.findLastVisibleItemPosition();
MainActivity.linearLayoutManager.scrollToPosition(lastVis + 1);
rightOrLeftClicks(myAdapter.currentPosition + 1);
}
}
});
}
Below code is of the function rightOrLeftClicks
public void rightOrLeftClicks(int position) {
finish();
Log.d("rl", "working");
nextView = MainActivity.linearLayoutManager.getChildAt(position);
Log.d("ll", "The last element is " + MainActivity.linearLayoutManager.findLastVisibleItemPosition());
if(nextView != null)myAdapter.onClickView(position, myAdapter.localDataSet[position].symptom, nextView);
}
Below code is for onClickView, It is same code for clicking any view (Story) or clicking the right or left buttons. Basically I just opened the another activity by passing an intent with the next or previous view I obtained when clicked on right or left respectively.
public static void onClickView(int position, String element, View v) {
Intent intent = new Intent(v.getContext(), MainActivity2.class);
idPosition = "";
idPosition = element;
ArrayList<String> passingContent = new ArrayList<>();
currentPosition = position;
passingContent.add(localDataSet[position].description);
passingContent.add(localDataSet[position].imageUrl);
intent.putExtra(element + "", passingContent);
v.getContext().startActivity(intent);
}
The problem is it only gives the visible views to me. Not the rest of the views. I tried auto scrolling so that the next or the previous view become visible but it doesn't seems to work. The last visible position of the child remains the same and it return a null view to me when I try to open the view just after the last visible view.
Assume there are 5 stories. Then I will see only 3 stories in the screen the rest can be seen by scrolling. When I click any of the stories. I can only navigate between 1,2 and 3. When I try to click right from story 3 to see the story 4. The current story is killed by finish() function and null is returned in the nextView variable because of which the story 4 is not loaded and I am returned to the MainActivity.
I have to navigate to all stories present in my recycler view.
Do these changes may help:
1/
I think you should use ViewPager. It automatically snaps to the item without scrollTo to index. Or keep using RecyclerView with the help of SnapHelper
2/
Modify onClickView method not to be static, and don't need View v to work. I see you just need View v just for the context. Why not just pass the context to that method. It's not proper to use a parameter like that, and that approach traps you into 3/ problem.
3/
nextView = MainActivity.linearLayoutManager.getChildAt(position);
You already know how RecyclerView works, just avoid using getChildAt because in some cases, the child you want hasn't been created yet. Base on your code, I think you don't even need to get the nextView
As told by Tam Huynh. The 1st point helped me in getting the new views in the linear layout. The views were working the fine.
There was a problem in the parameter of the function getChildAt. I have to pass the same position I was in previously. Because relatively the position(index) will not change for the child views.
index 0 -> story number 1
index 1 -> story number 2
index 2 -> story number 3
Like if there were story number 1,2 and 3 visible, the index 0 will contain 1st story, index 1 will contain 2nd and index 2 will contain 3rd story. when user clicked the right button on the 3rd story. The 4th story is first visible and now the screen have 2,3 and 4 visible. but the index of the 4th story will remain 3rd only. As now indexing will be like
index 0 -> story number 2
index 1 -> story number 3
index 2 -> story number 4
So, instead of passing position as parameter, currentPosition should be passed in getChildAt
I have a RecyclerView that displays a vertical list of strings:
Row0
Row1
Row2
Row3
Row4
...
I'm using the function recyclerView.scrollToPosition(position); to jump to a row. However, the row I want to jump to, ends up on the BOTTOM of the view!
For example if I do recyclerView.scrollToPosition(17); I get:
Row13
Row14
Row15
Row16
Row17 <--- 17 is at bottom (last visible row)
What I want is:
Row17 <-- 17 to be on top (first visible row)
Row18
Row19
Row20
Row21
How can I achieve this?
The default behavior of the .scrollToPosition() is to stop scrolling once the row you scroll to shows up on the screen. You can use scrollToPositionWithOffset() with a fixed offset, so that it sums up to the scroll value.
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager != null) {
layoutManager.scrollToPositionWithOffset(position, 20);
}
UPDATE
how can I compute the offset value? Each row in my RecyclerView has a different height. Also I don't see how to measure it.
Now you can compute the difference between the first and last visible items on the screen and that will work only when the last visible item on the screen is current your item that you want to push to first.
layoutManager.scrollToPosition(position));
int firstItemPosition = ((LinearLayoutManager) recyclerview.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
int lastItemPosition = ((LinearLayoutManager) recyclerview.getLayoutManager())
.findLastCompletelyVisibleItemPosition();
layoutManager.scrollToPositionWithOffset(position,
Math.abs(lastItemPosition - firstItemPosition));
I got 3 types of items I want to add to my RecyclerView, a button (item with a button inside of it), a dummy (item with empty display), a normal item (item with some stuff inside of it).
I'm going to write a scenario that describes the adding/remove process when it should work but it isn't for some reason.
Scenario:
-add a button + dummy to RecyclerView, they get displayed just fine.
WidgetItem dummy = new WidgetItem(true);
arrayList.add(0, dummy);
ca.notifyItemInserted(0);//ca is my adapter
WidgetItem btnHolder = new WidgetItem();
btnHolder.setButton(true);
arrayList.add(0, btnHolder);
ca.notifyItemInserted(0);
-add another normal item + 2 dummies -->fine
if (nbrItems % 3 == 0) {
for (int i = 0; i < 2; i++) {
WidgetItem dummy = new WidgetItem(true);
dummy.setTilte("dummy" + i);
arrayList.add(0, dummy);
ca.notifyItemInserted(0);
}
}
arrayList.add(0, wItem);
ca.notifyItemInserted(0);
-remove 1 dummy + add a normal item -->fine
if ((nbrDummy == 3)||(nbrDummy == 2))
for (int i = 0; !foundDummy; i++) {
if (cii.get(i).getDummy()) {
arrayList.remove(i);
ca.notifyItemRemoved(i);
foundDummy = true;
}
}
arrayList.add(0, wItem);
ca.notifyItemInserted(0);
-remove 1 dummy + add a normal item (the same as above) -->not fine
This's when the problem happens and instead of getting a 3rd 'normal item' i get another dummy instead in the display.
What confused me was that the "onCreateViewHolder" method in my custom adapter didn't even get called at this step while it got called just fine in the previous ones.
Even if the item that just got displayed was a dummy the "onCreateViewHolder" method should execute at least like how it should.
But the code just skipped right to the "onBindViewHolder" method.
I'm suspecting that maybe something happened after the deletion of that dummy but then again why it worked fine in the code above.
I tried "notifyItemRangeChanged" after deleting the dummy but the whole recyclerView got messed up.
So yeah, what's going on here?
My problem was due to how RecyclerView works I suppose.
After I delete an item from the list and create another, the same old back item that I deleted come back instead.
So after trying a couple of things i found out that putting recyclerView.getRecycledViewPool().clear(); between the items deletion and the creation code solved my problem.
I want to Highlight a single row of recyclerview with sound one after another and scroll the highlight row to top of screen This is what i have done:
Here its code:
fun AnimateToSurahAlFeel(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, currentPosition: Int) {
var position: Int = currentPosition / 1000
when (position) {
0 -> {
recyclerView.smoothScrollToPosition(0)
recyclerView.getChildAt(0).isSelected = true
}
4 -> {
recyclerView.smoothScrollToPosition(1)
recyclerView.getChildAt(0).isSelected = false
recyclerView.getChildAt(1).isSelected = true
}
11 -> {
recyclerView.smoothScrollToPosition(2)
recyclerView.getChildAt(1).isSelected = false
recyclerView.getChildAt(2).isSelected = true
}
17 -> {
recyclerView.smoothScrollToPosition(3)
recyclerView.getChildAt(2).isSelected = false
recyclerView.getChildAt(3).isSelected = true
}
21 -> {
recyclerView.smoothScrollToPosition(4)
recyclerView.getChildAt(3).isSelected = false
recyclerView.getChildAt(4).isSelected = true
}
28 -> {
recyclerView.smoothScrollToPosition(5)
recyclerView.getChildAt(4).isSelected = false
if (recyclerView.getChildAt(5) != null)
recyclerView.getChildAt(5).isSelected = true
}
}
}
In the Function currentPosition is Media player current position
Problem in this code is:
In the Screen shot Row 4 and 5 are currently not visible,when highlighting Row 4 and 5 the App crash and giving Null Pointer Exception, according to my knowledge these two row are not yet created that's why
recyclerview.getChildAt(4) or recyclerview.getChildAt(5) return null and that cause the App crash.
Now
How to fix the App crash that recyclerview.getchildAt(4) or recyclerview.getchildAt(5) return null and also getChildAt(position) return n-1 row, so the App crash at recyclerview.getchildAt(5) will occur anyhow but i want n Row because i want to highlight all rows
How to scroll the highlighted row to position 0 (at top)
i.e. Row 0 go up from screen and Row 1 take it position and so on...
I want to achieve like this the highlighted one is at top and that will go off from screen when another row is highlighted
You need time for View to Bind. Just for ex. you can post action.
....
17 -> {
recyclerView.smoothScrollToPosition(3)
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
recyclerView.getChildAt(2).isSelected = false
recyclerView.getChildAt(3).isSelected = true
}, 500);
}
....
But I strongly recommend you use some state Collection, which will save and handle states of your running and showing Views.
This is because recycler views don't have all the views inflated, but only the visible ones. This is by design and should not be tinkered with. Personally, I think you should use the recycling functionality.
You need to make the selected state part of your model in the adapter - the items in the adapter. Let's say this is called RowItem, so in your adapter you'd have a list of RowItems for example. Aside from the text in both languages, you need to add the selected state too. Then it's just a matter of getting the list's adapter, setting the correct position to selected and deselecting the ones you want.
For example, in your adapter:
fun select(position: Int) {
data[position].selected = true
notifyItemChanged(position)
// deselect all other positions that you don't want selected
}
When you bind the view holder you could do then:
public void onBindViewHolder(ViewHolder viewHolder, int position) {
val item = data[position]
viewHolder.itemView.selected = item.selected
// take care of the rest of the views
}
data would be a list where you store your RowItems
Now you can scroll with no problem and set the item to selected. Once the view is visible in the recycler view, the adapter will set the correct state.
It's fair to say I'm guessing a bit since there's no adapter code in your question, but the idea I think it's easy to understand. You need to change the state in the adapter and let the implementation of the recycler view handle it for you. After all the purpose is to get the recycler view to recycle your views based on the models adapted by the adapter.
Remember you can always get your adapter from the recycler view itself. In the end you can do something like this:
...
0 -> {
recyclerView.smoothScrollToPosition(0)
(recyclerView.adapter as MyAdapter).select(0)
}
Here MyAdapter would be the class name of your adapter
For the scrolling part you can take a look at this
Hello Can i make like this image with recycle-view layout manger?
and this image show what i want to do thank in advance.
Use ReycyleView using GridLayoutManager
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (position == 0) {
return 2; // Splitting into 2 columns
} else {
return 3; // Splitting into 3 columns
}
}
});
Yes u can ...in recycler_adapter check if position == 0 then make the two cardsview visible and set them... and in else {cardview4 ,cardview5,cardview6 visible and set them and make cardview1 and cardview2 Gone the inflate layout will contain a relative lat
yout with two horizontal linearlayouts just hide make the layouts gone and visible at proper position in onBind()
for achieving this view i think GridLayoutManager with it's setSpanSizeLookup() method is the best choice. you can find complete explanation in this answer. In case you face any problem while implementing you can ask.