Reputation: 5805
A common Android application feature is to swipe on each detail item to get to the next item or previous item.
The database cursor contains a list of items which are displayed in the onCreate of a ListActivity:
if (cursor.moveToFirst()) {
listThings.setAdapter(new ResourceCursorAdapter(this, R.layout.my_simple_expandable_list_item_2, cursor) {
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView tvFirst = (TextView)view.findViewById(android.R.id.text1);
TextView tvSecond = (TextView)view.findViewById(android.R.id.text2);
tvFirst.setText(cursor.getString(1) + " - " + getPctString(cursor.getString(2)));
tvSecond.setText(cursor.getString(3));
}
});
} else {
....
That works fine. The built-in layout resource R.layout.my_simple_expandable_list_item_2
tells the adapter to display each item in the cursor in a single text view. On click, I'm able to show the details of the item in the follow-on activity:
AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> listView, View v, int position, long id) {
Intent intent = new Intent(ThingListActivity.this, ThingActivity.class);
intent.putExtra(FollowOnActivity.EXTRA_ID, (int) id);
startActivity(intent);
}
};
Once in FollowOnActivity
I can lookup the details and populate the view. I can also listen for and react to swipes.
Problem: I don't have access to what the next and previous items in the list are. So far, I've not come-up with what I consider a clean solution. Since this is a common requirement, there's probably a nice pattern for this. I'd like to know what that is.
I was able to get this screen slide example working, but the list in that example is fixed at five items, and those items are text in xml files instead of database items. I created ThingSlideActivity
based on their ScreenSlidePagerActivity
, ThingSlidePageAdapter
based on their ScreenSlidePageAdapter
and ThingSlideFragment
based on their ScreenSlidePageFragment
. But I don't know how to wire that up to the list view that I'm coming from. It might have a big list, and I'm trying not to pull details on all of them.
I'm sitting in a list view that's been populated with a cursor (the first block of code at the top of this question). The user clicks on a random one in the list and onListItemClick runs. What should it do to enable swiping left and right to see previous and next items?
Upvotes: 0
Views: 1421
Reputation: 5805
This is what ended-up working. It uses the pattern found in screen-slide example from developer.android.com/training, with one difference: in ThingSlideActivity.getItem()
, instead of running ThingSlideFragment.onCreate(pos)
, I used ThingSlideFragment.newInstance(pos)
. Also, I added static Cursor getCursor()
which made available the original Cursor
that populated the ListActivity
in the first place. This allowed me to translate from the position within the list to the database key. If there are any faults with this pattern, please advise.
The initial activity This activity has a list from the database and a cursor left open until the list is destroyed :
public class ThingListActivity extends ListActivity {
private SQLiteDatabase db;
private static Cursor cursor;
static Cursor getCursor() {
return cursor;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listThings = getListView();
int position = getIntent().getIntExtra("POSITION", 0);
try {
SQLiteOpenHelper tableDatabaseHelper = new TableDatabaseHelper(this);
db = tableDatabaseHelper.getReadableDatabase();
cursor = db.query("TABLE", pullFields, selectionFields, selectionArgs, null, null, null); // you define your own query
if (cursor.moveToFirst()) {
listThings.setAdapter(new ResourceCursorAdapter(this, R.layout.my_simple_expandable_list_item_2, cursor) { //built-in layout
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView tvFirst = (TextView)view.findViewById(android.R.id.text1);
TextView tvSecond = (TextView)view.findViewById(android.R.id.text2);
tvFirst.setText(cursor.getString(1) + " - " + getPctString(cursor.getString(2)));
tvSecond.setText(cursor.getString(3));
}
});
} else {
Toast toast = Toast.makeText(this, "The list was empty.", Toast.LENGTH_SHORT);
toast.show();
}
} catch (SQLiteException e) {
Toast toast = Toast.makeText(this, "Database Unavailable", Toast.LENGTH_SHORT);
toast.show();
} finally {
//Not closing cursor. Will do in onDestroy()
}
}
@Override
public void onDestroy(){
super.onDestroy();
if (cursor != null) cursor.close();
if (db != null) db.close();
}
@Override
protected void onListItemClick(ListView listView, View view, int position, long id) {
Intent intent = new Intent(ThingListActivity.this, ThingSlideActivity.class);
intent.putExtra(ThingSlideActivity.EXTRA_POSITION, (int) position);
startActivity(intent);
}
}
The follow-on FragmentActivity
. This activity has an inner class ThingSlidePageAdapter
. Notice setCurrentItem
, which places you in the right spot within the list. :
public class ThingSlideActivity extends FragmentActivity{
public static final String EXTRA_POSITION = "extra_position";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen_slide);
ViewPager mPager = (ViewPager)findViewById(R.id.pager);
PagerAdapter mPagerAdapter = new ThingSlidePageAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
mPager.setCurrentItem((Integer)getIntent().getExtras().get(EXTRA_POSITION));
}
private static class ThingSlidePageAdapter extends FragmentStatePagerAdapter {
int count;
public ThingSlidePageAdapter(FragmentManager fm){
super(fm);
this.count = ThingListActivity.getCursor().getCount();
}
@Override
public int getCount() {
return this.count;
}
@Override
public Fragment getItem(int position) {
return ThingSlideFragment.newInstance(position); // onCreate() doesn't work well.
}
}
}
**The Fragment
we are paging through. This is where we use the Cursor
from the original ListView
in order to get the database key (_id
). Then a fresh query is executed, pulling details for only for one item. But under the covers, the previous and next items are also pulled.
public class ThingSlideFragment extends Fragment {
private static final String ARG_POSITION = "position";
public static ThingSlideFragment create(int position) {
ThingSlideFragment fragment = new ThingSlideFragment();
Bundle args = new Bundle();
args.putInt(ARG_POSITION, position);
fragment.setArguments(args);
return fragment;
}
public static ThingSlideFragment newInstance(int position) {
ThingSlideFragment fragment = new ThingSlideFragment();
Bundle args = new Bundle();
args.putInt(ARG_POSITION, position);
fragment.setArguments(args);
return fragment;
}
public ThingSlideFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.activity_thing, container, false);
TextView tv = (TextView)rootView.findViewById(R.id.thingname);
Cursor mCursor = ThingListActivity.getCursor();
if (mCursor != null) {
mCursor.moveToPosition(getArguments().getInt(ARG_POSITION));
int id = Integer.parseInt(mCursor.getString(0));
populateView(id, rootView);
}
return rootView;
}
private void populateView(int id, View rootView) {
Cursor cursor = null;
SQLiteDatabase db = null;
try {
SQLiteOpenHelper tableDatabaseHelper = new TableDatabaseHelper(this.getContext()) ;
db = tableDatabaseHelper.getReadableDatabase();
cursor = db.query("TABLE", new String[] {"NAME", "DESCRIPTION", "DETAIL1", "DETAIL2", "DETAIL3", "DETAIL4"}, "_id = ?", new String[] {Integer.toString(id)}, null, null, null);
//Move to first record in the cursor (should be just one since our query used database key "CREATE TABLE TABLE (_id INTEGER PRIMARY KEY AUTOINCREMENT, ...."
if (cursor.moveToFirst()) {
// Get thing details from the cursor
String nameText = cursor.getString(0);
String descriptionText = cursor.getString(1);
// ........ continue with other fields
// Populate TextView items
((TextView)rootView.findViewById(R.id.thingname)).setText(nameText);
((TextView)rootView.findViewById(R.id.description)).setText(descriptionText);
// ........ continue with other fields
}
} catch (SQLiteException e) {
Toast toast = Toast.makeText(this.getContext(), "Database unavailable. "+ e.getMessage(), Toast.LENGTH_LONG) ;
} finally {
try {cursor.close();} catch (Throwable t) {}
try {db.close();} catch (Throwable t) {}
}
}
}
And, left out of many of the examples here, the contents of the supporting xml files!
<!-- R.layout.activity_screen_slide.xml containing R.id.pager -->
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- R.layout.activity_thing.xml containing R.id.thingname and R.id.description -->
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:id="@+id/thingRelativeLayout"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.someones.package.ThingActivity"
tools:showIn="@layout/activity_thing">
<TextView
android:id="@+id/thingname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:paddingTop="40dp"
android:textSize="40dp"
android:gravity="center"
tools:text="Name Of Thing"
/>
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:textSize="20dp"
android:layout_below="@id/thingname"
android:paddingTop="10dp"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris et magna ut erat elementum cursus in quis ipsum. Nam faucibus ultrices eros, vel tempor leo semper sit amet."
/>
</RelativeLayout>
</ScrollView>
I hope this works for you and hope a lot of people are able to find it and benefit from it, because I spent a couple of days fiddling around with this until I got it working to my satisfaction. Possibly an easy one for the more highly experienced here, but helpful for those at a certain level of experience.
Upvotes: 0
Reputation: 30985
You ran a query and got a Cursor
for your ListActivity
. You attached the Cursor
to a CursorAdapter
which is showing your list.
If the user selects the third item (Item 2), then in your OnItemClickListener
, the position
parameter would be 2.
So what if you passed the position (2) to your follow-on activity, ran the same query there and started with cursor row 2? With the cursor, you would know previous, next... you'd have data for everything in the list!
The way to show large detail views and swipe through them horizontally left and right is to use a widget called a ViewPager
. ViewPager
is driven by a PagerAdapter
, which has some similarities but is definitely not the same as a ListAdapter
.
What you want to do is create a PagerAdapter
that has your Cursor
so when you create a page, you can give it the data from the Cursor
. When the adapter is asked for page 3, you create a detail view, read the third record from the cursor and put that data in the detail view.
It's really that simple. The ResourceCursorAdapter
is doing this under the covers. When the adapter is asked for the view for the third item, a view is created/recycled, the cursor is positioned on the third record, and finally your bindView
method is called to put the data in the view.
Here's a rough sample of a PagerAdapter
:
public class CursorPagerAdapter extends PagerAdapter {
/** Note that this class does not handle closing the cursor. */
private Cursor mCursor;
public CursorPagerAdapter(Cursor mCursor) {
this.mCursor = mCursor;
}
@Override
public int getCount() {
return mCursor == null ? 0 : mCursor.getCount();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
mCursor.moveToPosition(position);
String data1 = mCursor.getString(1);
String data2 = mCursor.getString(2);
String data3 = mCursor.getString(3);
View view = LayoutInflater.from(container.getContext()).inflate(R.layout.detail_view, container, false);
// TODO fill in the view with the cursor data
container.addView(view);
return view;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
ViewPager
is a tricky little bugger, but as an Android developer you will need it sooner or later so it's good to start learning how to work with them now. I recommend you dive in and check out any ViewPager
tutorials you can get your hands on.
Upvotes: 1