I'm building an app similar to instagram just for training purposes and basically I have a list of images that I populate through a grid view adapter. In my XML file for 'single_list_item' I have defined a button to be added along with the image for each single item on the grid view list.
However, I want to be able to click on these buttons to 'place a like' on these images separately (similar to instagram's like system). In other words: a user could place a like on the first image and not like the rest of the images. I display a like using a different image resource.
Images get populated by the grid view
Each image has a Like button beneath it
I click on a random like button belonging to a specific image
that image should be selected as 'liked'
What I tried so far:
My grid adapter:
public class ProductGridInflator extends ArrayAdapter<Product>{
.......
.......
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View child = convertView;
final RecordHolder holder;
LayoutInflater inflater = ((Activity) ctx).getLayoutInflater();
if (child == null) {
child = inflater.inflate(R.layout.grid_single, parent, false);
holder = new RecordHolder();
holder.productName = (TextView) child.findViewById(R.id.grid_text);
holder.image = (ImageView) child.findViewById(R.id.grid_image);
holder.heartButton = (ImageButton) child.findViewById(R.id.btnHeartLike);
holder.heartButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (v.isSelected()){
v.setSelected(false);
} else {
holder.heartButton.setSelected(false);
v.setSelected(true);
}
}
});
child.setTag(holder);
}else{
holder = (RecordHolder) child.getTag();
}
}
}
And I'm selecting the appropriate image from a selector (Liked or unliked)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/heart_clicked" android:state_selected="true"/>
<item android:drawable="#drawable/heart304"/>
</selector>
However this code is misbehaving when I test it. When I click on the like button of a specific image list item, two or three other list item's like buttons get clicked as well. Is there a better way to get this done? Individual buttons for each list item?
THe Problem is that you are reusing the onclicklisteners .. Remove this from your getView
holder.heartButton.setOnClickListener(new OnClickListener() {
And instead write in your activity
YouGridView.setOnItemClickListener
And add what you want to do when an item is clicked.
Related
So I am trying to create a challenge game where you can have 2 options to pick from, I have 2 card views setup in a recycler view setup, how would I change the values in them to get a new challenge with the click of a button from the main activity if a user wanted a new challenge?
I am fetching the data from each battle at random using an SQLite database.
My onBindViewHolder
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
SQLiteDatabase sqLiteDatabase = faceMashDatabaseHelper.getReadableDatabase();
cursor = sqLiteDatabase.query("images", new String[]{"id","filename","name"},null,null,null,null,null);
MyViewHolder myViewHolder = (MyViewHolder) holder;
int position1 = (int)(Math.random()*(faceMashDatabaseHelper.getCount()-0+1)+0);
cursor.moveToPosition(position1);
String filename = cursor.getString(cursor.getColumnIndex("filename"));
String name = cursor.getString(cursor.getColumnIndex("name"));
CardView cardView = myViewHolder.getCardView();
ImageView imageView = (ImageView) cardView.findViewById(R.id.imageView);
TextView nameView = (TextView) cardView.findViewById(R.id.nameView);
nameView.setText(name);
File file = context.getFileStreamPath(filename);
if(file.exists()){
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
imageView.setImageBitmap(bitmap);
}
cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String name = nameView.getText().toString();
listener.onClick(position,name);
}
}); //This on click is for the card views themselves.
}
My onClick at the current moment, I'm using a toolbar option
case R.id.newbattle:
faceMashAdapter.notifyDataSetChanged();
First of all, you should never do heavy work in onBindViewHolder since it will be called every time your View is recreated after it was recycled (because it was unused).
You should query your Database outside of the Adapter instead and occupy a List with your queried data. Your List should have as many entries as your RecyclerView has entries. Then you can use the position parameter in onBindViewHolder to always get the correct data for the entry (list.get(position)).
This way you can just change the content of your List and notify the Adapter which items have changed via notifyItemChanged (or if all items should be refreshed, use notifyDataSetChanged). That way the content of the Adapter is refreshed.
So what you should do in your onClick is update the content of your List and notify the Adapter.
I have a custom listview, each item have a imagebutton, i want to change image of each imagebutton immediately when I click. Each item in custom listview is a object named baiHat{}.
I use BaiHatAdapter like this, but it not chane immediately, i must scroll listview to see change.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater=this.context.getLayoutInflater();
View row=inflater.inflate(this.resource, null);
TextView txtMs = (TextView) row.findViewById(R.id.ms);
TextView txtBh = (TextView) row.findViewById(R.id.bh);
TextView txtCs = (TextView) row.findViewById(R.id.cs);
TextView txtLr = (TextView) row.findViewById(R.id.lr);
ImageButton imglike = (ImageButton) row.findViewById(R.id.imageButton);
final BaiHat baiHat = this.objects.get(position);
txtMs.setText(baiHat.getTxtms());
txtBh.setText(baiHat.getTenBh());
txtCs.setText(baiHat.getTxtcs());
txtLr.setText(baiHat.getTxtLr());
imglike.setImageResource(baiHat.getImg());
imglike.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
xulythich(baiHat);
}
});
return row;
}
private void xulythich(BaiHat baiHat) {
if(baiHat.getThich()){
baiHat.setThich(false);
baiHat.setImg(R.drawable.addfav);
}else{
baiHat.setThich(true);
baiHat.setImg(R.drawable.added);
}
}
Create a selector file and assigning it as src would work. But as a better approach you can try to change it to togglebutton, since imageview is not meant to be clickable and give it the background of selector source.
It will just look like any imageview but with the capability of being clickable and changing its background from the selector file.
I have a 'ChecklistItem' class that has the following properties:
private CheckBox checkBox;
private ImageButton noteButton;
private TextView vitalField;
I have an onClick Listener for my checkbox. Now the problem is, when I click on that checkbox and the OnClick() method gets called, how can I figure out what ChecklistItem that checkbox is a part of?
Whenever I click on a checkbox, I want to add the ChecklistItem that the checkbox is a part of to an array, but the OnClick() only knows about the checkbox that called it.
How can I get around this?
Ok so this answer is according to the "long discussion" we had
let's assume you want to make a - re usable - view of your list and you wrote a separate xml layout file called list_item as the following:
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/checkbox"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/text_view"/>
so now let's assume you are in the activity or fragment or wherever you want to host your view , NOW I have to point out this is just an example , usually a list view is what you would need in this case but again I have very little details about your app so I'm going to keep it simple
Assuming you have a vertical linear layout and you want to add these "rows" to it, each row represents one of your custom view
LinearLayout layout = findViewById(R.id.layout);
LayoutInflater inflater = LayoutInflater.from(this); // This inflater is responsible of creating instances of your view
View myView = inflater.inflate(R.layout.list_item, layout, false); // This view objects is the view you made in your xml file
CheckBox checkBox = (CheckBox) myView.findViewById(R.id.checkbox);
TextView textView = (TextView) myView.findViewById(R.id.text_view);
checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//if checkbox is checked enable textview for example
// here you have a reference to all the views you just created
// Weather you want to save them in a class together that's up to you and your app's logic
}
});
layout.addView((myView));
if the list is might exceed the screen height you may want to wrap your linear layout in a scroll view.
BTW: ListView is just a neat way to do this automatically by defining how you want each row to appear, and of course it manages your views for you and recycle them when they get of screen, but I just wanted to point out the concept.
Hope this helps you
Is it possible to have methods and actual coding inside each item for a GridView?
The app that I am attempting to create is a currency converter, and I am currently displaying 3 images in the gridView: Euros, Pesos, and Rupees.
Once the user clicks on one, I want the open to open up a new XML which displays a textView. The user enters the value of US dollars in the textView and clicks the compute button. The app then displays the converted amount in the bottom of the screen.
The problem is that I am unable to figure out how to open up a new XML every time a picture is clicked on in the gridView. Assuming that I am able to do this, I am also unsure of where to place the code that goes behind the conversions. Would I make a new .java or just place is all in MainActivity.java?
Thanks.
What you might be best doing is when the user clicks on a currency it takes them to another activity where you would then load another xml for whatever you want to show.
In order to detect which item had been clicked you can implement an onItemClickListener for example
gridView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//this assumes you give the gridview a list of currency which it then displays. Here we get the currency selected to then pass to our new activity
String selectedCurrency = myArrayOfCurrencies.get(position);
//then start new activity and give it the currency. This means we won't have to create an activity for each currency. You just need to create 1 and then based on what currency you give it will change the functionality
Intent intent = new Intent(this, Converter.class);
Intent.putExtra("currency", selectedCurrency);
startActivity(intent);
}
First you should be able to detect the clicks on each item of the GridView by calling the setOnItemClickListener() method.
If you set the clicklistener and you still can't detect the clicks, then most probably you need to add those attribtutes to your imageView in the xml
android:focusable="false"
android:focusableInTouchMode="false"
Second, once you are able to detect the clicks you can start new activity or add fragment that contains that edit text that will promote the user to enter the value.
Third, I would suggest to put the code responsible for the currency conversion in a class separately and create static methods that takes a value and convert it to the other curreny such as:
public class CurrencyConverter {
public static double convertToRupees (String currencyType, double currencyValue){
....
return currencyInRupees;
}
}
and by the way I would suggest you to use RecyclerView with grid layout manager instead of GridView.
I would create more classes.
You asked how to open a different XML file for each gridView item.
Create a custom adapter that extends BaseAdapter.
Override getView and for each view attach the right Xml file, according to the position.
For example:
YourActivity.java:
GridView gridView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gridView = (GridView) findViewById(R.id.gridView);
gridView.setAdapter(new MyAdapter(getApplicationContext());
}
MyAdapter.java:
...
#Override
public int getCount() {
return XmlArr.length;
}
#Override
public Object getItem(int position) {
return XmlArr[position];
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Xml myXml = (Xml) getItem(position);
Holder holder;
if (convertView == null) {
// Set your view's layout. Consider using LayoutInflater.
// Use a static holder to prevent re-initialization:
holder = new Holder();
// holder.textView = ...
// holder.Xml = ...
// Or whatever you decided to have in each gridView item.
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.Xml = myXml;
...
return convertView;
}
static class Holder() {
TextView tv;
Xml xml;
...
}
I assumed you would used an Xml array (xmlArr).
Now you have option to play with each gridView item as you wish. You can set each view/button/textView an onItemClickListener, or you can also set the whole gridView an onItemClickListener (from YourActivity.java).
Hope this helps.
I am developing a tabbed application in which one of the fragments, CollectionsFragment, contains a GridView with an ImageView in each slot. I would like the to use a selector to give feedback to users when the user clicks on one of the images.
I have successfully implemented the selector, however, my problem is that the selector is only drawing in the background of the image, but I would like to the selector to draw over the entire image. I have seen this problem referenced elsewhere, however, the solution selected by many, setting the drawSelectorOnTop property of the GridView, is not working for me.
The relevant fragment with the relevant adapter code:
public class CollectionsFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_collections, container, false);
// Grid view is inside the xml view inflated above
GridView gridView = (GridView)view.findViewById(R.id.gridview);
gridView.setDrawSelectorOnTop(true);
((GridView) gridView).setAdapter(new CustomGridViewAdapter(getActivity()));
return view;
}
private class CustomGridViewAdapter extends BaseAdapter {
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
View v = view;
ImageView picture;
TextView name;
if(v == null) {
v = inflater.inflate(R.layout.collections_item, viewGroup, false);
v.setTag(R.id.picture, v.findViewById(R.id.picture));
v.setTag(R.id.text, v.findViewById(R.id.text));
}
picture = (ImageView)v.getTag(R.id.picture);
name = (TextView)v.getTag(R.id.text);
Item item = (Item)getItem(i);
name.setText(item.name);
picture.setImageResource(item.drawableId);
picture.setBackgroundResource(R.drawable.selector);
return v;
}
}
}
And my selector for completeness sake:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="#color/buttonhighlight"/> <!-- pressed state -->
<item android:state_focused="true"
android:drawable="#color/buttonhighlight"/> <!-- focused state -->
<item android:drawable="#android:color/transparent"/> <!-- default state -->
</selector>
Thanks for any help,
I think you are mistaken about setDrawSelectorOnTop(boolean). The selector drawable that is being referenced here is GridView's internal selector drawable.
Even in the simplest implementation of GridView, when a grid item is clicked, the blue border is drawn around it. This is because, by default, gridview's own selector is drawn behind the item. If you call setDrawSelectorOnTop(true), the selector (blue) will be drawn over the item.
But setDrawSelectorOnTop(boolean) has nothing to do with the selector you are setting in the adapter. Whether you pass true, or false, the ImageView's selector's behavior won't change.
Solution:
Instead of setting the selector on each ImageView inside the adapter, make the GridView use your selector drawable:
GridView gridView = (GridView)view.findViewById(R.id.gridview);
gridView.setDrawSelectorOnTop(true);
// Make GridView use your custom selector drawable
gridView.setSelector(getResources().getDrawable(R.drawable.selector));
Now, there's no need for:
picture.setBackgroundResource(R.drawable.selector);
Edit:
Although I don't recommend this (obvious overhead), it should work:
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
View v = view;
ImageView picture;
....
....
LayerDrawable ld = new LayerDrawable(new Drawable[]
// Drawable from item
{ getResources().getDrawable(item.drawableId),
// Selector
getResources().getDrawable(R.drawable.selector)});
// Set the LayerDrawable
picture.setImageDrawable(ld);
// Don't need this
// picture.setBackgroundResource(R.drawable.selector);
return v;
}
Try setting the xml attribute android:drawSelectorOnTop in your activity_collections.xml file.
See if placing gridView.setDrawSelectorOnTop(true); after gridView.setAdapter(); helps. Sometimes, the order matters (weird).
If all else fails, you may have to switch GridView to some other view where setDrawSelectorOnTop() is proven to work consistently.