I have a strange problem - I am using an activity with a ViewSwitcher, this ViewSwitcher has a ListView and a GridView which use the same ArrayAdapter{SomeContent}.
I have made the Adapters so that I can pass them the R.layout.value that they use to inflate - this is how I manage consistency across ListView / GridView since I want somewhat different views in these two forms.
And all of this works somewhat perfectly, just not for the first time.
No, for the first time that I run my application in GRID display mode, my gridview tries to use the wrong XML, and ends up trying to use (recycle?) the ListView's XML. I know so because GridView's XML has no reference of checkboxes and I see them constantly.
But when I leave this screen (not even going to say 'activity') and come back to it, everything is inflated perfectly. List always works off the bat, GridViews ~80% don't work only first time.
Any ideas?
Here is some code to get you started.
ADAPTER:
public class AudioAdapter extends ArrayAdapter<MusicContent>
{
private Context context;
private ImageView albumArt;
private TextView songName;
private TextView artistName;
private TextView albumName;
private TextView genre;
private TextView duration;
private int viewToUse;
private CheckBox checkbox;
private OnItemClickListener clickListener;
private OnItemSelectedListener focusListener;
private List<MusicContent> content = new ArrayList<MusicContent>();
public AudioAdapter(Context context, int textViewResourceId, List<MusicContent> objects,
OnItemClickListener clickListener, OnItemSelectedListener focusListener)
{
super(context, textViewResourceId, objects);
this.context = context;
this.content = objects;
this.clickListener = clickListener;
this.focusListener = focusListener;
this.viewToUse = textViewResourceId;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
/**
* Removed following optimization on purpose due to dynamically using
* different layouts which may result in wrong view being recycled for
* use
*/
//removing it fixed nothing really
if (row == null)
{
// ROW INFLATION
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(viewToUse, parent, false);
}
// initiate helpers for onClick hack
final AdapterView fParent = (AdapterView) parent;
final View fView = row;
final int fInt = position;
final long fLong = row.getId();
// Get item
MusicContent item = getItem(position);
if (item == null)
return row;
RelativeLayout root = (RelativeLayout) row.findViewById(R.id.list_cell_layout);
// perform a series of checks to maintain customizability
albumArt = (ImageView) row.findViewById(R.id.list_cell_image);
if (albumArt != null)
{
if (item.hasAlbumArt()) {
albumArt.setImageBitmap(item.getAlbumBitmap(context));
}
else
albumArt.setImageDrawable(context.getResources().getDrawable(
R.drawable.ic_music_album));
}
LinearLayout checkLL = (LinearLayout) row.findViewById(R.id.list_cell_music_info);
if (checkLL != null)
{
// display some song info
songName = (TextView) checkLL.findViewById(R.id.list_cell_title);
if (songName != null)
{
songName.setText(item.getDisplayName());
songName.setVisibility(View.VISIBLE);
}
// attach artificial OnItemClick and OnItemSelected listeners
if (clickListener != null)
{
OnClickListener cross = new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("SHARK", "internal onClick from adapter!!" + fView);
clickListener.onItemClick(fParent, fView, fInt, fLong);
}
};
checkLL.setOnClickListener(cross);
}
if (focusListener != null)
{
OnFocusChangeListener cross = new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus)
focusListener.onItemSelected(fParent, fView, fInt, fLong);
}
};
checkLL.setOnFocusChangeListener(cross);
}
checkLL = (LinearLayout) row.findViewById(R.id.list_cell_artist_info);
if (checkLL != null)
{
// display artist info too
artistName = (TextView) checkLL.findViewById(R.id.list_cell_artist_name);
if (artistName != null)
artistName.setText(item.getArtist());
albumName = (TextView) checkLL.findViewById(R.id.list_cell_album);
if (albumName != null)
albumName.setText(item.getAlbum());
duration = (TextView) checkLL.findViewById(R.id.list_cell_duration);
if (duration != null)
duration.setText(item.getDurationString());
genre = (TextView) checkLL.findViewById(R.id.list_cell_genre);
if (genre != null)
genre.setText(item.getGenre());
// block focus on descendants
checkLL.setDescendantFocusability(RelativeLayout.FOCUS_BLOCK_DESCENDANTS);
// attach artificial listeners
if (clickListener != null)
{
OnClickListener cross = new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("SHARK", "internal onClick from adapter!!" + fView);
clickListener.onItemClick(fParent, fView, fInt, fLong);
}
};
checkLL.setOnClickListener(cross);
}
if (focusListener != null)
{
OnFocusChangeListener cross = new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus)
focusListener.onItemSelected(fParent, fView, fInt, fLong);
}
};
checkLL.setOnFocusChangeListener(cross);
}
}
// FrameLayout checkFL = (FrameLayout)
// row.findViewById(R.id.endoflineinfo);
checkLL = (LinearLayout) row.findViewById(R.id.endoflineinfo);
if (checkLL != null)
{
checkbox = (CheckBox) checkLL.findViewById(R.id.in_playlist);
if (checkbox != null)
{
checkbox.setVisibility(View.VISIBLE);
if (item.getPlaylist() != null)
checkbox.setChecked(!item.getPlaylist().isEmpty());
}
checkLL.setDescendantFocusability(RelativeLayout.FOCUS_BLOCK_DESCENDANTS);
if (clickListener != null)
{
OnClickListener cross = new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("SHARK", "internal onClick from adapter!!" + fView);
clickListener.onItemClick(fParent, fView, fInt, fLong);
}
};
checkLL.setOnClickListener(cross);
}
if (focusListener != null)
{
OnFocusChangeListener cross = new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus)
focusListener.onItemSelected(fParent, fView, fInt, fLong);
}
};
checkLL.setOnFocusChangeListener(cross);
}
}
}
// magic happens where we bind an OnItemClick call to OnClick
root.setDescendantFocusability(RelativeLayout.FOCUS_BLOCK_DESCENDANTS);
if (clickListener != null)
{
OnClickListener cross = new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("SHARK", "internal onClick from adapter!!");
clickListener.onItemClick(fParent, fView, fInt, fLong);
}
};
// assign this listener
root.setOnClickListener(cross);
}
if (focusListener != null)
{
OnFocusChangeListener cross = new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus)
focusListener.onItemSelected(fParent, fView, fInt, fLong);
}
};
root.setOnFocusChangeListener(cross);
}
return row;
}
As for why so many ifs and checks - it has to be able to survive different underlying XMLs with missing elements (customizability) and don't worry too much about the onClick / onFocus hacks - they're much needed workarounds....
After looking into how my views are recycled, I came to a conclusion that I was running the app in a different viewmode than it was last left in - this caused GRID views to be saved up and used in LIST, or vice-versa.
After revising, I made sure to restart the app in the same mode that it was previously used or force the new views upon restart be recreating the adapter and reassigning it to my view.
Related
I have ImageView inside of an item in a RecyclerView. Every time I click on an item, I want the image to become visible and the previous clicked item's image becomes invisible.
public static class MyHolder extends RecyclerView.ViewHolder {
private ImageView image_view;
private TextView text_view_name;
private TextView text_view_abilities;
private ImageView heart_image_view;
public MyHolder(#NonNull final View itemView, final OnItemClickListener listener) {
super(itemView);
image_view = itemView.findViewById(R.id.image_view);
text_view_name = itemView.findViewById(R.id.text_view_name);
text_view_abilities = itemView.findViewById(R.id.text_view_abilities);
heart_image_view = itemView.findViewById(R.id.heart_image_view);
heart_image_view.setImageResource(R.drawable.heart);
heart_image_view.setVisibility(View.GONE);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position);
heart_image_view.setVisibility(View.VISIBLE);
}
}
});
}
}
#NonNull
#Override
public MyHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.single_item_layout, viewGroup, false);
MyHolder holder = new MyHolder(v, mlistener);
return holder;
}
#Override
public void onBindViewHolder(#NonNull MyHolder myHolder, int position) {
Item item = myList.get(position);
myHolder.text_view_name.setText(item.getTitle());
stringArray = item.getAbilitiesObj();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < stringArray.size(); i++) {
try {
builder.append(stringArray.get(i));
builder.append(", ");
} catch (NumberFormatException ex) {
ex.printStackTrace();
}
myHolder.text_view_abilities.setText(builder);
}
if (myHolder.image_view != null) {
String photoUrl = item.getImageUrl();
Picasso.with(mContext).load(photoUrl).into(myHolder.image_view);
}
}
In MyHolder class where item.setOnClick, I set the clicked item image visible but then every time I click on another item, the image of the new item becomes visible and the image of the previous item does not get invisible.
You need to have another array of integers in your adapter to keep track of the items which is clicked. At first, initialize the array of integers with all ones.
// Define an array like the following in your adapter
private int[] selectedItems = new int[yourDataList.size()]; // Initialize the array to have the same size as your data list.
Then initialize the array with all ones. Try having a function like this in your adapter.
private void initializeSeledtedItems() {
for(int item : selectedItems) item = 1;
}
Now in your onBindViewHolder, set the visibility of the ImageView in your RecyclerView items based on the value found in the selectedItems array.
if(selectedItems[position] == 1) heart_image_view.setVisibility(View.VISIBLE);
else heart_image_view.setVisibility(View.GONE);
In the onClickListener modify the selectedItems array as such that, only the item selected has the value one and all the others have zeros. Then call notifyDataSetChanged to take your changes into effect.
private void setSelectedItem(int position) {
for(int i = 0; i < selectedItems.length(); i++) {
if(i == position) selectedItems[i] = 1;
else selectedItems[i] = 0;
}
}
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
setSelectedItem(position);
notifyDataSetChanged();
}
}
}
});
Hope that helps!
My code is for setting Visibility on recycler view's item , when you click on recycler views item it set VISIBLE on that particular item and INVISIBLE on other items.
this code works for me for setting visibility on clicked item and hiding previous icon.
initialize
int selecteditem =0;
onBindViewHolder
if(selecteditem==position){
holder.icSelect.setVisibility(View.VISIBLE);
}
else{
holder.icSelect.setVisibility(View.INVISIBLE);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
selecteditem=position;
holder.icselect.setVisibility(View.VISIBLE);
notifyDataSetChanged();
}
});
i uploaded image for basic idea .
enter image description here
I currently have a ListView which uses a CustomAdapter to display the information from my Firebase Database. Which looks like the following:
Upon creation I want the text in the green boxes (The ListView Items) to be bold. Once the user presses on that ListView item I want the text to go to Normal, unbolded.
I want it to work similar to an email. When you get a new email the title is bold and once that email is opened and you go back to your inbox the text is the Normal, unbolded.
The following is all my relevant code:
MyJobsFragment, SelectItem
// Press the object and display the information and sign the job of with signature pad
jobListViewMyAcJobs.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
ActiveJobDetailsFragment activeJobDetailsFragment = new ActiveJobDetailsFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("Job", adapterActiveJobs.mData.get(position));
bundle.putSerializable("JobId", adapterActiveJobs.mDataKeys.get(position));
activeJobDetailsFragment.setArguments(bundle);
getFragmentManager().beginTransaction().replace(R.id.content, activeJobDetailsFragment).addToBackStack(host.getCurrentTabTag()).commit();
}
});
MyCustomAdapter
public class MyCustomAdapter extends BaseAdapter
{
private ArrayList<JobInformation> mData = new ArrayList<>();
private ArrayList<JobInformation> mDataOrig = new ArrayList<>();
private ArrayList<String> mDataKeys = new ArrayList<>();
private LayoutInflater mInflater;
... Removed Code ...
#Override
public View getView(final int position, View convertView, ViewGroup parent)
{
// Bid on holder
MyJobsFragment.MyCustomAdapter.GroupViewHolderBidOn holderBidOn;
// Accepted holder
final MyJobsFragment.MyCustomAdapter.GroupViewHolderAccepted holderAccepted;
// Completed holder
MyJobsFragment.MyCustomAdapter.GroupViewHolderCompleted holderCompleted;
if (convertView == null)
{
// Bid on
if (host.getCurrentTab() == 0)
{
convertView = mInflater.inflate(R.layout.job_info_bid_on, null);
holderBidOn = new MyJobsFragment.MyCustomAdapter.GroupViewHolderBidOn();
holderBidOn.textViewJobName = convertView.findViewById(R.id.textName);
holderBidOn.textViewJobDescription = convertView.findViewById(R.id.textDesc);
holderBidOn.textViewJobName.setText(mData.get(position).getAdvertName());
holderBidOn.textViewJobDescription.setText(mData.get(position).getAdvertDescription());
convertView.setTag(holderBidOn);
}
// Accepted
else if (host.getCurrentTab() == 1)
{
convertView = mInflater.inflate(R.layout.job_info_accepted, null);
holderAccepted = new MyJobsFragment.MyCustomAdapter.GroupViewHolderAccepted();
holderAccepted.textViewJobName = convertView.findViewById(R.id.textName);
holderAccepted.textViewDescription = convertView.findViewById(R.id.textDesc);
holderAccepted.textViewJobName.setText(mData.get(position).getAdvertName());
if(text != null)
{
text.setTypeface(null, Typeface.BOLD);
}
holderAccepted.textViewDescription.setText(mData.get(position).getAdvertDescription());
// TODO - Was going to add the the users bid into this here, however it's difficult as the bid isnt stored in the jobs table
// TODO - I tried to add it in, looped through the bids table and found the bids with the mDataKeys. But it always displayed the last value.
convertView.setTag(holderAccepted);
}
// Completed
else if (host.getCurrentTab() == 2)
{
convertView = mInflater.inflate(R.layout.job_info_list_completed, null);
holderCompleted = new MyJobsFragment.MyCustomAdapter.GroupViewHolderCompleted();
holderCompleted.textViewJobName = convertView.findViewById(R.id.textName);
holderCompleted.textViewJobName.setText(mData.get(position).getAdvertName());
convertView.setTag(holderCompleted);
}
} else
{
if (host.getCurrentTab() == 0)
{
holderBidOn = (MyJobsFragment.MyCustomAdapter.GroupViewHolderBidOn) convertView.getTag();
} else if (host.getCurrentTab() == 1)
{
holderAccepted = (MyJobsFragment.MyCustomAdapter.GroupViewHolderAccepted) convertView.getTag();
} else if (host.getCurrentTab() == 2)
{
holderCompleted = (MyJobsFragment.MyCustomAdapter.GroupViewHolderCompleted) convertView.getTag();
}
}
return convertView;
}
public class GroupViewHolderBidOn
{
public TextView textViewJobName;
public TextView textViewJobDescription;
}
public class GroupViewHolderAccepted
{
public TextView textViewJobName;
public TextView textViewDescription;
}
public class GroupViewHolderCompleted
{
public TextView textViewJobName;
}
}
Well there multiple ways of achieving that. I would suggest you to add a field in your JobInformation class, something like
public class JobInformation{
.
.
.
.
. // other fields
private boolean isSelected;
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
.
.
. // Other getters/setters
}
Now you need to set this field when you click an item, in your onItemClick function do this
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
ActiveJobDetailsFragment activeJobDetailsFragment = new ActiveJobDetailsFragment();
Bundle bundle = new Bundle();
adapterActiveJobs.mData.get(position).setSelected(true); // add this line in your code
bundle.putSerializable("Job", adapterActiveJobs.mData.get(position));
bundle.putSerializable("JobId", adapterActiveJobs.mDataKeys.get(position));
activeJobDetailsFragment.setArguments(bundle);
getFragmentManager().beginTransaction().replace(R.id.content, activeJobDetailsFragment).addToBackStack(host.getCurrentTabTag()).commit();
}
finally in your getView method in your custom adapter, check for isSelected field and style your textview accordingly, add this in your getView method.
if(mData.get(position).isSelected()){
// set your textview typeface to normal
}
else{
// set your texview typeface to bold.
}
and you would need to call adapter.notifyDataSetChanged() whenever you update selected variable so that your getView is called.
I've been working on creating a single choice ListView populated by a custom ArrayAdapter (which works), except I need to set a pre-determined RadioButton in the ListView as setChecked(true) when the activity launches.
I'm populating my ListView with a List<Server> servers object at inflation that contains a boolean 'default_server' used to determine which row / RadioButton should be setChecked(true).
Selecting the various ListView items after the activity has launched correctly flags the specific RadioButton as setChecked(true) in Single Choice mode as desired.
My code:
ServerActivity.java
public class ServersActivity extends FragmentActivity
//FragmentActivity needed to display dialog fragment when ListView item clicked
...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.server_list);
servers = getServers();
lv = (ListView) findViewById(android.R.id.list);
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
adapter = new ServerActivityArrayAdapter(this, R.layout.server_list_item, servers);
lv.setAdapter(adapter);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// show dialog if not long clicked
if (!longClicked) {
lv.setItemChecked(position, true);
showServerDialog(position);
}
}
});
}
ServerActivityArrayAdapter.java
public class ServerActivityArrayAdapter extends ArrayAdapter<Server> {
private int layout;
private int mSelectedPosition = -1;
private RadioButton mSelectedRB;
private static LayoutInflater inflater;
public ServerActivityArrayAdapter(Context context, int layout, List<Server> servers) {
super(context, layout, servers);
ListArrays listArrays = new ListArrays(context);
this.layout = layout;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
private static class ServerViewHolder {
private TextView textView;
private RadioButton radioButton;
public ServerViewHolder() {
// EMPTY DEFAULT CONSTRUCTOR
}
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Server server = this.getItem(position);
final ServerViewHolder viewHolder;
if (convertView == null) {
convertView = inflater.inflate(layout, parent, false);
viewHolder = new ServerViewHolder();
viewHolder.textView = (TextView) convertView.findViewById(R.id.tvServerName);
viewHolder.radioButton = (RadioButton) convertView.findViewById(R.id.rbDefault);
convertView.setTag(viewHolder);
} else {
viewHolder = (ServerViewHolder) convertView.getTag();
}
// TODO: SET THE DEFAULT SERVER
viewHolder.radioButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (position != mSelectedPosition && mSelectedRB != null) {
mSelectedRB.setChecked(false);
}
mSelectedPosition = position;
notifyDataSetChanged();
mSelectedRB = (RadioButton) v;
server.setDefaultServer(mSelectedRB.isChecked());
}
});
if (mSelectedPosition != position) {
viewHolder.radioButton.setChecked(false);
} else {
viewHolder.radioButton.setChecked(true);
if (mSelectedRB != null && viewHolder.radioButton != mSelectedRB) {
mSelectedRB = viewHolder.radioButton;
}
}
viewHolder.textView.setText(server.getName());
return convertView;
}
}
Again, my List<Server> servers object is populating the ListView, the subsequent dialog popups (when a row is clicked) correctly and the RadioButtons on each row are currently functioning in Single Choice mode. I'm using server.setDefaultServer(mSelectedRB.isChecked()); in the setOnClickListener of ServerActivityArrayAdapter to update which server has default_server(true).
Everything I've tried so far in the ServerActivityArrayAdapter seems to break the Single Choice mode requirement of the RadioButton. How can I fix this?
I did go back and implement Shared Preferences into my project as Mridul suggested, but the solution really didn't require them.
Basically, I updated the constructor of my ServerActivityArrayAdapter class to let me pass the ID of the default server from the ServerActivity class when it's created. Also, I set mSelectionPostion = defServer, so the correct RadioButton in my ListView is checked when everything is inflated.
public ServerActivityArrayAdapter(Context context, int layout, List<Server> servers, int defServer) {
super(context, layout, servers);
ListArrays listArrays = new ListArrays(context);
this.layout = layout;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSelectedPosition = defServer;
}
As I mentioned in the title, the listview duplicates edittexts values for the children after i=10 in the for loop in my code.
public class ExerciseAdapter extends ArrayAdapter<ExerciseSet> {
private ArrayList<ExerciseSet> mExercise;
private LayoutInflater inflater;
private List<String> texts = new ArrayList<String>();
public ExerciseAdapter(Context context, int textViewResourceId, ArrayList<ExerciseSet> objects) {
super(context, textViewResourceId, objects);
mExercise = objects;
inflater = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getItemViewType(int position) {
if(mExercise.get(position).isSet() == false) return 0;
else return 1;
}
#Override
public int getViewTypeCount() {
return 2; // two types of rows, exercise headers and bodies (sets)
}
public View getView(final int position, View convertView, ViewGroup parent){
View view = convertView;
final Holder holder;
for(int i = 0; i < mExercise.size(); i++) {
texts.add(String.valueOf(i));
}
if (getItemViewType(position) == 0) {
if (view == null) {
view = inflater.inflate(R.layout.item_exercise_header, null);
holder = new Holder();
holder.exerciseTitleView = (TextView) view.findViewById(R.id.tv_exercice_title);
view.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
ExerciseSet exercise = mExercise.get(position);
if (exercise != null)
holder.exerciseTitleView.setText(exercise.getExerciseTitle());
} else {
if (view == null) {
view = inflater.inflate(R.layout.item_set, null);
holder = new Holder();
holder.setCounterView = (TextView) view.findViewById(R.id.tv_set_counter);
holder.cbSet = (CheckBox) view.findViewById(R.id.cb_setdone);
holder.repsCapt = (EditText) view.findViewById(R.id.et_reps_number);
holder.repsCapt.setTag(position);
holder.repsCapt.setText(texts.get(position));
//holder.weightCapt = (EditText) view.findViewById(R.id.et_weight_number);
//db = new DatabaseHandler(this.getContext());
//holder.weightCapt.setTag(position);
view.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
ExerciseSet exercise = mExercise.get(position);
/*
* edit texts
*/
int tag_position=(Integer) holder.repsCapt.getTag();
holder.repsCapt.setId(tag_position);
holder.repsCapt.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
final EditText repsText = (EditText) holder.repsCapt;
if(repsText.getText().toString().length()>0){
texts.add(repsText.getText().toString());
}else{
Toast.makeText(getContext(), "Please enter some value", Toast.LENGTH_SHORT).show();
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
#Override
public void afterTextChanged(Editable s) {
}
});
if (exercise != null) {
holder.setCounterView.setText(String.valueOf(exercise.getSetId()));
}
/*
* check boxes
*/
holder.cbSet.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton button, boolean isChecked) {
if(isChecked)
mExercise.get(position).setCbChecked(true);
else
mExercise.get(position).setCbChecked(false);
}
});
holder.cbSet.setChecked(exercise.isCbChecked());
}
return view;
}
static class Holder {
public EditText weightCapt;
public EditText repsCapt;
// header row items
TextView exerciseTitleView;
// body row item
CheckBox cbSet;
TextView setCounterView;
}
}
I dont know why, please help..
Edit_1: I tried saving the values into an arraylist and retrieve them while the views are reused but it still gives me duplicated values while I scroll down.
Edit_2: If my question is unclear please let me know!
Edit_3: I placed holder.repsCapt.setText(texts.get(position)) inside the if (exercise != null) { condition and the values are not duplicated anymore. But still if I scroll down and up the values get messed up.
Your problem is surely connected with item views being reused by the list.
So check your conditions and make sure that you provide alternative text in all cases. Let's begin with item type==0 like this:
if (exercise != null)
holder.exerciseTitleView.setText(exercise.getExerciseTitle());
add
else
holder.exerciseTitleView.setText("debugging: nothing to show");
Edit
The same is about type==1:
- holder.repsCapt.setText(texts.get(position)) is called only on freshly inflated view
- holder.setCounterView.setText(String.valueOf(exercise.getSetId())) only when excersise is not null
Edit2
Well, for DEBUG purposes try not to reuse views in getView() - always inflate right from the start. If everything becomes OK (or you get some empty fields) then return to my initial recommendation in this post. I suspect this scenario
If problem persists than you should pay attention to correctly getting data according to position.
I managed to solve my issue after some digging in google, here's the outcome:
#SuppressWarnings("unused")
#Override
public View getView(final int position, View convertView, ViewGroup arg2) {
final Holder holder;
convertView = null;
if(getItemViewType(position) == 0) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_exercise_header, null);
holder = new Holder();
holder.exerciseTitleView = (TextView) convertView.findViewById(R.id.tv_exercice_title);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
ExerciseSet exercise = exerciseList.get(position);
if (exercise != null)
holder.exerciseTitleView.setText(exercise.getExerciseTitle());
}
else if (getItemViewType(position) == 1) {
if (convertView == null) {
holder = new Holder();
convertView = inflater.inflate(R.layout.item_set, null);
// reps e.t
holder.repsCapt = (EditText) convertView
.findViewById(R.id.et_reps_number);
holder.repsCapt.setTag(position);
// weight e.t
if(exerciseList.get(position).getExerSave()!=null) {
holder.repsCapt.setText(exerciseList.get(position).getExerSave().getReps());
}
// t.v
holder.setCounterView = (TextView) convertView.findViewById(R.id.tv_set_counter);
// c.b
holder.cbSet = (CheckBox) convertView.findViewById(R.id.cb_setdone);
convertView.setTag(holder);
}else {
holder = (Holder) convertView.getTag();
}
/*
* text views
*/
ExerciseSet exercise = exerciseList.get(position);
if (exercise != null) {
holder.setCounterView.setText(String.valueOf(exercise.getSetId()));
}
/*
* edit texts
*/
int tag_position=(Integer) holder.repsCapt.getTag();
holder.repsCapt.setId(tag_position);
holder.repsCapt.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
final int position2 = holder.repsCapt.getId();
final EditText repsText = (EditText) holder.repsCapt;
if(repsText.getText().toString().length()>0){
Save sv;
ExerciseSet es = exerciseList.get(position2);
if(es.getExerSave() != null)
sv = es.getExerSave();
else sv = new Save();
sv.setSet(position2);
sv.setReps(repsText.getText().toString());
es.setExerSave(sv);
exerciseList.set(position2,es);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
/*
* checkboxes
*/
holder.cbSet.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton button, boolean isChecked) {
if(isChecked)
exerciseList.get(position).setCbChecked(true);
else
exerciseList.get(position).setCbChecked(false);
}
});
holder.cbSet.setChecked(exercise.isCbChecked());
}
return convertView;
}
In my app, I display a check list using a ListView. Every row in my check list is represented by a model, which is an object of the ChecklistenPunkt class. There are three different types of rows:
a headline
an additional text
a question
The third type has two Buttons in it which are linked by two OnClickListeners: The first Button is for a positive answer, the second one for a negative answer. When the view is build, both of them are gray, indicating that this question is not yet answered. If the left Buttonis clicked it turns green and the second one is getting a dark gray background. If the right Button is clicked it turns red and the first one is getting a dark gray background. When a Button is clicked the answering state is saved as an ChecklistenPunktStatus. This is an Enum which has three entries (okay, not okay, not answered).
Here is a little image of the Buttons in three rows, showing the different states:
Here is my adapter code:
public class ChecklisteAdapter extends ArrayAdapter<Object> {
private List<ChecklistenPunkt> list;
private SparseArray<ChecklistenPunktStatus> sparseArray;
private Context context;
public ChecklisteAdapter(Context context, List<ChecklistenPunkt> objects) {
super(context, 0);
list = objects;
sparseArray = new SparseArray<ChecklistenPunktStatus>();
for (int i = 0; i < list.size(); i++) {
sparseArray.put(i, ChecklistenPunktStatus.NICHT_SELEKTIERT);
}
this.context = context;
}
#Override
public int getCount() {
return list.size();
}
#Override
public ChecklistenPunkt getItem(int position) {
return list.get(position);
}
#Override
public int getItemViewType(int position) {
ChecklistenPunkt p = (ChecklistenPunkt) list.get(position);
return p.getTyp();
}
#Override
public int getViewTypeCount() {
return 3;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View v = null;
int type = getItemViewType(position);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final ViewHolder viewHolder = new ViewHolder();
switch (type) {
case Util.CHECKLISTE_FRAGE:
v = inflater.inflate(R.layout.pruefung_durchfuehren_list_item_frage, null);
viewHolder.nummer = (TextView) v.findViewById(R.id.check_nummer);
viewHolder.komponente = (TextView) v.findViewById(R.id.check_komponente);
viewHolder.funktion = (TextView) v.findViewById(R.id.check_funktion);
viewHolder.kriterium = (TextView) v.findViewById(R.id.check_kriterium);
viewHolder.erlaeuterung = (TextView) v.findViewById(R.id.check_erlaeuterung);
viewHolder.io = (Button) v.findViewById(R.id.button_i_o);
viewHolder.nio = (Button) v.findViewById(R.id.button_n_i_o);
viewHolder.io.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
viewHolder.io.setBackgroundColor(Color.GREEN);
viewHolder.io.setEnabled(false);
viewHolder.nio.setBackgroundColor(Color.GRAY);
viewHolder.nio.setEnabled(true);
sparseArray.put(position, ChecklistenPunktStatus.IN_ORDNUNG);
}
});
viewHolder.nio.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
viewHolder.nio.setBackgroundColor(Color.RED);
viewHolder.nio.setEnabled(false);
viewHolder.io.setBackgroundColor(Color.GRAY);
viewHolder.io.setEnabled(true);
sparseArray.put(position, ChecklistenPunktStatus.NICHT_IN_ORDNUNG);
}
});
ChecklistenPunktStatus status = sparseArray.get(position);
if (status == ChecklistenPunktStatus.IN_ORDNUNG) {
viewHolder.io.setBackgroundColor(Color.GREEN);
viewHolder.io.setEnabled(false);
viewHolder.nio.setBackgroundColor(Color.GRAY);
viewHolder.nio.setEnabled(true);
} else if (status == ChecklistenPunktStatus.NICHT_IN_ORDNUNG) {
viewHolder.nio.setBackgroundColor(Color.RED);
viewHolder.nio.setEnabled(false);
viewHolder.io.setBackgroundColor(Color.GRAY);
viewHolder.io.setEnabled(true);
} else if (status == ChecklistenPunktStatus.NICHT_SELEKTIERT) {
viewHolder.nio.setBackgroundColor(Color.parseColor("#cccccc"));
viewHolder.nio.setEnabled(true);
viewHolder.io.setBackgroundColor(Color.parseColor("#cccccc"));
viewHolder.io.setEnabled(true);
}
v.setTag(viewHolder);
break;
case Util.CHECKLISTE_UEBERSCHRIFT:
v = inflater.inflate(R.layout.pruefung_durchfuehren_list_item_head, null);
viewHolder.headline = (TextView) v.findViewById(R.id.check_headline);
viewHolder.headnummer = (TextView) v.findViewById(R.id.check_headnummer);
v.setTag(viewHolder);
break;
case Util.CHECKLISTE_ZUSATZTEXT:
v = inflater.inflate(R.layout.pruefung_durchfuehren_list_item_text, null);
viewHolder.text = (TextView) v.findViewById(R.id.check_text);
viewHolder.textnummer = (TextView) v.findViewById(R.id.check_textnummer);
v.setTag(viewHolder);
break;
default:
throw new IllegalArgumentException("ViewType " + type + " unbekannt!");
}
} else {
v = convertView;
}
ChecklistenPunkt clp = getItem(position);
ViewHolder viewHolder = (ViewHolder) v.getTag();
if (clp != null) {
switch (type) {
case Util.CHECKLISTE_FRAGE: {
viewHolder.nummer.setText(clp.getNummer());
viewHolder.komponente.setText(clp.getKomponente());
viewHolder.funktion.setText(clp.getFunktion());
viewHolder.kriterium.setText(clp.getKriterium());
viewHolder.erlaeuterung.setText(clp.getErlaeuterung());
ChecklistenPunktStatus status = sparseArray.get(position);
if (status == ChecklistenPunktStatus.IN_ORDNUNG) {
viewHolder.io.setBackgroundColor(Color.GREEN);
viewHolder.io.setEnabled(false);
viewHolder.nio.setBackgroundColor(Color.GRAY);
viewHolder.nio.setEnabled(true);
} else if (status == ChecklistenPunktStatus.NICHT_IN_ORDNUNG) {
viewHolder.nio.setBackgroundColor(Color.RED);
viewHolder.nio.setEnabled(false);
viewHolder.io.setBackgroundColor(Color.GRAY);
viewHolder.io.setEnabled(true);
} else if (status == ChecklistenPunktStatus.NICHT_SELEKTIERT) {
viewHolder.nio.setBackgroundColor(Color.parseColor("#cccccc"));
viewHolder.nio.setEnabled(true);
viewHolder.io.setBackgroundColor(Color.parseColor("#cccccc"));
viewHolder.io.setEnabled(true);
}
break;
}
case Util.CHECKLISTE_UEBERSCHRIFT: {
viewHolder.headline.setText(clp.getHeadline());
viewHolder.headnummer.setText(clp.getNummer());
break;
}
case Util.CHECKLISTE_ZUSATZTEXT: {
viewHolder.text.setText(Html.fromHtml(clp.getZusatz()));
viewHolder.textnummer.setText(clp.getNummer());
break;
}
default:
throw new IllegalArgumentException("ViewType " + type + " unbekannt!");
}
}
return v;
}
public SparseArray<ChecklistenPunktStatus> getSparseArray() {
return sparseArray;
}
static class ViewHolder {
protected TextView nummer;
protected TextView komponente;
protected TextView kriterium;
protected TextView funktion;
protected TextView erlaeuterung;
protected TextView text;
protected TextView textnummer;
protected TextView headline;
protected TextView headnummer;
protected Button io;
protected Button nio;
}
}
EDIT:
When the convertView is null, I create a new View by inflating my layout. Then I create a new ViewHolder, find the different TextViews and Buttons according to the Views viewType and put them into the ViewHolder. After that, I set this ViewHolder as the tag of the View. I distinguish according to the viewType again and set the text and Listeners of the Views inside the ViewHolder. Then I return the view.
When the convertView is not null I reuse the View. I get the ViewHolder with the getTag() method, set the text and Button state according to the model class and return the view at the end of the method.
My problem is that the answering state is correctly saved in the SparseArray, but it is not displayed correctly. When I answer a question in the first rows of my list and then scroll down, answered questions appear at the end of the list. By scrolling up and down I can mess up the answering states completely. But while this is happening, the states in the sparseArray are always correct.
Am I missing something here?
I suppose there is something wrong with the code. When you use the same cell and when the view is not null you should reuse the same holder and not the convertView. Something like that:
ViewHolder viewHolder = new ViewHolder();
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
.
.
.
}
else {
viewHolder = convertView.getTag();
}