I have two RecylerViews in a LinearLayout with vertical orientation inside a ScrollView. Both RecyclerViews want to display a list of 6 strings in TextViews. The arrangement is such that at the start, the TextViews of just the first RecyclerView show up and upon scrolling, those of the second RecyclerView show up.
My expectation is that since none of the TextViews of the second RecyclerView is visible, they won't be created, and once they come in view, the onBindViewHolder will be called to reuse existing ViewHolders. However, all of the individual ViewHolders are created (onCreateView called). Am I doing something wrong? Because if this is expected, there is no point of setting a common ViewPool, is there?
Activity Code
public class MainActivity extends AppCompatActivity {
private List<String> names = Arrays.asList(
"Name 1", "Name 2", "Name 3", "Name 4", "Name 5", "Name 6"
);
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
RecyclerView recycler1 = (RecyclerView) findViewById(R.id.recycler1);
RecyclerView recycler2 = (RecyclerView) findViewById(R.id.recycler2);
recycler1.setLayoutManager(new LinearLayoutManager(this));
recycler2.setLayoutManager(new LinearLayoutManager(this));
recycler1.setRecycledViewPool(viewPool);
recycler2.setRecycledViewPool(viewPool);
recycler1.setNestedScrollingEnabled(false);
recycler2.setNestedScrollingEnabled(false);
recycler1.setAdapter(new SampleAdapter(new ArrayList<>(names), 1));
recycler2.setAdapter(new SampleAdapter(new ArrayList<>(names), 2));
}
private class SampleAdapter extends RecyclerView.Adapter {
private final List<String> mSampleList;
private final int mRecyclerId;
SampleAdapter(List<String> sampleList, int recyclerId) {
mSampleList = sampleList;
mRecyclerId = recyclerId;
}
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.i("TESTING", "creating a new view for parent Id: " + mRecyclerId);
TextView textView = (TextView) getLayoutInflater().inflate(R.layout.text_view_name, null);
return new SampleViewHolder(textView);
}
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Log.i("TESTING", "binding existing view for parent Id: " + mRecyclerId + " for position: " +
position);
((SampleViewHolder)holder).bind(mSampleList.get(position));
}
public int getItemCount() {
return mSampleList.size();
}
}
private class SampleViewHolder extends RecyclerView.ViewHolder {
private TextView mText;
public SampleViewHolder(TextView itemView) {
super(itemView);
mText = itemView;
}
public void bind(String s) {
mText.setText(s);
}
}
}
Layout
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/scroll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/recycler_containers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorAccent"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"/>
</LinearLayout>
</ScrollView>
TextView
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/name_text_view"
android:layout_width="match_parent"
android:layout_height="400dp"
android:paddingTop="40dp"
android:paddingRight="40dp"
android:paddingBottom="40dp"
android:paddingLeft="40dp"
android:textColor="#color/white"
android:gravity="center">
</TextView>
First off, using RecyclerView inside a ScrollView is never a good idea. A ScrollView will layout its child, in your case a LinearLayout, which will also layout its children: The 2 RecyclerViews.
I will start from bottom up: Both RecyclerView get to be as tall as their parent, or less, since they are told to wrap_content. They both get layouted, and are within the LinearLayout, which will have the height of both RecyclerViews. In case the LinearLayout is bigger than the screen, the ScrollView will make everything scrollable. You now have a tall layout that shows the full content and is scrollable.
Things to notice: A scrollview does not recycle any views. If a view is off screen it might not get drawn, but the view itself does not "act" upon this information, for all the view knows it is currently displayed to the user. Basically both RecyclerViews are properly layouted to their full height within the LinearLayout, which will stretch to the height needed. Al the RecyclerViews know is that they are visible—and need to have all their children bound. This is your "problem".
there is no point of setting a common ViewPool, is there?
Since both RecyclerViews are "visible" there is no recycling and no reuse. A RecyclerView that doesn't scroll because it is inside a ScrollView will not recycle its views. You are correct, there is no recycling going on, and the shared ViewPool is useless.
With the layout you describe you should get rid of the ScrollView altogether and use a single RecyclerView to properly scroll and recycle the views. Whatever you need the ScrollView for, you can accomplish the same with a RecyclerView. ScrollView is for static layouts, RecyclerView for lists.
Shared ViewPools are intended to be used if you have multiple Fragments or things like ViewPagers, where you have a lot of the same Views over different screens.
Related
I have a LinearLayout xml with a RecyclerView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:id="#+id/r1"
android:background="#DBDBDB">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#DBDBDB" />
</LinearLayout>
In my java class I am setting the background image of whole Linearlayout:
LinearLayout linear;
linear = findViewById(R.id.r1);
linear.setBackgroundResource(R.drawable.visitedbg);
This is working fine. But I want to set background for the Linearlayout also in Recycleradapter.
In the list the user can remove any item by clicking on it. And if the last item is removed, I want to show a background image.
In onBindViewHoldermethod I have:
movieList.remove(position);notifyDataSetChanged();
if (getItemCount()<=0){holder.linear.setBackgroundResource(R.drawable.visitedbg);}
Problem is the Nullpointerexception, as I can't define the LinearLayout view in Recycleradapter.
I tried to put it to public MyviewHolder(View itemView), also in public RecyclerAdapter.MyviewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) as
LinearLayout linear = itemView.findViewById(R.id.r1);
but this is still not working. Is there any solution for this?
First of all, I recommend migrating from findViewById() to viewBindng to prevent app from crashing in running time.
Second, You can only access views inside recyclerView's items from adapter. to implement what you want, you should listen for recyclerView's adapter change. You can use AdapterDataObserver:
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
//...
}
});
Take a look at this link in android documentation.
Another way to achieve that will be checking for adapter's item count everytime you remove an element:
movieList.remove(position);
notifyItemRemoved(position);
if(adapter.getItemCount() == 0)
linear.setBackgroundResource(R.drawable.visitedbg)
Here is what I am trying to do:
What is the simplest way to create rows that scroll together and are composed of variable sized clickable Views with the same height on Android
Basically create variable width columns that have the same width in every row. Also need to add, delete and add listeners. Seems like a fairly simple task, but I am finding Android's GUI library a lot harder to figure out than Java's and WPF's GUI library.
Here is my RecyclerView:
public class MainActivity extends AppCompatActivity {
private RecyclerView ampRecyclerView;
private RecyclerView.Adapter ampAdapter;
private RecyclerView.LayoutManager ampLayoutManager;
List<FunctionView> myDataset = new ArrayList<FunctionView>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setUpRecyclerView();
}
private void setUpRecyclerView() {
LinearLayout linearLayout = findViewById(R.id.main_ll);
linearLayout.setWillNotDraw(false);
ampRecyclerView = (RecyclerView) findViewById(R.id.AmpRecyclerView);
// use a linear layout manager
ampLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
ampRecyclerView.setLayoutManager(ampLayoutManager);
myDataset.add(new FunctionView(this));
myDataset.add(new FunctionView(this));
myDataset.add(new FunctionView(this));
// specify an adapter
ampAdapter = new MainActivityAdapter(myDataset, 1);
ampRecyclerView.setAdapter(ampAdapter);
}
}
My adapter
class MainActivityAdapter extends RecyclerView.Adapter<MainActivityAdapter.FunctionViewHolder> {
private List<FunctionView> views = new ArrayList<FunctionView>();
private List<LinearLayout> llViews = new ArrayList<>();
private int rows;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class FunctionViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public LinearLayout linearLayout;
public FunctionViewHolder(LinearLayout v) {
super(v);
linearLayout = v;
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MainActivityAdapter(List<FunctionView> myDataset, int rows) {
views = myDataset;
this.rows = rows;
}
// Create new views (invoked by the layout manager)
#Override
public MainActivityAdapter.FunctionViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
LinearLayout linearLayout = (LinearLayout) LayoutInflater.from(parent.getContext())
.inflate(R.layout.function_holder, parent, false);
llViews.add(linearLayout);
MainActivityAdapter.FunctionViewHolder vh = new MainActivityAdapter.FunctionViewHolder(linearLayout);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(MainActivityAdapter.FunctionViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
int width = 0;
for(FunctionView fv : views){
holder.linearLayout.addView(fv);
width += fv.getWidth();
}
holder.linearLayout.setMinimumWidth(width);
//TODO set the data
// holder.functionView = views.get(position);
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return rows;
}
I know there are glaring design flaws. I am trying to get the scrolling working first, because every layout I try doesn't work how I'd like.
Here is the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_ll"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/AmpRecyclerView"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
and the holder:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:isScrollContainer="true"
android:nestedScrollingEnabled="true"
>
</LinearLayout>
as you said there are glaring design flaws, but you need to change the android:layout_height to wrap_content and android:layout_width to match_Parent.
if your item's hight is match_parent then your inner layout's hight becomes the recyclerview's hight then there is no room for other items so there will be no scrolling.
also, put something like a textView in it to be able to see the items.
another note, the name is supposed to be item not holder. holder is related to ViewHolder which is a totally different thing. you can name it according to your activity for example if Your activity name is MainActivity so your activity layout is activity_main, then you can call the inner layouts item_main
I recommend watching a tutorial on youtube or read an article from medium or anywhere (you can simply just google android recyclerview example) to learn the basics.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:id="#+id/txt_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SAMPLE"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:textSize="30sp"
/>
</LinearLayout>
I am using a recycler view to show some data. When the app is launched it looks correct as follows with wrap content for height. After I scroll past the last item, I am able to keep scrolling and the data is no longer wrapped, looking like match parent instead for height. Scrolling back up, everything has changed to match parent for the height.
Using past references here, I have tried with ConstraintLayouts and switched height wrapping between the parent layout and the recyclerview itself. Both doesn't help. I am guessing this has to do more with the xml. Please advice.
This is what I expect to always get. This is what I current get when app launches, but changes after I scroll to last item.
When I scroll to last item this happens.
Now if I scroll back up, the height is no longer wrapped. Everything seems to have changed to match parent.
This is xml for the custom view I am using to inflate.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/feed_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/feed_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingTop="5dp"
android:paddingRight="10dp"
android:textSize="18sp"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="0dp"
tools:text="Test Title" />
<TextView
android:id="#+id/feed_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="12sp"
app:layout_constraintTop_toBottomOf="#+id/feed_title"
tools:layout_editor_absoluteX="0dp"
android:layout_below="#+id/feed_title"
tools:text="This is some random description for testing purposes. Other wise just typing on to create more stuff..." />
</RelativeLayout>
This is layout for the Recycler View which is placed on a Fragment activity.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".fragment.CurrentFeedFragment">
<android.support.v7.widget.RecyclerView
android:id="#+id/current_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
This is over at my FragmentActivity where I am loading the data for the RecyclerView.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_current_feed, container, false);
initiateTestData();
loadDataToRecyclerView(view);
return view;
}
private void initiateTestData(){
testTitles = new ArrayList<>();
testDescriptions = new ArrayList<>();
for (int i = 5; i < 25; i++) {
testTitles.add("title " + i);
testDescriptions.add("This is some random description for testing purposes. Other wise just typing on to create more stuff... " + i);
Log.d(TAG, "initiateTestData: " + "title " + i);
}
}
private void loadDataToRecyclerView(View v){
Log.d(TAG, "loadDataToRecyclerView: " + testTitles.size());
RecyclerView recyclerView = v.findViewById(R.id.current_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
FeedAdapter adapter = new FeedAdapter(testTitles, testDescriptions, getActivity());
recyclerView.setAdapter(adapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
Don't think this is relevant. But for reference, this is my adapter class.
public class FeedAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private static final String TAG = "FeedAdapter";
private List<String> titles;
private List<String> descriptions;
private Context context;
public FeedAdapter(List<String> titles, List<String> descriptions, Context context) {
this.titles = titles;
this.descriptions = descriptions;
this.context = context;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.custom_current_feed, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewHolder, int i) {
final ViewHolder holder = (ViewHolder) viewHolder;
holder.feedTitle.setText(titles.get(i));
holder.feedDescription.setText(descriptions.get(i));
holder.layout.setOnClickListener(v -> {
Log.d(TAG, "Clicked: " + titles.get(i));
Toast.makeText(context, titles.get(i), Toast.LENGTH_SHORT).show();
});
}
#Override
public int getItemCount() {
return titles.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView feedTitle;
TextView feedDescription;
RelativeLayout layout;
public ViewHolder(#NonNull View itemView) {
super(itemView);
feedTitle = itemView.findViewById(R.id.feed_title);
feedDescription = itemView.findViewById(R.id.feed_description);
layout = itemView.findViewById(R.id.feed_layout);
}
}
}
Your recycler view height is wrap_content
And recycler view item height match_parent
I would think you'd want them the other way around.
The RV's height = match_parent, i.e. the recycler view occupies all available height.
The RV item's height = wrap_content, i.e. each item only as tall as it needs to be, so that multiple items can fit.
i am facing a problem i cannot resolve. i googled it but couldnt get the solution im looking for. i have a recycle view as follow:
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:paddingBottom="70dp"
android:layout_marginTop="#dimen/activity_vertical_margin"
this recycle view is showing in the screen as follow
if you notice, there is no spacing above the text and bottom of the text for each item. most likely due to wrap_content. i want to add space within the item cell on top and bottom. something like this image
if you noticed, i draw red arrows to indicate the extra space and the text in the center of each item list. how can i add space within the cell(space on top of text and space on bottom of text? left and right space will be cool too.
when i googled this, i only found code to add spacing between items. but what i am looking for is to add spacing within the cell item itself like the second picture attach. i would appreciate your help. thanks in advance
Definitely you are using an adapter for your recycler view and that adapter is responsible to create children. As #cocored said you have to create your own layout. you have to do it in your adapter (usually in onCreateViewHolder).
you can use inflater service to inflate an xml layout for each child.
recyclerview_child.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"/>
...
</LinearLayout>
and in your adapter do something like this
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private List<Whatever> mData;
private LayoutInflater mInflater;
MyRecyclerViewAdapter(Context context, List<Whatever> data) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
}
// inflates the child layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recyclerview_child, parent, false);
return new ViewHolder(view);
}
// binds the data to the TextView in each child
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Whatever obj = mData.get(position);
holder.myTextView.setText(obj.getName());
...
}
// total number of children
#Override
public int getItemCount() {
return mData.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder{
TextView myTextView;
ViewHolder(View itemView) {
super(itemView);
myTextView = itemView.findViewById(R.id.tv);
}
}
}
hope it helps
You would have to add a padding to your recycler item. If you're using a default item layout from android I would suggest creating your own layout.
I have used RecyclerView several times before, but it is the first time that it is working too slow.
In this case, the items are represented by a simple LinearLayout with 3 views inside it:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="#+id/linearLayout"
android:orientation="horizontal"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tvTicketNumber"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center"
android:padding="8dp"/>
<EditText
android:id="#+id/etTotalSold"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:padding="8dp"
android:gravity="center" />
<TextView
android:id="#+id/tvSurplus"
android:layout_width="0dp"
android:layout_weight="1"
android:padding="8dp"
android:layout_height="match_parent"
android:gravity="center"/>
</LinearLayout>
The RecyclerView uses the previous layout in its adapter:
public class TicketAdapter extends RecyclerView.Adapter<TicketAdapter.ViewHolder> {
private ArrayList<Ticket> dataSet;
// Define references to the views for each data item
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView tvTicketNumber, tvSurplus;
public EditText etQuantity;
public ViewHolder(View v) {
super(v);
tvTicketNumber = (TextView) v.findViewById(R.id.tvTicketNumber);
etQuantity = (EditText) v.findViewById(R.id.etQuantity);
tvSurplus = (TextView) v.findViewById(R.id.tvSurplus);
}
}
public TicketAdapter() {
dataSet = new ArrayList<>();
}
public void setDataSet(ArrayList<Ticket> dataSet) {
this.dataSet = dataSet;
}
private String twoDigits(final int i) {
final String pre = (i<=9 ? "0" : "");
return pre + i;
}
#Override
public TicketAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.table_row, parent, false);
// set the view's size, margins, padding and layout parameters
return new ViewHolder(v);
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// get element from the data set
Ticket ticket = dataSet.get(position);
// replace the contents of the view with that element
holder.tvTicketNumber.setText(twoDigits(position));
holder.tvSurplus.setText("6");
}
#Override
public int getItemCount() {
return dataSet.size();
}
}
Before, I was using TableLayout with TableRows created programmatically, but I have read that layout have to be used for a defined number of rows in XML.
I have to load a list of 100 items in a fragment. But it takes approximately 4 seconds to load. For that reason I wrote some logic to show a progressBar and next hide it and show the scrollView (the recycler is within it).
The fragmentTransaction was still slow, so I moved the code to the onViewCreated method.
The transaction was still slow and I added an AsyncTask. With this last change, the transaction is faster, but the progressBar looks stopped all the time and the buttons can't be used (the onPostExecute is taking 4 seconds to load and show the recyclerView).
I want to show the animation of the progressBar, but the onPostExecute is executed in the UI thread and all the app is stopped for 4 seconds while the RecyclerView is loading.
Please give me some ideas. Before I have used items with images loaded from internet, and the RecyclerView was working faster. It is too strange.