In a ListView (that uses a custom CursorAdapter), I am trying to display a separator solely on the basis of whether the date between two ListView items changes, as so: .
Unfortunately, the way I have my code set up, as I scroll down, pause for a second, and then start scrolling again, I have the separator turning up at the wrong location (such as here, see the disappearance of the separator in the second item in the ListView):
Or, it might appear in the third item.
I have encountered two main issues:
I understand in principle that these issues arise because the behaviour of lastDate
in my adapter really only takes into account unidirectional downward scrolling, and in addition are caused by how ListView recycles views and containers.
My question, essentially: how can I rework my custom CursorAdapter in MyAdapter.java
or how I store my data in MainFragment.java
so that I can display the separater appropriately regardless of the direction of the scroll, or momentary pauses and then rescrolls?
Thank you so much in advance!
Below is the MainFragment's code, the adapter's code, as well as the XML for the ListView items' layout.
MyAdapter.java
package com.cpsashta.seplistview;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.cpsashta.seplistview.DbContract.DbEntry;
public class MyAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
private Context mContext;
private Cursor mCursor;
private String lastDate;
TextView tvSep;
TextView tvName;
TextView tvTitle;
TextView tvDate;
TextView tvExp;
public MyAdapter(Context context, Cursor c, int flags) {
super(context,c,flags);
this.mContext = context;
this.mCursor = c;
cursorInflater = LayoutInflater.from(context);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return cursorInflater.inflate(R.layout.list_layout_1, parent, false);
}
@Override
public void bindView(View view, Context context, Cursor c) {
tvTitle = (TextView) view.findViewById(R.id.tv_title);
tvName = (TextView) view.findViewById(R.id.tv_name);
tvSep = (TextView) view.findViewById(R.id.separator);
tvDate = (TextView) view.findViewById(R.id.tv_date);
tvExp = (TextView) view.findViewById(R.id.is_expanded_view);
String title = c.getString(c.getColumnIndex(DbEntry.COLNAME_TITLE));
String name = c.getString(c.getColumnIndex(DbEntry.COLNAME_NAME));
String date = c.getString(c.getColumnIndex(DbEntry.COLNAME_DATE));
tvTitle.setText(title);
tvName.setText(name);
tvDate.setText(date);
if (!date.equals(lastDate) || c.getPosition() == 0) {
tvSep.setVisibility(View.VISIBLE);
tvSep.setText(date);
tvExp.setText("true");
lastDate = date;
} else {
tvSep.setVisibility(View.GONE);
tvExp.setText("false");
}
}
}
list_layout_1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/separator"
style="?android:attr/listSeparatorTextViewStyle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:background="#FF0000"
android:visibility="gone"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:id="@+id/is_expanded_view"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="@+id/tv_title"
android:layout_gravity="center_horizontal" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_date"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Small Text"
android:id="@+id/tv_name"
android:layout_gravity="right" />
</LinearLayout>
MainFragment.java
package com.cpsashta.seplistview;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import com.cpsashta.seplistview.DbContract.*;
public class MainFragment extends Fragment {
String[] names = {"Dick", "Sammy", "Manuel", "Jenny", "Kirk", "Shimmy"};
public static String titleSmile = "Damned crazy folks, Smile, you're on camera!";
String[] titles = {"Here we go again!", titleSmile};
String dateMonth = "Aug";
DbHelper mDbHelper;
SQLiteDatabase db;
Button button;
public MainFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
button = (Button) rootView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LinearLayout ll = (LinearLayout) v.getParent();
addOneItem(ll);
}
});
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
ListView lv = (ListView) view.findViewById(R.id.listview);
Cursor c = getAll();
MyAdapter adapter = new MyAdapter(getActivity(),c,0);
lv.setAdapter(adapter);
}
//Occurs on button press, the view pressed is intended to be the rootView (a.k.a. the LinearLayout housing the ListView and button)
public void addOneItem(View view) {
newItemToDb();
ListView lv = (ListView) view.findViewById(R.id.listview);
MyAdapter adapter = (MyAdapter) lv.getAdapter();
adapter.notifyDataSetChanged();
}
//Adds random name, title, and date to the database
public void newItemToDb() {
if (db == null || !db.isOpen()) {
if (mDbHelper == null) {
mDbHelper = new DbHelper(getActivity());
}
db = mDbHelper.getWritableDatabase();
}
ContentValues values = new ContentValues();
int nameVal = (int)(Math.random() * names.length);
values.put(DbEntry.COLNAME_NAME, names[nameVal]);
int titleVal = (int) (Math.random() * titles.length);
values.put(DbEntry.COLNAME_TITLE, titles[titleVal]);
//Chooses a date from August 1 to August 10
//int dateDay = (int)(Math.random() * 10 + 1);
//FOR NOW choosing the date of Aug 1 or 2 only
int dateDay = (int)(Math.random() * 2 + 1);
String date = dateMonth + " " + dateDay;
values.put(DbEntry.COLNAME_DATE, date);
db.insert(DbEntry.TABLE_NAME, null, values);
db.close();
}
public Cursor getAll() {
if (db == null || !db.isOpen()) {
if (mDbHelper == null) {
mDbHelper = new DbHelper(getActivity());
}
db = mDbHelper.getWritableDatabase();
}
String[] projection = { DbEntry._ID,
DbEntry.COLNAME_TITLE,
DbEntry.COLNAME_NAME,
DbEntry.COLNAME_DATE
};
String sortOrder = null;
//TODO delete the sortOrder below
//String sortOrder = RecordingEntry.COLNAME_TAG_FULL
// + " DESC";
String[] selectionArgs = null;
String selection = null; // Setting this to null
Cursor c = db.query(DbEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null, // don't group the rows
null, // don't filter by row groups
sortOrder
);
return c;
}
}
EDIT: The solution I've put into use, based on @N.T.'s suggestion, replaces the end of the bindView section of MainFragment with the following:
String lastDate = "";
int position = c.getPosition();
if (position != 0 ) {
c.moveToPosition(position - 1);
lastDate = c.getString(c.getColumnIndex(DbEntry.COLNAME_DATE));
c.moveToPosition(position);
}
if (position == 0 || !date.equals(lastDate)) {
separator.setVisibility(View.VISIBLE);
separator.setText(date);
} else {
separator.setVisibility(View.GONE);
}
You cannot control the order in which the newView
/bindView
are called. Let's say you are displaying items 10-20 on screen and the last one drawn is the one form position 20, therefore your lastDate
will point to that. Now you scroll up and the system will ask you to draw item#9. At this point you do comparisons between item#9 and lastDate
that points to item#20, which is not what you wanted.
To fix things you need to move your cursor to the previous position (mCursor.moveToPosition(position - 1)
) and check your current item against the previous item in the list, not against the previously requested one to be drawn.