I have a bottom sheet, where i can dynamically pass to it the layout id of the layout i want it to show. This works fine! The problem with this is that, to me, it is somewhat cumbersome to assign the listeners I want to every UI element of that layout.
I can also pass the assign a pre-infalted view with all the listeners assigned to a member of the bottomsheet, which also works.
I was wondering if there is a best practice established for this, or any pros/cons in the approaches!
Thanks!
If I understood correctly, You can define a loop on ViewGroup of Inflated and get each of View finally define a onClickListener and set it on views
override fun onViewInflated() {
viewGroupInflated?.forEach {
it?.setOnClickListener(onClickListener)
}
}
private val onClickListener = View.OnClickListener {
when(it.id){
R.id.btn_a -> //Todo an action
R.id.btn_b -> //Todo an action
}
}
Related
i need help.
I wanted to change button visibility in any other item inside recyclerview to dissapear, so other button except item that i selected become invisible ...
The trigger is when item get clicked/selected then any other item's button goes invisible/gone except the one i clicked... how do i do that? im using Kotlin btw.
Here is where i want to implement it inside "if (!b)" (if possible) in onBindViewHolder:
holder.mainLayout.setOnClickListener {
if (!b){
b = true
holder.addsLayout.isVisible = true
} else {
b = false
holder.addsLayout.isVisible = false
}
}
Edit:
Here is my viewholder:
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val itemTitle: TextView = itemView.findViewById(R.id.itemTitleRecordLocal)
val itemDelete: Button = itemView.findViewById(R.id.recordlocalDelete)
val itemTranscribe: Button = itemView.findViewById(R.id.transcribelocalTxt)
val mainLayout: RelativeLayout = itemView.findViewById(R.id.recordlocalTitleDateLayout)
val addsLayout: RelativeLayout = itemView.findViewById(R.id.addsRecordLocalLayout)
}
I just updated my code earlier, so its not button anymore but layout, but i think its similar bcuz i just need to change the visibility.
itemDelete and itemTranscribe are buttons that i want to hide, and i put it in addsLayout.
mainLayout is the item where i need to click to be selected.
As per your code, I understand that you are passing any data class to the recycler view adapter of type suppose myData
Here is what you can do:
Take one boolean variable in your data class item, say isSelected, default value will be false.
Then whenever you get click of your mainLayout, change the value of that boolean.
Check condition according to that boolean and manage visibility according to that.
Also, best practice is you manage your clickevents in activity/fragment. So my suggestion is give callback of click of mainLayout to the activity/fragment and perform the next operations there only.
After that, in activity/fragment you need to make for loop and make isSelected false for all data except the one you selected.
For example in my case I have one music player recyclerview and need to change icon if one is being played then all others should be pause. So I need to change icon based on that condition. My data class contain one variable isPlaying.
In adapter:
// Inside view holder
if (data.isPlaying) {
// set image of play
} else {
// set image of pause
}
In on click, I have make one mutable live data and observe that in activity/fragment and get callback of click there.
Inside activity/fragment:
adapter.getListItems().forEach {
it.isPlaying = false
}
data.isPlaying = true
notifyDataSetChanged()
Can someone explain the logic on how to handle this matter:
I have a fragment that after a WebSocket call inflates 2 Recyclerviews.
Child Recyclerview is nested to Parent Recyclerview and the parent adapter calls the child adapter.
I want to put an Interface for a click listener which handles the click in the Child Items in the Fragment.
Where should I put the interface and which class should implement it?
What you're trying to do has been done multiple times.
There are various approaches you can try, but in general, responsibilities would look something like this:
YourContext (Fragment/Activity)
Inflates a layout with a RecyclerView.
Instantiates YourAdapter
Subscribes, Requests, Waits, for your data and passes it onto YourAdapter.
Maintains an interface for click handling, like:
interface YourThingClickHandler {
fun onThingWasClicked(thing: Thing) // and any other thing you may need.
}
Can be YourContext: YourThingClickHandler or if you want, you can keep an anonymous/local instance of that. I usually do the former and then implement the fun onThingWasClicked(...) in the fragment/activity, it depends what you need to do when the item was clicked.
YourAdapter
Expects a list of Things and one YourThingClickHandler instance. So in your Fragment/Activity you'd do, something like (pseudo code):
// This is called once your ViewModel/Presenter/Repository/etc. makes the data available.
fun onThingsLoaded(things: List<Thing>) {
adapter.setClickHandler(this) // this can be passed when you construct your adapter too via constructor like adapter = YourAdapter(this)
adapter.submitList(things) // if the adapter extends a `ListAdapter` this is all you need.
}
Now that you've passed an outer click handler, you need to deal with the inner list. Now you have a few choices:
1. pass the same click handler all the way in and let the innerAdapter directly talk to this.
2. Have the outerAdapter act as an intermediate between the clicks happening in the innerAdapter and bubble them up via this click handler you just supplied.
Which one you chose, will depend largely on what you want to do with it, and how you want to handle it. There's no right or wrong in my opinion.
Regardless of what you do, you still need to get from the view holder to this click handler...
So in YourAdapter you should have another Interface:
interface InternalClickDelegate {
fun onItemTappedAt(position: Int)
}
This internal handler, will be used to talk from the viewHolder, back to your Adapter, and to bubble the tap up to the external click handler.
Now you can have a single instance of this, defined like so in your adapter class (remember this is Pseudo-Code):
private val internalClickHandler: InternalClickDelegate? = object : InternalClickDelegate {
override fun onItemTappedAt(position: Int) {
externalClickHandler?.run {
onThingWasClicked(getItem(position))
}
}
}
So if the external click handler (the YourThingClickHandler you supplied) is not null, then fetch the item from the adapter's data source, and pass it along.
How do you wire this internal handler with each view holder?
When you do onCreateViewHolder, have a ViewHolder that takes... you guessed, a InternalClickDelegate instance and so...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
// decide which viewHolder you're inflating... and...
return YourViewHolder(whateverViewYouInflate, internalClickHandler)
Now your ViewHolder(s) have a reference to this internal click handler...
so when you do onBindViewHolder(...) you probably call a common ViewHolder method of your choice, for example if your View holder can be of different types, you probably have an Abstract viewHolder with a fun bind(thing: Thing) method or similar that each concrete viewHolder subType will have to implement... in there, you'd do something like this:
override fun bind(thing: Thing) {
if (clickHandler != null) {
someViewYourViewHolderInflated.setOnClickListener(this) // this assumes your ViewHolder implements View.OnClickListener from the framework
}
}
Because your ViewHolder implements View.OnClickListener, you must implement the onClick method in it:
override fun onClick(v: View?) {
clickHandler?.onItemTappedAt(adapterPosition)
}
And this is how your ViewHolder, will receive the tap/click event from Android in the onClick method, if you supplied a click Handler (you did in the adapter onCreateViewHolder when you passed the internalClickHandler), it will simply bubble the tap, passing the position. adapterPosition is the Kotlin equivalent of calling getAdapterPosition() in a RecyclerView adapter.
TOO LONG, DIDN'T READ GRAPH
Fragment: ExternalClickListener -> passes an instance of it to the Adapter.
Adapter: Receives the ExternalClickListener, passes an InternalClickListener to each ViewHolder.
ViewHolder: Receives the internal Click Listener, sets itself as Clickable (either the entire itemView or just any widgets you want to make clickable, if you want the whole cell to be clickable, simply use itemView which is the "whole" view of the ViewHolder.
When the viewHolder's view is tapped, android calls the click listener's onClick method. In there, and because you are in a ViewHolder, you can do getAdapterPosition() and pass this to the internal click handler you received.
The Adapter then can transform that position back into data, and because you supplied an External clickListener, it can pass the actual item back to the external click listener.
Wait, but how about a NESTED RecyclerView.
There's nothing special about that, you simply need to provide the same mechanism, and keep passing things around. What you do or how many of these interfaces you have, depends entirely on what you're trying to achieve; like I said at the beginning, each solution is different and other factors must be taken into account when making architectural decisions.
In general, keep this thing in mind: Separation of Concerns: keep things small and to the point. For E.g.: it may seem crazy to have this double interface, but it's very clear what each does. The internal one, is simply concerned about a "tap" in a "view", and to provide the position in a list where said tap occurred.
This is "all" the adapter needs to fetch the data and make an informed guess at what item was truly tapped.
The fragment doesn't know (or care) about "positions", that's an Adapter's implementation detail; the fact that positions exist, is oblivious to the Fragment; but the Fragment is happy, because it receives the Thing in the callback, which is what most likely needs to know (if you needed to know the position for whatever reason, tailor and modify the externalCallback to have the signature of your choice.
Now replicate the "passing hands" from your OuterAdapter to your InnerAdapter, and you have done what you wanted to do.
Good luck!
1) You should put interface in child adapter and implement that in parent and then pass another one interface (long peocess)
2) Use local broadcast manager
you will add ClickListener in parent adapter and also add it in constructor of adapter
public interface HandleClickListener
{
void onItemClicked(int position, SurveysListModel surveysListModel);
}
Make an instance of your clicklistener and then on holderclick listner get the position of item and its value from your model list
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
handleClickListener.onItemClicked(position, yourModelList.get(position));
});
and get in to your activity like this making an instance of you adapter
adapter = new InstrumentsSearchAdapter(yourModelsList, activity.this, new SearchAdapter.HandleClickListener() {
#Override
public void onItemClicked(int position, Model listModel) {
instumentChild = listModel.getInstrument_title();
Intent intent = new Intent(Activity.this, Listing.class);
intent.putExtra("selectedQuestions", listModel.getQuestions());
startActivityForResult(intent, 5);
}
});
And if you want to go to parent recyclerview class implement onActivityResutlt method and get data back from child through intent and get that intent in onActivityResutlt method
On activity A I have a button named btnA which will show an activity B whenever it is clicked.
But if I click on this btnA button too fast like 2 clicks within a second then 2 activity B will be shown. Obviously.
I know the solution for this is debouncing the on click listener of btnA using RxBinding or Kotlin coroutine...
But what if a have 100 views which all behave just like btnA in my app. How can I somehow override or intercept their on click listener to apply debouncing in one place?
Edit:
I'm using Kotlin which mean I can add an extension function to apply the debounce logic over View's on click listener but that won't work if the click event handler is bound by ButterKnife or other alternatives library. That why I'm finding a way to override or intercept the View's click listener.
Also, creating some sort of root View/ base View which all app's View will extend from might be overkill, I think.
This might be impossible at the moment but somehow I have an urge that there is a solution somewhere out there.
I do not know of any way to globally override all click listeners. Likely, this is not possible. But if you use Kotlin, you could do something like this:
fun View.onClickThrottled(skipDurationMillis: Long = 750, action: () -> Unit) {
var isEnabled = true
this.setOnClickListener {
if (isEnabled) {
action()
isEnabled = false
Handler().postDelayed({ isEnabled = true }, skipDurationMillis)
}
}
}
And then, change your views to use this instead, like this:
button.onClickThrottled {
//TODO - do something
}
You can have your custom click listener which will implement View.OnClickListener, additionally you can pass a callback to your activity/fragment using your Fragment_ActivityListener. Fragment_ActivityListener is a interface which your activity/fragment receive it back from OnClickListener after debounce or logic performed.
private class CustomClickListener implements View.OnClickListener {
Fragment_ActivityListener listener;
CustomClickListener(Fragment_ActivityListener listener){
this.listener = listener;
}
#Override
public void onClick(View view) {
//Custom logic for debounce & call activity or fragment listener
}
}
}
call this in yourFragment or Activity like this
btnNext.setOnClickListener(new CustomClickListener(this));
'this' implement Fragment_ActivityListener if required otherwise you can remove it.
My problem is to understand how RecyclerView works.. I have RecyclerView with a little bit complicated item in every row, but the main thing is that Item has a child ImageView and LinearLayout. I want to press ImageView and set Visibility of LinearLayout to GONE or VISIBLE and rotate my ImageView. I tried to do this in my onBindViewHolder:
holder.mIVExpandBtn.setOnClickListener(new OnClickListener() {
boolean isOpen = false;
#Override
public void onClick(View v) {
if (isOpen) {
CounterListAdapter.this.notifyItemChanged(position);
holder.mLLDetails.setVisibility(GONE);
holder.mDivider.setVisibility(VISIBLE);
holder.setArrowUp(false);
isOpen = false;
counterItem.setDetailsOpened(false);
} else {
holder.mLLDetails.setVisibility(VISIBLE);
holder.mDivider.setVisibility(GONE);
holder.setArrowUp(true);
isOpen = true;
counterItem.setDetailsOpened(true);
}
}
});
And I have some problems here.
I have a boolean variable inside OnClickListener, I know its wrong, so it changes only one time when I expand my LinearLayout. If I make this boolean global variable, if I expand one row of RecyclerView isOpen = true for any other item and it doesn't expand itself when I click on ImageView.. Where should I place this boolean?
And the second question - how can I save the state of my RecyclerView rows on screen rotation? For example I expanded one of my rows, LinearLayout.setVisibility(VISIBLE), change screen orientation and its closed.
For your first problem, you should put your boolean variable where you also define your views, i.e., inside your ViewHolder, ir order that onClick you call the boolean this way
if(holder.isOpen)
In this way you keep the reference of each boolean to each row.
For your second problem, the solution is pretty simple. In your manifest, in the activity where you have your RecyclerView, define the following:
android:configChanges="keyboardHidden|orientation|screenSize"
This prevents your activity from being recreated on configuration change in case you rotate the screen, so the activity will keep it's state and your RecyclerView will therefor not be recreated along with your adapter.
Notice that this means that, if your activity is not recreated, onPause, onStop, etc, will not run. This is only for screen rotation, your activity will still run the method onConfigurationChanged() which is where you should define any changes you need in case the screen rotates.
You better put the OnClickListener in the Holder class, something like this:
private class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
#Override
public void onClick(View view) {
....
}
About how to save state I think all things that define the rows should be saved in the array you pass to the adapter contructor, you can add fields in the array item object that save VISIBILITY state of the row views.
On screen rotation then two options:
1 - as #Ricardo said avoiding Activity recreation
2 - onSaveInstanceState / onRestoreInstanceStates save/restore the array that define the rows .. my prefered method for that is to use JSON and put it in a String that can be saved/restored in the Bundle.
I am using the same onClick listener for a number of items.
When I click I want to know which one.
I know that I can do a Switch statement on the getId() but would rather be able to get at the name of the item.
Is there any easy way to do this?
I think what you are referring to when you say "get the name" is the id string from resources. So you would have a switch statement like:
switch(view.getId()) {
case R.id.HomeButtonOne:
// Do Button One Action
break;
case R.id.HomeButtonTwo:
// Do Button Two Action
break;
}
otherwise please elaborate more on what you are trying to achieve.
You have a few options.
You could extend View with a class which you create and includes additional identifiable information. Then in onClick, cast the View to your class type.
You could use an Adapter to better manager your views. This works best if you are displaying views of data instead of inert layouts or Drawables.
It's going to boil down to what you want to store and what you are viewing.
Just make a class which implements OnClickListner and set an instance to your views:
class MyListener implements OnClickListener() {
// ...
}
MyListener listener = new MyListener();
View view = (View) findViewById(R.id.myViewId);
view.setOnClickListener(listener);
view = (View) findViewById(R.id.myAnotherViewId);
view.setOnClickListener(listener);
...