I customized RecyclerView by adding separate layouts for header and footer. I created constants to determine the property of header, footer and list item in the adapter class. I also created a ViewHolder pattern and assign the layouts to be shown based on the view type. I fixed the header at zeroth position and footer at last position of the array by override getItemViewType method.
I want to make the footer element clickable, so I assign a setOnClickListener(new View.OnClickListener()) and overwrote onClick(view: View)
My goal is to click on the footer and scrollToPosition 0 or 1 (0 = Header, 1 = first item element).
That's MyAdapter definition:
class MyAdapter(context: Context, data: Array[String]) extends RecyclerView.Adapter[RecyclerView.ViewHolder]
...
override def onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int): Unit = holder match {
...
case holder:FooterViewHolder =>
holder.backToTop.setOnClickListener(new View.OnClickListener() {
override def onClick (view: View) {
backToTop(???)
Toast.makeText (context, "Clicked Footer", Toast.LENGTH_SHORT).show()
}
})
...
}
...
I read I just need to do this: recyclerView.getLayoutManager().scrollToPosition(position)
Unfortunately I can't access the LayoutManager from my Adapter class. Any ideas what I can do?
Another approach
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}
Make constuctor of MyAdapter as follow.
MyAdapter myAdapter=new MyAdapter(context,list,mLayoutManager);
Related
Well as the title says, I need a listener that gives me the position of the item which was recycled.
So far I only found how to get the holder of the recycled item:
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
}
and I don't get why it doesn't also return the damn position of it
You might want to use either of the methods according to your use case to get the position of the view to be recycled to create the new view.
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
holder.layoutPosition
holder.absoluteAdapterPosition
holder.bindingAdapterPosition
}
holder.layoutPosition: Returns the position of the ViewHolder in terms of the latest layout pass.
holder.absoluteAdapterPosition: Returns the Adapter position of the item represented by this ViewHolder with respect to the RecyclerView's Adapter.
holder.bindingAdapterPosition Returns the Adapter position of the item represented by this ViewHolder with respect to the Adapter that bound it.
You can use the tag of the view to complete
override fun onBindViewHolder(holder: HostingAndAdditionalHolder, position: Int)
{
...
holder.binding.cb.tag = position
...
}
Set up the listener when creating the view. In the code below, I set up the listener for cb.
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): HostingAndAdditionalHolder {
return HostingAndAdditionalSettingsItemViewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).run {
cb.setOnClickListener(checkOnClickListener)
HostingAndAdditionalHolder(this)
}
}
This is my listener code
private val checkOnClickListener = View.OnClickListener { view ->
view.tag.let {
if (it is Int) it else null
}?.let {
...
}
}
I have a recycleView containing n reviews. Each review has a nickname, body and a button that gives the opportunity to report my review if something is wrong. Problem is I don't know how to retrieve these info on click. Can someone show me some code draft maybe?
On your RecyclerView class override the following:
#Override
public void onViewAttachedToWindow(#NonNull MyViewHolder holder) {
super.onViewAttachedToWindow(holder);
//assuming your 'body' is TextView:
TextView tv = holder.body; //body should have been set in your ViewHolder inner class
Button button = holder.button; //assume button was defined in your ViewHolder inner class
//now you have a reference to your objects!
}
A clean way to do that is making a interface, here i put an example.
class NewsAdapter(private val noticias: List<Noticia>, private val layout: Int, private val activity: Activity, private val listener: OnItemClickListener) : RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(noticias[position], listener)
}
override fun getItemCount() = noticias.size
// ViewHolder
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(noticia: Noticia, listener: OnItemClickListener) {
itemView.textViewNewsTitulo.text = noticia.titulo
Glide.with(activity)
.load(noticia.foto_url)
.fitCenter()
.placeholder(R.drawable.placeholder)
.into(itemView.imageViewNews)
itemView.textViewNewsDesc.text = noticia.desc
// bind the click listener with the itemView
itemView.setOnClickListener { listener.onItemClick(noticia, adapterPosition) }
}
}
// Interface onClick define in adapter adapter
interface OnItemClickListener {
fun onItemClick(noticia: Noticia?, posicion: Int)
}
}
For use in your activity you need to implement the interface like this.
class HomeActivity : BaseActivity(), NewsAdapter.OnItemClickListener
The interface force you to implement the methods in the activity.
override fun onItemClick(noticia: Noticia?, posicion: Int) {
// do your logic on click
}
My question is, is the initialization of a new RecyclerView adapter an asynchronous call?
I have an adapter that I am creating:
mRecyclerAdapter = new TestAdapter(mContext, mListImages);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(mRecyclerAdapter);
After initializing it, I can call .add() directly after these methods without calling .notifyDataSetChanged() and they would still be added to my adapter, and displayed.
mRecyclerAdapter = new TestAdapter(mContext, mListImages);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(mRecyclerAdapter);
mListImages.add( . . .);
mListImages.add( . . .);
mListImages.add( . . .);
Are RecyclerView adapters automatically initialized on a background thread?
Here is my adapter:
public class SelectBucketAdapter extends RecyclerView.Adapter<SelectBucketAdapter.ViewHolder> {
private static final String TAG = "SelectBucketAdapter";
private Context mContext;
private ArrayList<String> mBucketList;
public SelectBucketAdapter(Context mContext, ArrayList<String> mBucketList,
) {
this.mContext = mContext;
this.mBucketList = mBucketList;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.vh_selectbucketmenu_layout, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int i) {
... binding views
}
#Override
public int getItemCount() {
return mBucketList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
#BindView(R.id.vh_selectbucketmenu_name)
TextView vhBucketName;
int mPosition;
public ViewHolder(#NonNull View itemView) {
super(itemView);
}
}
}
Are RecyclerView adapters automatically initialized on a background thread?
No, they are not.
Is the initialization of a new RecyclerView adapter an asynchronous call?
No, it is not.
The layout creation and attachment to window is async.
What this means?
Assume we have following code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
adapter.list.add("1")
}
In this case we will see the "1" being displayed on the screen, because at the point when adapter.list.add("1") was executed RecyclerView hasn't yet passed through its measure-layout-draw cycle.
Now let's consider following code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
Handler().postDelayed({ adapter.list.add("AAA") }, 2000)
}
In this case adapter.list.add("AAA") will be executed in roughly 2 seconds. As long as RecyclerView will already be laid out by that time, then mutating the adapter dataset won't make the RecyclerView show the item, because RecyclerView doesn't know if dataset has suffered a change.
Let's consider following case:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
recyclerView.doOnPreDraw { adapter.list.add("preDraw") }
recyclerView.doOnLayout { adapter.list.add("layout") }
adapter.list.add("onCreate")
}
In this case still only "onCreate" will be displayed on screen.
To sum up: as soon as RecyclerView has passed its measure step (i.e. View#onMeasure) then mutating adapter won't be reflected unless adapter explicitly notifies RecyclerView.
The term 'intiializing' is quite ambiguous. At what point do you consider the adapter 'intiialized'?. To me, an Adapter can be intiialized with 0 items. So you can't really measure whether the Adapter has been intiialized by looking at the contents of the RecyclerView.
Secondly, you're asking if 'intiializing is an asynchronous call'. The 'initialization' of a RecyclerView Adapter is a whole bunch of calls. What you've observed is that the result of these calls is not always immediately visible - which tells you that at least some of what is happening behind the scenes is asynchronous.
I think what you're trying to ask is 'at what point in the lifecycle of a RecyclerView are you required to notify the Adapter of changes'. And it sounds like the answer is 'once the RecyclerView has reached onMeasure() (based on #azizbekian's answer).
If you want to add items to the Adapter without having to call notifyDataSetChanged(), then I would suggest adding them before calling RecyclerView.setAdapter(). Once you've set the Adapter, any further changes you make to the Adapter's dataset should be followed with a notifyDataSetChanged() call (or preferably, one of the more specific notifyX() calls).
I have a RecyclerView that shows a list of items.
If there is no item to show, The recyclerview shows one item with a specific view (to tell the user there is no item instead of a white screen).
Within HistoryFragment:
private void initRecyclerView(Boolean isNoResult){
HistoryRecyclerViewAdapter adapter = new HistoryRecyclerViewAdapter(mContext, mRecords, **isNoResult**);
mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
mRecyclerView.setAdapter(adapter);
}
Within HistoryRecyclerViewAdapter:
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view;
if(**isEmpty**) {
**view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_listitem_prhistory_empty, parent, false);**
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_listitem_prhistory, parent, false);
}
ViewHolder holder = new ViewHolder(view);
return holder;
}
So, it's possible to remove items one by one, if we click on them.
I would like to set isEmpty to true and refresh the RecyclerView when the dataSet is null.
I already know where to call that method but I really don't know how I can do that? (i.e. refresh the RecyclerView with isEmpty = true so I can display the cell that explain to the user that there is no record anymore).
Don't inflate different view-holders, because when the adapter has no items, not a single one of them will ever be inflated. Instead one can wrap the RecyclerView together with a "no data" Fragment into a ViewFlipper, which then can be switched to the Fragment, when the RecyclerView adapter has no items.
Best practice is to use an empty view outside of RecyclerView but in case you like to do what you want:
1.in onCreateViewHolder only inflate one layout which has empty and item views
on item delete check if your array is empty and then add a null item
then in onBindViewHolder check the model if model is Null visible empty view otherwise show item view
summary:
onBind:
model is null : empty View visible
model is not null: item View visible
use interface to refresh the RecyclerView after remove something like this
public interface RefreshRecyclerView {
public void Refresh();
}
then in activity or fragment implement the interface
Fragment implements RefreshRecyclerView
you will have override method like this
#Override
public void Refresh() {
// set adapter again here
}
then pass the interface to adapter like this
RefreshRecyclerView refresh = (RefreshRecyclerView) this;
yourRecycler.setadapter(refresh);
fially when user clicked on adapter use this
refresh. Refresh();
I'm new to android, but have a good JavaFX experience. I'm trying to create a custom view that i can reuse, but having a hard time figuring out the correct way to do it.
In javafx i could achieve this by: Creating a separate fxml file defining the layout of the custom view, then create a controller class linked to the fxml file, in that class, i'd have a method to retrieve the data model of the controller and use it to fill in the labels, etc.
The custom view i want would be
Constrained Layout
TextView (constrained to right anchor)
Round TextView (constrained to left anchor)
What is the best way to do this in android? Also, Is it possible to achieve this with a RecyclerView? If yes, how can i use a custom view for each item and set its data?
The question is broad. You may need additional research on creating views
Create a recyclerview in the main.xml,
a separate file with an item view.
You have 3 views in your item view - white background with margins (linearlayout?), right textView, and left textview.
The left textview should have android:background="drawable/round_shape" and round_shape.xml defined in your drawables folder. Everything is done in 3 xml files, main.xml for recyclerview, item.xml, round_background.xml. Then, the recyclerview adapter to bind the textviews with your array, and recyclerview initialization
A typical RV adaptor
public class MyRV extends RecyclerView.Adapter<MyRV.ViewHolder> {
private List<MyModelItemWith2Strings> mDataSet; // You may need to setup an array,
// with 2 String objects - for the right and left textviews
// Use an array of class with 2 elements rather than <String>, e.g. List<MyModelItemWith2Strings>
// pass your model here
// this setData will be used to provide the contents for the textviews
void setData(List< /* set your 2 string class here*/ > dataSet) {
mDataSet = dataSet;
}
static class ViewHolder extends RecyclerView.ViewHolder {
// Here you bind item TV's
// first you declare textviews that you will use to fill with data
// Add any other item views you will need to fill in
public TextView tv;
public TextView tv2;
public ViewHolder(LinearLayout v) {
super(v);
// Bind itemview views here. Put R.id.tv from your itemview.xml
tv = v.findViewById(R.id.....);
tv2 = v...
}
}
// Add your itemview layout here
#Override
public MyRV.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
.inflate(/***R.layout.item_view***/, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder( MyRV.ViewHolder h, int position) {
// get content from your model (the above list) and fill in the the itemview textviews
String a= mDataSet.get(position).getItem1();
String b = mDataSet.get(position). getItem2();
...
h.tv.setText(a);
// set clickers if you want to. The clicker class is below.
h.tv.setOnClickListener(new Click(position));
h.tv2.setText(...)
}
// This is obligatory to pass for your RV to initialize. It won't work if you don' t tell Android how to count your array soze
#Override
public int getItemCount() {
return mDataSet.size();
}
// These are my implementation of clickers. I prefer to put them in the nested class of the adapter.
private class Click implements OnClickListener {
private int pos;
Click(int position) {
pos = position;
}
#Override
public void onClick(View p1) {
// get data from your array on click
mDataSet.get(pos);
// Use pos as position on the array, mData.get(pos)
}
}
}
Then, in your main class set a recyclerview
RecyclerView rv = (RecyclerView) findViewById(R.id.rv_In_Main_Xml);
// just additional tunings.
rv.setHasFixedSize(true);
rv.setLayoutManager(new LinearLayoutManager(context)); // <- context = this, if you are in the Main activity
Then set the adapter
MyRV rva = new MyRV();
rva.setData(myArray_with_2_string_objects_to_fill_tvs);
rv.setAdaptor(rva);
And your recycler view gets filled with data