1

I am developing an extremely simple list app to save items to an SQLite DB, and populate a ListView with it's contents.

This can be done using a SimpleCursorAdapter, which I do like this:

mySQLiteAdapter.openToRead();
cursor = mySQLiteAdapter.queueAll();
String[] from = new String[]{mySQLiteAdapter.getKeyContent()};
int[] to = new int[]{R.id.normal};
cursorAdapter = new SimpleCursorAdapter(this, R.layout.row, cursor, from, to, 0);
listView.setAdapter(cursorAdapter);
mySQLiteAdapter.close();

Now however I would like to implement the ability to either strike out text on a row by click or long click, or change the row to a different layout (which could also strike the text)

At first I simply set the onClickListener for the listview and changed the paint flags to strike the text. This works fine until there are enough items in the list to scroll the view, or until the activity is reloaded. In the latter the strike is gone (since nothing was persistent), and in the former other rows are striked, and the intended ones are not. Then is changes as you scroll around. See Custom ListView adapter, strange ImageView behavior for a similar situation to my own.

From this I have found that I will need use a custom adapter to do what I want. So I have created a CustomCursorAdapter which extends SimpleCursorAdapter and overriden some methods to attempt to inflate a seperate layout with a background colour. I am not having much luck.

Here is what I have so far:

// Creating a new instance of the custom adapter and assigning it to the listview
mySQLiteAdapter.openToRead();
cursor = mySQLiteAdapter.queueAll();
String[] from = new String[]{mySQLiteAdapter.getKeyContent()};
int[] to = new int[]{R.id.normal};
cursorAdapter = new CustomCursorAdapter(this, R.layout.row, cursor, from, to, 0);
listView.setAdapter(cursorAdapter);
mySQLiteAdapter.close();

and

// CustomCursorAdapter class
private class MyCursorAdapter extends SimpleCursorAdapter {
    private LayoutInflater inflater;

    public MyCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
        super(context, layout, c, from, to, flags);
        inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Get reference to the row
        View view = super.getView(position, convertView, parent);
        //View view;

        if (getItemViewType(position) == 0) {
            view = inflater.inflate(R.layout.row, null);
        }
        else {
            view = inflater.inflate(R.layout.rowstrike, null);
        }
        return view;
    }

    @Override
    public int getItemViewType(int position) {
        int row;
        if (cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyStrike())) == 1) {
            Log.d("DEBUG", "Row " + position + " is STRIKED");
            row =  1;
        }
        else  {
            Log.d("DEBUG", "Row " + position + " is normal");
            row =  0;
        }
        return row;
    }
}

The implementation of the CustomCursorAdapter works and shows the correct amount of rows. The logic to determine if the row should contain striked text by querying the db is correct, however the returned inflated views are completely blank. I think it may be to do with the way my XML files are arranged and which ones I pass to the adapter but all my testing and tinkering to try to get this working have failed spectacularly so far.

Here are my XML files for the layouts

// activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <EditText
        android:layout_width="fill_parent"
        android:layout_height="35dp"
        android:id="@+id/editText"
        android:hint="Press here to add an item"
        android:maxLines="1"
        android:imeOptions="actionDone"
        android:inputType="textAutoCorrect"/>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:id="@+id/listView"
        android:layout_below="@id/editText"/>
</RelativeLayout>

.

// row.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp" >
    <TextView
    android:id="@+id/normal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textStyle="bold"/>
</RelativeLayout>

.

//rowstrike.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp" >
    <TextView
    android:id="@+id/striked"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textColor="#F05"/>
</RelativeLayout>

I have been searching on this for days and days, each time getting closer but nothing seems to work, or the explanations are not beginner friendly for someone like myself.

The closest post I have found to what I am after is ListView view recycling with CustomAdapter

However I think I need more code snippets as I must be doing something wrong elsewhere in my app? There is a mention of overriding the getViewTypeCount method but I am unsure of how this is done...

There is also How would I use a different row layout in custom CursorAdapter based on Cursor data? However I am not quite sure where to go from this post...

EDIT: Solution based on Luksprogs post.

mySQLiteAdapter.openToRead();
cursor = mySQLiteAdapter.queueAll();

String[] from = new String[]{mySQLiteAdapter.getKeyContent()};
int[] to = new int[]{R.id.normal};

cursorAdapter = new SimpleCursorAdapter(this, R.layout.row, cursor, from, to, 0);
cursorAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {

    @Override
    public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
        if (view.getId() == R.id.normal) {
            TextView tv = (TextView)view.findViewById(R.id.normal);

            if (cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyStrike())) == 1) {
                tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            }
            else {
                tv.setPaintFlags(tv.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            }
        }
        return false;
    }

});

listView.setAdapter(cursorAdapter);
mySQLiteAdapter.close();

and I have an OnItemClickListener as follows to strike and unstrike rows

private ListView.OnItemClickListener listViewOnItemClickListener
        =new ListView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        final int rowID = cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyID()));
        mySQLiteAdapter.openToWrite();

        if (cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyStrike())) == 0) {
            mySQLiteAdapter.setKeyStrike(rowID, 1);
        }
        else {
            mySQLiteAdapter.setKeyStrike(rowID, 0);
        }
        cursor = mySQLiteAdapter.queueAll();
        cursorAdapter.swapCursor(cursor);
        mySQLiteAdapter.close();
};
Community
  • 1
  • 1
Michael
  • 53
  • 6
  • Please don't prefix your questions titles with tag names as ListView, Android etc, the tags at the bottom are more than enough to show the scope of the question. *however the returned inflated views are completely blank* -that's because in the getView() method you first call the super implementation that would build the row and setup the data only to completely drop that row view and return a view that's simply an inflated layout(according to the item type) There's no data associated with that inflated layout, it's blank. Secondly, you're doing things wrong and you shouldn't override getView() – user Jul 30 '14 at 10:46
  • Hi Luksprog, thank you for the swift reply! I have removed the prefix for the title. Could you help me understand how this should be done? I am only a beginner at java and android. I have read a few intro books but nothing seems to cover what I am trying to do. So what I have here is what I have found and pieced together from others attempting a similar goal... – Michael Jul 30 '14 at 10:54

1 Answers1

0

however the returned inflated views are completely blank.

As a side note, you shouldn't override the getView() method of a Cursor based adapter because this type of adapters implemented getView() to separate building the row layout and binding the data in two separate methods, newView() and bindView(). This two methods should be overridden. Also, SimpleCursorAdapter is a class designed for basic scenarios, if you need to implement (really) different row types then extending CursorAdapter would be a better approach.

You get blank row layouts because you don't do any data binding on them. In the getView() method you do:

View view = super.getView(position, convertView, parent);

which will return you a properly built row layout(by the SimpleCursorAdapter class implementation)only to discard that view and inflate a new row layout based on the row type(the if-else piece of code). You don't bind any data to those views so you return just the inflated layout which will be blank.

If your two rows are only different by a strikethrough text then you shouldn't be using two row types(different row types should be used when the rows are different in structure). You could implement what you want through a ViewBinder:

cursorAdapter = new SimpleCursorAdapter(this, R.layout.row, cursor, from, to, 0);
cursorAdapter.setViewBinder(new ViewBinder() {

     @override
     public void setViewValue(View view, Cursor cursor, int columnIndex) {
           if (view.getId() == R.id.normal) {
                // I'm assuming that the TextView with the id R.id.normal
                // is the on to strike through                    
                // use the cursor to get the value from the column on which you
                // do the strikethrough
                if (cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyStrike()) == 1) {
                     // strike the text  
                } else {
                     // otherwise un-strike the text
                } 

           }
           return false; 
     }

});
listView.setAdapter(cursorAdapter);

If you want

I would like to implement the ability to either strike out text on a row by click or long click

Then you need to remember the row status somewhere so you can use it to restore it.

user
  • 86,916
  • 18
  • 197
  • 190
  • That's exactly what I wanted :) Thank you so much Luksprog! You're a lifesaver! This was driving me nuts. I'll post my altered code for others to see. – Michael Jul 30 '14 at 12:26
  • @Michael Keep in mind that my code will only use the statuses that are stored in the database. If your users click a row and strike it then you scroll the list you'll lose the striked row(unless you save the new state to the database). https://gist.github.com/luksprog/951a696ced41b0f6dd24 Here's a sample of an adapter that keeps the statuses while scrolling(of course is something basic) – user Jul 30 '14 at 12:43
  • I've made the onClick action write an integer value of 1 to the row selected which denotes a strike with the following line 'mySQLiteAdapter.setKeyStrike(rowID, 1);'. With this the viewBinder correctly queries the keyStrike value of that row while scrolling and set the strike flags accordingly =). Works perfectly! – Michael Jul 30 '14 at 13:00