Android:getting number of views in recyclerview - android

I am implementing recyclerview with multiple layouts.Usually we have multiple viewholders for different layouts and override other methods as per the required layout.I have successfully implemented this.But now i have a different scenario like: A recyclerview that shows some videos (say 3) then another layout(say layout x), again 3 videos and then again layout x and so on.Suppose i have 10 videos then in this case the itemcount would be 10 + 3 as 3 layout x would be displayed.But the videos are loaded while scrolling.So how can i determine the number of views to return in getItemCount();
I mean
#Override
public int getItemCount() {
return ListofVideos.size() + "WHAT??"
}
layout is like this
If all the videos are loaded at at once then it is easy to calculate the number of views like if i have 21 videos i would have total 27 views(i.e 21 videos and 6 layout X views). But when the list is loaded on scroll how can i determine the number of views?

Your Adapter is responsible to populate view so it has all views of your RecyclerView while your ListofVideos (may) have only video links.
Whenever you scroll your RecyclerView, Adapter is responsible to inflate views.
What you should do?
Create an interface
public interface BaseItem {
int ITEM_TYPE_HEADER = 0;
int ITEM_TYPE_SUB_HEADER = 1;
int ITEM_TYPE_ROW_NORMAL = 2;
int getItemType();
}
And implement this interface with your adapter's video item like
public class YourAdapterVideoItem implements BaseItem {
// rest of your code
#Override
public int getItemType() {
return ITEM_TYPE_ROW_NORMAL;
}
}
Create your adapter's header item
public class YourAdapterHeaderItem implements BaseItem {
// rest of your code
#Override
public int getItemType() {
return ITEM_TYPE_HEADER;
}
}
Update your adapter with
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<BaseItem> items = new ArrayList<BaseItem>();
#Override
public BaseRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
BaseRecyclerViewHolder holder;
switch (viewType) {
case BaseItem.ITEM_TYPE_ROW_NORMAL:
default:
// inflate your default items
break;
case BaseItem.ITEM_TYPE_HEADER:
// inflate your default items
break;
}
return holder;
}
#Override
public void onBindViewHolder(BaseRecyclerViewHolder viewHolder, int position) {
BaseItem base = getItemAt(position);
switch (base.getItemType()) {
case BaseItem.ITEM_TYPE_HEADER:
// populate your header view
break;
case BaseItem.ITEM_TYPE_ROW_NORMAL:
// populate your actual view
break;
}
}
#Override
public int getItemCount() {
return items == null ? 0 : items.size();
}
#Override
public int getItemViewType(int position) {
return getItemAt(position).getItemType();
}
public BaseItem getItemAt(int position) {
return items == null ? null : items.get(position);
}
}
When you want to add header use YourAdapterHeaderItem for your videos use YourAdapterVideoItem.
Hope this helps
Edit
For adding headers in GridLayoutManager have a look at RecyclerView GridLayoutManager with full width header

Related

Android ListView items displayed in wrong order

I am having an issue updating the ListView in my Android application. I have searched for the solution and read multiple answers but none solved my issue:
android-listview-repeating-old-data-after-refresh
android-requestlayout-improperly-called
android-listview-not-refreshing-after-notifydatasetchanged
android-listview-getview-being-called-multiple-times-on-unobservable-views
Issue
I have a listview with 2 items displayed like this:
Item 1 (position 0)
Item 2 (position 1)
After reloading the data from the source I get the same 2 items, but in the listview it is displayed like this:
Item 2 (position 0)
Item 2 (position 1)
However, when I click on the position 0 in new list it shows correct data of Item 1 (click on position 1 it also shows correct data of Item 2).
The problem is that it displays Item 2 on position 0 and on position 1 (twice).
Here is the code where list is updated and adapter is setup:
public class FishTankFragment extends DeviceFragment {
...
private final List<FishTankStatus.Schedule> schedulesList = new ArrayList<>();
private ScheduleAdapter scheduleAdapter;
...
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
scheduleAdapter = new ScheduleAdapter(view.getContext(), schedulesList);
screenBinding.lvSchedules.setAdapter(scheduleAdapter);
screenBinding.lvSchedules.setOnItemClickListener((parent, view1, position, id) -> {
new ScheduleItemClickListener(this.getContext(), schedulesList.get(position), position);
});
...
}
#Override
public <T> void onResponse(T responseObject) {
...
schedulesList.clear();
schedulesList.addAll(data.getSchedules());
scheduleAdapter.notifyDataSetChanged();
...
}
Here is Adapter code:
public class ScheduleAdapter extends BaseAdapter {
private ScheduleItemBinding itemBinding;
private final List<FishTankStatus.Schedule> schedules;
private final Context context;
public ScheduleAdapter(#NonNull Context context, #NonNull List<FishTankStatus.Schedule> objects) {
this.context = context;
schedules = objects;
}
#Override
public int getCount() {
return schedules.size();
}
#Override
public FishTankStatus.Schedule getItem(int position) {
return schedules.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
itemBinding = ScheduleItemBinding.inflate(LayoutInflater.from(context));
view = itemBinding.getRoot();
}
if (!schedules.isEmpty()) {
String start = StringUtils.printTime(schedules.get(position).getStart());
String end = StringUtils.printTime(schedules.get(position).getEnd());
itemBinding.tvScheduleStart.setText(start);
itemBinding.tvScheduleEnd.setText(end);
FishTankStatus.Schedule schedule = schedules.get(position);
for (String device : schedule.getDevices()) {
switch (device) {
case "something":
itemBinding.ivYellowlightIcon.setVisibility(View.VISIBLE);
break;
case "something 1":
itemBinding.ivBluelightIcon.setVisibility(View.VISIBLE);
break;
case "something 2":
itemBinding.ivAirIcon.setVisibility(View.VISIBLE);
}
}
if (schedules.get(position).getActive()) {
ColorStateList white = ColorStateList.valueOf(
view.getResources().getColor(R.color.white, view.getContext().getTheme()));
itemBinding.lySchedule.setBackground(ResourcesCompat.getDrawable(view.getResources(),
R.drawable.rectangle_p_light_8,
view.getContext().getTheme()));
...
}
}
return view;
}
}
ListView has width and height set to match_parent in parent ConstraintLayout where width=0dp (has parent) and height=match_parent
See the video:
screen recording
Thank you for all the help.
I debugged the app. After clearing schedulesList.clear() it contained 0 items in Fragment and also in BaseAdapter. After addAll items from the source it contained correct items in schedulesList both in Fragment and BaseAdapter.
I tried to fill the data in Adapter as separate List object using clear and addAll.
I will answer my own question for the future visitors...
Just use RecyclerView
It solved all my issues. But I still do not know why the above problem happened.

RecyclerView 2 lists in one

I am trying to create something similar to this:
RecyclerView
Instead of folders and files I want to have incomplete items and completed items.
I am new to RecyclerViews, how would I manage to get two unrelated lists such as folders and files into one RecyclerView that scrolls as one?
You could use heterogenous RecyclerView which supports more than one viewType or view holders. Your dataSet could be List<Object> or a marker class which supports the models Files and Folders for example and then you could do something like this in your adapter :
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class ViewHolderFolders extends RecyclerView.ViewHolder {
...
public ViewHolderFolders(View itemView){
...
}
}
class ViewHolderFiles extends RecyclerView.ViewHolder {
...
public ViewHolderFiles(View itemView){
...
}
#Override
public int getItemViewType(int position) {
//Let us say you return 0 for folders and 1 for files
//This is just an example you could write your own logic but make sure to differenciate the two
//Folders and Files in here are model class used to populate the
//recyclerview with. This is just an example.
if (yourDataSet.get(position) instanceof Folders) {
return 0;
} else{
return 1;
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolderFolders(...);
case 1: return new ViewHolderFiles(...);
//Your code here
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case 0:
ViewHolderFolders viewHolderFolders = (ViewHolderFolders)holder;
...
break;
case 1:
ViewHolderFiles viewHolderFiles = (ViewHolderFiles)holder;
...
break;
}
}
}
You can use a sectioned recyclerView for this. Where you can have section as header and each header with its own items.
Refer to this library: Sectioned RecyclerView

RecyclerView architecture - onBindViewHolder nested data

i want to show list as per image for that i am using recycler view and showing row its easy .but inside each row i want to showing many rows
say
i have 10 rows and each row has different row inside
so 1 row have 3 rows where as 2nd have 2 as on
so what is best way to do this
is it possible we can have one more listview inside that row ?
or inside onBindViewHolder i have to manually loop
and inflate layout
Edit :-
when i am trying this is always shuffles
#Override
public void onBindViewHolder(final RecyclerViewHolder holder, int position) {
for (int i = 0; i < position; i++) {
View c = ((Activity) mContext).getLayoutInflater().inflate(R.layout.row2, null);
// ((TextView) c.findViewById(R.id.mis)).setText(data.get(position) + "");
holder.inner.addView(c);
}
holder.n.setText(position+"");
holder.itemView.setTag(position);
}
image as follows
Yes you can use recyclerview inside recycler view just need to maintain separate adapter for that.
Or in this case you can also use expandable list view which will be much easier to use in this case.
If in your case, you don't have many rows, you can apply this:
Use NestedScrollview and add 2 RecyclerViews inside of it.
If you have specific number of rows like 2-3, it will be easy to implement.
Add layout_behavior to your RecyclerViews like below:
<android.support.v7.widget.RecyclerView
android:id="#+id/myRecyclerView"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
And wrap content for layout height is important.
android:layout_height="wrap_content"
And last, you should add this, so scroll works only for NestedScrollView
myRecyclerView.setNestedScrollingEnabled(false);
If you have many items use Single RecyclerView with multiple types of viewholders.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final int TYPE_MAIN = 0;
private final int TYPE_SUB = 1;
private ArrayList<Object> dataSet;
class ViewHolderMain extends RecyclerView.ViewHolder {
...
}
class ViewHolderSub extends RecyclerView.ViewHolder {
...
}
#Override
public int getItemViewType(int position) {
if(dataSet.get(position) instance of MainRowObject){
return TYPE_MAIN;
}else{
return TYPE_SUB;
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TYPE_MAIN: return new ViewHolderMain(...);
case TYPE_SUB: return new ViewHolderSub(...);
...
}
}
}
With the library SectionedRecyclerViewAdapter you can group your items in sections:
class MySection extends StatelessSection {
List<String> list;
public MySection(List<String> list) {
// call constructor with layout resource for this Section items
super(R.layout.section_item);
this.list = list;
}
#Override
public int getContentItemsTotal() {
return list.size(); // number of items of this section
}
#Override
public RecyclerView.ViewHolder getItemViewHolder(View view) {
// return a custom instance of ViewHolder for the items of this section
return new MyItemViewHolder(view);
}
#Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
// bind your view here
itemHolder.tvItem.setText(list.get(position));
}
}
Then you set up the RecyclerView with your sections:
// Create an instance of SectionedRecyclerViewAdapter
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
// Create your sections with the list of data per row
MySection row1Section = new MySection(data1List);
MySection row2Section = new MySection(data2List);
// Add your Sections to the adapter
sectionAdapter.addSection(row1Section);
sectionAdapter.addSection(row2Section);
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);

Force RecyclerView to call onCreateViewHolder

I have a RecyclerView that can show items as list, small grids or large grid and this can be change at runtime. Depending on what style user chooses i inflate different layout in onCreateViewHolder.
I also use layoutManger.setSpanSizeLookUp() to switch between styles. My code looks like this
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
return 1;
else
return columnCount; //show one item per row
}
});
#Override
public void onClick(View v) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
showType = ProductAdapter.SHOW_TYPE_LARGE_GRID;
else
showType = ProductAdapter.SHOW_TYPE_SMALL_GRID;
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
adapter = new ProductAdapter(getActivity(), productList, showType);
recyclerView.setAdapter(adapter);
layoutManager.scrollToPosition(firstVisibleItem);
}
The problem is to force onCreateViewHolder to be called I'm creating a new object every time user changes the style. Is there any other way?! to force onBindViewHolder() to be recalled. I simply use adapter.notifyDataSetChanged() How can i get something similar for onCreateViewHolder?
Any solution that doesn't uses multiple adapters is good enough!
What you need to do is:
Modify your Adapter:
Specify two types of Views that your Adapter can inflate:
private static final int LARGE_GRID_ITEM = -1;
private static final int SMALL_GRID_ITEM = -2;
Create a field that can store current type mCurrentType
Use your Adapter's getItemViewType. For example like this:
#Override
public int getItemViewType (int position) {
return mCurrentType;
}
In your createViewHolder use the viewType to decide what type of ViewHolder you need to create.
public final RecyclerView.ViewHolder createViewHolder (ViewGroup parent, int viewType){
if (viewType == LARGE_GRID_ITEM) {
//return large grid view holder
} else {
//return small grid view holder
}
}
Additionally you can create methods:
public void toggleItemViewType () {
if (mCurrentType == LARGE_GRID_ITEM){
mCurrentType = SMALL_GRID_ITEM;
} else {
mCurrentType = LARGE_GRID_ITEM;
}
}
public boolean displaysLargeGrid(){
return mCurrentType == LARGE_GRID_ITEM;
}
Modify the code you posted:
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (adapter.displaysLargeGrid()) {
return 1;
} else {
return columnCount; //show one item per row
}
}
});
#Override
public void onClick(View v) {
adapter.toggleItemViewType();
adapter.notifyDataSetChanged();
}
Its not the optimal choice but it's better to create a new Adapter, which will call onCreateViewHolder(). This way you can avoid your troubles, by the cost of very tiny performance issues.

Scroll View relative to parent View

Hi Guys My question is very simple
I want to add images in a row like a flowlayout or GridLayout as you can see in the Image below
Above that layout i want to add a button such that it comes in between rows.
When i scroll my grid View , the button Image also scrolls respective with the gridview.
Can any one suggest me some ideas how it can be possible
If it's always a fourth item - than must be no problem.
Impelment a GridView with android:numColumns="3"
In your Adapter implement three view types
The idea is to add two blank items in a second row and a button to the middle.
private static final int TYPE_NORMAL = 0;
private static final int TYPE_BLANK = 1;
private static final int TYPE_BUTTON = 2;
#Override
public int getViewTypeCount() {
return 3;
}
#Override
public int getCount() {
return yourdata.size() + 3;
}
// return your real data by skipping row with the button
#Override
public Object getItem(int position) {
if (position > 3) {
position += 3;
}
return yourdata.get(position);
}
// return your real data ID by skipping row with the button The button probably should catch it's own onClickListemer
#Override
public long getItemId(int position) {
if (position > 3) {
position += 3;
}
return yourdata.get(position).getId();
}
#Override
public int getItemViewType(int position) {
switch(position) {
case 4:
case 6:
return TYPE_BLANK;
case 5:
return TYPE_BUTTON;
default:
return TYPE_NORMAL;
}
}
// only your items should be clickable
#Override
public boolean isEnabled(int position) {
return position < 4 && position > 6;
}
// nope, only your specific data items are enabled.
#Override
public boolean areAllItemsEnabled() {
return false;
}
In yout getView method just check the item view type and inflate the proper view.
For more details implementing adapters with multiple item types refer to example of ListView with section headers etc.
How to generate a ListView with headers above some sections?
http://w2davids.wordpress.com/android-sectioned-headers-in-listviews/

Categories

Resources