benawad benawad - 5 months ago 37
Android Question

Drag and Drop ListView Eating Other Rows Android

I am trying to create a listview that has drag and drop capability. I googled around and found this video by Google that shows you how to make a DynamicListView: https://www.youtube.com/watch?v=_BZIvjMgH-Q

I copied the code as best I could into my project, but I found a small bug. When I have a list with 2 of the same word it gets messed up. I was curious and changed the Google code to have a listview with all the same name items and it broke their code too. Here is a link to a YouTube video I made outlining the bug: https://youtu.be/t7ghuUU80KY

Does anyone have any suggestions of how I can fix this bug? Here is what my code looks like:

item_row.xml

<?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:text="New Text"
android:textSize="30sp"
android:id="@+id/itemText"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:singleLine="true"
android:layout_alignParentLeft="true"/>


NoteActivity.java

public class NoteActivity extends ActionBarActivity {

private final static String TAG = NoteActivity.class.getSimpleName();
public final static int SLASHED = 1;
public final static int UNSLASHED = 0;
private static final StrikethroughSpan STRIKE_THROUGH_SPAN = new StrikethroughSpan();
ItemsArrayAdapter mItemsArrayAdapter;
FinishedItemsArrayAdapter mFinishedItemsArrayAdapter;
EditText mNewItemText;
DynamicListView mItemsListView;
ListView mFinishedItemsListView;
ArrayList<String> mItems;
ArrayList<String> mFinishedItems;
// FloatingActionButton fab;
private Uri noteUri;
public ArrayList<String> slashes;

@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_note);
slashes = new ArrayList<>();

mFinishedItems = new ArrayList<>();
mFinishedItemsArrayAdapter = new FinishedItemsArrayAdapter(this, mFinishedItems, true);
mFinishedItemsListView = (ListView) findViewById(R.id.finishedItems);
mFinishedItemsListView.setAdapter(mFinishedItemsArrayAdapter);
mFinishedItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView item = (TextView) view.findViewById(R.id.itemText);
String text = item.getText().toString();
mFinishedItems.remove(text);
mItems.add(text);
mFinishedItemsArrayAdapter.notifyDataSetChanged();
mItemsArrayAdapter.notifyDataSetChanged();
}
});

mItems = new ArrayList<String>();
mItemsArrayAdapter = new ItemsArrayAdapter(this, mItems, false);
mItemsListView = (DynamicListView) findViewById(R.id.itemsListView);
mItemsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mItemsListView.setAdapter(mItemsArrayAdapter);
mItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView item = (TextView) view.findViewById(R.id.itemText);
String text = item.getText().toString();
mItems.remove(text);
mFinishedItems.add(text);
mFinishedItemsArrayAdapter.notifyDataSetChanged();
mItemsArrayAdapter.notifyDataSetChanged();
}
});

Bundle extras = getIntent().getExtras();

// check from the saved Instance
noteUri = (bundle == null) ? null : (Uri) bundle
.getParcelable(NoteContentProvider.CONTENT_ITEM_TYPE);

// Or passed from the other activity
if (extras != null) {
noteUri = extras
.getParcelable(NoteContentProvider.CONTENT_ITEM_TYPE);

fillData(noteUri);

}

mItemsListView.setCheeseList(mItems);

}

private void fillData(Uri uri) {

String[] projection = {NoteTable.COLUMN_ITEMS, NoteTable.COLUMN_SLASHED};
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri, projection, null, null,
null);
} catch (NullPointerException e) {
Log.e(TAG, "NullPointerException caught: ", e);
}
if (cursor != null) {
cursor.moveToFirst();

String sItems = cursor.getString(cursor
.getColumnIndexOrThrow(NoteTable.COLUMN_ITEMS));

String sSlashes = cursor.getString(cursor.getColumnIndexOrThrow(NoteTable.COLUMN_SLASHED));

try {
JSONArray jsonArray = new JSONArray(sItems);
// for (int i = 0; i < jsonArray.length(); i++) {
// mItems.add((String) jsonArray.get(i));
// }
JSONArray slashesJsonArray = new JSONArray(sSlashes);
for (int i = 0; i < slashesJsonArray.length(); i++) {
slashes.add("" + slashesJsonArray.get(i));
if(slashesJsonArray.get(i).equals(NoteActivity.UNSLASHED)){
mItems.add((String) jsonArray.get(i));
}
else{
mFinishedItems.add((String) jsonArray.get(i));
}
}
mFinishedItemsArrayAdapter.notifyDataSetChanged();
mItemsArrayAdapter.notifyDataSetChanged();
} catch (JSONException ignored) {
}

// always close the cursor
cursor.close();
}

}

protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveState();
outState.putParcelable(NoteContentProvider.CONTENT_ITEM_TYPE, noteUri);
}

protected void onPause() {
super.onPause();
saveState();
}

private void saveState() {
String note = new JSONArray(mItems).toString();
ArrayList<Integer> slashes = new ArrayList<>();

// for (int i = 0; i < mItemsListView.getChildCount(); i++) {
// View row = mItemsListView.getChildAt(i);
// TextView textView = (TextView) row.findViewById(R.id.itemText);
//
// if (17 == textView.getPaintFlags()) {
// slashes.add(NoteActivity.SLASHED);
// } else {
// slashes.add(NoteActivity.UNSLASHED);
// }
// }

for (int i = 0; i < mItemsListView.getChildCount(); i++) {
slashes.add(NoteActivity.UNSLASHED);
}
for (int i = 0; i < mFinishedItemsListView.getChildCount(); i++) {
slashes.add(NoteActivity.UNSLASHED);
}

String sSlashes = new JSONArray(slashes).toString();

// only save if either summary or description
// is available

if (mItems.isEmpty()) {
return;
}

ContentValues values = new ContentValues();
values.put(NoteTable.COLUMN_ITEMS, note);
values.put(NoteTable.COLUMN_SLASHED, sSlashes);

if (noteUri == null) {
noteUri = getContentResolver().insert(NoteContentProvider.CONTENT_URI, values);
} else {
getContentResolver().update(noteUri, values, null, null);
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds mItems to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_note, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.addItem) {
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
addItem();
return false;
}
});
return true;
}

return super.onOptionsItemSelected(item);
}

public void addItem() {

if (mItems.size() < 100) {
getDialog().show();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Max Items")
.setMessage("You have reached the maximum " +
"number of items (100) one note can hold.")
.setPositiveButton("OK", null);
builder.show();
}

}

public AlertDialog.Builder getDialog() {
LayoutInflater li = LayoutInflater.from(this);
LinearLayout newNoteBaseLayout = (LinearLayout) li.inflate(R.layout.new_item_dialog, null);

mNewItemText = (EditText) newNoteBaseLayout.getChildAt(0);

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mItems.add(mNewItemText.getText().toString());
mItemsArrayAdapter.notifyDataSetChanged();
}
});
builder.setNegativeButton("Cancel", null)
.setTitle("New Item");

builder.setView(newNoteBaseLayout);
return builder;
}

public void deleteItem(int position) {
mItems.remove(position);
slashes.remove(position);
mItemsArrayAdapter.notifyDataSetChanged();
}

public void editItem(final int position) {
AlertDialog.Builder builder = getDialog();
mNewItemText.setText(mItems.get(position));
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mItems.set(position, mNewItemText.getText().toString());
mItemsArrayAdapter.notifyDataSetChanged();
}
});
builder.show();
}

public void uncheckAll(View view) {
for (int i = 0; i < mItemsListView.getChildCount(); i++) {
View row = mItemsListView.getChildAt(i);
TextView textView = (TextView) row.findViewById(R.id.itemText);

textView.setPaintFlags(textView.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));

}

}
}


ItemArrayAdapter.java

public class ItemsArrayAdapter extends ArrayAdapter<String> {

public static final String TAG = ItemsArrayAdapter.class.getSimpleName();
Context mContext;
ArrayList<String> mArrayList;
boolean wantsSlash;
NoteActivity mNoteActivity;

final int INVALID_ID = -1;

HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();

public ItemsArrayAdapter
(NoteActivity context, ArrayList<String> arrayList, boolean slashes){
super(context, R.layout.item_row,arrayList);
mContext = context;
mArrayList = arrayList;
mNoteActivity = context;
wantsSlash = slashes;
for (int i = 0; i < arrayList.size(); ++i) {
mIdMap.put(arrayList.get(i), i);
}
}

public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;

if(convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_row, parent, false);
holder = new ViewHolder();

holder.itemName = (TextView)convertView.findViewById(R.id.itemText);

for (int i = 0; i < mArrayList.size(); ++i) {
mIdMap.put(mArrayList.get(i), i);
}

// holder.delete = (ImageView)convertView.findViewById(R.id.delete_item);
// holder.edit = (ImageView)convertView.findViewById(R.id.edit_item);
convertView.setTag(holder);
}
else{
holder = (ViewHolder) convertView.getTag();
}

holder.itemName.setText(mArrayList.get(position));
if(wantsSlash){
holder.itemName.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG);
}
// holder.delete.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// mNoteActivity.deleteItem(position);
// }
// });
// holder.edit.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// mNoteActivity.editItem(position);
// }
// });


return convertView;
}

private static class ViewHolder {
TextView itemName;
ImageView edit;
ImageView delete;
}

@Override
public long getItemId(int position) {
if (position < 0 || position >= mIdMap.size()) {
return INVALID_ID;
}
String item = "";
try {
item = getItem(position);
}
catch(Exception e){
return 130298312;
}
return mIdMap.get(item);
}

@Override
public boolean hasStableIds() {
return true;
}

}


DynamicListView.java

I reached the maximum character limit pasting the code in, so if you want to see what the code looks like here is a link to pastebin: http://pastebin.com/x41RKfEU

I added the code that I thought was the most relevant to the question, but if there is any other file that would be helpful to look at let me know and I can add it in. Thanks in advance.

Answer

DynamicListview strongly relies on getItemId() of the adapter .And as you can see in your adapter these ids are stored on mIdMap

It is filled on the constructor

  for (int i = 0; i < arrayList.size(); ++i) {
        mIdMap.put(arrayList.get(i), i);
    }

What does it mean ? If arrayList contains apple at index 1 and 4. At the end of for loop. The map will contain only one entry for key "apple" : 4. That means getItemId(1) and getItemId(4) will return both the id 4.

That's the underlying problem. If your list can contain the same strings you have to correct this point.

It 's weird to have a list the user can order but can't make the difference between some of the items.

Best solution alter the initial string list. And number items. {"apple", "apple"} becomes {"apple 1", "apple 2"}. Or group them by names or whatever makes sense in your app.

Is you want absolutely to have the weird beahvior. (Distinct items that user can't differentiate but re-order). You will have to add more info from your model and create ids based on this infos

Comments