I'm trying to find the best solution to handle OnClick event, which generates by my card's button (see the picture bellow) within GridView.
So as you can see, I have just a normal GridView with cells made of my custom Card.
I just initialize GridView and it's adapter:
mGrid = (GridView) findViewById(R.id.grid);
mAdapter = new ImageTopicsAdapter(..blah blah blah..);
mGrid.setAdapter(mAdapter);
As you probably know I can easily handle OnClick events generated by GridView. But it will work only if I click on the card itself:
mGrid.setOnItemClickListener(..blah blah blah..);
I want to build something similar to this (see code bellow), so I can easily "implement" my Activity to handle my card's button OnClick event:
mGrid.setOnItemButtonClickListener(..blah blah blah..);
What is the best (clean\easy\elegant) way to do this?
Any help is truly appreciated. Alex. P.S. Sorry for my English:)
Since you want to dispatch to your activity, I would recommend exposing a method in the activity and call it directly from your click listener. The shortest (and cleanest from my perspective):
in your Adapter, say ArrayAdapter
define to listen for clicks (to avoid multitude of anonymous listener instances)
dispatch a call directly to your activity (since every view context is an activity)
context above can be treated as your ApplicationActivity only if you didn't manually provide some other context, say application context
private final MyAdapter extends ArrayAdapter implements View.OnClickListener {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// inflate your card then get a reference to your button
View card = ....;
card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(this);
return card;
}
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (activity != null && !activity.isFinishing()) {
applicationActivity.onCardButtonClick();
}
}
}
// in your ApplicationActivity
public final class ApplicationActivity extends Activity {
...
public void onCardButtonClick() {
// deal with your click
}
}
There are other, textbook options (setting a listener, or activity in your view creation and so forth) but I avoid them since they don't solve absolutely anything.
They just add more dust in your code.
Any View context defined properly points to the activity (since it is a context too) which holds all view structure. This way you can access your activity quick and relatively easy.
BTW Event bus is not a good option since event buses are great for one-to-many relations (one dispatcher, many listeners) but add more complexity when used intensively for one-to-one calls (dispatcher-listener)
Addition for the comment
You can tweak a little the code and rather using the adapter, you can dispatch directly from your cell. In other words rather using the adapter as a delegate, create an anonymous listener and then reach and call the activity directly from your card button click:
public final MyAdapter extends ArrayAdapter {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// inflate your card then get a reference to your button
View card = ....;
card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
applicationActivity.onCardButtonClick();
}
}
});
return card;
}
}
Addition for the comment - Compound View
To encapsulate all cell logic, you can create a custom view from scratch or use a compound view. The example below is using a compound view:
public class ApplicationActivity extends Activity {
....
public void onCardButtonClick(Cell cell) {
// do whatever you want with the model/view
}
}
// ViewModel instances are used in your adapter
public final class ViewModel {
public final String description;
public final String title;
public ViewModel(String title, String description) {
this.title = title != null ? title.trim() : "";
this.description = description != null ? description.trim() : "";
}
}
public final class Cell extends LinearLayout {
private View button;
private ViewModel model;
// ViewModel is data model and is the list of items in your adapter
public void update(ViewModel model) {
this.model = model;
// update your card with your model
}
public ViewModel getModel() {
return model;
}
#Override
protected void onAttachedToWindow() {
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener {
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (model != null && activity != null && !activity.isFinishing() && !activity.isDestroyed() {
activity.onCardButtonClick(Cell.this);
}
}
});
}
}
// then your adapter `getView()` needs to inflate/create your compound view and return it
public final MyAdapter extends ArrayAdapter {
private final List<ViewModel> items;
public MyAdapter() {
// update your models from outside or create on the fly, etc.
this.items = ...;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// inflate - say it is a layout file 'cell.xml'
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cell);
}
((Cell) convertView).update(items.get(position));
return convertView;
}
}
Adapter should handle this. Generally your Adapter should have method like setOnOptionsClickListener(OnOptionsClickListener listener) assuming that we are talking about ellipsis button.
So in your Activity/Fragment you use following code
public interface OnOptionsClickListener {
void onOptionsClicked(View view, PictureItem item);
}
mAdapter= new MyGridAdapter();
mAdapter.setOnOptionsClickListener(new OnOptionsClickListener() {
public void onClick(View view, PictureItem item) {
//process click
}
});
And following inside Adapter
public void setOnOptionsClickListener(OnOptionsClickListener l) {
mOnOptionsClickListener = l;
}
findViewById(R.id.btn_options).setOnClickListener(new OnClickListener(){
public void OnClick(View view) {
mOnOptionsClickListener.onOptionsClicked(view, currentPictureItem);
}
});
Please notice. You need to declare interface only if you need to have extra parameters in OnClick() method (for example currentPictureItem to get image url or item id). Otherwise, you can use just OnClickListener.
Edit
So here is explanation. Adapter serves like a View-provider for your GridView. It creates views and it configure it basic state. That's why all click listeners should be set in Adapter during views initializing. Moreover, we don't want to have a messy Activity with nested Adapter, but we want to have Adapter as a separate class. This is the reason you will usually need to create additional interface in order to have an access to currentItem object to extract data from.
Looks like nobody knows how to do this. So I found solution myself with help of #Dimitar G. and #Konstantin Kiriushyn. Thank you, guys.
1) I will create my own custom CardView using Compound View system, which will be pretty simple: LinearLayout + ImageView + TextView + Button.
public class TopicCardView extends LinearLayout {
private ImageView mImage;
private Button mButtonMenu;
private TextView mTitle;
public TopicCardView (Context context) {
initializeViews(context);
}
private void initializeViews(Context context) {
LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.topic_card_view, this);
}
private void setTitle(...) {
...
}
private void setImage(...) {
...
}
private void setMenuClickListener(...) {
...
}
// and so on...
}
2) Then I will create method called createListOfGridCardsFromDB(...) in Activity\Fragment. It will generate list (LinkedList) of my custom CardViews (and it will also set titles\images and listeners to CardViews).
3) And then I will pass this generated LinkedList of my CardViews to GridViewAdapter.
This system makes able to use only one Adapter for all my card-grids in app. It also makes able to do nothing with clicks, interfaces, listeners and stuff in Adapter.
Related
I'm building an Android app of media, and trying to add a Playlist feature to it, the user will be able to create a playlist of his own and modify it.
I'm using a RecyclerView to show the user list of songs which he can choose from.
The problem is I don't understand how to pass the Arraylist of chosen songs from the adapter to the fragment.
I've tried to use the Observer pattern but the don't know how to use that information.
This is my Fragment for creating the playlist:
public class CreatePlaylistFragment extends Fragment implements PlaylistAdapterInterface {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_create_playlist, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ArrayList<ProgramsData> dataArrayList = ProgramsReceiver.getPrograms();
ArrayList<ProgramsData> sortedList = new ArrayList<>(dataArrayList);
adapter = new CreatePlaylistAdapter(dataArrayList, view.getContext(), this);
adapter.adapterInterface = this;
ivCreatePlaylist.setOnClickListener(v -> {
Toast.makeText(v.getContext(), "Creating Playlist!", Toast.LENGTH_SHORT).show();
new PlaylistsJsonWriter(playlistArrayList,getContext()).execute();
});
}
#Override
public void OnItemClicked(ArrayList<ProgramsData> programs) {
programsToCreate = programs;
String s = etListName.getText().toString();
playlistArrayList.add(new Playlist(s, programsToCreate));
}
}
This is the Recycler Adapter with ViewHolder as inner class:
public class CreatePlaylistAdapter extends RecyclerView.Adapter<CreatePlaylistViewHolder> {
List<ProgramsData> programsDataList;
Context context;
public PlaylistAdapterInterface adapterInterface = null;
public CreatePlaylistAdapter(List<ProgramsData> programsDataList, Context context , PlaylistAdapterInterface adapterInterface) {
this.programsDataList = programsDataList;
this.context = context;
this.adapterInterface = adapterInterface;
}
#NonNull
#Override
public CreatePlaylistViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.chose_program_to_playlist_item, viewGroup, false);
return new CreatePlaylistViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CreatePlaylistViewHolder holder, int i) {
ProgramsData programsData = programsDataList.get(i);
holder.tvProgramName.setText(programsData.getProgramName());
if (programsData.getStudentName() != null)
holder.tvStudentName.setText(programsData.getStudentName());
else holder.tvLine.setText(""); //if there is no student the line won't be printed
holder.ivProfilePic.setImageResource(programsData.getProfilePic());
holder.programsData = programsData;
// holder.mAdapterInterface = adapterInterface;
adapterInterface.OnItemClicked(holder.programs);
}
#Override
public int getItemCount() {
return programsDataList.size();
}
}
class CreatePlaylistViewHolder extends RecyclerView.ViewHolder {
TextView tvProgramName;
TextView tvStudentName;
TextView tvLine;
CircleImageView ivProfilePic;
ToggleButton tbCheck;
ProgramsData programsData;
ArrayList<ProgramsData> programs;
PlaylistAdapterInterface mAdapterInterface;
public CreatePlaylistViewHolder(#NonNull View itemView) {
super(itemView);
tvProgramName = itemView.findViewById(R.id.tvProgramName);
tvStudentName = itemView.findViewById(R.id.tvStudentName);
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
tvLine = itemView.findViewById(R.id.tvLine);
tbCheck = itemView.findViewById(R.id.tbCheck);
programs= new ArrayList<>();
tbCheck.setOnClickListener(v -> {
if (tbCheck.isChecked()) {
tbCheck.setBackgroundResource(R.drawable.ic_radio_button_checked);
programs.add(programsData);
} else if (!tbCheck.isChecked()) {
tbCheck.setBackgroundResource(R.drawable.ic_check);
programs.remove(programsData);
}
});
}
}
And this is the interface for the Observer Pattern:
public interface PlaylistAdapterInterface {
void OnItemClicked(ArrayList<ProgramsData> programs);
}
I know it's a lot of code, but I just don't understand how to pass the data from the adapter back to the fragment...
I don't understand exactly what are you trying to do.
The code contains several errors that I'll try to explain.
A clear error that you have made stays in onBindViewholder where you call the listener at the creation of every item instead than after clicking on it.
You have simply add an onClickListener in the viewHolder.getItemView() or in a specific view of the viewholder and then perform the operation you need to do once an item is clicked.
If you set a listener inside onBindViewHolder, you also have a method called
holder.getAdapterPosition() that you can use to understand which item are you clicking on.
The viewholder should be used only to setup the views accordingly to the data you are binding and nothing else. For this reason, you should not pass any object or listener to it and instead use the approach above.
If you have just to retrieve the selected songs after an user confirms it's playlist you can just add a public method on your adapter
public List<ProgramsData> getSelectedSongs()
that you can call from your fragment when an user click a confirm button.
In order to have a list of all selected song, you can have another list
ArrayList<ProgramsData> selectedPrograms;
that you are going to fill after the click.
The content of the listener inside the onBindViewHolder could be
ProgramsData currentProgram = programs.get(holder.getAdapterPosition());
if(selectedPrograms.contains(currentProgram){
selectedPrograms.remove(currentProgram);
}else{
selectedPrograms.add(currentProgram);
}
notifyItemChanged(holder.getAdapterPosition); //You can use this to update the view of the selected item
Then inside the onBindViewHolderMethod you can check whether the items you are binding are part of the selectedList and update the views accordingly.
You can use callback method. Maintain list of selected items in array list and send back to fragment when done button is clicked or any other button you have placed for complete action.
Follow these steps
-Create an Interface with list parameter.
-Fragment should implement this interface.
-Then when you initialize Recyclerview adapter pass this interface object.
-When done is clicked call overridden method of this interface and send selected songs list as argument.
Edited:
I want know about creating Anonymous Listener in bindViewHolder method cause any performance problem or not for large data set.
Suppose i have a RecyclerView Adapter. And in bindViewHolder method if i set all my listeners Anonymously does this cause any performance problem? Because when user scrolls the RecyclerView it will create lots of Anonymous listeners and set them to the views.
Example:
view.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
}
});
Or i can implements OnClickListener in my ViewHolder class and just add to views.Like
view.setOnClickListener(this);
Here lots of Anonymous Listeners are not created. Does this perform better from previous in performance calculation for large dataset?
Thanks in advance.
RecyclerView will only display few items, ViewHolder will only be created for items that are visible, so even if you have 1000s of items in your adapter, only small fraction of ViewHolders are created.
But you will have to be careful with addListener methods, for most setListener methods, you will be setting same listener again and again when item is recycled, which does not take less then few milliseconds as it only keeps reference of the listener implementation.
But with addListener, you will have to remove old listener before adding new one.
Example of setListener is setClickListener and example of addListener is addTextWatcher
//.. part of adapter
private TextWatcher textWatcher;
public void bindViewHolder(DataViewHolder holder, int index){
// no performance issue
holder.button.setClickListener( .... );
// wrong, this is added everytime
holder.editText.addTextWatcher( .... );
// this is safe...
if(textWatcher != null)
holder.editText.removeTextWatcher(textWatcher);
textWatcher = new TextWatcher(){
// ... implementation
};
holder.editText.addTextWatcher(textWatcher);
}
Basically, you set a OnClickListener in every item of your RecyclerView and "connect" it to your Activity or Fragment. This "connection" is important, so you can have your onItemClick method inside your Activity or Fragment and access the members there.
A minimal implementation would look like this (in a Fragment, but you can also use an Activity):
public class YourFragment extends Fragment implements RecyclerViewAdapter.ItemClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_your, container, false);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(activity);
recyclerViewAdapter.setClickListener(this);
recyclerView.setAdapter(recyclerViewAdapter);
return view;
}
#Override
public void onItemClick(View view, int position) {
// do something here
}
}
And the Adapter class
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private ItemClickListener itemClickListener;
void setClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
interface ItemClickListener {
void onItemClick(View view, int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// TextView is an example
final TextView textView;
ViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
textView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (itemClickListener != null) {
itemClickListener.onItemClick(view, getAdapterPosition());
}
}
}
}
I'm pretty sure the compiler just creates a no-name concrete version of your anonymous class under the hood. That is nearly identical to implementing the interface and providing this as a concrete listener. Realistically, you shouldn't have a performance problem with either.
Just keep in mind that of the fact that an anonymous class holds a reference to the outer class. That might create memory leaks (example: if the outer class is an activity) or just make it so that garbage collection happens all at once instead of small pieces over time. See Implicit and Synthetic Parameters in the oracle documentation for more details on that.
I want to create an interface between an adapter and a view holder (this view holder is an inner class of another adapter) so that I can update the text view (number). How can I do this?
In detail:
I have two recycle views (Main List Recycler View and Sub List Recycler View horizontally placed as shown in the fig) one having a number (as one of its item) and other having checkbox (as its item).
I have two adapters FilterMainListAdapter and FilterSubListAdapter with view holders FilterMainListViewHolder and FilterSubListViewHolder populating the fields.
When checkboxes are selected in the Sub List Recycler View, I want the corresponding number in the Main List Recycler View to update.
For this, I'm using and Interface.
public interface ChangeFilterMainNumber {
void OnChangeFilterMainNumberListener(int totalCheckedNumber);
}
I've checkbox's onClick method inside the FilterSubListViewHolder and I'm trying to send the total check boxes checked number as follows.
changeFilterMainNumber.OnChangeFilterMainNumberListener(totalCheckedNumber);
After that, I'm implementing ChangeFilterMainNumber interface inside the FilterMainListViewHolder
public class FilterMainListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,
ChangeFilterMainNumber {...}
But How can I define this interface inside the FilterSubListAdapter?
changeFilterMainNumber = ???;
[If it is an activity one can define the interface like this changeFilterMainNumber = (ChangeFilterMainNumber) context inside the default constructor of FilterSubListAdapter. But what about a view holder that is an inner class of another adapter?]
or is there a better approach in finding a solution to my problem other than this?
Update: You can take a look at the code here https://github.com/gSrikar/FilterScreen
If I implement the function as you want, I will implement like this:
(This is like an Observer pattern)
class Fragment/Activity implement OnChangeFilterMainNumberListener{
FilterMainListAdapter mainAdapter;
FilterSubListAdapter subAdapter;
void oncreate() {
mainAdapter = new FilterMainListAdapter(this);
}
#Override
void OnChangeFilterMainNumberListener(int totalCheckedNumber) {
.....
// Update data to sub list
}
}
class FilterMainListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
public interface ChangeFilterMainNumber {
void OnChangeFilterMainNumberListener(int totalCheckedNumber);
}
ChangeFilterMainNumber listener;
FilterMainListAdapter(ChangeFilterMainNumber listener) {
this.listener = listener;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
item.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(listener != null) {
listener.OnChangeFilterMainNumberListener(position)
}
}
});
}
}
class FilterSubListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
}
EDIT: I've solved this issue, if interested, please take a look at my answer to see how I did it!
I am currently working in Android Studio. I have a ListView that I populate with several items. Within each of these items is an ImageButton that has a "+" as the image. What I want to do is, whenever that image is clicked (not the entire ListView item, just the image), I want that image of "+" to become another image. Any help would be appreciated, as this has been an ongoing issue for a while!
Here is the current code that I attempt to use to achieve this:
final ImageButton movieSeen = (ImageButton convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
}
});
Currently this does update the image that I click correctly, BUT it also updates images that are not yet rendered on the screen, so when I scroll the list view down, other objects are also changed to ic_check_circle_black_24dp.
What I want is pretty straightforward, I just don't know how to achieve it. I just want to click an ImageButton, that's inside an item on a ListView, and have that ImageButton change its image resource.
Here is my custom array adapter as requested!
private class MovieScrollAdapter extends ArrayAdapter<Movie> {//custom array adapter
private Context context;
private List<Movie> movies;
public MovieScrollAdapter(Context context, List<Movie> movies){
super(context, -1, movies);
this.context = context;
this.movies = movies;
if(this.movies.isEmpty()){//if no results were returned after all processing, display a toast letting the user know
Toast.makeText(getApplicationContext(), R.string.no_matches, Toast.LENGTH_SHORT).show();
}
}
#Override
public View getView(final int position, View convertView, ViewGroup parent){
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.movie_layout, parent, false);
}
TextView title = (TextView) convertView.findViewById(R.id.title);
title.setText(movies.get(position).getTitle());
TextView plot = (TextView) convertView.findViewById(R.id.plot);
plot.setText(movies.get(position).getPlot());
TextView genre = (TextView) convertView.findViewById(R.id.genre);
genre.setText(movies.get(position).getGenre());
TextView metaScore = (TextView) convertView.findViewById(R.id.metascore);
if(movies.get(position).getMetaScore() == -1){//if the metaScore is set to -1, that means movie has not been rated, which by inference means it is not yet released
metaScore.setText(R.string.movie_not_released);
metaScore.setTextSize(TypedValue.COMPLEX_UNIT_SP, 9.5f);//smaller text so it fits without breaking anything
metaScore.setTextColor(getColor(R.color.colorAccent));
} else {
metaScore.setText(" " + Integer.valueOf(movies.get(position).getMetaScore()).toString() + " ");//using white space for minor formatting, instead of altering margins each time this is rendered
metaScore.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
//setting up a "highlighted" background to achieve metacritic square effect
Spannable spanText = Spannable.Factory.getInstance().newSpannable(metaScore.getText());
spanText.setSpan(new BackgroundColorSpan(getColor(R.color.metaScore)), 3, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
metaScore.setText(spanText);
metaScore.setTextColor(getColor(android.R.color.primary_text_dark));
}
ImageView image = (ImageView) convertView.findViewById(R.id.imageView);
new ImageDownloadTask((ImageView)image).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, movies.get(position).getPosterURL());//because there are several images to load here, we let these threads run parallel
title.setOnClickListener(new View.OnClickListener() {//setting up a simple onClickListener that will open a link leading to more info about the movie in question!
#Override
public void onClick(View v) {
Uri uri = Uri.parse(movies.get(position).getMovieURL());
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
final ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
}
});
return convertView;
}
}
The problem is on a ListView, the views are being reused to save memory and avoid creating a lot of views, so when you change a view it keeps the state while it's being reused to show another item.
For example, you have 100 elements, you touch the first element ImageButton and that button is changed. Maybe on the screen there are 5 elements of the list showing, and you changed the first one. But if you scroll to the element number 15 the system is not creating 15 views, is taking the first one you clicked before and is changing the content.
So, you are expecting to have a view with a "+" ImageButton icon but you see another icon, that's because you must keep the view state inside a model object and set the state every time 'getView' is called.
Post your list adapter to see how is implemented.
UPDATE:
Now I see your adapter implementation I suggest you to add an int field inside Movie class to save the resource id you want to show on the ImageButton. Then inside the onClickListener you must set to this field the resource you want to show on the view when its clicked, and call notifyDataSetChanged(). After that you must do inside getView():
movieSeen.setImageResource(movies.get(position).getButtonImageResource());
Use RecyclerView and set the OnItemClickListener on your ImageButton within your view holder.
This already answered question should help.
The adapted code below is coming from this nice tutorial. Using ReciclerView with an adapter like this will solve your concern.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<String> mDataset;
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public TextView txtHeader;
public ViewHolder(View v) {
super(v);
txtHeader = (TextView) v.findViewById(R.id.xxx);
imageView = (ImageView) v.findViewById(R.id.yyy);
}
}
public MyAdapter(ArrayList<String> myDataset) {
mDataset = myDataset;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.rowlayout, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
final String name = mDataset.get(position);
holder.txtHeader.setText(mDataset.get(position));
holder.imageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// Do here what you need to change the image content
}
});
holder.itemView.setBackground(....); // Initialize your image content here...
}
//...
}
Here is my suggestion to achieve what you want :
Create An Interface in your adapter :
public interface YourInterface{
void selectedImage(int position,ImageView iamgeView);
}
Create variable interface in your adapter that you just created :
private YourInterface yourInterface;
and make your adapter constructor like this :
public YourAdapterConstructor(YourInterface yourInterface){
this.yourInterface = yourInterface;
}
in your ImageView onClickListener :
final ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
yourInterface.selectedImage(position, imageView);
}
});
and then finally in your class activity, Implements YourInterface and change you ImageView there :
#Override
public void selectedImage(final int position,final ImageView imageView) {
//change your image view here
}
I'd like to thank everyone for their support. Unfortunately, with the way my code is written (rather messily and without much regard for what my professors taught me), I was unable to get most of these solutions to work. I did however, find a solution that falls in line with my own framework that I've had going into this. Unfortunately I could not redo my entire adapter method, or implement various interfaces that would cause me to have to rewrite a huge chunk of code for something seemingly trivial.
So, if anyone finds themselves in this situation in the future, here is my solution:
In the Movie class, I add a boolean value that represents my values, along with some getters and setters:
private boolean watchedStatus;
public boolean hasSeen() {return watchedStatus;}
public void toggleWatchedStatus(){
watchedStatus = !watchedStatus;
}
In the getView method, I simply get a reference to the ImageButton, and then based on the boolean value returned by "hasSeen," I set the ImageResource to one of two states:
#Override
public View getView(final int position, View convertView, ViewGroup parent){
ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieSeen);
if(movies.get(position).hasSeen()){
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
} else {
movieSeen.setImageResource(R.drawable.ic_add_circle_black_24dp);
}
}
Next, I override the OnClickListener, and make it so that whenever the button is clicked, the boolean value in the Movie.java class is toggled. The key here was using the ArrayAdapter's method "notifyDataSetChanged()" This completes the process, and lets the ListView know that it should update itself:
final ImageButton movieSeenForClick = (ImageButton) convertView.findViewById(R.id.movieSeen);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//movieSeenForClick.setImageResource(R.drawable.ic_check_circle_black_24dp);
movies.get(position).toggleWatchedStatus();
System.out.println(movies.get(position).hasSeen() + " ------- position: " + position);
notifyDataSetChanged();
}
});
Thanks again for the time taken to provide information, a lot of it really did steer me int he right direction, I just had to use the information correctly with the way my code was structured.
I am writing an android application where the user can add and remove fields interactively. Each field the user add has some buttons, and value which the user should be able to interact with. I thought to create a subclass to handle the field I can add which will hold it's own onClickListener but I'm not sure how to do so.
Here is some pseudo code which should make my intention clear.
Say I have a class , vClass:
public class sClass extends View implements onClickListener{
this.setContextView(R.layout.vClass);//how do I do this in a correct way?
#Override
public void onClick(View v){ //add code here
}
}
and aClass which is the main class of the application.
public class aClass extends Activity implements onClickListener{
Button b;
LayoutInflater i;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.b =(Button)this.findViewById(R.id.btn);
b.setOnClickLister(this);
}
#Override
public void onClick(View v){
//this is what I have now to add View
final LinearLayout canvas =(LinearLayout)aClass.this.findViewById(R.id.main);
View cv =this.inflater.inflate(R.layout.counter, canvas, false);
canvas.addView(cv);
}
}
how can I use the vClass to add elements to the aClass.
Typing this is I thought about another solution.
If I keep track of the id's of all the views I have added (without the subcomponents) can I do something of that kind:
View vv = findViewById(id);
Button bb = vv.findViewByIf(R.id.xmlId);
where id is an id I have assigned to the view which I know and xmlId is a string I have specified in the xml file?
Thanks
Yotam
For solution, read the discussion below
IDs used in layouts are not necessarily unique, so i guess you should keep the added Views in an ArrayList, as
View cv =this.inflater.inflate(R.layout.counter, canvas, false);
this.viewList.add(cv);
canvas.addView(cv);
or you could declare an index member inside your sClass implementation, and store the added indices in an ArrayList:
private int index;
public sClass(final int index)
{
this.index = index;
}
public int getIndex()
{
return this.index;
}
#override
public boolean equals(Object obj)
{
return ((obj instanceof sClass) && (((sClass)obj).getIndex() == this.index));
}
Both ways you have access to the view you want.
The button that lays inside the view is accessible via the findViewById() method
Button bb = vv.findViewById(R.id.buttonId);
where R.id.buttonId was declared in the vv view's layout xml file, as follows:
<Button android:id="#+id/buttonId" [...] />