As a follow up to this question, I have successfully implemented the WheelView with custom TextView (which has custom font for translation purposes).
adapter.setItemResource(R.layout.layout_item);
adapter.setItemTextResource(R.id.text);
The problem now is that the wheelview doesn't highlight current Item as it should.
Adapter code:
/**
* Adapter for string based wheel. Highlights the current value.
*/
private class DateArrayAdapter extends ArrayWheelAdapter<String> {
// Index of current item
int currentItem;
// Index of item to be highlighted
int currentValue;
/**
* Constructor
*/
public DateArrayAdapter(Context context, String[] items, int current) {
super(context, items);
this.currentValue = current;
setTextSize(16);
}
#Override
protected void configureTextView(TextView view) {
super.configureTextView(view);
if (currentItem == currentValue) {
view.setTextColor(getResources().getColor(R.color.holo_blue));
}
view.setTypeface(Typeface.SANS_SERIF);
view.setTextSize(18);
}
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
currentItem = index;
return super.getItem(index, cachedView, parent);
}
}
In the above code, it does not go to configureTextView at all to highlight the item.
Original source of WheelView.
Okay, I figured it out. In case it helps anybody else, here's the code:
/**
* Adapter for string based wheel. Highlights the current value.
*/
private class DateArrayAdapter extends ArrayWheelAdapter<String> {
// Index of current item
int currentItem;
// Index of item to be highlighted
int currentValue;
/**
* Constructor
*/
public DateArrayAdapter(Context context, String[] items, int current) {
super(context, items);
this.currentValue = current;
setItemResource(R.layout.wheelview_textview);
setItemTextResource(R.id.wheelview_date);
setTextSize(16);
}
protected void configureTextView(CustomTextView view) {
super.configureTextView(view);
if (currentItem == currentValue) {
view.setTextColor(getResources().getColor(R.color.holo_blue));
}
}
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
currentItem = index;
View view = super.getItem(index, cachedView, parent);
CustomTextView date = (CustomTextView) view.findViewById(R.id.wheelview_date);
configureTextView(date);
return view;
//return super.getItem(index, cachedView, parent);
}
}
Related
I have a layout, default_label.xml, like so:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Then, I have this class, which basically allows me to set the default button text for a spinner:
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;
protected TextView label;
/**
* 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 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 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.
}
public int getPosition(String value) {
int index = 0;
for (int i = 1; i < getCount() + EXTRA; i++) {
if (getItem(i).equals(value)) {
index = i;
break;
}
}
return index;
}
public TextView getNothingSelectedView(ViewGroup parent) {
label = (TextView) layoutInflater.inflate(nothingSelectedLayout, parent, false);
return label;
}
#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 new View(context);
}
if (adapter.getItem(position-EXTRA).toString().equals("")){
View view = nothingSelectedDropdownLayout == -1 ?
new View(context) :
getNothingSelectedDropdownView(parent);
view.setEnabled(false);
view.setOnClickListener(null);
return view;
}
// Could re-use the convertView if possible, use setTag...
return adapter.getDropDownView(position - EXTRA, null, parent);
}
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.
}
}
What I want to be able to do is change the text of the TextView, something like:
NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(adapter, R.layout.default_label, getContext());
myAdapter.setText("Custom Label");
mySpinner.setAdapter(myAdapter);
However, when I try adding the following method to the above NothingSelectedSpinnerAdapter class:
public void setText(String text) {
label.setText(text);
}
I get the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void
android.widget.TextView.setText(java.lang.CharSequence)' on a null
object reference
What should I change?
Take a look at the following three lines:
NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(adapter, R.layout.default_label, getContext());
The Adapter is instantiated, label is null
myAdapter.setText("Custom Label");
The TextView label is still null, that's why label.setText(); causes a NullPointerException
mySpinner.setAdapter(myAdapter);
Now the runtime will be able to draw the Spinner items. This involves repeated calling of getView() (at least once for every item which should be drawn). After getView() has been executed for position = 0, you will have assigned a value to label. From now on, you can safely call setText() on it.
If you want to be able to call myAdapter.setText("Custom Label"); whenever you like without having to bother about the inner workings of the Adapter, then you can introduce a field private String mLabelText and implement the method as follows
public void setText(String labelText){
mLabelText = labelText;
if (label != null){
label.setText(labelText);
}
}
One last step: don't forget to set the text right after initializing label
public TextView getNothingSelectedView(ViewGroup parent) {
label = (TextView) layoutInflater.inflate(nothingSelectedLayout, parent, false);
label.setText(mLabelText);
return label;
}
When end items in a ListView, i upload new, and after update adapter:
ListView lvMain = (ListView) findViewById(R.id.list);
boxAdapter = null;
boxAdapter = new BoxAdapter(this, products);
lvMain.setAdapter(boxAdapter);
But after this, elements are loaded but the scroll position the top. Ie the position of ListView is lost, and look again at the beginning of all
How fix it?
BoxAdapter code:
public class BoxAdapter extends BaseAdapter {
private final Context ctx;
private final LayoutInflater lInflater;
private final ArrayList<ItemInfo> objects;
private final int loadCount = 10;
private int count = 10;
private String name, desc;
BoxAdapter(Context context, ArrayList<ItemInfo> products) {
this.ctx = context;
this.objects = products;
this.lInflater = (LayoutInflater) ctx
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
// кол-во элементов
#Override
public int getCount() {
//return objects.size();
return this.count;
}
// элемент по позиции
#Override
public ItemInfo getItem(int position) {
return objects.get(position);
}
// id по позиции
#Override
public long getItemId(int position) {
return position;
}
public void loadAdditionalItems() {
this.count += this.loadCount;
if (this.count > this.objects.size()) {
this.count = this.objects.size();
}
notifyDataSetChanged();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
view = lInflater.inflate(R.layout.item, parent, false);
ItemInfo p = getItem(position);
TextView desc_id = (TextView) view.findViewById(R.id.desc);
if (p.username.contains("null"))
{
name = "Автор: Неизвестен";
}
else
{
name = "Автор: " + p.username;
}
if(!p.description.contains("null"))
{
desc = p.description.replaceAll("<br />", "");
desc = desc.replaceAll(""", "");
}
else
{
desc = "";
desc_id.setVisibility(View.GONE);
}
((TextView) view.findViewById(R.id.name)).setText(name);
((TextView) view.findViewById(R.id.desc)).setText(desc);
return view;
}
}
P.S setOnScrollListener code:
lvMain.setOnScrollListener(new OnScrollListener()
{
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
ListView lvMain = (ListView) findViewById(R.id.list);
if(firstVisibleItem + visibleItemCount >= totalItemCount) {
boxAdapter.loadAdditionalItems();
loading = false;
}
if (!loading && (lvMain.getLastVisiblePosition() + 10) >= (60))
{
new LoadLastestPost().execute();
loading = true;
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
});
The best solution would be to create a setProducts method in your boxAdapter and then just call boxAdapter.notifyDataSetChanged(). For example:
boxAdapter.setProducts(products);
boxAdapter.notifyDataSetChanged();
If you implement this method, there is no need to call lvMain.setAdapter(boxAdapter) more than once.
To add the setProducts() method to your adapter:
public BoxAdapter extends BaseAdapter {
Context mContext;
ArrayList<ItemInfo> objects;
public BoxAdapter(Context context, ArrayList<ItemInfo> products) {
mContext = context;
objects = products;
}
public View getView(int position, View convertView, ViewGroup parent) {
// inflate and adjust view
}
public int getCount() {
return objects.size();
}
public Object getItem(int position) {
return objects.get(position);
}
public void setProducts(ArrayList<ItemInfo> newData) {
objects = newData;
}
}
Also, I wouldn't use a count variable. I would just use the size method in the ArrayList. I would remove count altogether.
On my project I have to show to the user a ListView which is contained basically of EditText.
It is like a questionnaire, when he answer to the question he can go down and answer to the next question. (and go back)
public class onAdapter extends BaseAdapter {
List<PointVerification> mObjects;
Context mContext;
LayoutInflater mInflater;
HashMap<Integer, List<ChoixPointVerification>> mChoix;
HashMap<Integer, ReponsePointVerification> mReponses;
HashMap<Integer, String> mReponsesActuel;
Integer mPositionSelectionne;
Integer mIdIntervention;
/**
* Constructeur
* #param context
* #param listePointsVerification Liste des points de vérification à afficher.
* #param listeChoixPointsVerification liste des choix de points de vérification pour chaque point de vérification.
* #param listeReponsesPointsVerification réponses déjà fournies pour chaque point de vérification
* #param idIntervention Identifiant de l'intervention
*/
public onAdapter(
Context context,
Integer idIntervention,
List<PointVerification> listePointsVerification,
List<ChoixPointVerification>> listeChoixPointsVerification,
ReponsePointVerification> listeReponsesPointsVerification) {
this.mInflater = LayoutInflater.from(context);
this.mContext = context;
this.mObjects = listePointsVerification;
this.mChoix = listeChoixPointsVerification;
this.mReponses = listeReponsesPointsVerification;
this.mIdIntervention = idIntervention;
// préparation des réponses par position
this.mReponsesActuel = new HashMap<Integer, String>();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row;
final Integer idPointVerification = getItem(position).id;
if (convertView == null)
{
row = mInflater.inflate(R.layout.intervention_reponses_controle_nombre, null);
EditText edValeur = (EditText) row.findViewById(R.id.edValeur);
// Ajout de l'évènement lorsque l'on change la valeur
// evènement d'enregistrement
edValeur.addTextChangedListener(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) {
// This hashmap is a try workaround to the bug
// i register all modifications into this hashmap
mReponsesActuel.put(
idPointVerification,
s.toString());
// SAVE PROCEDURE
// REMOVED FOR THE EXAMPLE
// BUT I UPDATE MY DATABASE
}
});
}
else
{
row = convertView;
}
EditText edValeur = (EditText) row.findViewById(R.id.edValeur);
// update of the text
// it is the contained in the hashmap
if (mReponsesActuel.containsKey(idPointVerification)) {
String valeur = mReponsesActuel.get(idPointVerification);
edValeur.setText(valeur);
// otherwhise i will look into the database
} else if (mReponses != null && mReponses.containsKey(idPointVerification)
&& mReponses.get(idPointVerification).valeur != null) {
edValeur.setText(mReponses.get(idPointVerification).valeur);
}
else
{
edValeur.setText("");
}
return row;
}
}
I don't know why but the user go down in the ListView, the content is unsaved and show some special values like others EditText. I didn't find how to correct that behaviour and it's really in a hurry.
Basically I made a TextWatcher which register data into the database and also into a temporary HashMap which contains all values. That data is called back when GetView is called. If I remove that initialisation, the EditText is erased.
Note : I made some test in my code so there may be different problem.
Edit
I uploaded a project with the problem : http://dl.free.fr/rbqOhUfJF
Here the steps to reproduce the problem :
launch the project in debug mode
write in the first row A, in the second B, in the third C
Scroll down until C is hidden
scroll up to the top. Result : There is no more text.
If you look closely on the debug log you can see line with afterText. Each times we write some text in the part 2, there will be a debug line with the registration of the event.
But at the phase 3, when you hide an item. the event will be launched with ""
Result : in the 4th phase, it loads the "" string
To speed up things in most mobile apps (Android iOS ...) cells of lists are usually recycled. This spares memory especially for long lists. Therefore you have to get the data of a new displayed cell. When the cell goes outside the screen, its layout/view is destroyed. In your case you have to save the text of the edit text somewhere. That's what you are trying to do with the hashmap.
I don't see any particular mistake in your code.
If the question is about the "workaround" of using the hashmap, I confirm that for me it is up to you to save the states of the edit text. Using a hashmap is one way of doing it.
By the way getItem(position).id can be replaced by getItemId(position) which is here for that purpose.
Not sure if all this answers your question.
EDIT
Now that I understood your question correctly I can provide some code. I must say that I am not fully happy with the solution found but at least it is working.
The problem with the TextWatcher is that you have no access to the context and of the corresponding view.
Using setOnFocusChangeListener solved the problem. So here is the code I finally got working.
public final class PointVerificationAdapter extends BaseAdapter {
List<BasicNameValuePair> mObjects;
Context mContext;
LayoutInflater mInflater;
HashMap<Integer, String> mReponsesActuel;
ArrayList<String> myItems = new ArrayList<String>();
public PointVerificationAdapter(
Context context,
List<BasicNameValuePair> listObjets
) {
this.mInflater = LayoutInflater.from(context);
this.mContext = context;
this.mObjects = listObjets;
for (int i = 0; i < 30; i++) {
myItems.add(Integer.toString(i));
}
}
#Override
public int getCount() {
return mObjects.size();
}
#Override
public BasicNameValuePair getItem(int position) {
return mObjects.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
static class ViewHolder {
EditText yourEditText;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null)
{
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.intervention_reponses_controle_nombre, parent, false);
convertView.setId(position);
holder.yourEditText = (EditText) convertView.findViewById(R.id.edValeur);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
((TextView) convertView.findViewById(R.id.tvNom)).setText(Integer.toString(position));
holder.yourEditText.setText(myItems.get(position));
holder.yourEditText.setId(position);
holder.yourEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View view, boolean hasFocus) {
if (!hasFocus){
final int position = view.getId();
final EditText editText = (EditText) view;
myItems.set(position, editText.getText().toString());
}
}
});
return convertView;
}
}
FINAL EDIT
The previous code is working but I wasn't happy with it because you asked for addTextChangedListenerand not for onFocusChange.
So here is the solution :
public final class PointVerificationAdapter extends BaseAdapter {
List<BasicNameValuePair> mObjects;
Context mContext;
LayoutInflater mInflater;
HashMap<Integer, String> mReponsesActuel;
ArrayList<String> myItems = new ArrayList<String>();
public PointVerificationAdapter(
Context context,
List<BasicNameValuePair> listObjets
) {
this.mInflater = LayoutInflater.from(context);
this.mContext = context;
this.mObjects = listObjets;
for (int i = 0; i < 30; i++) {
myItems.add(Integer.toString(i));
}
}
#Override
public int getCount() {
return mObjects.size();
}
#Override
public BasicNameValuePair getItem(int position) {
return mObjects.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
static class ViewHolder {
EditText yourEditText;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null)
{
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.intervention_reponses_controle_nombre, parent, false);
convertView.setId(position);
holder.yourEditText = (EditText) convertView.findViewById(R.id.edValeur);
holder.yourEditText.setId(position);
holder.yourEditText.addTextChangedListener(new GenericTextWatcher(holder.yourEditText));
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
holder.yourEditText.setId(position);
}
((TextView) convertView.findViewById(R.id.tvNom)).setText(Integer.toString(position));
holder.yourEditText.setText(myItems.get(position));
return convertView;
}
private class GenericTextWatcher implements TextWatcher{
private View view;
private GenericTextWatcher(View view) {
this.view = view;
}
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
public void afterTextChanged(Editable editable) {
final int position = view.getId();
final EditText editText = (EditText) view;
myItems.set(position, editText.getText().toString());
}
}
}
I have created an expandable listview, but the onclick listener to the child list items could not be attached.
The activity code:
public class MyActivity extends Activity {
private ExpandableListView mExpandableList;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mExpandableList = (ExpandableListView)findViewById(R.id.expandable_list);
ArrayList<Parent> arrayParents = new ArrayList<Parent>();
ArrayList<String> arrayChildren = new ArrayList<String>();
//here we set the parents and the children
for (int i = 0; i < 2; i++){
//for each "i" create a new Parent object to set the title and the children
Parent parent = new Parent();
parent.setTitle("Parent " + i);
arrayChildren = new ArrayList<String>();
for (int j = 0; j < 3; j++) {
arrayChildren.add("Child " + j);
}
parent.setArrayChildren(arrayChildren);
//in this array we add the Parent object. We will use the arrayParents at the setAdapter
arrayParents.add(parent);
}
//sets the adapter that provides data to the list.
mExpandableList.setAdapter(new MyCustomAdapter(MyActivity.this,arrayParents));
}
}
The custom adapter for the lists:
public class MyCustomAdapter extends BaseExpandableListAdapter {
private LayoutInflater inflater;
private ArrayList<Parent> mParent;
public MyCustomAdapter(Context context, ArrayList<Parent> parent){
mParent = parent;
inflater = LayoutInflater.from(context);
}
#Override
//counts the number of group/parent items so the list knows how many times calls getGroupView() method
public int getGroupCount() {
return mParent.size();
}
#Override
//counts the number of children items so the list knows how many times calls getChildView() method
public int getChildrenCount(int i) {
return mParent.get(i).getArrayChildren().size();
}
#Override
//gets the title of each parent/group
public Object getGroup(int i) {
return mParent.get(i).getTitle();
}
#Override
//gets the name of each item
public Object getChild(int i, int i1) {
return mParent.get(i).getArrayChildren().get(i1);
}
#Override
public long getGroupId(int i) {
return i;
}
#Override
public long getChildId(int i, int i1) {
return i1;
}
#Override
public boolean hasStableIds() {
return true;
}
#Override
//in this method you must set the text to see the parent/group on the list
public View getGroupView(int i, boolean b, View view, ViewGroup viewGroup) {
if (view == null) {
view = inflater.inflate(R.layout.list_item_parent, viewGroup,false);
}
TextView textView = (TextView) view.findViewById(R.id.list_item_text_view);
//"i" is the position of the parent/group in the list
textView.setText(getGroup(i).toString());
//return the entire view
return view;
}
#Override
//in this method you must set the text to see the children on the list
public View getChildView(int i, int i1, boolean b, View view, ViewGroup viewGroup) {
if (view == null) {
view = inflater.inflate(R.layout.list_item_child, viewGroup,false);
}
TextView textView = (TextView) view.findViewById(R.id.list_item_text_child);
//"i" is the position of the parent/group in the list and
//"i1" is the position of the child
textView.setText(mParent.get(i).getArrayChildren().get(i1));
//return the entire view
return view;
}
#Override
public boolean isChildSelectable(int i, int i1) {
return true;
}
#Override
public void registerDataSetObserver(DataSetObserver observer) {
/* used to make the notifyDataSetChanged() method work */
super.registerDataSetObserver(observer);
}
}
The parent class:
public class Parent {
private String mTitle;
private ArrayList<String> mArrayChildren;
public String getTitle() {
return mTitle;
}
public void setTitle(String mTitle) {
this.mTitle = mTitle;
}
public ArrayList<String> getArrayChildren() {
return mArrayChildren;
}
public void setArrayChildren(ArrayList<String> mArrayChildren) {
this.mArrayChildren = mArrayChildren;
}
}
What should I do to add onclick listener to the child list items?
Add this after you setAdapter of the Expandable list
mExpandableList.setOnChildClickListener(new OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v,int groupPosition, int childPosition, long id) {
/* You must make use of the View v, find the view by id and extract the text as below*/
TextView tv= (TextView) v.findViewById(R.id.childTextView);
String data= tv.getText().toString();
return true; // i missed this
}
});
You need to add
ChildClickListener
like this : mExpandableList.setOnChildClickListener
add this line to onCreate method
read here
also this is a good example
Declare your listView with an overridden onChildClick
ExpandableListView listView = getExpandableListView();
listView.setChoiceMode(ExpandableListView.CHOICE_MODE_SINGLE);
listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v,int groupPosition, int childPosition,long id) {
Log.d(TAG,"I got clicked childPosition:["+childPosition+"] groupPosition:["+groupPosition+"] id:["+id+"]");
return true;
}
});
I'm trying to create a spinner with default empty selected item, but it displays the first item from the choices of spinner. If I add null value to my string, which is the source of choices in spinner, then after opening spinner that empty row is displayed. How should I do it? Here's code I'm using:
String[] ch = {"Session1", "Session2", "Session3"};
Spinner sp = (Spinner)findViewById(R.id.spinner1);
TextView sess_name = findViewById(R.id.sessname);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item,ch);
sp.setAdapter(adapter);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sp.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener({
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
int index = arg0.getSelectedItemPosition();
sess_name.setText(ch[index]);
Toast.makeText(getBaseContext(), "You have selected item : " + ch[index], Toast.LENGTH_SHORT).show();
}
Barak's solution have a problem. When you select the first item, Spinner won't call OnItemSelectedListener's onItemSelected() and refresh the empty content because the previous position and selection position both is 0.
First put a empty string at the begin of your string array:
String[] test = {" ", "one", "two", "three"};
Second build adapter, don't modify getView(), modify getDropDownView(). Set the empty View's height to 1px.
public class MyArrayAdapter extends ArrayAdapter<String> {
private static final int ITEM_HEIGHT = ViewGroup.LayoutParams.WRAP_CONTENT;
private int textViewResourceId;
public MyArrayAdapter(Context context,
int textViewResourceId,
String[] objects) {
super(context, textViewResourceId, objects);
this.textViewResourceId = textViewResourceId;
}
#Override
public View getDropDownView(int position, View convertView, #NonNull ViewGroup parent) {
TextView textView;
if (convertView == null) {
textView = (TextView) LayoutInflater.from(getContext())
.inflate(textViewResourceId, parent, false);
} else {
textView = (TextView) convertView;
}
textView.setText(getItem(position));
if (position == 0) {
ViewGroup.LayoutParams layoutParams = textView.getLayoutParams();
layoutParams.height = 1;
textView.setLayoutParams(layoutParams);
} else {
ViewGroup.LayoutParams layoutParams = textView.getLayoutParams();
layoutParams.height = ITEM_HEIGHT;
textView.setLayoutParams(layoutParams);
}
return textView;
}
}
I'm a little late to the party, but here is what I did to solve this.
If the user cancels out of selecting an initial item the spinner will retain the initial empty state. Once an initial item has been selected it works as 'normal'
Works on 2.3.3+, I have not tested on 2.2 and below
First, create an adapter class...
public class EmptyFirstItemAdapter extends ArrayAdapter<String>{
//Track the removal of the empty item
private boolean emptyRemoved = false;
/** Adjust the constructor(s) to fit your purposes. */
public EmptyFirstitemAdapter(Context context, List<String> objects) {
super(context, android.R.layout.simple_spinner_item, objects);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
}
#Override
public int getCount() {
//Adjust the count based on the removal of the empty item
if(emptyRemoved){
return super.getCount();
}
return super.getCount()-1;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
if(!emptyRemoved){
// Remove the empty item the first time the dropdown is displayed.
emptyRemoved = true;
// Set to false to prevent auto-selecting the first item after removal.
setNotifyOnChange(false);
remove(getItem(0));
// Set it back to true for future changes.
setNotifyOnChange(true);
}
return super.getDropDownView(position, convertView, parent);
}
#Override
public long getItemId(int position) {
// Adjust the id after removal to keep the id's the same as pre-removal.
if(emptyRemoved){
return position +1;
}
return position;
}
}
Here is the string array I used in strings.xml
<string-array name="my_items">
<item></item>
<item>Item 1</item>
<item>Item 2</item>
</string-array>
Next, add an OnItemSelectedListener to your Spinner...
mSpinner = (Spinner) mRootView.findViewById(R.id.spinner);
String[] opts = getResources().getStringArray(R.array.my_items);
//DO NOT set the entries in XML OR use an array directly, the adapter will get an immutable List.
List<String> vals = new ArrayList<String>(Arrays.asList(opts));
final EmptyFirstitemAdapter adapter = new EmptyFirstitemAdapter(getActivity(), vals);
mSpinner.setAdapter(adapter);
mSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
//Track that we have updated after removing the empty item
private boolean mInitialized = false;
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if(!mInitialized && position == 0 && id == 1){
// User selected the 1st item after the 'empty' item was initially removed,
// update the data set to compensate for the removed item.
mInitialized = true;
adapter.notifyDataSetChanged();
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
// Nothing to do
}
});
It may not be a 'perfect' solution, but I hope it helps someone.
After some thinking, I believe I've come up with a method to achieve your goal. It involves creating a
custom adapter and setting/maintaining a flag to determine if an item from the spinner has been selected.
Using this method you do not need to create/use false data (your empty string).
Basically, the adapters getView method sets the text for the closed spinner. So if you override that
and set a conditional in there, you can have a blank field on startup and after you make a selection have
it appear in the closed spinner box. The only thing is you need to remember to set the flag whenever you
need to see the value in the closed spinner.
I've created a small example program (code below).
Note that I only added the single constructor I needed for my example. You can implement all the standard
ArrayAdapter constructors or only the one(s) you need.
SpinnerTest.java
public class SpinnerTestActivity extends Activity {
private String[] planets = { "Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune" };
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Spinner spinner = (Spinner) findViewById(R.id.spinner);
CustomAdapter adapter = new CustomAdapter(this, // Use our custom adapter
android.R.layout.simple_spinner_item, planets);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
#Override
public void onItemSelected(AdapterView<?> parent, View view,
int pos, long id) {
CustomAdapter.flag = true; // Set adapter flag that something
has been chosen
}
});
}
}
CustomAdapter.java
public class CustomAdapter extends ArrayAdapter {
private Context context;
private int textViewResourceId;
private String[] objects;
public static boolean flag = false;
public CustomAdapter(Context context, int textViewResourceId,
String[] objects) {
super(context, textViewResourceId, objects);
this.context = context;
this.textViewResourceId = textViewResourceId;
this.objects = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
convertView = View.inflate(context, textViewResourceId, null);
if (flag != false) {
TextView tv = (TextView) convertView;
tv.setText(objects[position]);
}
return convertView;
}
}
Here is what I use. It properly handles null (empty) selection in a generic manner. It works with any model class T, as long as class T properly implements toString(), to display the text shown in the spinner, and equals(), so that items may be selected by reference rather than by positional index.
package com.10xdev.android.components;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* A spinner where no selection is possible, and other enhancements.
* requires model class to properly implement Object.equals, with semantic comparaison (such as id comparaison)
* and a proper toString(), whose result will be displayed in the spinner
*
* #author tony.benbrahim
*/
public class EnhancedSpinner<T> extends Spinner {
private final EnhanceArraySpinnerAdapter<T> spinnerAdapter;
private final List<T> items = new ArrayList<>();
private T selected = null;
public EnhancedSpinner(final Context context, final AttributeSet attributeSet) {
super(context, attributeSet);
spinnerAdapter = new EnhanceArraySpinnerAdapter<>(context, items);
setAdapter(spinnerAdapter);
}
/**
* sets the items to be displayed
*
* #param items
*/
public void setItems(final List<T> items) {
this.items.clear();
//very iffy, but works because of type erasure
this.items.add((T) "");
this.items.addAll(items);
spinnerAdapter.notifyDataSetChanged();
updateSelected();
}
/**
* set the selected item. this may be called before or after setting items
*
* #param item the item to select, or null to clear the selection
*/
public void setSelected(final T item) {
this.selected = item;
updateSelected();
}
/**
* gets the selected item, or null if no item is selected
*
* #return
*/
#Override
public T getSelectedItem() {
return getSelectedItemPosition() != 0 ? (T) super.getSelectedItem() : null;
}
/**
* set the error message for the select
*
* #param errorMessage
*/
public void setError(final String errorMessage) {
final TextView errorText = (TextView) getSelectedView();
errorText.setError("error");
errorText.setTextColor(Color.RED);
errorText.setText(errorMessage);
}
private void updateSelected() {
if (selected == null) {
setSelection(0);
} else {
for (int i = 1; i < items.size(); ++i) {
if (selected.equals(items.get(i))) {
setSelection(i);
break;
}
}
}
}
private class EnhanceArraySpinnerAdapter<T> extends ArrayAdapter<T> {
private final LayoutInflater inflater;
public EnhanceArraySpinnerAdapter(final Context context, final List<T> objects) {
super(context, android.R.layout.simple_spinner_item, objects);
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getDropDownView(final int position, final View convertView, final ViewGroup parent) {
final TextView textView = convertView != null ? (TextView) convertView
: (TextView) inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
final Object item = getItem(position);
textView.setText(item.toString());
final ViewGroup.LayoutParams layoutParams = textView.getLayoutParams();
layoutParams.height = position == 0 ? 1 : LayoutParams.WRAP_CONTENT;
textView.setLayoutParams(layoutParams);
return textView;
}
}
}
You have to put the first element of the spinner empty, or with an string indicating that nothing is selected like the following:
String[] ch= {"","Session1", "Session2", "Session3"};
or
String[] ch= {"Nothing selected", "Session1", "Session2", "Session3"};
hope to help