I have the database already finished and I would like to add a few rows that are present on the first time opening the app. The main issue, not knowing where in the application to implement this. For example, when the user opens the app for the first time, there is an example item. The item can be deleted. After the row is deleted it will never show up again. I am using Androrm (object relational mapper) androrm home page. My main question: How do I add a single row to the database (where & how) before release. Within the onCreate, will add a row each time, the class is opened.
Took out most code to make it simple.
Implementation
public class LogFirst extends Model {
protected CharField db_oneName;
public LogFirst() {
super(true);
db_oneName = new CharField(80);
}
public void setDB_oneName(String name1) {
db_oneName.set(name1);
}
public String getDB_oneName() {
return db_oneName.get();
}
#Override
public String toString() {
return db_oneName.get();
}
public static List<LogFirst> all() {
return LogFirst.objects().all().toList();
}
public static QuerySet<LogFirst> objects() {
return LogFirst.objects(context(), LogFirst.class);
}
public boolean save() {
Format formatter = new SimpleDateFormat("ddmmhhss");
String id = formatter.format(Calendar.getInstance().getTime()) + "";
return this.save(context(), Integer.valueOf(id));
}
public boolean delete() {
return this.delete(context());
}
private static Context context() {
return ExtendsActivity.context();
}
}
Saving
LogFirst lf = new LogFirst();
lf.setDB_oneName(name.getText().toString());
lf.save();
Adapter
public class LogFirstAdapter extends ArrayAdapter<LogFirst> {
Context mContext;
List<LogFirst> mLogs;
public LogFirstAdapter(Context context, int textViewResourceId, List<LogFirst> logs) {
super(context, textViewResourceId);
mContext = context;
mLogs = logs;
}
public void setLogs(List<LogFirst> logs) {
mLogs = logs;
}
public List<LogFirst> getLogs() {
return mLogs;
}
public void add(LogFirst log) {
mLogs.add(log);
}
public void remove(LogFirst log) {
mLogs.remove(log);
}
public int getCount() {
return mLogs.size();
}
public LogFirst getItem(int position) {
return mLogs.get(position);
}
public View getView(int position, View convertView, ViewGroup parent) {
LogFirstRow view = (LogFirstRow) convertView;
if (view == null) {
view = new LogFirstRow(mContext);
}
LogFirst log = getItem(position);
view.setLog(log);
return view;
}
}
Ideally you'd do something like that in a migration. I'm not familiar with Androrm but it looks like they have some support for migration: http://www.androrm.com/documentation/models/migrations/
Try putting in the code to create the new records in the overriden migrate function. The docs say migrations will be kept track of but I'm not sure how it will play out so test to see what happens.
Related
I am working on a project where i need to create Image Preview Functionality.For that i have created a recyclerview in which i am passing ArrayList of bitmap and displaying it in recyclerview.Now i am converting that arraylist into base64 string array and want to pass that arraylist into new activity using parcelable.
But i am getting TransactionTooLarge Execption.
Is there another way to pass the array to another activity?
Here is my adapter
public class ImageListAdapter extends RecyclerView.Adapter<ImageListAdapter.ViewHolder> {
private ArrayList<UploadImageModel> mBitmapArray;
private Context context;
private UploadImageModel mUploadImageModel;
private ArrayList<Base64ArrayModel> mBase64ArrayList;
private Base64ArrayModel mBase64ArrayModel;
public ImageListAdapter(ArrayList<UploadImageModel> mBitmapArray, ArrayList<Base64ArrayModel> mBase64ArrayList, Context context) {
this.mBitmapArray = mBitmapArray; //Here i am getting arraylist that contains bitmaps
this.context = context;
}
#Override
public ImageListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(R.layout.image_set, null);
ViewHolder holder = new ViewHolder(itemView);
return holder;
}
#Override
public void onBindViewHolder(ImageListAdapter.ViewHolder holder, int position) {
mUploadImageModel = mBitmapArray.get(position);
holder.UploadImageView.setImageBitmap(mUploadImageModel.getUploadImageBitmap());
holder.UploadImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent openPreviewActivity = new Intent(context, PreviewActivity.class);
openPreviewActivity.putParcelableArrayListExtra("myImageList",encodeList());
context.startActivity(openPreviewActivity);
}
});
}
#Override
public int getItemCount() {
return mBitmapArray.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView UploadImageView;
public ViewHolder(View itemView) {
super(itemView);
UploadImageView = (ImageView) itemView.findViewById(R.id.UploadImageView);
}
}
private ArrayList<Base64ArrayModel> encodeList() {
mBase64ArrayList = new ArrayList<>();
for (int i = 0; i < mBitmapArray.size(); i++) {
mBase64ArrayList.add(new Base64ArrayModel(ConstantFunction.encodeToBase64(mBitmapArray.get(i).getUploadImageBitmap(), Bitmap.CompressFormat.JPEG, 100)));
}
return mBase64ArrayList;
}
}
and the model i am using is as follows
public class Base64ArrayModel implements Parcelable {
public String mBase64BitmapString;
public String getmBase64BitmapString() {
return mBase64BitmapString;
}
public void setmBase64BitmapString(String mBase64BitmapString) {
this.mBase64BitmapString = mBase64BitmapString;
}
public Base64ArrayModel(String mBase64BitmapString)
{
this.mBase64BitmapString=mBase64BitmapString;
}
protected Base64ArrayModel(Parcel in) {
mBase64BitmapString = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mBase64BitmapString);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<Base64ArrayModel> CREATOR = new Parcelable.Creator<Base64ArrayModel>() {
#Override
public Base64ArrayModel createFromParcel(Parcel in) {
return new Base64ArrayModel(in);
}
#Override
public Base64ArrayModel[] newArray(int size) {
return new Base64ArrayModel[size];
}
};
}
How can i pass that arrayList to new activity?
From the doc,
During a remote procedure call, the arguments and the return value of
the call are transferred as Parcel objects stored in the Binder
transaction buffer. If the arguments or the return value are too large
to fit in the transaction buffer, then the call will fail and
TransactionTooLargeException will be thrown.
The Binder transaction buffer has a limited fixed size, currently 1Mb,
which is shared by all transactions in progress for the process.
Consequently this exception can be thrown when there are many
transactions in progress even when most of the individual transactions
are of moderate size.
So, this basically means, you're trying to pass data with a size greater than the Binder Transaction Buffer can contain. To overcome this, you've to reduce the size of the data(base64String size, for your case). I can see you've this
ConstantFunction.encodeToBase64(mBitmapArray.get(i).getUploadImageBitmap(), Bitmap.CompressFormat.JPEG, 100) method for encoding a bitmap to base64String where you've passed 100 as compression level. In your implementation, if you use bitmap.compress method to compress the bitmap then try to reduce the number. The less the number the less quality it would get after the compression hence, you'll get small sized base64String in the end.
first, you add this line into your manifest file.
android:largeHeap="true"
Because simultaneously at a time your transaction too large. So make one singleton class like. It is not preferred way I want to suggest use database but if you have not any other choice than this one is better for you.
public class DataTransactionModel {
private static volatile DataTransactionModel instance = null;
private ArrayList<Base64ArrayModel> list = null;
private DataTransactionModel() {
}
public static synchronized DataTransactionModel getInstance() {
if (instance == null) {
synchronized (DataTransactionModel.class) {
if (instance == null) {
instance = new DataTransactionModel();
}
}
}
return instance;
}
public Bitmap getList() {
return list;
}
public void setList(Bitmap bitmap) {
this.bitmap = list;
}
}
Set data into this singleton class and then after get list of images with the help of singleton class methods.
...
#Override
public void onBindViewHolder(ImageListAdapter.ViewHolder holder, int position) {
mUploadImageModel = mBitmapArray.get(position);
holder.UploadImageView.setImageBitmap(mUploadImageModel.getUploadImageBitmap());
holder.UploadImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DataTransactionModel model = DataTransactionModel.getInstance();
model.setList(encodeList());
Intent openPreviewActivity = new Intent(context, PreviewActivity.class);
context.startActivity(openPreviewActivity);
}
});
}
You can use EventBus :
Create event and then post the arraylist and receive wherever you want to..
for E.g
compile 'org.greenrobot:eventbus:3.0.0'
//create class
public class Base64Event {
public final List< Base64ArrayModel > base64Array;
public Base64Event(List< Base64ArrayModel > base64Array){
}
}
//Post
EventBus.getDefault().postSticky(new Base64Event(base64Array));
//Reciever in activity
#Subscribe(threadMode = ThreadMode.MAIN)
public void anyName(Base64Event event) {
event. base64Array //here is the data passed
}
newbie to Android here!
I've been learning how to implement SQLite in my app, and to sum it up, I have an Accountant class which has access to the SQLite database. The class pulls up the items from the database and puts them in an ArrayList. This ArrayList is what is used for my adapter for the recyclerView.
Whenever I add a new item in the app, the the item's data is stored in the database and the Accountant class's ArrayListgets updated with this info.
Then, the adapter calls its notifyDataSetChanged() method to update the View. This is where the problem occurs; the RecyclerView DOES display all items, but only upon app startup, NOT when a new item is added.
I've done all I can, it just LOOKS like it's supposed to work, but it doesn't and it's driving me nuts.
Here's the code
ItemAdapter Class
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
private List<Item> mItemList;
public ItemAdapter(List<Item> itemList) {
mItemList = itemList;
}
public ItemHolder onCreateViewHolder(ViewGroup parent, int ViewType) {
View view = getLayoutInflater().inflate(R.layout.list_item_item, parent, false);
return new ItemHolder(view);
}
public void onBindViewHolder(ItemHolder holder, int position) {
Item item = mItemList.get(position);
holder.bindItem(item);
}
public int getItemCount() {
return mItemList.size();
}
}
Accountant Class
public class Accountant {
private static Accountant sAccountant;
private double mTotalMoney;
private Context mContext;
private SQLiteDatabase mDatabase;
private List<Item> mItemList;
public static Accountant get(Context context) {
sAccountant = sAccountant == null ? new Accountant(context) : sAccountant;
return sAccountant;
}
private Accountant(Context context) {
mTotalMoney = 0;
mContext = context.getApplicationContext();
mDatabase = new ItemBaseHelper(mContext).getWritableDatabase();
mItemList = getListFromSQL();
}
private static ContentValues getContentValues(Item i) {
ContentValues values = new ContentValues();
values.put(ItemTable.cols.NAME, i.getName());
values.put(ItemTable.cols.PRICE, i.getPrice());
values.put(ItemTable.cols.COUNT, i.getCount());
return values;
}
public void addItem(Item item) {
ContentValues cv = getContentValues(item);
mDatabase.insert(ItemTable.NAME, null, cv);
mItemList = getListFromSQL();
}
public void removeItem(int i) {
}
public void addMoney(double money, boolean isSet) {
mTotalMoney += isSet ? money - mTotalMoney : money;
}
public String getTotalMoney() {
return MoneyUtils.prep(mTotalMoney);
}
public String getChange() {
double cost = 0;
for (Item item : getItemList())
cost += item.getPrice() * item.getCount();
return MoneyUtils.prep(mTotalMoney - cost);
}
public List<Item> getItemList() {
return mItemList;
}
private List<Item> getListFromSQL() {
List<Item> itemList = new ArrayList<>();
ItemCursorWrapper cursor = queryItems(null, null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
itemList.add(cursor.getItem());
cursor.moveToNext();
}
} finally {
cursor.close();
}
return itemList;
}
public ItemCursorWrapper queryItems(String whereClause, String[] whereArgs) {
Cursor cursor = mDatabase.query(ItemTable.NAME, null, whereClause, whereArgs, null, null, null);
return new ItemCursorWrapper(cursor);
}
public String individualPriceOf(Item i) {
return MoneyUtils.prep(i.getPrice());
}
public String totalPriceOf(Item i) {
return MoneyUtils.prep(i.getCount() * i.getPrice());
}
public String countOf(Item i) {
return String.valueOf(i.getCount());
}
public void clearList() {
mDatabase.delete(ItemTable.NAME, null, null);
}
}
Item adding logic
public void addItem(Item item) {
mAccountant.addItem(item);
mAdapter.notifyItemInserted(mAccountant.getListFromSQL().size() - 1);
mAdapter.notifyDataSetChanged();
mChangeButton.setText(mAccountant.getChange());
}
Well there is fundamental problem not even related to RecyclerView.
First let's see how to fix your issue then explanation of what's wrong.
change this
private List<Item> mItemList;
to this
private final List<Item> mItemList;
then instead of any assignment like mItemList = getListFromSQL(); write this
mItemList.clear();
mItemList.addAll(getListFromSQL());
Now explanation why your code is not working. The thing is that when you assign your dataSource (i.e. mItemList) to some new value you are changing reference to it (that's a java fundamental thing) so that your RecyclerView doesn't know anything about it and it's own dataSource which you assign only once in constructor remains the same old one which is not changed therefore your notifyDataSetChanged call does nothing.
General advice whenever using RecyclerView or a ListView make sure you define your dataSource as final.
This is happening because you do not add the item into your Adpater's list. Make a method inside your adapter and call this method from your Accountant class.
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
public void addItem(Item item) {
mItemList.add(item); ///Add the item to your arrayList and then notify
notifyItemInserted(mItemList.size());
}
When you add single item in Adapter dont call notifyDataSetChanged() method because it will notify the whole list. Instead only use notifyItemInserted() method.
Another think is make sure when you notify the adapter it must be from UI thread.
When you add your item then just call this adapter addItem() method from your Accountant class.
public void addItem(Item item) { ///This method is from Accountant Class
mAccountant.addItem(item);
mAdapter.addItem(item); // Call the addItem() from Adapter class
mChangeButton.setText(mAccountant.getChange());
}
Hope it will work...
I use a custom ParseQueryAdapter to load data in listview. I want to show a message when there is no data but the message is shown even when data are not empty. I think it is due to the fact that data are not yet loaded. I tried with setEmptyView and also with a test on the adapter if mAdapter.isEmpty().
I tried waiting a few seconds before testing if adapter is empty but although it works, I think it's not a good practice.
My custom adapter where I make the query:
public class CategoryEventsAdapter extends ParseQueryAdapter<Event> {
public CategoryEventsAdapter(Context context, final String c) {
super(context, new ParseQueryAdapter.QueryFactory<Event>() {
public ParseQuery<Event> create() {
ParseQuery<Event> query = new ParseQuery<Event>("Event");
query.whereEqualTo("published", true);
query.whereEqualTo("category", c);
return query;
}
});
}
#Override
public View getItemView(Event event, View v, ViewGroup parent) {
...
}
}
And I simply call it in a Fragment:
mAdapter = new CategoryEventsAdapter(getActivity(), category);
listview.setAdapter(mAdapter);
if (mAdapter.isEmpty()) {
// show message
}
I've never used this particular part of Parse but looking at the docs, the query seems to be async. Instead you can try this maybe:
mAdapter = new CategoryEventsAdapter(getActivity(), category);
// add a listener for when the query is done.
mAdapter.addOnQueryLoadListener(new OnQueryLoadListener<ParseObject>() {
public void onLoaded(List<ParseObject> objects, ParseException e) {
// Check if empty here and show message.
if (objects.size == 0){
// show message
}
}
});
listview.setAdapter(mAdapter);
So once the query is done, it should call onLoaded so then you can determine if it is empty or not. In onLoaded you can check the count of the objects parameter. Not sure if it's already set in the adapter if you do mAdapter.isEmpty at that point.
This is my ParseQueryAdapter implementation that lets you choose an "empty" placeholder. You just call adapter.setEmptyLayoutId(R.layout.empty) from outside.
public class ParseAdapter extends ParseQueryAdapter {
private final static int EMPTY_VIEW = 2;
private int emptyViewLayoutId;
private boolean isEmpty;
public ParseAdapter(Context c, QueryFactory<? extends ParseObject> q) {
super(c, q);
addOnQueryLoadListener(new OnQueryLoadListener() {
#Override
public void onLoading() {
isEmpty = false;
}
#Override
public void onLoaded(List list, Exception e) {
if (list == null || list.size() == 0) {
isEmpty = true;
}
}
});
}
#Override
public void notifyDataSetChanged() {
isEmpty = false;
super.notifyDataSetChanged();
}
public void setEmptyLayoutId(int emptyViewLayoutId) {
this.emptyViewLayoutId = emptyViewLayoutId;
}
#Override
public View getItemView(ParseObject object, View v, ViewGroup parent) {
if (isEmpty) {
v = View.inflate(getContext(), emptyViewLayoutId, null);
return v;
}
v = v != null ? v : View.inflate(getContext(), rowLayoutId, null);
//do whatever you want on v
return v;
}
#Override
public int getViewTypeCount() {
return super.getViewTypeCount() + 1; //3
}
#Override
public int getItemViewType(int position) {
return isEmpty ? EMPTY_VIEW : super.getItemViewType(position);
}
#Override
public int getCount() {
return isEmpty ? 1 : super.getCount();
}
}
You need to add an item view type because otherwise, if empty, getItemView() won't be called in some cases. Overriding getCount() to return 1 is not enough, because ParseQueryAdapter performs some checks over the view type inside getView(), and won't pass the call to getItemView().
I want to create a customized ListView (or similar) which will behave like a closed (circular) one:
scrolling down - after the last item was reached the first begins (.., n-1, n, 1, 2, ..)
scrolling upward - after the first item was reached the last begins (.., 2, 1, n, n-1, ..)
It sounds simple conceptually but, apparently, there is no straightforward approach to do this.
Can anyone point me to the right solution ?
Thank you !
I have already received an answer (from Streets Of Boston on Android-Developers google groups), but it sounds somehow ugly :) -
I did this by creating my own
list-adapter (subclassed from
BaseAdapter).
I coded my own list-adapter in such a
way that its getCount() method returns
a HUUUUGE number.
And if item 'x' is selected, then this
item corresponds to adapter
position='adapter.getCount()/2+x'
And for my adapter's method
getItem(int position), i look in my
array that backs up the adapter and
fetch the item on index:
(position-getCount()/2) % myDataItems.length
You need to do some more 'special'
stuff to make it all work correctly,
but you get the idea.
In principle, it is still possible to
reach the end or the beginning of the
list, but if you set getCount() to
around a million or so, this is hard
to do :-)
My colleague Joe, and I believe we have found a simpler way to solve the same problem. In our solution though instead of extending BaseAdapter we extend ArrayAdapter.
The code is as follows :
public class CircularArrayAdapter< T > extends ArrayAdapter< T >
{
public static final int HALF_MAX_VALUE = Integer.MAX_VALUE/2;
public final int MIDDLE;
private T[] objects;
public CircularArrayAdapter(Context context, int textViewResourceId, T[] objects)
{
super(context, textViewResourceId, objects);
this.objects = objects;
MIDDLE = HALF_MAX_VALUE - HALF_MAX_VALUE % objects.length;
}
#Override
public int getCount()
{
return Integer.MAX_VALUE;
}
#Override
public T getItem(int position)
{
return objects[position % objects.length];
}
}
So this creates a class called CircularArrayAdapter which take an object type T (which may be anything) and uses it to create an array list. T is commonly a string though may be anything.
The constructor is the same as is for ArrayAdapter though initializes a constant called middle. This is the middle of the list. No matter what the length of the array MIDDLE can be used to center the ListView in the mid of the list.
getCount() is overrides to return a huge value as is done above creating a huge list.
getItem() is overrides to return the fake position on the array. Thus when filling the list the list is filled with objects in a looping manner.
At this point CircularArrayAdapter simply replaces ArrayAdapter in the file creating the ListView.
To centre the ListView the fallowing line must be inserted in your file creating the ListView after the ListView object has been initialised:
listViewObject.setSelectionFromTop(nameOfAdapterObject.MIDDLE, 0);
and using the MIDDLE constant previously initialized for the list the view is centered with the top item of the list at the top of the screen.
: ) ~ Cheers, I hope this solution is useful.
The solution you mention is the one I told other developers to use in the past. In getCount(), simply return Integer.MAX_VALUE, it will give you about 2 billion items, which should be enough.
I have, or I think I have done it right, based on the answers above.
Hope this will help you.
private static class RecipeListAdapter extends BaseAdapter {
private static LayoutInflater mInflater;
private Integer[] mCouponImages;
private static ImageView viewHolder;
public RecipeListAdapter(Context c, Integer[] coupomImages) {
RecipeListAdapter.mInflater = LayoutInflater.from(c);
this.mCouponImages = coupomImages;
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public Object getItem(int position) {
// you can do your own tricks here. to let it display the right item in your array.
return position % mCouponImages.length;
}
#Override
public long getItemId(int position) {
return position;
// return position % mCouponImages.length;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.coupon_list_item, null);
viewHolder = (ImageView) convertView.findViewById(R.id.item_coupon);
convertView.setTag(viewHolder);
} else {
viewHolder = (ImageView) convertView.getTag();
}
viewHolder.setImageResource(this.mCouponImages[position % mCouponImages.length]);
return convertView;
}
}
And you would like to do this if you want to scroll down the list.
Commonly we can just scroll up and list then scroll down.
// see how many items we would like to sroll. in this case, Integer.MAX_VALUE
int listViewLength = adapter.getCount();
// see how many items a screen can dispaly, I use variable "span"
final int span = recipeListView.getLastVisiblePosition() - recipeListView.getFirstVisiblePosition();
// see how many pages we have
int howManySpans = listViewLength / span;
// see where do you want to be when start the listview. you dont have to do the "-3" stuff.
it is for my app to work right.
recipeListView.setSelection((span * (howManySpans / 2)) - 3);
I could see some good answers for this, One of my friend has tried to achieve this via a simple solution. Check the github project.
If using LoadersCallbacks I have created MyCircularCursor class which wraps the typical cursor like this:
#Override
public void onLoadFinished(Loader<Cursor> pCursorLoader, Cursor pCursor) {
mItemListAdapter.swapCursor(new MyCircularCursor(pCursor));
}
the decorator class code is here:
public class MyCircularCursor implements Cursor {
private Cursor mCursor;
public MyCircularCursor(Cursor pCursor) {
mCursor = pCursor;
}
#Override
public int getCount() {
return mCursor.getCount() == 0 ? 0 : Integer.MAX_VALUE;
}
#Override
public int getPosition() {
return mCursor.getPosition();
}
#Override
public boolean move(int pOffset) {
return mCursor.move(pOffset);
}
#Override
public boolean moveToPosition(int pPosition) {
int position = MathUtils.mod(pPosition, mCursor.getCount());
return mCursor.moveToPosition(position);
}
#Override
public boolean moveToFirst() {
return mCursor.moveToFirst();
}
#Override
public boolean moveToLast() {
return mCursor.moveToLast();
}
#Override
public boolean moveToNext() {
if (mCursor.isLast()) {
mCursor.moveToFirst();
return true;
} else {
return mCursor.moveToNext();
}
}
#Override
public boolean moveToPrevious() {
if (mCursor.isFirst()) {
mCursor.moveToLast();
return true;
} else {
return mCursor.moveToPrevious();
}
}
#Override
public boolean isFirst() {
return false;
}
#Override
public boolean isLast() {
return false;
}
#Override
public boolean isBeforeFirst() {
return false;
}
#Override
public boolean isAfterLast() {
return false;
}
#Override
public int getColumnIndex(String pColumnName) {
return mCursor.getColumnIndex(pColumnName);
}
#Override
public int getColumnIndexOrThrow(String pColumnName) throws IllegalArgumentException {
return mCursor.getColumnIndexOrThrow(pColumnName);
}
#Override
public String getColumnName(int pColumnIndex) {
return mCursor.getColumnName(pColumnIndex);
}
#Override
public String[] getColumnNames() {
return mCursor.getColumnNames();
}
#Override
public int getColumnCount() {
return mCursor.getColumnCount();
}
#Override
public byte[] getBlob(int pColumnIndex) {
return mCursor.getBlob(pColumnIndex);
}
#Override
public String getString(int pColumnIndex) {
return mCursor.getString(pColumnIndex);
}
#Override
public short getShort(int pColumnIndex) {
return mCursor.getShort(pColumnIndex);
}
#Override
public int getInt(int pColumnIndex) {
return mCursor.getInt(pColumnIndex);
}
#Override
public long getLong(int pColumnIndex) {
return mCursor.getLong(pColumnIndex);
}
#Override
public float getFloat(int pColumnIndex) {
return mCursor.getFloat(pColumnIndex);
}
#Override
public double getDouble(int pColumnIndex) {
return mCursor.getDouble(pColumnIndex);
}
#Override
public int getType(int pColumnIndex) {
return 0;
}
#Override
public boolean isNull(int pColumnIndex) {
return mCursor.isNull(pColumnIndex);
}
#Override
public void deactivate() {
mCursor.deactivate();
}
#Override
#Deprecated
public boolean requery() {
return mCursor.requery();
}
#Override
public void close() {
mCursor.close();
}
#Override
public boolean isClosed() {
return mCursor.isClosed();
}
#Override
public void registerContentObserver(ContentObserver pObserver) {
mCursor.registerContentObserver(pObserver);
}
#Override
public void unregisterContentObserver(ContentObserver pObserver) {
mCursor.unregisterContentObserver(pObserver);
}
#Override
public void registerDataSetObserver(DataSetObserver pObserver) {
mCursor.registerDataSetObserver(pObserver);
}
#Override
public void unregisterDataSetObserver(DataSetObserver pObserver) {
mCursor.unregisterDataSetObserver(pObserver);
}
#Override
public void setNotificationUri(ContentResolver pCr, Uri pUri) {
mCursor.setNotificationUri(pCr, pUri);
}
#Override
public boolean getWantsAllOnMoveCalls() {
return mCursor.getWantsAllOnMoveCalls();
}
#Override
public Bundle getExtras() {
return mCursor.getExtras();
}
#Override
public Bundle respond(Bundle pExtras) {
return mCursor.respond(pExtras);
}
#Override
public void copyStringToBuffer(int pColumnIndex, CharArrayBuffer pBuffer) {
mCursor.copyStringToBuffer(pColumnIndex, pBuffer);
}
}
I want to create a customized ListView (or similar) which will behave like a closed (circular) one:
scrolling down - after the last item was reached the first begins (.., n-1, n, 1, 2, ..)
scrolling upward - after the first item was reached the last begins (.., 2, 1, n, n-1, ..)
It sounds simple conceptually but, apparently, there is no straightforward approach to do this.
Can anyone point me to the right solution ?
Thank you !
I have already received an answer (from Streets Of Boston on Android-Developers google groups), but it sounds somehow ugly :) -
I did this by creating my own
list-adapter (subclassed from
BaseAdapter).
I coded my own list-adapter in such a
way that its getCount() method returns
a HUUUUGE number.
And if item 'x' is selected, then this
item corresponds to adapter
position='adapter.getCount()/2+x'
And for my adapter's method
getItem(int position), i look in my
array that backs up the adapter and
fetch the item on index:
(position-getCount()/2) % myDataItems.length
You need to do some more 'special'
stuff to make it all work correctly,
but you get the idea.
In principle, it is still possible to
reach the end or the beginning of the
list, but if you set getCount() to
around a million or so, this is hard
to do :-)
My colleague Joe, and I believe we have found a simpler way to solve the same problem. In our solution though instead of extending BaseAdapter we extend ArrayAdapter.
The code is as follows :
public class CircularArrayAdapter< T > extends ArrayAdapter< T >
{
public static final int HALF_MAX_VALUE = Integer.MAX_VALUE/2;
public final int MIDDLE;
private T[] objects;
public CircularArrayAdapter(Context context, int textViewResourceId, T[] objects)
{
super(context, textViewResourceId, objects);
this.objects = objects;
MIDDLE = HALF_MAX_VALUE - HALF_MAX_VALUE % objects.length;
}
#Override
public int getCount()
{
return Integer.MAX_VALUE;
}
#Override
public T getItem(int position)
{
return objects[position % objects.length];
}
}
So this creates a class called CircularArrayAdapter which take an object type T (which may be anything) and uses it to create an array list. T is commonly a string though may be anything.
The constructor is the same as is for ArrayAdapter though initializes a constant called middle. This is the middle of the list. No matter what the length of the array MIDDLE can be used to center the ListView in the mid of the list.
getCount() is overrides to return a huge value as is done above creating a huge list.
getItem() is overrides to return the fake position on the array. Thus when filling the list the list is filled with objects in a looping manner.
At this point CircularArrayAdapter simply replaces ArrayAdapter in the file creating the ListView.
To centre the ListView the fallowing line must be inserted in your file creating the ListView after the ListView object has been initialised:
listViewObject.setSelectionFromTop(nameOfAdapterObject.MIDDLE, 0);
and using the MIDDLE constant previously initialized for the list the view is centered with the top item of the list at the top of the screen.
: ) ~ Cheers, I hope this solution is useful.
The solution you mention is the one I told other developers to use in the past. In getCount(), simply return Integer.MAX_VALUE, it will give you about 2 billion items, which should be enough.
I have, or I think I have done it right, based on the answers above.
Hope this will help you.
private static class RecipeListAdapter extends BaseAdapter {
private static LayoutInflater mInflater;
private Integer[] mCouponImages;
private static ImageView viewHolder;
public RecipeListAdapter(Context c, Integer[] coupomImages) {
RecipeListAdapter.mInflater = LayoutInflater.from(c);
this.mCouponImages = coupomImages;
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public Object getItem(int position) {
// you can do your own tricks here. to let it display the right item in your array.
return position % mCouponImages.length;
}
#Override
public long getItemId(int position) {
return position;
// return position % mCouponImages.length;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.coupon_list_item, null);
viewHolder = (ImageView) convertView.findViewById(R.id.item_coupon);
convertView.setTag(viewHolder);
} else {
viewHolder = (ImageView) convertView.getTag();
}
viewHolder.setImageResource(this.mCouponImages[position % mCouponImages.length]);
return convertView;
}
}
And you would like to do this if you want to scroll down the list.
Commonly we can just scroll up and list then scroll down.
// see how many items we would like to sroll. in this case, Integer.MAX_VALUE
int listViewLength = adapter.getCount();
// see how many items a screen can dispaly, I use variable "span"
final int span = recipeListView.getLastVisiblePosition() - recipeListView.getFirstVisiblePosition();
// see how many pages we have
int howManySpans = listViewLength / span;
// see where do you want to be when start the listview. you dont have to do the "-3" stuff.
it is for my app to work right.
recipeListView.setSelection((span * (howManySpans / 2)) - 3);
I could see some good answers for this, One of my friend has tried to achieve this via a simple solution. Check the github project.
If using LoadersCallbacks I have created MyCircularCursor class which wraps the typical cursor like this:
#Override
public void onLoadFinished(Loader<Cursor> pCursorLoader, Cursor pCursor) {
mItemListAdapter.swapCursor(new MyCircularCursor(pCursor));
}
the decorator class code is here:
public class MyCircularCursor implements Cursor {
private Cursor mCursor;
public MyCircularCursor(Cursor pCursor) {
mCursor = pCursor;
}
#Override
public int getCount() {
return mCursor.getCount() == 0 ? 0 : Integer.MAX_VALUE;
}
#Override
public int getPosition() {
return mCursor.getPosition();
}
#Override
public boolean move(int pOffset) {
return mCursor.move(pOffset);
}
#Override
public boolean moveToPosition(int pPosition) {
int position = MathUtils.mod(pPosition, mCursor.getCount());
return mCursor.moveToPosition(position);
}
#Override
public boolean moveToFirst() {
return mCursor.moveToFirst();
}
#Override
public boolean moveToLast() {
return mCursor.moveToLast();
}
#Override
public boolean moveToNext() {
if (mCursor.isLast()) {
mCursor.moveToFirst();
return true;
} else {
return mCursor.moveToNext();
}
}
#Override
public boolean moveToPrevious() {
if (mCursor.isFirst()) {
mCursor.moveToLast();
return true;
} else {
return mCursor.moveToPrevious();
}
}
#Override
public boolean isFirst() {
return false;
}
#Override
public boolean isLast() {
return false;
}
#Override
public boolean isBeforeFirst() {
return false;
}
#Override
public boolean isAfterLast() {
return false;
}
#Override
public int getColumnIndex(String pColumnName) {
return mCursor.getColumnIndex(pColumnName);
}
#Override
public int getColumnIndexOrThrow(String pColumnName) throws IllegalArgumentException {
return mCursor.getColumnIndexOrThrow(pColumnName);
}
#Override
public String getColumnName(int pColumnIndex) {
return mCursor.getColumnName(pColumnIndex);
}
#Override
public String[] getColumnNames() {
return mCursor.getColumnNames();
}
#Override
public int getColumnCount() {
return mCursor.getColumnCount();
}
#Override
public byte[] getBlob(int pColumnIndex) {
return mCursor.getBlob(pColumnIndex);
}
#Override
public String getString(int pColumnIndex) {
return mCursor.getString(pColumnIndex);
}
#Override
public short getShort(int pColumnIndex) {
return mCursor.getShort(pColumnIndex);
}
#Override
public int getInt(int pColumnIndex) {
return mCursor.getInt(pColumnIndex);
}
#Override
public long getLong(int pColumnIndex) {
return mCursor.getLong(pColumnIndex);
}
#Override
public float getFloat(int pColumnIndex) {
return mCursor.getFloat(pColumnIndex);
}
#Override
public double getDouble(int pColumnIndex) {
return mCursor.getDouble(pColumnIndex);
}
#Override
public int getType(int pColumnIndex) {
return 0;
}
#Override
public boolean isNull(int pColumnIndex) {
return mCursor.isNull(pColumnIndex);
}
#Override
public void deactivate() {
mCursor.deactivate();
}
#Override
#Deprecated
public boolean requery() {
return mCursor.requery();
}
#Override
public void close() {
mCursor.close();
}
#Override
public boolean isClosed() {
return mCursor.isClosed();
}
#Override
public void registerContentObserver(ContentObserver pObserver) {
mCursor.registerContentObserver(pObserver);
}
#Override
public void unregisterContentObserver(ContentObserver pObserver) {
mCursor.unregisterContentObserver(pObserver);
}
#Override
public void registerDataSetObserver(DataSetObserver pObserver) {
mCursor.registerDataSetObserver(pObserver);
}
#Override
public void unregisterDataSetObserver(DataSetObserver pObserver) {
mCursor.unregisterDataSetObserver(pObserver);
}
#Override
public void setNotificationUri(ContentResolver pCr, Uri pUri) {
mCursor.setNotificationUri(pCr, pUri);
}
#Override
public boolean getWantsAllOnMoveCalls() {
return mCursor.getWantsAllOnMoveCalls();
}
#Override
public Bundle getExtras() {
return mCursor.getExtras();
}
#Override
public Bundle respond(Bundle pExtras) {
return mCursor.respond(pExtras);
}
#Override
public void copyStringToBuffer(int pColumnIndex, CharArrayBuffer pBuffer) {
mCursor.copyStringToBuffer(pColumnIndex, pBuffer);
}
}