I have the following scenario: I have a LinearLayout on which I then add "cards" to which is a custom class which extends LinearLayout.
The problem is that each card contains an image. Now if I have too many cards to display I get an out of memory error because of the size of the images.
How can I dynamically see which cards are currently displayed on the screen and only load the images of those cards and keep the rest null?
I am struggling to detect which card is currently displayed on the screen and which ones are not. And then also to have an event load and clear images as the user scrolls though the list.
You have to implement a RecyclerView, which does the job for you.
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
final Adapter adapter = new Adapter();
recyclerView.setAdapter(adapter);
The adapter:
private class Adapter extends RecyclerView.Adapter<MyViewHolder> {
#Override
public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_main, viewGroup, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(final MyViewHolder myViewHolder, int i)
// set the content of the card
}
#Override
public int getItemCount() {
return // number of cards
}
}
The ViewHolder
private class MyViewHolder extends RecyclerView.ViewHolder {
public TextView text;
public TextView text2;
public ImageView imageView;
public MyViewHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(/* your textView */);
text2 = (TextView) itemView.findViewById(/* another textView */);
imageView = (ImageView) itemView.findViewById(/* an image */);
}
}
The Layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:design="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivityFragment">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recycler_view"
/>
</RelativeLayout>
For something like this, you should probably use a Recycler View. This way you can recycle the views and ideally not run into memory issues and not have to have hacky solutions that check what is on the screen and what isn't.
Related
I have created recyclerView and it's adapter. In adapter depend on my class that i pass to it, row item generate automatically .
This is recycler view item `add_row_item:
<?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:layout_margin="4dp"
android:orientation="horizontal">
</LinearLayout>
And this is a main layout :
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:background="#color/md_indigo_50">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
<Button
android:id="#+id/save_to_grid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/recycler_view_item"
android:layout_centerHorizontal="true"
android:layout_margin="10dp"
android:background="#drawable/bg_round"
android:padding="5dp"
android:text="ذخیره"
android:textColor="#424242"
android:visibility="visible" />
</RelativeLayout>
</layout>
This is Adapter:
public class RevisitGridAdapter extends RecyclerView.Adapter<RevisitGridAdapter.GridHolder> {
private List<DataGridColumn> column;
public RevisitGridAdapter(List<DataGridColumn> column) {
this.column = column;
}
public int dip2pix(#NonNull Context context, int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
context.getResources().getDisplayMetrics());
}
#NonNull
#Override
public GridHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
Context context = parent.getContext();
View root = LayoutInflater.from(context).inflate(R.layout.add_row_item, parent, false);
return new GridHolder(root);
}
#Override
public void onBindViewHolder(#NonNull GridHolder holder, int position) {
holder.setPosition(holder, position);
}
#Override
public int getItemCount() {
return column.size();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
class GridHolder extends RecyclerView.ViewHolder {
private TextView tv;
private LinearLayout parent;
public GridHolder(#NonNull View itemView) {
super(itemView);
parent = (LinearLayout) itemView;
}
public void setPosition(GridHolder holder, int position) {
if (!column.get(position).getName().startsWith("CI")) { // todo add EUM
EditText edt = createEditText();
holder.parent.addView(edt);
tv = createTextView();
tv.setText(column.get(position).getHeader());
holder.parent.addView(tv);
} else {
holder.parent.addView(createSpinner());
tv = createTextView();
tv.setText(column.get(position).getHeader());
holder.parent.addView(tv);
}
}
private Spinner createSpinner() {
final Spinner sp = new Spinner(itemView.getContext());
int padding = (int) itemView.getContext().getResources().getDimension(R.dimen.elevation_header);
final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
// layoutParams.setMargins(padding, padding, padding, padding);
sp.setPadding(padding, padding, padding, padding);
sp.setLayoutParams(layoutParams);
return sp;
}
private TextView createTextView() {
final LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(dip2pix(itemView.getContext(), 120), LinearLayout.LayoutParams.WRAP_CONTENT);
final TextView textView = new TextView(itemView.getContext());
textView.setLayoutParams(lparams);
return textView;
}
private EditText createEditText() {
final LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
final EditText editText = new EditText(itemView.getContext());
editText.setLayoutParams(lparams);
ViewCompat.setBackground(editText,
ContextCompat.getDrawable(itemView.getContext(), com.safarayaneh.map.R.drawable.bg_round));
editText.getBackground().setColorFilter(ContextCompat.getColor(itemView.getContext(),
com.safarayaneh.map.R.color.md_indigo_100), PorterDuff.Mode.SRC_ATOP);
return editText;
}
}
}
When i run applicatin and when data is enough to view in one single page every think is ok but data is more than is shown in one page means when i scroll the page the problem is appeared!!
The problem is when for first time i scroll to end of the recyclerview everything is ok but when i scroll to first page of the recyclerview the view's and data repeatedly repeat and recycler view is completely mess up like this image:
As you can see in my picture data and view's on top of the image repeated and exit of 2 columns shape.
What is my mistake?
You are using onCreateViewHolder() and onBindViewHolder() wrongly.
onCreateViewHolder should be used to create list items, onBindViewHolder should only be used to fill already created views with data. The reason is, that the views that are created by onCreateViewHolder are repeatedly used for showing different items' data (hence the name RecyclerView).
If you need to create different types of views for different types of data, you need to implement getItemViewType().
Then the flow is this:
At first, your getItemViewType() method is called and depending on given position you return the needed type.
If there is already a view of that type, which can be reused, your onBindViewHolder() method is called directly with that view and the position.
Otherwise, your onCreateViewHolder() method is called and creates a new instance of the view needed for that type. Afterwards, again onBindViewHolder() is called with the created view and the position.
So, to repeat myself - onCreateViewHolder() is there to create views, onBindViewHolder() is there to bind data (i.e. set text etc.) to an already created view.
Ridcully is right and i could created 2 views and changed it by implementing getItemViewType() method. However in my scenario i changed onBindViewHolder method to this:
#Override
public void onBindViewHolder(#NonNull GridHolder holder, int position) {
holder.parent.removeAllViews();
DataGridColumn column = this.column.get(position);
holder.setPosition(holder, position);
}
everything now is good. holder.parent.removeAllViews(); remove view's that created before.
I'm using a RecyclerView to display some data. For each item I have a custom layout which holds beside some text views a RecyclerView:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:layout_width="180dp"
android:layout_height="45dp"
android:id="#+id/recycler_view"/>
//Other text views
</RelativeLayout>
This is my adapter class:
class UserViewHolder extends RecyclerView.ViewHolder {
View itemView;
RecyclerView recyclerView;
UserViewHolder(View itemView) {
super(itemView);
this.itemView = itemView;
recyclerView = itemView.findViewById(R.id.recycler_view);
}
void bind(User user) {
itemView.setOnClickListener(view -> {
//Move to user Activity
});
UserAdapter adapter = new UserAdapter(user.list);
recyclerView.setAdapter(adapter);
itemView.bringToFront(); //Doesn't work!!!
}
}
I have set on click listener on the entire itemView so I can be redirected to user's activity. The problem is that when I click on the elements that exist in the RecyclerView, I'm not redirected. I tried to bring the itemView in front of the RecyclerView but with no luck. How should I do so I can be redirected to the user activity even if I click on the RecyclerView? Thanks!
Edit:
This is how my itemView layout looks like:
<android.support.v7.widget.CardView
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:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:clickable="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
//One TextView
//One ImageView
//One RecyclerView
</RelativeLayout>
</android.support.v7.widget.CardView>
This is an image of it:
From what I understand you want to redirect user by click on the while recycler row/item. Then you need to make your parent layout of itemView clickable and focusable. You didn't provide the xml layout for itemView but I will try to explain. Your itemView is a separate xml layout file which contains images and text boxes, they all are added to the parent Linear/Constraint/Relative Layout. This parent layout needs to have clickable = true and focusable = true in order to accept click events.
The problem as we established is that the nested recycler consumes the click event. If the nested recycler is not expected to be scrollable the way to block this event is to add setLayoutFrozen(true) to the nested recycler.
Tutorial link and docs
Simple, instead of trying to navigate on that new screen from within the adapter, you can implement an interface and use it from the activity / fragment in which the RecyclerView is visible..
Demonstration
class UserViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
View itemView;
RecyclerView recyclerView;
UserViewHolder(View itemView) {
super(itemView);
this.itemView = itemView;
recyclerView = itemView.findViewById(R.id.recycler_view);
}
#Override
public void onClick(View v) {
if (listener != null)
listener.onItemListClicked(getAdapterPosition());
}
void bind(User user) {
itemView.setOnClickListener(this);
UserAdapter adapter = new UserAdapter(user.list);
recyclerView.setAdapter(adapter);
itemView.bringToFront(); //Doesn't work!!!
}
}
The interface should placed outside the UserViewHolder class.
public interface MyClickListener{
void onItemListClicked(int position);
}
You will have a public method in the adapter, that will set the listener
public void setListenerForAdapter(MyClickListener listener) {
this.listener = listener;
}
Finally in your activity will set the listener to the adapter
adapter.setListener(/*here you will implement your custom listener and start a new activity / fragment based on which position was clicked*/);
For any other questions, feel free to ask.
Happy coding !
If it is a static activity and it won't change over time you can make something like this, Otherwise use an interface.
class UserViewHolder extends RecyclerView.ViewHolder {
View itemView;
RecyclerView recyclerView;
UserViewHolder(View itemView) {
super(itemView);
this.itemView = itemView;
recyclerView = itemView.findViewById(R.id.recycler_view);
}
void bind(User user) {
itemView.setOnClickListener(view -> {
view?.context?.startActivity(Intent(view?.context, YourDestinationActivity::class.java))
// this is kotlin code just parse it to java
});
UserAdapter adapter = new UserAdapter(user.list);
recyclerView.setAdapter(adapter);
itemView.bringToFront(); //Doesn't work!!!
}
I have been developing my first Android app the past days, using this guide: Material Design Guide from Google.
I have decided to go for the Tile fragments as my choice, but the problem is that the content in these tiles are static / the content in tile 1 is the same as in tile 2, 3, 4 and so on.
How do I change this so that each tile has unique content?
Any help would be greatly appreciated!
Normally you would have some data that you would want to present in this tiled list format. This would normally be passed into the ContentAdapter so that you can use it to fill each tile in. At the minute all the content is being set in XML, not in your adapter.
If you want to change the images for each you need to add an id attribute to the item_tile layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:padding="#dimen/tile_padding">
<ImageView
!--Add the id for the ImageView-->
android:id="#+id/tile_image"
android:layout_width="match_parent"
android:layout_height="#dimen/tile_height"
android:scaleType="centerCrop"
android:src="#drawable/paris" />
...
</RelativeLayout>
Then you should change the ViewHolder class in the TileContentFragment so that we can get hold of the ImageView and TextView in the item_tile layout.
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView tileImage;
TextView tileTitle;
public ViewHolder(View itemView) {
super(itemView);
this.tileImage = (ImageView) itemView.findViewById(R.id.tile_image);
this.tileTitle = (TextView) itemView.findViewById(R.id.tile_title);
}
}
Then just for example purposes lets set each tile's title to "Hello":
public static class ContentAdapter extends RecyclerView.Adapter<ViewHolder> {
Other class methods...
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_tile, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.tileTitle.setText("Hello");
//If you had images for the different tile you could set them here too
//holder.tileImage.setImageResource([THE ID OF YOUR IMAGE HERE])
}
}
Hope this helps.
I have created a basic app using RecyclerView and CardView from get tutorials from websites.
App is working fine and I have some confusion.(I am showing my whole code here)
confusion is that how code works step by step. So please clear my concept on it.
Basic Structure of my App :
I have created a row_data_layout xml file to bind on recycler_view.
Created an Data class file (Here I have defined my variable that I used in App).
Created an Adapter file (here I want to clear how it works step by step first which class gets called and why?).
Bind Data to RecyclerView on MainActivity file.
row_data_layout.xml file:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/CardView"
android:paddingBottom="16dp"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/txt_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
</android.support.v7.widget.CardView>
Data Class File:
public class Data {
public String Name;
Data(String Name)
{
this.Name=Name;
}
}
Data_Adapter Class file:
public class Data_Adapter extends RecyclerView.Adapter<Data_Adapter.View_holder> {
List<Data> list = Collections.emptyList();
Context context;
public Data_Adapter(List<Data> list, Context context) {
this.list = list;
this.context = context;
}
#Override
public Data_Adapter.View_holder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_data_layout,parent,false);
View_holder holder=new View_holder(v);
return holder;
}
#Override
public void onBindViewHolder(Data_Adapter.View_holder holder, int position) {
holder.name.setText(list.get(position).Name);
}
#Override
public int getItemCount() {
return list.size();
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
public class View_holder extends RecyclerView.ViewHolder{
CardView cv;
TextView name;
public View_holder(View itemView) {
super(itemView);
cv = (CardView) itemView.findViewById(R.id.CardView);
name = (TextView) itemView.findViewById(R.id.txt_name);
}
}
}
MainActivity File:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<Data> data = fill_data();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
Data_Adapter adapter = new Data_Adapter(data,getApplicationContext());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
public List<Data> fill_data()
{
List<Data> data = new ArrayList<>();
data.add(new Data("Bred Pit"));
data.add(new Data("Leonardo"));
return data;
}
}
Once you have a basic understanding of how a RecyclerView.Adapter works, it would make sense to take a deeper dive into the documentation.
What the adapter does is keep a pool of inflated views (this can be as many different types of ViewHolder as you would like) that it populates with the data you supply. When the adapter does not have an empty view in the pool it creates a new one.
When a view is attached to the RecyclerView, it is removed from the pool, and when it is detached (scrolls beyond view, to some distance), it is added back to the pool of empty views--this is why it is important to reset everything when you populate your ViewHolders.
The onCreateViewHolder() function is where a new, empty view (wrapped by a RecyclerView.ViewHolder) is created and added to the pool.
The onBindViewHolder() function gets a view from the empty pool and populates this view using the data you supplied to the adapter.\
You can use the onViewRecycled() method to perform specific actions like setting an ImageView's bitmap to null (on detach) in order to reduce memory usage.
I don't normally override onAttachedToRecyclerView(), but if you need to do something specific when your adapter is associated with the RecyclerView, you would do it here.
I am making an app with 100 list items and was wondering if I could get away with not implementing the RecyclerView as I find it hard to implement it.
Quite frankly it depends up to you, Listview makes it easy for you by taking a lot of responsibility which makes it slow at time when you have to show a lot of data, on other hand RecyclerView does what it is best at make's things fast by taking care or bare minimum structure.
RecyclerView is quite easy to implement and you will get chance to learn some of the touch framework of Android because of it.
And performing Animation on RecyclerView is quite easy as well and way better than Listview
Making a custom listview is piece of cake with RecyclerView
here's an example for RecyclerView
private RecyclerView recyclerView;
recyclerView = (RecyclerView)findViewById(R.id.recycler);
MyViewComplainAdapter adapter = new MyViewComplainAdapter(getApplicationContext(), createComplainList());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
in XML
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" />
your Adapter (Whatever you want to call this thing... lol )
private class MyViewComplainAdapter extends RecyclerView.Adapter<MyViewComplainAdapter.MyViewComplainViewHolder>{
private Context _Context;
private ArrayList<ViewMyComplainData> _List;
private LayoutInflater _Inflater;
public MyViewComplainAdapter(Context context, ArrayList<ViewMyComplainData> list){
_Context = context;
_List = list;
_Inflater = LayoutInflater.from(_Context);
}
#Override
public MyViewComplainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = _Inflater.inflate(R.layout.single_item_view_my_complain,parent,false);
MyViewComplainViewHolder holder = new MyViewComplainViewHolder(layout);
return holder;
}
#Override
public void onBindViewHolder(MyViewComplainViewHolder holder, int position) {
ViewMyComplainData data = _List.get(position);
holder.complaint_number.setText(data.getComplaint_Number()+"");
holder.complaint_type.setText(data.getComplaint_Type()+"");
holder.status.setText(data.getStatus()+"");
}
#Override
public int getItemCount() {
return _List.size();
}
public class MyViewComplainViewHolder extends RecyclerView.ViewHolder{
TextView complaint_number;
TextView complaint_type;
TextView status;
public MyViewComplainViewHolder(View itemView) {
super(itemView);
complaint_number = (TextView)itemView.findViewById(R.id.textView_complaint_number_single_item_view_my_complain);
complaint_type = (TextView)itemView.findViewById(R.id.textView_complaint_type_single_item_view_my_complain);
status= (TextView)itemView.findViewById(R.id.textView_status_single_item_view_my_complain);
}
}
}
yes you will have to make a ArrayList<ViewMyComplainData> using this method createComplainList(), you should figure this out
Technically speaking, RecyclerView doesn't need anything like "notifyDataSetChanged()" when an item is added or deleted from your List, which is a huge improvement performance-wise.