How to mark views in a ListView? - android

I have an app with a list view. The listview works fine. The problem starts, when I want the list to start with some of the rows marked. I can mark a row, if I press on it. But, don't seem to find a way to mark any row on initialization.
This is my code:
listViewOfBluetooth = getListView();
setInitialEnabledDevices();
listViewOfBluetooth.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String chosenBluetoothDevice = (String) ((TextView) view).getText();
BluetoothEnableOrDisable(view, chosenBluetoothDevice);
Toast.makeText(getApplicationContext(), chosenBluetoothDevice, Toast.LENGTH_SHORT).show();
editor.putString("bluetooth_name_from_list1", chosenBluetoothDevice);
editor.putBoolean("have_the_cars_bluetooth", true);
editor.commit();
Intent intent = new Intent(List.this, ParkOGuardActivity.class);
startActivity(intent);
}
});
}
public static void setInitialEnabledDevices(){
int length = listViewOfBluetooth.getChildCount();
View view = null;
String first = prefs.getString("bluetooth_name_from_list0", "");
String second = prefs.getString("bluetooth_name_from_list1", "");
String third = prefs.getString("bluetooth_name_from_list2", "");
for(int i = 0; i < length; i++){
view = listViewOfBluetooth.getChildAt(i);
if(view.equals(first) || view.equals(second) || view.equals(third)) {
view.setBackgroundColor(Color.GRAY);
}
}
}
How can I fix it?
Thanks!

You can achive this by using custom adapter. Here is the workaround.
Initialize your custom adapter
Add some flag for marked device names.
Override the getView() & check for the flag. And set the background of the list item accordingly.
Reply if you don't get it or face any complexity.
Update:
Here is a sample adapter. I didn't compile the code. So there might be some errors.
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class TestAdapter extends BaseAdapter
{
ArrayList<String> deviceNames;
ArrayList<Boolean> selected;
Context context;
public TestAdapter(Context context)
{
this.context = context;
deviceNames = new ArrayList<String>();
selected = new ArrayList<Boolean>();
}
public void addDeviceToList(String deviceName, boolean isSelected)
{
deviceNames.add(deviceName);
selected.add(isSelected);
notifyDataSetChanged();
}
public int getCount()
{
return deviceNames.size();
}
public Object getItem(int position)
{
return deviceNames.get(position);
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
TextView tv = new TextView(context);
tv.setText(deviceNames.get(position));
if(selected.get(position) == true)
{
tv.setBackgroundColor(Color.parseColor("#ff0000"));
}
return tv;
}
}
Now create adapter object and set the adapter to listView. And add single item by calling addDeviceToList() method.

That seems to nasty
but i think you want to modify the views inside listview before loading it
The thing is, that your list won't have children as long as the list is not displayed to the user.so you may not modify the view before showing it to the user
But if you really need to communicate with the views on a such low level you could try to attach a scroll listener to your list:
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
for (int i = 0; i < visibleItemCount; i++) {
View child = getChildAt(i);
Now edit this view
}
}

Related

ExpandableListAdapter delete group problem

I have a big problem deleting a group from an ExpandableListView. Even after google a lot and trying a lot of tutorials and examples I was not able to solve it.
Though I have a lot of programming experiences I am relative knew to Android programming. So I am sure there are many things in the source which are not yet well done. But as of now I wanted to focus on the problem with a wrong view after deleting a group from the list.
To give a good overview to the problem here are some screenshots
Start of the App
List after click to the button List All Budgets
All Groups Expandet
Before delete the last child of the last group
Remaining group show children twice
Last group this time with two children
Before deleting the last children of the last group
Correct result after deleting last child of last group
I hope the problem becomes clear. If the last group has only one child and this was deleted the whole group will be deleted by the app - but than the children of the first group show up twice.
During a debugging session I checked all the ressources behind the data and they are all ok. If I go back to MainActivity and start the list again the view ist totally correct. So it must be a problem of an incorrect population after deleting a whole group.
As you can see if I only delete the last child from a last group with two childs the populating of the whole list is correct.
Here are some more information about the app:
I use a room database with two tables holding the data.
One tabel contains the categories with name and id and the other tabel is for single budget records with the category id as an foreign key
In onCreate of the BudgetListActivity I created two DAO's budgetDAO and categoryDAO to get the data and fill the lists allBudgetsList and all CatList.
With this informations I create a new array List allGroups with the structure I need for the view
- Categories as header
- budgets as children due to the foreign key
(just one remark here:
meanwhile I tried already using a hashmap for the data given to the ExpandableListAdapter - but the result was the same wrong view population!)
There is a contentView "budget_expandable_list" which is set to the ExpandableListAdapter. The adapter should populate the groups and childs for this list using the data from the ArrayList "allGroups"
This is the structure of the app
It could be that there are some ressources which are not used actually.
I will give now the soure code for the importand classes
BudgetListActivity:
package com.wbapps.WBEasyBudgetManagement;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ExpandableListView;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BudgetListActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
CoordinatorLayout coordinatorLayout;
private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
//wb, 23Oct2018: now using an array list for the expandable list adapter
ArrayList<Group> allGroups = new ArrayList();
private ArrayAdapter adapter;
private final int REQUEST_CODE_EDIT = 1;
private BudgetDAO budgetDAO;
private CategoryDAO categoryDAO;
List<Budget> allBudgetsList;
List<Category> allCatsList;
ExpandableListView expListView;
List<String> expListViewTitle;
ExpandableListAdapter expAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.budget_expandable_list);
if (allGroups.size() > 0 ) {allGroups.clear();}
//get instances for DAO's of db from MainActivity
budgetDAO = MainActivity.getBudgetDAO();
categoryDAO = MainActivity.getCategoryDAO();
//the list for budgets and categories
allBudgetsList = budgetDAO.getBudgets();
allCatsList = categoryDAO.getCategories();
//temporary Group-Object for the ArrayList allGroups
Group tmpGroup;
double sumExpenses = 0;
//Start with reading all categories
for (int i=0;i<allCatsList.size(); i++) {
String tmpCat = allCatsList.get(i).getCategory();
tmpGroup = new Group(tmpCat);
sumExpenses = 0.0;
//now read all budgets for the current category and fill the rest of the temporary Group-Object
for (int j=0;j<allBudgetsList.size();j++){
if (allBudgetsList.get(j).getCategoryId() == allCatsList.get(i).getId()){
//tmpGroup.budgetId = allBudgetsList.get(j).getId();
tmpGroup.catId = allBudgetsList.get(j).getCategoryId();
tmpGroup.children.add(Arrays.asList
(
" Date: " + sdf.format(allBudgetsList.get(j).getDateTime())
+ " - Expenses: " + Double.toString(allBudgetsList.get(j).getExpenses()),
Long.toString(allBudgetsList.get(j).getId())
)
);
sumExpenses = sumExpenses + allBudgetsList.get(j).getExpenses();
tmpGroup.sumExpenses = sumExpenses;
}
}
//if at least one children for the current category was found
// =>> write all the group information the the array list
if (tmpGroup.children.size() > 0 ) {allGroups.add(tmpGroup);}
}
expListView = (ExpandableListView) findViewById(R.id.expandableList);
expAdapter = new ExpandableListAdapter(this, allGroups);
expListView.setAdapter(expAdapter);
expListView.setOnItemClickListener(this);
registerForContextMenu(expListView);
}
#Override
public void onCreateContextMenu(ContextMenu contMenu, View v,
ContextMenu.ContextMenuInfo contextMenuInfo) {
super.onCreateContextMenu(contMenu, v, contextMenuInfo);
ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) contextMenuInfo;
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
// Show context menu for groups
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
contMenu.setHeaderTitle("Budget");
contMenu.add(R.string.context_editBudget);
contMenu.add(R.string.context_delBudget);
// Show context menu for children
} else if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
contMenu.setHeaderTitle("Child");
contMenu.add(R.string.context_editChild);
contMenu.add(R.string.context_delChild);
}
}
#Override
public boolean onContextItemSelected(MenuItem item) {
Integer tmpInt = item.getItemId();
ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) item
.getMenuInfo();
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
//TextView vItem = info.targetView.findViewById(R.id.context_editBudget);
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
//Toast.makeText(this, "Click auf Group: " + Integer.toString(item.getGroupId()), Toast.LENGTH_SHORT).show();
if (item.getTitle().toString().equals(getString(R.string.context_editBudget))){
Toast.makeText(this, "Edit Budget clicked in Budget Context Menu", Toast.LENGTH_SHORT).show();
}
if (item.getTitle().toString().equals(getString(R.string.context_delBudget))){
int size = allGroups.get(groupPosition).children.size();
for (int i = 0; i<size; i++) {
budgetDAO.delAllBudgetsForCategory(allGroups.get(groupPosition).catId);
}
allGroups.remove(groupPosition);
//expAdapter.notifyDataSetChanged();
if (allGroups.size() == 0){
Intent intent = new Intent(BudgetListActivity.this, MainActivity.class);
startActivity(intent);
}
}
}
if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
if (item.getTitle().toString().equals(getString(R.string.context_editChild))){
Toast.makeText(this, "Edit Child clicked in Child Context Menu", Toast.LENGTH_SHORT).show();
}
if (item.getTitle().toString().equals(getString(R.string.context_delChild))){
//wb, 27Oct2018: Delete the selected child for a budget with given category
budgetDAO.delBudgetChildForCategory(Integer.parseInt(allGroups.get(groupPosition).children.get(childPosition).get(1)));
allGroups.get(groupPosition).children.remove(childPosition);
//expAdapter.notifyDataSetChanged();
//wb, 28Oct2018: If no more budget rows available delete the whole budget for category
if (allGroups.get(groupPosition).children.size() == 0) {
allGroups.remove(groupPosition);
//expAdapter.notifyDataSetChanged();
//expAdapter.notifyDataSetChanged();
if (allGroups.size() ==0){
Intent intent = new Intent(BudgetListActivity.this, MainActivity.class);
startActivity(intent);
}
}
/*
else {
//allGroups.get(groupPosition).sumExpenses = 0.0;
//allGroups.get(groupPosition) = expAdapter.getSum(groupPosition)
for (int i = 0; i < allBudgetsList.size(); i++) {
if (allBudgetsList.get(i).getCategoryId() == allGroups.get(groupPosition).catId) {
allGroups.get(groupPosition).sumExpenses =
allGroups.get(groupPosition).sumExpenses + allBudgetsList.get(i).getExpenses();
}
}
}*/
}
}
expAdapter.notifyDataSetChanged();
//return super.onContextItemSelected(item);
return true;
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Budget budget = (Budget)adapter.getItem(position);
editEntry(budget, position);
}
private void editEntry(Budget budget, int position) {
Intent intent = new Intent(this, EditBudgetActivity.class);
intent.putExtra("position", position);
startActivityForResult(intent, REQUEST_CODE_EDIT);
}
}
As you can see I use a context menu for editing and deleting groups and/or childs. Some features are not yet fully implemented. Please understand that I will first focus on my main problem with the correct population of the ExpandableView.
Also other things - like the incorrect update of the summery of the expences after deleting a child - are not yet very important and will be done later.
Here the class for a Group Object:
package com.wbapps.WBEasyBudgetManagement;
import java.util.ArrayList;
import java.util.List;
public class Group {
public long budgetId;
public long catId;
public String category;
public final List<List<String>> children = new ArrayList<List<String>>();
public final List<Long> BudIds = new ArrayList<Long>();
public double sumExpenses;
public Group(String pcategory) {
category = pcategory;
}
}
Here is the ExpandableListAdapter source:
package com.wbapps.WBEasyBudgetManagement;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckedTextView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.Locale;
public class ExpandableListAdapter extends BaseExpandableListAdapter{
Context context;
Locale locale;
Currency curr;
//array list to take the data for the list from the activity
private final ArrayList<Group> allGroups;
public LayoutInflater inflater;
public AppCompatActivity activity;
public int times = 0;
//Constructor for ExpandableListAdapter
//public ExpandableListAdapter(AppCompatActivity act, SparseArray<Group> groups) {
public ExpandableListAdapter(AppCompatActivity act, ArrayList<Group> allGroups) {
this.activity = act;
this.allGroups = allGroups;
inflater = act.getLayoutInflater();
}
#Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
times = times + 1;
Log.d("Info getGroupView","In getGroupView " + Integer.toString(times) + " times");
for (Locale wbLocale : Locale.getAvailableLocales()) {
//Log.d("LOCALES", wbLocale.getLanguage() + "_" + wbLocale.getCountry() + " [" + wbLocale.getDisplayName() + "]");
if (wbLocale.getCountry().equals("PH")) {
curr = Currency.getInstance(wbLocale);
curr.getSymbol(wbLocale);
break;
}
}
if (convertView == null || convertView.findViewById(R.id.tvCatGroup)==null){
convertView = inflater.inflate(R.layout.list_row_group, null);
}
convertView = inflater.inflate(R.layout.list_row_group, null);
String tmpCat = allGroups.get(groupPosition).category;
Group tmpGroup = new Group(tmpCat);
sortList();
Group group = (Group) getGroup(groupPosition);
//((CheckedTextView) convertView).setText(group.category + "\nTotal Expenses: " + group.sumExpenses + " " + curr.getSymbol());
((CheckedTextView) convertView).setText(group.category + "\nTotal Expenses: " + getSum(groupPosition) + " " + curr.getSymbol());
((CheckedTextView) convertView).setChecked(isExpanded);
return convertView;
}
/* wb, 18Sep2017: sort the list_selectedShoppingItems list */
public void sortList() {
Collections.sort(allGroups, new Comparator<Group>() {
#Override
public int compare(Group content1, Group content2) {
/* ignore case sensitivity */
return content1.category.compareToIgnoreCase(content2.category);
}
});
}
#Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent)
{
if(childPosition < getChildrenCount(groupPosition)-1) {
//holds the detail string for one child
final String children = (String) getChild(groupPosition, childPosition);
if (convertView == null || convertView.findViewById(R.id.tvChildRow)==null)
convertView = inflater.inflate(R.layout.list_row_details, null);
convertView = inflater.inflate(R.layout.list_row_details, null);
TextView txtChildRow = (TextView)convertView.findViewById(R.id.tvChildRow);
txtChildRow.setText(children + " " + curr.getSymbol());
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(activity, children + " " + curr.getSymbol(),
Toast.LENGTH_SHORT).show();
}
});
}
//children is the last one
if(childPosition == getChildrenCount(groupPosition)-1)
{
if (convertView == null || convertView.findViewById(R.id.tvSum)==null)
convertView = inflater.inflate(R.layout.listview_footer,null);
TextView txtFooter = (TextView)convertView.findViewById(R.id.tvSum);
//txtFooter.setText("Total expenses: " + allGroups.get(groupPosition).sumExpenses + " " + curr.getSymbol() );
txtFooter.setText("Total expenses: " + getSum(groupPosition) + " " + curr.getSymbol() );
//Log.e(TAG, "getChildView - sumExpenses: "+txtFooter.getText().toString());
}
convertView.setLongClickable( true);
return convertView;
}
#Override
public Object getChild(int groupPosition, int childPosition) {
return allGroups.get(groupPosition).children.get(childPosition).get(0);
}
public Object getSum(int groupPosition) {
return allGroups.get(groupPosition).sumExpenses;
}
#Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
}
//Add 1 to childCount. The last row is used as footer to childView
#Override
public int getChildrenCount(int groupPosition) {
return allGroups.get(groupPosition).children.size() +1;
}
#Override
public Object getGroup(int groupPosition) {
return allGroups.get(groupPosition);
}
#Override
public int getGroupCount() {
return allGroups.size();
}
#Override
public void onGroupCollapsed(int groupPosition) {
super.onGroupCollapsed(groupPosition);
}
#Override
public void onGroupExpanded(int groupPosition) {
super.onGroupExpanded(groupPosition);
}
#Override
public long getGroupId(int groupPosition) {
return 0;
}
#Override
public boolean hasStableIds() {
return false;
}
#Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
}
}
Some remarks might be helpful:
- in getChildrenCount I added 1 to the number of size because I use one last children as a footer to show the summary of expenses
for a better understanding here is a picture of the list "allGroups"
I hope I could support you with all the neccessary informations. Please let me know if some is missing. I will add it soon.
Hopefully there is someone out there with a solution for me.
Have a nice day
Andreas
meanwhile I found the reason for that behaviour. There is a method "getGroupID" at the end of the source code of the adapter. The return value here was set to 0 which caused the trouble. It has to be set to the groupPosition and then it works!
#Override
public long getGroupId(int groupPosition) {
/* wb, 10Nov2018: this statement was due to the error of deleting a last child of a group
With "return 0" the children of the remaining group was shown twice !!!
return 0;
*/
return groupPosition;
}
This is hopefully helpful to all who also run into this problem.
Have a nice time
Andreas

How to set default text in spinner? [duplicate]

I want to use a Spinner that initially (when the user has not made a selection yet) displays the text "Select One". When the user clicks the spinner, the list of items is displayed and the user selects one of the options. After the user has made a selection, the selected item is displayed in the Spinner instead of "Select One".
I have the following code to create a Spinner:
String[] items = new String[] {"One", "Two", "Three"};
Spinner spinner = (Spinner) findViewById(R.id.mySpinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
With this code, initially the item "One" is displayed. I could just add a new item "Select One" to the items, but then "Select One" would also be displayed in the dropdown list as first item, which is not what I want.
How can I fix this problem?
What you can do is decorate your SpinnerAdapter with one that presents a 'Select Option...' View initially for the Spinner to display with nothing selected.
Here is a working example tested for Android 2.3, and 4.0 (it uses nothing in the compatibility library, so it should be fine for awhile) Since it's a decorator, it should be easy to retrofit existing code and it works fine with CursorLoaders also. (Swap cursor on the wrapped cursorAdapter of course...)
There is an Android bug that makes this a little tougher to re-use views. (So you have to use the setTag or something else to ensure your convertView is correct.) Spinner does not support multiple view types
Code notes: 2 constructors
This allows you to use a standard prompt or define your own 'nothing selected' as the first row, or both, or none. (Note: Some themes show a DropDown for a Spinner instead of a dialog. The Dropdown doesn't normally show the prompt)
You define a layout to 'look' like a prompt, for example, grayed out...
Using a standard prompt (notice that nothing is selected):
Or with a prompt and something dynamic (could have had no prompt also):
Usage in above example
Spinner spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.planets_array, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setPrompt("Select your favorite Planet!");
spinner.setAdapter(
new NothingSelectedSpinnerAdapter(
adapter,
R.layout.contact_spinner_row_nothing_selected,
// R.layout.contact_spinner_nothing_selected_dropdown, // Optional
this));
contact_spinner_row_nothing_selected.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textSize="18sp"
android:textColor="#808080"
android:text="[Select a Planet...]" />
NothingSelectedSpinnerAdapter.java
import android.content.Context;
import android.database.DataSetObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.SpinnerAdapter;
/**
* Decorator Adapter to allow a Spinner to show a 'Nothing Selected...' initially
* displayed instead of the first choice in the Adapter.
*/
public class NothingSelectedSpinnerAdapter implements SpinnerAdapter, ListAdapter {
protected static final int EXTRA = 1;
protected SpinnerAdapter adapter;
protected Context context;
protected int nothingSelectedLayout;
protected int nothingSelectedDropdownLayout;
protected LayoutInflater layoutInflater;
/**
* Use this constructor to have NO 'Select One...' item, instead use
* the standard prompt or nothing at all.
* #param spinnerAdapter wrapped Adapter.
* #param nothingSelectedLayout layout for nothing selected, perhaps
* you want text grayed out like a prompt...
* #param context
*/
public NothingSelectedSpinnerAdapter(
SpinnerAdapter spinnerAdapter,
int nothingSelectedLayout, Context context) {
this(spinnerAdapter, nothingSelectedLayout, -1, context);
}
/**
* Use this constructor to Define your 'Select One...' layout as the first
* row in the returned choices.
* If you do this, you probably don't want a prompt on your spinner or it'll
* have two 'Select' rows.
* #param spinnerAdapter wrapped Adapter. Should probably return false for isEnabled(0)
* #param nothingSelectedLayout layout for nothing selected, perhaps you want
* text grayed out like a prompt...
* #param nothingSelectedDropdownLayout layout for your 'Select an Item...' in
* the dropdown.
* #param context
*/
public NothingSelectedSpinnerAdapter(SpinnerAdapter spinnerAdapter,
int nothingSelectedLayout, int nothingSelectedDropdownLayout, Context context) {
this.adapter = spinnerAdapter;
this.context = context;
this.nothingSelectedLayout = nothingSelectedLayout;
this.nothingSelectedDropdownLayout = nothingSelectedDropdownLayout;
layoutInflater = LayoutInflater.from(context);
}
#Override
public final View getView(int position, View convertView, ViewGroup parent) {
// This provides the View for the Selected Item in the Spinner, not
// the dropdown (unless dropdownView is not set).
if (position == 0) {
return getNothingSelectedView(parent);
}
return adapter.getView(position - EXTRA, null, parent); // Could re-use
// the convertView if possible.
}
/**
* View to show in Spinner with Nothing Selected
* Override this to do something dynamic... e.g. "37 Options Found"
* #param parent
* #return
*/
protected View getNothingSelectedView(ViewGroup parent) {
return layoutInflater.inflate(nothingSelectedLayout, parent, false);
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
// Android BUG! http://code.google.com/p/android/issues/detail?id=17128 -
// Spinner does not support multiple view types
if (position == 0) {
return nothingSelectedDropdownLayout == -1 ?
new View(context) :
getNothingSelectedDropdownView(parent);
}
// Could re-use the convertView if possible, use setTag...
return adapter.getDropDownView(position - EXTRA, null, parent);
}
/**
* Override this to do something dynamic... For example, "Pick your favorite
* of these 37".
* #param parent
* #return
*/
protected View getNothingSelectedDropdownView(ViewGroup parent) {
return layoutInflater.inflate(nothingSelectedDropdownLayout, parent, false);
}
#Override
public int getCount() {
int count = adapter.getCount();
return count == 0 ? 0 : count + EXTRA;
}
#Override
public Object getItem(int position) {
return position == 0 ? null : adapter.getItem(position - EXTRA);
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public long getItemId(int position) {
return position >= EXTRA ? adapter.getItemId(position - EXTRA) : position - EXTRA;
}
#Override
public boolean hasStableIds() {
return adapter.hasStableIds();
}
#Override
public boolean isEmpty() {
return adapter.isEmpty();
}
#Override
public void registerDataSetObserver(DataSetObserver observer) {
adapter.registerDataSetObserver(observer);
}
#Override
public void unregisterDataSetObserver(DataSetObserver observer) {
adapter.unregisterDataSetObserver(observer);
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
return position != 0; // Don't allow the 'nothing selected'
// item to be picked.
}
}
Here's a general solution that overrides the Spinner view. It overrides setAdapter() to set the initial position to -1, and proxies the supplied SpinnerAdapter to display the prompt string for position less than 0.
This has been tested on Android 1.5 through 4.2, but buyer beware! Because this solution relies on reflection to call the private AdapterView.setNextSelectedPositionInt() and AdapterView.setSelectedPositionInt(), it's not guaranteed to work in future OS updates. It seems likely that it will, but it is by no means guaranteed.
Normally I wouldn't condone something like this, but this question has been asked enough times and it seems like a reasonable enough request that I thought I would post my solution.
/**
* A modified Spinner that doesn't automatically select the first entry in the list.
*
* Shows the prompt if nothing is selected.
*
* Limitations: does not display prompt if the entry list is empty.
*/
public class NoDefaultSpinner extends Spinner {
public NoDefaultSpinner(Context context) {
super(context);
}
public NoDefaultSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoDefaultSpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setAdapter(SpinnerAdapter orig ) {
final SpinnerAdapter adapter = newProxy(orig);
super.setAdapter(adapter);
try {
final Method m = AdapterView.class.getDeclaredMethod(
"setNextSelectedPositionInt",int.class);
m.setAccessible(true);
m.invoke(this,-1);
final Method n = AdapterView.class.getDeclaredMethod(
"setSelectedPositionInt",int.class);
n.setAccessible(true);
n.invoke(this,-1);
}
catch( Exception e ) {
throw new RuntimeException(e);
}
}
protected SpinnerAdapter newProxy(SpinnerAdapter obj) {
return (SpinnerAdapter) java.lang.reflect.Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
new Class[]{SpinnerAdapter.class},
new SpinnerAdapterProxy(obj));
}
/**
* Intercepts getView() to display the prompt if position < 0
*/
protected class SpinnerAdapterProxy implements InvocationHandler {
protected SpinnerAdapter obj;
protected Method getView;
protected SpinnerAdapterProxy(SpinnerAdapter obj) {
this.obj = obj;
try {
this.getView = SpinnerAdapter.class.getMethod(
"getView",int.class,View.class,ViewGroup.class);
}
catch( Exception e ) {
throw new RuntimeException(e);
}
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
try {
return m.equals(getView) &&
(Integer)(args[0])<0 ?
getView((Integer)args[0],(View)args[1],(ViewGroup)args[2]) :
m.invoke(obj, args);
}
catch (InvocationTargetException e) {
throw e.getTargetException();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
protected View getView(int position, View convertView, ViewGroup parent)
throws IllegalAccessException {
if( position<0 ) {
final TextView v =
(TextView) ((LayoutInflater)getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE)).inflate(
android.R.layout.simple_spinner_item,parent,false);
v.setText(getPrompt());
return v;
}
return obj.getView(position,convertView,parent);
}
}
}
I ended up using a Button instead. While a Button is not a Spinner, the behavior is easy to customize.
First create the Adapter as usual:
String[] items = new String[] {"One", "Two", "Three"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_dropdown_item, items);
Note that I am using the simple_spinner_dropdown_item as the layout id. This will help create a better look when creating the alert dialog.
In the onClick handler for my Button I have:
public void onClick(View w) {
new AlertDialog.Builder(this)
.setTitle("the prompt")
.setAdapter(adapter, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// TODO: user specific action
dialog.dismiss();
}
}).create().show();
}
And that's it!
First, you might be interested in the prompt attribute of the Spinner class. See the picture below, "Choose a Planet" is the prompt that can be set in the XML with android:prompt="".
I was going to suggest subclassing Spinner, where you could maintain two adapters internally. One adapter that has the "Select One" option, and the other real adapter (with the actual options), then using the OnClickListener to switch the adapters before the choices dialog is shown. However, after trying implement that idea I've come to the conclusion you cannot receive OnClick events for the widget itself.
You could wrap the spinner in a different view, intercept the clicks on the view, and then tell your CustomSpinner to switch the adapter, but seems like an awful hack.
Do you really need to show "Select One"?
This code has been tested and works on Android 4.4
Spinner spinner = (Spinner) activity.findViewById(R.id.spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity, android.R.layout.simple_spinner_dropdown_item) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
if (position == getCount()) {
((TextView)v.findViewById(android.R.id.text1)).setText("");
((TextView)v.findViewById(android.R.id.text1)).setHint(getItem(getCount())); //"Hint to be displayed"
}
return v;
}
#Override
public int getCount() {
return super.getCount()-1; // you dont display last item. It is used as hint.
}
};
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapter.add("Daily");
adapter.add("Two Days");
adapter.add("Weekly");
adapter.add("Monthly");
adapter.add("Three Months");
adapter.add("HINT_TEXT_HERE"); //This is the text that will be displayed as hint.
spinner.setAdapter(adapter);
spinner.setSelection(adapter.getCount()); //set the hint the default selection so it appears on launch.
spinner.setOnItemSelectedListener(this);
I found this solution:
String[] items = new String[] {"Select One", "Two", "Three"};
Spinner spinner = (Spinner) findViewById(R.id.mySpinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id) {
items[0] = "One";
selectedItem = items[position];
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
Just change the array[0] with "Select One" and then in the onItemSelected, rename it to "One".
Not a classy solution, but it works :D
Lots of answers here but I'm surprised no one suggested a simple solution: Place a TextView on top of the Spinner. Set a click listener on the TextView which hides the TextView shows the Spinner, and calls spinner.performClick().
There is no default API to set hint on Spinner. To add it we need a small workaround with out that not safety reflection implementation
List<Object> objects = new ArrayList<Object>();
objects.add(firstItem);
objects.add(secondItem);
// add hint as last item
objects.add(hint);
HintAdapter adapter = new HintAdapter(context, objects, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner spinnerFilmType = (Spinner) findViewById(R.id.spinner);
spinner.setAdapter(adapter);
// show hint
spinner.setSelection(adapter.getCount());
Adapter source:
public class HintAdapter
extends ArrayAdapter<Objects> {
public HintAdapter(Context theContext, List<Object> objects) {
super(theContext, android.R.id.text1, android.R.id.text1, objects);
}
public HintAdapter(Context theContext, List<Object> objects, int theLayoutResId) {
super(theContext, theLayoutResId, android.R.id.text1, objects);
}
#Override
public int getCount() {
// don't display last item. It is used as hint.
int count = super.getCount();
return count > 0 ? count - 1 : count;
}
}
Original source
I got the same problem for spinner, with an empty selection, and I found a better solution. Have a look at this simple code.
Spinner lCreditOrDebit = (Spinner)lCardPayView.findViewById(R.id.CARD_TYPE);
spinneradapter lAdapter =
new spinneradapter(
BillPayScreen.this,
ndroid.R.layout.simple_spinner_item,getResources().getStringArray(R.array.creditordebit));
lAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
lCreditOrDebit.setAdapter(lAdapter);
Here spinneradapter is a small customization for arrayadapter. It looks like this:
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
public class spinneradapter extends ArrayAdapter<String>{
private Context m_cContext;
public spinneradapter(Context context,int textViewResourceId, String[] objects) {
super(context, textViewResourceId, objects);
this.m_cContext = context;
}
boolean firsttime = true;
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(firsttime){
firsttime = false;
//Just return some empty view
return new ImageView(m_cContext);
}
//Let the array adapter take care of it this time.
return super.getView(position, convertView, parent);
}
}
You can change it to a Text View and use this:
android:style="#android:style/Widget.DeviceDefault.Light.Spinner"
and then define the android:text property.
XML file:
<Spinner android:id="#+id/locationSpinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="#string/select_location" />
Activity:
private Spinner featuresSelection;
private ArrayAdapter<CharSequence> featuresAdapter;
private List<CharSequence> featuresList;
onCreate:
featuresList = new ArrayList<CharSequence>();
featuresAdapter = new ArrayAdapter<CharSequence>(this,
android.R.layout.simple_spinner_item, featuresList);
featuresAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
featuresSelection = ((Spinner) yourActivity.this
.findViewById(R.id.locationSpinner));
featuresSelection.setAdapter(featuresAdapter);
featuresSelection.setOnItemSelectedListener(
new MyOnItemSelectedListener());
Some function (add things to the adapter programmatically)>
featuresAdapter.add("some string");
Now you have an empty spinner and you can write code to not open the dialog if empty. Or they can press back. But you also populate it with a function or another list during run time.
In addition, There is a simple trick to show default:
You can add a default value in your list and then add all of your collection using list.addAll(yourCollection);
Sample workable code here:
List<FuelName> fuelList = new ArrayList<FuelName>();
fuelList.add(new FuelName(0,"Select One"));
fuelList.addAll(response.body());
ArrayAdapter adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, fuelList);
//fuelName.setPrompt("Select Fuel");
fuelName.setAdapter(adapter);
I have a spinner on my main.xml and its id is #+id/spinner1
this is what i write in my OnCreate function :
spinner1 = (Spinner)this.findViewById(R.id.spinner1);
final String[] groupes = new String[] {"A", "B", "C", "D", "E", "F", "G", "H"};
ArrayAdapter<CharSequence> featuresAdapter = new ArrayAdapter<CharSequence>(this, android.R.layout.simple_spinner_item, new ArrayList<CharSequence>());
featuresAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner1.setAdapter(featuresAdapter);
for (String s : groupes) featuresAdapter.add(s);
spinner1.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id) {
// Here go your instructions when the user chose something
Toast.makeText(getBaseContext(), groupes[position], 0).show();
}
public void onNothingSelected(AdapterView<?> arg0) { }
});
It doesn't need any implementation in the class.
I have tried like the following. Take a button and give the click event to it. By changing the button background, it seems to be a spinner.
Declare as global variables alertdialog and default value..
AlertDialog d;
static int default_value = 0;
final Button btn = (Button) findViewById(R.id.button1);
btn .setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v)
{
//c.show();
final CharSequence str[] = {"Android","Black Berry","Iphone"};
AlertDialog.Builder builder =
new AlertDialog.Builder(TestGalleryActivity.this).setSingleChoiceItems(
str, default_value,new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int position)
{
Toast.makeText(TestGalleryActivity.this,
"" + position,
Toast.LENGTH_SHORT).show();
default_value = position;
btn.setText(str[position]);
if(d.isShowing())
d.dismiss();
}
}).setTitle("Select Any");
d = builder.create();
d.show();
}
});
Take a look at the iosched app for a general purpose solution to adding an element to the top of a list. In particular, if you are using a CursorAdapter, look at TracksAdapter.java which extends that definition to provide a "setHasAllItem" method and associated code to manage the list count to deal with the extra item at the top.
Using the custom adapter you can set the text to "Select One" or whatever else you may want that top item to say.
I found many good solutions for this. most is working by adding an item to the end of adapter, and don't display the last item in drop-down list.
The big problem for me was the spinner drop-down list will start from the bottom of the list. So user see the last items instead of the first items (in case of have many items to show), after touch the spinner for the first time.
So I put the hint item to the beginning of the list. and hide the first item in drop-down list.
private void loadSpinner(){
HintArrayAdapter hintAdapter = new HintArrayAdapter<String>(context, 0);
hintAdapter.add("Hint to be displayed");
hintAdapter.add("Item 1");
hintAdapter.add("Item 2");
.
.
hintAdapter.add("Item 30");
spinner1.setAdapter(hintAdapter);
//spinner1.setSelection(0); //display hint. Actually you can ignore it, because the default is already 0
//spinner1.setSelection(0, false); //use this if don't want to onItemClick called for the hint
spinner1.setOnItemSelectedListener(yourListener);
}
private class HintArrayAdapter<T> extends ArrayAdapter<T> {
Context mContext;
public HintArrayAdapter(Context context, int resource) {
super(context, resource);
this.mContext = context
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
TextView texview = (TextView) view.findViewById(android.R.id.text1);
if(position == 0) {
texview.setText("");
texview.setHint(getItem(position).toString()); //"Hint to be displayed"
} else {
texview.setText(getItem(position).toString());
}
return view;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view;
if(position == 0){
view = inflater.inflate(R.layout.spinner_hint_list_item_layout, parent, false); // Hide first row
} else {
view = inflater.inflate(android.R.layout.simple_spinner_dropdown_item, parent, false);
TextView texview = (TextView) view.findViewById(android.R.id.text1);
texview.setText(getItem(position).toString());
}
return view;
}
}
set the below layout in #Override getDropDownView() when position is 0, to hide the first hint row.
R.layout.spinner_hint_list_item_layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</LinearLayout>
I think the easiest way is creating a dummy item on index 0 saying "select one" and then on saving maybe check that selection is not 0.
So this is my final example "all-in" for a button-spinner
In activity_my_form.xml
<Button
android:id="#+id/btnSpinnerPlanets"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:singleLine="true"
android:text="#string/selectAPlanet"
android:textSize="10sp"
android:background="#android:drawable/btn_dropdown">
</Button>
In strings.xml
<string name="selectAPlanet">Select planet…</string>
<string-array name="planets__entries">
<item>The Sun with a name very long so long long long long longThe Sun with a name very long so long long long long longThe Sun with a name very long so long long long long long</item>
<item>Mercury</item>
<item>Venus</item>
<item>Earth</item>
<item>Mars</item>
<item>Jupiter</item>
<item>Saturn</item>
<item>Uranus</item>
<item>Neptune</item>
</string-array>
In MyFormActivity.java
public class MyFormActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
((Button) findViewById(R.id.btnSpinnerPlanets)).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final String[] items = view.getResources().getStringArray(R.array.planets__entries);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MyFormActivity.this, android.R.layout.simple_spinner_dropdown_item, items);
new AlertDialog.Builder(MyFormActivity.this).setTitle("the prompt").setAdapter(adapter, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((Button) findViewById(R.id.btnSpinnerPlanets)).setText(items[which]);
dialog.dismiss();
}
}).create().show();
}
});
}
}
Finally I obtained a font size configurable no first item selectable button spinner!!!
Thanks to HRJ
The best solution I found for this is actually not to use a Spinner but an AutoCompleteTextView. Its basically an EditText with attached Spinner to show suggestions as you type - but, with the right config, it can behave exactly as the OP wishes and more.
XML:
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/item"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatAutoCompleteTextView
android:id="#+id/input"
android:hint="Select one"
style="#style/AutoCompleteTextViewDropDown"/>
</com.google.android.material.textfield.TextInputLayout>
Style:
<style name="AutoCompleteTextViewDropDown">
<item name="android:clickable">false</item>
<item name="android:cursorVisible">false</item>
<item name="android:focusable">false</item>
<item name="android:focusableInTouchMode">false</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
</style>
As for the adapter use the basic ArrayAdapter or extend it to make your own, but no additional customization on the adapter side is necessary. Set the adapter on the AutoCompleteTextView.
When extending SpinnerAdapter, you override two View-producing methods, getView(int, View, ViewGroup) and getDropDownView(int, View, ViewGroup). The first one supplies the View inserted into the Spinner itself; the second supplies the View in the drop-down list (as the name suggests). You can override the getView(...) so that, until an item has been selected, it displays a TextView containing a prompt; then, when you detect an item has been selected, you change it to display a TextView corresponding to that.
public class PromptingAdapter extends SpinnerAdapter {
//... various code ...
private boolean selectionmade = false;
//call this method from the OnItemSelectedListener for your Spinner
public setSelectionState(boolean b) {
selectionmade = b;
}
#Override
public View getView(int position, View recycle, ViewGroup container) {
if(selectionmade) {
//your existing code to supply a View for the Spinner
//you could even put "return getDropDownView(position, recycle, container);"
}
else {
View output;
if(recycle instanceof TextView) {
output = recycle;
}
else {
output = new TextView();
//and layout stuff
}
output.setText(R.string.please_select_one);
//put a string "please_select_one" in res/values/strings.xml
return output;
}
}
//...
}
For those using Xamarin, here is the C# equivalent to aaronvargas's answer above.
using Android.Content;
using Android.Database;
using Android.Views;
using Android.Widget;
using Java.Lang;
namespace MyNamespace.Droid
{
public class NothingSelectedSpinnerAdapter : BaseAdapter, ISpinnerAdapter, IListAdapter
{
protected static readonly int EXTRA = 1;
protected ISpinnerAdapter adapter;
protected Context context;
protected int nothingSelectedLayout;
protected int nothingSelectedDropdownLayout;
protected LayoutInflater layoutInflater;
public NothingSelectedSpinnerAdapter(ISpinnerAdapter spinnerAdapter, int nothingSelectedLayout, Context context) : this(spinnerAdapter, nothingSelectedLayout, -1, context)
{
}
public NothingSelectedSpinnerAdapter(ISpinnerAdapter spinnerAdapter, int nothingSelectedLayout, int nothingSelectedDropdownLayout, Context context)
{
this.adapter = spinnerAdapter;
this.context = context;
this.nothingSelectedLayout = nothingSelectedLayout;
this.nothingSelectedDropdownLayout = nothingSelectedDropdownLayout;
layoutInflater = LayoutInflater.From(context);
}
protected View GetNothingSelectedView(ViewGroup parent)
{
return layoutInflater.Inflate(nothingSelectedLayout, parent, false);
}
protected View GetNothingSelectedDropdownView(ViewGroup parent)
{
return layoutInflater.Inflate(nothingSelectedDropdownLayout, parent, false);
}
public override Object GetItem(int position)
{
return position == 0 ? null : adapter.GetItem(position - EXTRA);
}
public override long GetItemId(int position)
{
return position >= EXTRA ? adapter.GetItemId(position - EXTRA) : position - EXTRA;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
// This provides the View for the Selected Item in the Spinner, not
// the dropdown (unless dropdownView is not set).
if (position == 0)
{
return GetNothingSelectedView(parent);
}
// Could re-use the convertView if possible.
return this.adapter.GetView(position - EXTRA, null, parent);
}
public override int Count
{
get
{
int count = this.adapter.Count;
return count == 0 ? 0 : count + EXTRA;
}
}
public override View GetDropDownView(int position, View convertView, ViewGroup parent)
{
// Android BUG! http://code.google.com/p/android/issues/detail?id=17128 -
// Spinner does not support multiple view types
if (position == 0)
{
return nothingSelectedDropdownLayout == -1 ?
new View(context) :
GetNothingSelectedDropdownView(parent);
}
// Could re-use the convertView if possible, use setTag...
return adapter.GetDropDownView(position - EXTRA, null, parent);
}
public override int GetItemViewType(int position)
{
return 0;
}
public override int ViewTypeCount => 1;
public override bool HasStableIds => this.adapter.HasStableIds;
public override bool IsEmpty => this.adapter.IsEmpty;
public override void RegisterDataSetObserver(DataSetObserver observer)
{
adapter.RegisterDataSetObserver(observer);
}
public override void UnregisterDataSetObserver(DataSetObserver observer)
{
adapter.UnregisterDataSetObserver(observer);
}
public override bool AreAllItemsEnabled()
{
return false;
}
public override bool IsEnabled(int position)
{
return position > 0;
}
}
}
I also solved this problem by using the following code. Suppose you are having a list of items e.g.
ArrayList<Item> itemsArrayList = new ArrayList<Item>();
Item item1 = new Item();
item1.setId(1);
item1.setData("First Element");
Item item2 = new Item();
item2.setId(2);
Item2.setData("Second Element");
itemsArrayList.add(item1);
itemsArrayList.add(item2);
Now we have to provide the strings to spinner because spinner can not understand the object. So we will create an new array list with string items like this ->
ArrayList<String> itemStringArrayList = new ArrayList<String>();
for(Item item : itemsArrayList) {
itemStringArrayList.add(item.getData());
}
Now we have itemStringArrayList array list with two string items. And we have to show the "Select Item" text as first item. So we have to insert an new string into the itemStringArrayList.
itemStringArrayList.add("Select Item");
Now we have an array list itemsArrayList and we want to show two elements in the drop down. But the condition here is ...If we don't select anything then Select Item should appear as first element which will not be enabled.
So we can implement this functionality like this. If you need to load the array list items into the android spinner. So you will have to use some adapter. So here i'll use the ArrayAdapter. We can use the customise adapter too.
ArrayAdapter<String> itemsArrayAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.spinner_item, itemsArrayList){
#Override
public boolean isEnabled(int position) {
if(position == 0)
{
return false;
}
else
{
return true;
}
}
#Override
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
View view = super.getDropDownView(position, convertView, parent);
TextView tv = (TextView) view;
if(position == 0){
// Set the hint text color gray
tv.setTextColor(Color.GRAY);
}
else {
tv.setTextColor(Color.BLACK);
}
return view;
}
};
itemsArrayAdapter.setDropDownViewResource(R.layout.spinner_item);
your_spinner_name.setAdapter(itemsArrayAdapter);
Here in this code. we are using the customised spinner layout i.e. R.layout.spinner_item. It's a simple text view
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:textStyle="italic"
android:fontFamily="sans-serif-medium"
/>
We need to disable the first text in the spinner. So for the position 0 we are disabling the text. And color also we can set by overiding getDropDownView method. So in this way we will get the expected spinner.
I was facing the same problem yesterday and did not want to add a hidden item to the ArrayAdapter or use reflections, which works fine but is kind of dirty.
After reading many posts and trying around I found a solution by extending ArrayAdapter and Overriding the getView method.
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
/**
* A SpinnerAdapter which does not show the value of the initial selection initially,
* but an initialText.
* To use the spinner with initial selection instead call notifyDataSetChanged().
*/
public class SpinnerAdapterWithInitialText<T> extends ArrayAdapter<T> {
private Context context;
private int resource;
private boolean initialTextWasShown = false;
private String initialText = "Please select";
/**
* Constructor
*
* #param context The current context.
* #param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
* #param objects The objects to represent in the ListView.
*/
public SpinnerAdapterWithInitialText(#NonNull Context context, int resource, #NonNull T[] objects) {
super(context, resource, objects);
this.context = context;
this.resource = resource;
}
/**
* Returns whether the user has selected a spinner item, or if still the initial text is shown.
* #param spinner The spinner the SpinnerAdapterWithInitialText is assigned to.
* #return true if the user has selected a spinner item, false if not.
*/
public boolean selectionMade(Spinner spinner) {
return !((TextView)spinner.getSelectedView()).getText().toString().equals(initialText);
}
/**
* Returns a TextView with the initialText the first time getView is called.
* So the Spinner has an initialText which does not represent the selected item.
* To use the spinner with initial selection instead call notifyDataSetChanged(),
* after assigning the SpinnerAdapterWithInitialText.
*/
#Override
public View getView(int position, View recycle, ViewGroup container) {
if(initialTextWasShown) {
return super.getView(position, recycle, container);
} else {
initialTextWasShown = true;
LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(resource, container, false);
((TextView) view).setText(initialText);
return view;
}
}
}
What Android does when initialising the Spinner, is calling getView for the selected item before calling getView for all items in T[] objects.
The SpinnerAdapterWithInitialText returns a TextView with the initialText, the first time it is called.
All the other times it calls super.getView which is the getView method of ArrayAdapter which is called if you are using the Spinner normally.
To find out whether the user has selected a spinner item, or if the spinner still displays the initialText, call selectionMade and hand over the spinner the adapter is assigned to.
See the answer with lightweight and high customisable library:
https://stackoverflow.com/a/73085435/6694920
<com.innowisegroup.hintedspinner.HintedSpinner
android:id="#+id/hintedSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
app:hintTextSize="24sp"
app:hintTextColor="#color/red"
app:hint="Custom hint"
app:withDivider="true"
app:dividerColor="#color/dark_green"
app:arrowDrawable="#drawable/example_arrow_4"
app:arrowTint="#color/colorAccent"
app:popupBackground="#color/light_blue"
app:items="#array/text" />
Collapsed spinner:
Expanded spinner:
I'd just use a RadioGroup with RadioButtons if you only have three choices, you can make them all unchecked at first.
None of the previously submitted answers really worked the way I wanted to solve this issue. To me the ideal solution would provide the “Select One” (or whatever initial text) when the spinner is first displayed. When the user taps the spinner, the initial text shouldn’t be a part of the drop down that is displayed.
To further complicate my particular situation, my spinner data is coming form a cursor that is loaded via the LoaderManager callbacks.
After considerable experimentation I came up with the following solution:
public class MyFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor>{
private static final String SPINNER_INIT_VALUE = "Select A Widget";
private Spinner mSpinner;
private int mSpinnerPosition;
private boolean mSpinnerDropDownShowing = false;
private View mSpinnerDropDown;
private MyCursorAdapter mCursorAdapter;
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
...
mCursorAdapter = new MyCursorAdapter(getActivity());
mSpinner = (Spinner) rootView.findViewById(R.id.theSpinner);
mSpinner.setOnTouchListener(mSpinnerTouchListener);
mSpinner.setAdapter(mCursorAdapter);
...
}
//Capture the touch events to toggle the spinner's dropdown visibility
private OnTouchListener mSpinnerTouchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if(mSpinnerDropDown != null && mSpinnerDropDownShowing == false){
mSpinnerDropDownShowing = true;
mSpinnerDropDown.setVisibility(View.VISIBLE);
}
return false;
}
};
//Capture the click event on the spinner drop down items
protected OnClickListener spinnerItemClick = new OnClickListener(){
#Override
public void onClick(View view) {
String widget = ((TextView) view.findViewById(android.R.id.text1)).getText().toString();
if(!widget.equals(SPINNER_INIT_VALUE)){
if(mCursorAdapter != null){
Cursor cursor = mCursorAdapter.getCursor();
if(cursor.moveToFirst()){
while(!cursor.isAfterLast()){
if(widget.equals(cursor.getString(WidgetQuery.WIDGET_NAME))){
...
//Set the spinner to the correct item
mSpinnerPosition = cursor.getPosition() + 1;
mSpinner.setSelection(mSpinnerPosition);
break;
}
cursor.moveToNext();
}
}
}
}
//Hide the drop down. Not the most elegent solution but it is the only way I could hide/dismiss the drop down
mSpinnerDropDown = view.getRootView();
mSpinnerDropDownShowing = false;
mSpinnerDropDown.setVisibility(View.GONE);
}
};
private class MyCursorAdapter extends CursorAdapter {
private final int DISPLACEMENT = 1;
private final int DEFAULT_ITEM_ID = Integer.MAX_VALUE;
private Activity mActivity;
public MyCursorAdapter(Activity activity) {
super(activity, null, false);
mActivity = activity;
}
//When loading the regular views, inject the defualt item
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(position == 0){
if(convertView == null){
convertView = mActivity.getLayoutInflater().inflate(R.layout.list_item_widget, parent, false);
}
return getDefaultItem(convertView);
}
return super.getView(position - DISPLACEMENT, convertView, parent);
}
//When loading the drop down views, set the onClickListener for each view
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent){
View view = super.getDropDownView(position, convertView, parent);
view.setOnClickListener(spinnerItemClick);
return view;
}
//The special default item that is being injected
private View getDefaultItem(View convertView){
TextView text = (TextView) convertView.findViewById(android.R.id.text1);
text.setText(SPINNER_INIT_VALUE);
return convertView;
}
#Override
public long getItemId(int position) {
if (position == 0) {
return DEFAULT_ITEM_ID;
}
return super.getItemId(position - DISPLACEMENT);
}
#Override
public boolean isEnabled(int position) {
return position == 0 ? true : super.isEnabled(position - DISPLACEMENT);
}
#Override
public int getViewTypeCount() {
return super.getViewTypeCount() + DISPLACEMENT;
}
#Override
public int getItemViewType(int position) {
if (position == 0) {
return super.getViewTypeCount();
}
return super.getItemViewType(position - DISPLACEMENT);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mActivity.getLayoutInflater().inflate(R.layout.list_item_widget, parent, false);
}
#Override
public void bindView(View view, Context context, Cursor cursor){
if(cursor.isAfterLast()){
return;
}
TextView text = (TextView) view.findViewById(android.R.id.text1);
String WidgetName = cursor.getString(WidgetQuery.WIDGET_NAME);
text.setText(WidgetName);
}
}
}
I handle this by using a button instead of a Spinner. I have the sample project up on GitHub.
In the project, i'm displaying both the Spinner and button to show that they indeed look identical. Except the button you can set the initial text to whatever you want.
Here's what the activity looks like:
package com.stevebergamini.spinnerbutton;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Spinner;
public class MainActivity extends Activity {
Spinner spinner1;
Button button1;
AlertDialog ad;
String[] countries;
int selected = -1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
spinner1 = (Spinner) findViewById(R.id.spinner1);
button1 = (Button) findViewById(R.id.button1);
countries = getResources().getStringArray(R.array.country_names);
// You can also use an adapter for the allert dialog if you'd like
// ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, countries);
ad = new AlertDialog.Builder(MainActivity.this).setSingleChoiceItems(countries, selected,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
button1.setText(countries[which]);
selected = which;
ad.dismiss();
}}).setTitle(R.string.select_country).create();
button1.setOnClickListener( new OnClickListener(){
#Override
public void onClick(View v) {
ad.getListView().setSelection(selected);
ad.show();
}});
}
}
NOTE: Yes, I realize that this is dependent on the applied Theme and the look will be slightly different if using Theme.Holo. However, if you're using one of the legacy themes such as Theme.Black, you're good to go.
Seems a banal solution but I usually put simply a TextView in the front of the spinner. The whole Xml looks like this. (hey guys, don't shoot me, I know that some of you don't like this kind of marriage):
<FrameLayout
android:id="#+id/selectTypesLinear"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<Spinner
android:id="#+id/spinnerExercises"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:entries="#array/exercise_spinner_entries"
android:prompt="#string/exercise_spinner_prompt"
/>
<TextView
android:id="#+id/spinnerSelectText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hey! Select this guy!"
android:gravity="center"
android:background="#FF000000" />
</FrameLayout>
Then I hide the TextView when an Item was selected. Obviously the background color of the TextView should be the same as the Spinner. Works on Android 4.0. Don't know on older versions.
Yes. Because the Spinner calls setOnItemSelectedListener at the beginning, the hiding of the textview could be a little bit tricky, but can be done this way:
Boolean controlTouched;
exerciseSpinner.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
controlTouched = true; // I touched it but but not yet selected an Item.
return false;
}
});
exerciseSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
if (controlTouched) { // Are you sure that I touched it with my fingers and not someone else ?
spinnerSelText.setVisibility(View.GONE);
}
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
for me it worked something like this. has the improvement that only changes the text in SOME options, not in all.
First i take the names of the spinner and create the arrayadapter with a customize view, but it doesn't matter now, the key is override the getView, and inside change the values u need to change. In my case was only the first one, the rest i leave the original
public void rellenarSpinnerCompeticiones(){
spinnerArrayCompeticiones = new ArrayList<String>();
for(Competicion c: ((Controlador)getApplication()).getCompeticiones()){
spinnerArrayCompeticiones.add(c.getNombre());
}
//ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this,R.layout.spinner_item_competicion,spinnerArrayCompeticiones);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this, R.layout.spinner_item_competicion, spinnerArrayCompeticiones){
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View v = vi.inflate(R.layout.spinner_item_competicion, null);
final TextView t = (TextView)v.findViewById(R.id.tvCompeticion);
if(spinnerCompeticion.getSelectedItemPosition()>0){
t.setText(spinnerArrayCompeticiones.get(spinnerCompeticion.getSelectedItemPosition()));
}else{
t.setText("Competiciones");
}
return v;
}
};
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCompeticion.setAdapter(spinnerArrayAdapter);
}
Refer to one of the above answers: https://stackoverflow.com/a/23005376/1312796
I added my code to fix a little bug. That where no data retrieved..How to show the prompt text..!
Here is my Trick...It works fine with me. !
Try to put your spinner in a Relative_layoutand align a Textview with your spinner and play with the visibility of the Textview (SHOW/HIDE) whenever the adapter of the spinner loaded or empty..Like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="20dp"
android:background="#ededed"
android:orientation="vertical">
<TextView
android:id="#+id/txt_prompt_from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textColor="#color/gray"
android:textSize="16sp"
android:layout_alignStart="#+id/sp_from"
android:text="From"
android:visibility="gone"/>
<Spinner
android:id="#+id/sp_from"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
/>
Here is the code:
txt__from = (TextView) rootView.findViewById(R.id.txt_prompt_from);
call this method after and before spinner adapter loaded and empty.
setPromptTextViewVisibility (); //True or fales
public void setPromptTextViewVisibility (boolean visible )
{
if (visible)
{
txt_from.setVisibility(View.VISIBLE);
}
else
{
txt_from.setVisibility(View.INVISIBLE);
}
}

How to add multiple header views in a ListView

I've a custom adapter for my ListView I want to add project names as the headers to my work requests. Adding a single header works just fine but I'm not sure how to add multiple headers using addHeaderView. I don't understand where exactly to place setAdapter or is it supposed to be placed multiple times?
This is my java code for a single header which works:
mListView = (ListView)findViewById(R.id.dashboardList);
View header1 = getLayoutInflater().inflate(R.layout.listview_header, null, false);
tv = (TextView) header1.findViewById(R.id.listHeader);
adapter = new MyCustomAdapter(MyDashboardActivity.this, R.layout.mydashboard_row, dashboardBean);
tv.setText("Project 1");
mListView.addHeaderView(header1, null, false);
for (int i=0; i < 7; i++) {
dashboardBean.add(new DashboardBean(workRequests[i],status[i],actualHours[i]));
}
mListView.setAdapter(adapter);
Now, I for two headers I tried this:
mListView = (ListView)findViewById(R.id.dashboardList);
View header1 = getLayoutInflater().inflate(R.layout.listview_header, null, false);
tv = (TextView) header1.findViewById(R.id.listHeader);
adapter = new MyCustomAdapter(MyDashboardActivity.this, R.layout.mydashboard_row, dashboardBean);
tv.setText("RxOffice");
mListView.addHeaderView(header1, null, false);
for (int i=0; i < 4; i++) {
dashboardBean.add(new DashboardBean(workRequests[i],status[i],actualHours[i]));
}
tv.setText(Project 2");
mListView.addHeaderView(header1, null, false);
for (int i=4; i < workRequests.length; i++) {
dashboardBean.add(new DashboardBean(workRequests[i],status[i],actualHours[i]));
}
mListView.setAdapter(adapter);
But this doesn't work! It gives me only the Project 2 header and all 7 entries below it. Could anyone please tell me what's wrong? I'm guessing it has something to do with setAdapter. Thanks!
I don't think what you want to do is possible the way you are trying to do it. When you use addHeaderView it wraps your ListAdapter in HeaderViewListAdapter. I looked at the docs for it here and that seems to imply that you could have multiple headers, but they would all be at the top (duh, header).
It sounds like what you actually want is seperators...
You could use CommonWare's MergeAdapter. It will let you insert adapters and views (in whatever order you wish) and present them all as a single adapter to a listview. You just hand it headers and adapters for each section of content and then set it to your list.
Pseudo-code example:
myMergeAdapter = new MergeAdapter();
myMergeAdapter.addView(HeaderView1);
myMergeAdapter.addAdapter(listAdapter1);
myMergeAdapter.addView(HeaderView2);
myMergeAdapter.addAdapter(listAdapter2);
setListAdapter(myMergeAdapter);
I achieved multiple header scenario using custom Section Adapter which is originally coded by CommonsWare, you can make section within listivew, like Books, Games and etc. check out below code.
Section Adapter:
package com.medplan.db;
import java.util.ArrayList;
import java.util.List;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.BaseAdapter;
abstract public class SectionedAdapter extends BaseAdapter {
String TAG = "========SectionedAdapter============";
abstract protected View getHeaderView(String caption,
int index,
View convertView,
ViewGroup parent);
private List<Section> sections=new ArrayList<Section>();
private static int TYPE_SECTION_HEADER=0;
public SectionedAdapter() {
super();
sections.clear();
}
public void addSection(String caption, Adapter adapter) {
sections.add(new Section(caption, adapter));
}
public void clear() {
sections.clear();
notifyDataSetChanged();
}
public Object getItem(int position) {
for (Section section : this.sections) {
if (position==0) {
return(section);
}
int size=section.adapter.getCount()+1;
if (position<size) {
return(section.adapter.getItem(position-1));
}
position-=size;
}
return(null);
}
public int getCount() {
int total=0;
for (Section section : this.sections) {
total+=section.adapter.getCount()+1; // add one for header
}
return(total);
}
public int getViewTypeCount() {
int total=1; // one for the header, plus those from sections
for (Section section : this.sections) {
total+=section.adapter.getViewTypeCount();
}
return(total);
}
public int getItemViewType(int position) {
int typeOffset=TYPE_SECTION_HEADER+1; // start counting from here
for (Section section : this.sections) {
if (position==0) {
return(TYPE_SECTION_HEADER);
}
int size=section.adapter.getCount()+1;
if (position<size) {
return(typeOffset+section.adapter.getItemViewType(position-1));
}
position-=size;
typeOffset+=section.adapter.getViewTypeCount();
}
return(-1);
}
public boolean areAllItemsSelectable() {
return(false);
}
public boolean isEnabled(int position) {
return(getItemViewType(position)!=TYPE_SECTION_HEADER);
}
public View getView(int position, View convertView,
ViewGroup parent) {
int sectionIndex=0;
for (Section section : this.sections) {
if (position==0) {
return(getHeaderView(section.caption, sectionIndex,
convertView, parent));
}
int size=section.adapter.getCount()+1;
if (position<size) {
return(section.adapter.getView(position-1,convertView,parent));
}
position-=size;
sectionIndex++;
}
return(null);
}
public long getItemId(int position) {
return(position);
}
class Section {
String caption = null;
Adapter adapter = null;
Section(String caption, Adapter adapter) {
this.caption=caption;
this.adapter=adapter;
}
}
}
Within Activity make Section adapter object,check out below code:
final SectionedAdapter adapter =new SectionedAdapter()
{
protected View getHeaderView(String caption, int index, View convertView,ViewGroup parent)
{
result=(TextView)convertView;
if (convertView==null)
{
result=(TextView)getLayoutInflater().inflate(R.layout.section_header,null);
}
result.setText(caption);
// temp=caption;
// ind=index;
return(result);
}
};
section_header.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#color/black"
android:textColor="#FFFFFF"
android:ellipsize="end"
android:textSize="11sp"
style="?android:attr/listSeparatorTextViewStyle" />
<!-- android:background="#515050"-->
Within Activity add section as many as you want like below:
Note: userPic & medPic are name of arraylist.
adapter.addSection("section first", new EfficientAdapter(getApplicationContext(),usersPic));
adapter.addSection("section second", new EfficientAdapter(getApplicationContext(),medPic));
listview.setAdapter(adapter);
I would solve this issue with ExpandableListView. It doesn't require any extra library.
Create your custom adapter.
Give your data and fill the methods you need to override.
After setting the adapter with ExpandableListView:
Override groupItemClick so clicking on the groups won't actually expand the view.
Loop through every parent in adapter and set them expanded.

android: remove a view from a gallery

I'm using a Gallery view in my app. The app is designed so that I can drag and drop a view from that Gallery.
How can I remove the dragged view from the Gallery?
You remove it from the underlying adapter. If you do this correctly, the Gallery will refresh itself. Otherwise, call notifyDataSetChanged() on the adapter to trigger a Gallery update.
If you override ImageAdapter you can modify the contents at will by adding methods to delete or add items to your image list(s) or in the case of the example, completely swap lists on the fly. I am displaying a app banner at startup, and then changing the Gallery to display the mode the app is in as a slider. Whenever you call a method that modifies the dataset in the ImageAdapter, call imageAdapter.notifyDataSetChanged() as CommonsWare says above :
// in onCreate
_gallery = (Gallery) this.findViewById(R.id.gallery_header);
_imageAdapter = new ImageAdapter(getApplicationContext(),screen_width,screen_height);
_imageAdapter.setBannerMode(true);
_gallery.setAdapter(_imageAdapter);
// the main activity, in my case in a message handler.
_imageAdapter.setBannerMode(false);
_imageAdapter.notifyDataSetChanged();
_gallery.setSelection(0,true);
// this is my extended image adapter class
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
public class ImageAdapter extends BaseAdapter
{
private Context _context = null;
private int[] imageIds = { R.drawable.add_banner,R.drawable.subtract_banner,R.drawable.multiply_banner,R.drawable.divide_banner };
private int[] bannerIds = { R.drawable.mathpiggie_banner };
private static boolean bannerEnabled = true;
int _screen_width;
int _screen_height;
public ImageAdapter(Context context, int screen_width, int screen_height) {
this._context = context;
_screen_width = screen_width;
_screen_height = screen_height;
}
public void setBannerMode(boolean val)
{
bannerEnabled = val;
}
#Override
public int getCount()
{
if (bannerEnabled)
return bannerIds.length;
else
return imageIds.length;
}
#Override
public Object getItem(int index)
{
if (bannerEnabled)
return bannerIds[index];
else
return imageIds[index];
}
#Override
public long getItemId(int index)
{
return index;
}
#Override
public View getView(int postion, View view, ViewGroup group)
{
ImageView imageView = new ImageView(_context);
if (bannerEnabled)
imageView.setImageResource(bannerIds[postion]);
else
imageView.setImageResource(imageIds[postion]);
return imageView;
}
}

Android: ListView elements with multiple clickable buttons

I've a ListView where every element in the list contains a TextView and two different Buttons. Something like this:
ListView
--------------------
[Text]
[Button 1][Button 2]
--------------------
[Text]
[Button 1][Button 2]
--------------------
... (and so on) ...
With this code I can create an OnItemClickListener for the whole item:
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> list, View view, int position, long id) {
Log.i(TAG, "onListItemClick: " + position);
}
}
});
However, I don't want the whole item to be clickable, but only the two buttons of each list element.
So my question is, how do I implement a onClickListener for these two buttons with the following parameters:
int button (which button of the element has been clicked)
int position (which is the element in the list on which the button click happened)
Update: I found a solution as described in my answer below. Now I can click/tap the button via the touch screen. However, I can't manually select it with the trackball. It always selects the whole list item and from there goes directly to the next list item ignoring the buttons, even though I set .setFocusable(true) and setClickable(true) for the buttons in getView().
I also added this code to my custom list adapter:
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
return false;
}
This causes that no list item is selectable at all any more. But it didn't help in making the nested buttons selectable.
Anyone an idea?
The solution to this is actually easier than I thought. You can simply add in your custom adapter's getView() method a setOnClickListener() for the buttons you're using.
Any data associated with the button has to be added with myButton.setTag() in the getView() and can be accessed in the onClickListener via view.getTag()
I posted a detailed solution on my blog as a tutorial.
This is sort of an appendage #znq's answer...
There are many cases where you want to know the row position for a clicked item AND you want to know which view in the row was tapped. This is going to be a lot more important in tablet UIs.
You can do this with the following custom adapter:
private static class CustomCursorAdapter extends CursorAdapter {
protected ListView mListView;
protected static class RowViewHolder {
public TextView mTitle;
public TextView mText;
}
public CustomCursorAdapter(Activity activity) {
super();
mListView = activity.getListView();
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// do what you need to do
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = View.inflate(context, R.layout.row_layout, null);
RowViewHolder holder = new RowViewHolder();
holder.mTitle = (TextView) view.findViewById(R.id.Title);
holder.mText = (TextView) view.findViewById(R.id.Text);
holder.mTitle.setOnClickListener(mOnTitleClickListener);
holder.mText.setOnClickListener(mOnTextClickListener);
view.setTag(holder);
return view;
}
private OnClickListener mOnTitleClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
final int position = mListView.getPositionForView((View) v.getParent());
Log.v(TAG, "Title clicked, row %d", position);
}
};
private OnClickListener mOnTextClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
final int position = mListView.getPositionForView((View) v.getParent());
Log.v(TAG, "Text clicked, row %d", position);
}
};
}
For future readers:
To select manually the buttons with the trackball use:
myListView.setItemsCanFocus(true);
And to disable the focus on the whole list items:
myListView.setFocusable(false);
myListView.setFocusableInTouchMode(false);
myListView.setClickable(false);
It works fine for me, I can click on buttons with touchscreen and also alows focus an click using keypad
I don't have much experience than above users but I faced this same issue and I Solved this with below Solution
<Button
android:id="#+id/btnRemove"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/btnEdit"
android:layout_weight="1"
android:background="#drawable/btn"
android:text="#string/remove"
android:onClick="btnRemoveClick"
/>
btnRemoveClick Click event
public void btnRemoveClick(View v)
{
final int position = listviewItem.getPositionForView((View) v.getParent());
listItem.remove(position);
ItemAdapter.notifyDataSetChanged();
}
Probably you've found how to do it, but you can call
ListView.setItemsCanFocus(true)
and now your buttons will catch focus
I am not sure about be the best way, but works fine and all code stays in your ArrayAdapter.
package br.com.fontolan.pessoas.arrayadapter;
import java.util.List;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import br.com.fontolan.pessoas.R;
import br.com.fontolan.pessoas.model.Telefone;
public class TelefoneArrayAdapter extends ArrayAdapter<Telefone> {
private TelefoneArrayAdapter telefoneArrayAdapter = null;
private Context context;
private EditText tipoEditText = null;
private EditText telefoneEditText = null;
private ImageView deleteImageView = null;
public TelefoneArrayAdapter(Context context, List<Telefone> values) {
super(context, R.layout.telefone_form, values);
this.telefoneArrayAdapter = this;
this.context = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.telefone_form, parent, false);
tipoEditText = (EditText) view.findViewById(R.id.telefone_form_tipo);
telefoneEditText = (EditText) view.findViewById(R.id.telefone_form_telefone);
deleteImageView = (ImageView) view.findViewById(R.id.telefone_form_delete_image);
final int i = position;
final Telefone telefone = this.getItem(position);
tipoEditText.setText(telefone.getTipo());
telefoneEditText.setText(telefone.getTelefone());
TextWatcher tipoTextWatcher = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
telefoneArrayAdapter.getItem(i).setTipo(s.toString());
telefoneArrayAdapter.getItem(i).setIsDirty(true);
}
};
TextWatcher telefoneTextWatcher = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
telefoneArrayAdapter.getItem(i).setTelefone(s.toString());
telefoneArrayAdapter.getItem(i).setIsDirty(true);
}
};
tipoEditText.addTextChangedListener(tipoTextWatcher);
telefoneEditText.addTextChangedListener(telefoneTextWatcher);
deleteImageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
telefoneArrayAdapter.remove(telefone);
}
});
return view;
}
}
I Know it's late but this may help, this is an example how I write custom adapter class for different click actions
public class CustomAdapter extends BaseAdapter {
TextView title;
Button button1,button2;
public long getItemId(int position) {
return position;
}
public int getCount() {
return mAlBasicItemsnav.size(); // size of your list array
}
public Object getItem(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.listnavsub_layout, null, false); // use sublayout which you want to inflate in your each list item
}
title = (TextView) convertView.findViewById(R.id.textViewnav); // see you have to find id by using convertView.findViewById
title.setText(mAlBasicItemsnav.get(position));
button1=(Button) convertView.findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//your click action
// if you have different click action at different positions then
if(position==0)
{
//click action of 1st list item on button click
}
if(position==1)
{
//click action of 2st list item on button click
}
});
// similarly for button 2
button2=(Button) convertView.findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//your click action
});
return convertView;
}
}
Isn't the platform solution for this implementation to use a context menu that shows on a long press?
Is the question author aware of context menus? Stacking up buttons in a listview has performance implications, will clutter your UI and violate the recommended UI design for the platform.
On the flipside; context menus - by nature of not having a passive representation - are not obvious to the end user. Consider documenting the behaviour?
This guide should give you a good start.
http://www.mikeplate.com/2010/01/21/show-a-context-menu-for-long-clicks-in-an-android-listview/

Categories

Resources