Listview Empty View is always shown - android

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().

Related

Android RecyclerView displayed item changed when I scrolled up and down everytime

I am using parse-server to develop the app which uses RecyclerView to display image items.
but the problem is that the items displayed in the view changed every time I scrolled up and down.
I want to know what is the problem on my code.
if you see below images, you can find the items are changing their position.
I tried to make holder image become null before call the holder again. but it's not working. I guess that the item's position number is changed when I call the item again.but I can't find the cause of the situation
enter image description here
enter image description here
RecyclerParseAdapter.java
public class MyTimelineAdapter extends RecyclerParseAdapter {
private interface OnQueryLoadListener<ParseObject> {
public void onLoading();
public void onLoaded(List<ParseObject> objects, Exception e);
}
private static ParseQueryAdapter.QueryFactory<ParseObject> queryFactory;
private static List<OnQueryLoadListener<ParseObject>> onQueryLoadListeners;
private static List<List<ParseObject>> objectPages;
private static ArrayList<ParseObject> items;
private static int currentPage;
private static RequestManager requestManager;
public MyTimelineAdapter(Context context, RequestManager requestManager) {
super(context);
this.requestManager = requestManager;
this.onQueryLoadListeners = new ArrayList<>();
this.currentPage = 0;
this.objectPages = new ArrayList<>();
this.items = new ArrayList<>();
this.queryFactory = new ParseQueryAdapter.QueryFactory<ParseObject>() {
#Override
public ParseQuery<ParseObject> create() {
ParseQuery<ParseObject> query = ParseQuery.getQuery("ImageClassName");
query.setCachePolicy(ParseQuery.CachePolicy.CACHE_THEN_NETWORK);
query.whereEqualTo("status", true);
query.orderByDescending("createdAt");
return query;
}
};
loadObjects(currentPage);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View timelineView;
timelineView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_timeline_item2, parent, false);
TimelineItemViewHolder timelineItemViewHolder = new TimelineItemViewHolder(timelineView);
return timelineItemViewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final ParseObject timelineOb = getItem(position);
FunctionPost functionPost = new FunctionPost(context);
functionPost.TimelineArtistPostAdapterBuilder( timelineOb, holder, requestManager);
//기능 추가
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public ParseObject getItem(int position) {
return items.get(position);
}
#Override
public void loadObjects(final int page) {
final ParseQuery<ParseObject> query = this.queryFactory.create();
if (this.objectsPerPage > 0 && this.paginationEnabled) {
this.setPageOnQuery(page, query);
}
this.notifyOnLoadingListeners();
if (page >= objectPages.size()) {
objectPages.add(page, new ArrayList<ParseObject>());
}
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> foundObjects, ParseException e) {
if ((e != null) && ((e.getCode() == ParseException.CONNECTION_FAILED) || (e.getCode() != ParseException.CACHE_MISS))) {
hasNextPage = true;
} else if (foundObjects != null) {
// Only advance the page, this prevents second call back from CACHE_THEN_NETWORK to
// reset the page.
if (page >= currentPage) {
currentPage = page;
// since we set limit == objectsPerPage + 1
hasNextPage = (foundObjects.size() > objectsPerPage);
}
if (paginationEnabled && foundObjects.size() > objectsPerPage) {
// Remove the last object, fetched in order to tell us whether there was a "next page"
foundObjects.remove(objectsPerPage);
}
List<ParseObject> currentPage = objectPages.get(page);
currentPage.clear();
currentPage.addAll(foundObjects);
syncObjectsWithPages(items, objectPages);
// executes on the UI thread
notifyDataSetChanged();
}
notifyOnLoadedListeners(foundObjects, e);
}
});
}
public void loadNextPage() {
if (items.size() == 0) {
loadObjects(0);
} else {
loadObjects(currentPage + 1);
}
}
public void syncObjectsWithPages(ArrayList<ParseObject> items, List<List<ParseObject>> objectPages) {
items.clear();
for (List<ParseObject> pageOfObjects : objectPages) {
items.addAll(pageOfObjects);
}
}
protected void setPageOnQuery(int page, ParseQuery<ParseObject> query) {
query.setLimit(this.objectsPerPage + 1);
query.setSkip(page * this.objectsPerPage);
}
public void addOnQueryLoadListener(OnQueryLoadListener<ParseObject> listener) {
this.onQueryLoadListeners.add(listener);
}
public void removeOnQueryLoadListener(OnQueryLoadListener<ParseObject> listener) {
this.onQueryLoadListeners.remove(listener);
}
public void notifyOnLoadingListeners() {
for (OnQueryLoadListener<ParseObject> listener : this.onQueryLoadListeners) {
listener.onLoading();
}
}
public void notifyOnLoadedListeners(List<ParseObject> objects, Exception e) {
for (OnQueryLoadListener<ParseObject> listener : this.onQueryLoadListeners) {
listener.onLoaded(objects, e);
}
}
}
I did find the problem
I add overide method in the adapter then It works find.
#Override
public int getItemViewType(int position) {
return position;
}
I am not sure why it happens now. any one help me to know the cause of problem?
I has a similar problem the other day see this post. onBindViewHolder needs to know how to display the row when it's called. I returned two different view types depending on the need in getItemViewType, inflated the view type conditionally in onCreateViewHolder, then I was able to set the data on the ViewHolder as needed.

Adding a row to a growing database pre-release.

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.

How to add native ads in a listview?

this is my activity
i want to insert a native ads into the list view.
I'm trying to follow this guide https://github.com/StartApp-SDK/Documentation/wiki/android-advanced-usage But I find it hard to understand.
can you give me a hand, maybe making examples of code? thank you
ACTIVITY
public class EpisodiActivity extends Activity {
private StartAppAd startAppAd = new StartAppAd(this);
public class ViewModel {
private String url;
private String name;
public ViewModel(String url, String name) {
this.url = url;
this.name = name;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// creazione fullscreen activity
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.episodi_activity);
String[] episodi = getIntent().getStringArrayExtra("Product");
String[] urls = getIntent().getStringArrayExtra("urls");
ListView mylist = (ListView) findViewById(R.id.listView1);
// And in this loop we create the ViewModel instances from
// the name and url and add them all to a List
List<ViewModel> models = new ArrayList<ViewModel>();
for (int i = 0; i < episodi.length; i++) {
String name = episodi[i];
String url = "No value";
if (i < urls.length) {
url = urls[i];
}
ViewModel model = new ViewModel(url, name);
models.add(model);
}
// Here we create the ArrayAdapter and assign it to the ListView
// We pass the List of ViewModel instances into the ArrayAdapter
final ArrayAdapter<ViewModel> adapter = new ArrayAdapter<ViewModel>(
this, android.R.layout.simple_list_item_1, models);
mylist.setAdapter(adapter);
mylist.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View v, int position,
long id) {
// Here we get the ViewModel at the given position
ViewModel model = (ViewModel) arg0.getItemAtPosition(position);
// And the url from the ViewModel
String url = model.getUrl();
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
});
}
#Override
public void onResume() {
super.onResume();
startAppAd.onResume();
startAppAd.showAd();
}
#Override
public void onPause() {
super.onPause();
startAppAd.onPause();
}
}
A listView BaseAdapter first calls the method getCount() to retrieve the number of rows to show and then for each row it calls the getView(int position, View convertView, ViewGroup parent) method to create and return a View object for the given position.
The approach is to extend BaseAdapter and create your own custom Adapter, instead of using the default ArrayAdapter. Then you need to return an “Ad” View instead of your usual normal View when you want to display an ad. This means that you need to override getCount() to return more rows (for example if you have 10 rows, that means you need to return 11 = 10 actual content + 1 ad)
Then you need to decide in which position to create this View, I think you can do it by simply checking the position variable:
if (position == VALUE) {
// Create and return Ad View
} else {
// Create and return a normal View
}
Anyway, this whole thing is really tricky as things can go easily out of hand (positions mismatching with Views etc). StartApp should be able to control your listView adapter to do all this for you. My guess is that your adapter is not communicating properly with StartApp (maybe you are not initialising correctly?).
Try to dig into the documentation or find an example by them. If you can’t figure it out, there are other alternatives you can use such as Avocarrot, Namomedia, Inmobi, etc
I have used Avocarrot which has an open source example in github for inserting ads in listViews.
You can run it and use it if it fits you: https://github.com/Avocarrot/android-demo-app
Recently I stucked with the same question but for a new Admob native ads. Then I decided to post my solution for that to admobadapter. Hope it will help you. I believe you could use the AdmobAdapterWrapper after some customizations for the StartApp-SDK...Kindly look at the AdmobFetcher.java. The result could appear like this
I've been working on this for a while now. I'm using a BaseAdapter and a LinearLayout holder for the ad. These are my global variables:
LinearLayout adHolderView;
private AdRequest adRequest;
NativeExpressAdView singleAdView;
I also have a few other global variables:
public static boolean adLoaded;
public static boolean allowShowAd = true;
public static int adLocation = 5;
Point adSanityCheck = new Point(0,0);
Here is my BaseAdapter, please read the comments
public BaseAdapter drawingsGridAdapter = new BaseAdapter() {
//viewHolder classes are very common with BaseAdapters to speed up loading
//It is not exactly needed but will be very usefull for different uses.
//For example if you are using a GridView instead of a ListView like I am in this case
//it would be good to have the orientation String which is used later on
//in the adapter
class ViewHolderItem {
ImageView imageView;
ImageView userImageView;
TextView userNameView;
TextView dateView;
TextView numCommentsView;
ImageView starView;
TextView starCount;
//below items are used for ads in a gridView
int orientation;
final int LANDSCAPE = 1;
final int PORTRAIT = 2;
}
#SuppressLint("SimpleDateFormat")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderItem viewHolder;
//check if you can use your viewHolderItem and make sure that
//the view is not an Adview
if (convertView == null || !(convertView.getTag() instanceof ViewHolderItem)) {
viewHolder = new ViewHolderItem();
LinearLayout ll = (LinearLayout) inflater.inflate(R.layout.gallery_item, null);
viewHolder.dateView = (TextView) ll.findViewById(R.id.gal_item_date);
//...
//initialize your viewHolder items here
//...
convertView = ll;
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolderItem) convertView.getTag();
}
//...
Tools.addDateToTextView(viewHolder.dateView, drawings.get(position).time);
//do your view modifications here
//...
//now we start doing the ad stuff
//this is optional. add it in to make it work on a gridview
boolean landscape = false; if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
landscape = true;
//in my case I wanted the ad to be the same size as all my other Items so I made sure the values are accesable.
//since you are only using a ListView, you only really need to check for getMeasuredHeight() or you could define your own
//AdSize
if (convertView.getMeasuredHeight()>0 && convertView.getMeasuredWidth()>0 && allowShowAd) {
int adH = convertView.getMeasuredHeight();
int adW = convertView.getMeasuredWidth();
//if the adview needs to be set up then it will set it up, else it will make sure that the ad is the correct size
//and fix it if it needs to
if (singleAdView == null){
if ((landscape && viewHolder.orientation == viewHolder.LANDSCAPE)
||(!landscape && viewHolder.orientation == viewHolder.PORTRAIT))
setUpAd(adW,adH, landscape);
}else{
if ((landscape && viewHolder.orientation == viewHolder.LANDSCAPE)
||(!landscape && viewHolder.orientation == viewHolder.PORTRAIT)) {
//Log.d("ads","Adsize"+singleAdView.getAdSize());
if (singleAdView.getAdSize().getHeight() !=Tools.convertPixelsToDp(adH-1,MainMenuActivity.this)
|| singleAdView.getAdSize().getWidth() !=Tools.convertPixelsToDp(adW-1,MainMenuActivity.this)) {
Log.d("Ads","ad sizesW:"+singleAdView.getAdSize().getWidth() + "vs" + Tools.convertPixelsToDp(adW-1,MainMenuActivity.this));
Log.d("Ads","ad sizesH:"+singleAdView.getAdSize().getHeight() + "vs" + Tools.convertPixelsToDp(adH-1,MainMenuActivity.this));
//sometimes the correct size is not reported so this will ensure ads are not loaded twice. This only really needs to be checked with a gridview
if (adSanityCheck.x == adW && adSanityCheck.y == adH) {
setUpAd(adW, adH, landscape);
}else{
adSanityCheck.x = adW;
adSanityCheck.y = adH;
}
}
}
}
}//Log.d("Ads","tag ="+av.getTag());
if (position == adLocation && singleAdView!=null && allowShowAd) {
if (adLoaded) {
adHolderView.postDelayed(new Runnable() {
#Override
public void run() {
//this seemed to help avoid ad flicker. may want to test to make sure
if (singleAdView!=null)
singleAdView.requestLayout();
}
}, 100);
return adHolderView;
}
}
if (landscape)
viewHolder.orientation = viewHolder.LANDSCAPE;
else
viewHolder.orientation = viewHolder.PORTRAIT;
return convertView;
}#Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
#Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
if (position == adLocation && singleAdView!=null && adLoaded && allowShowAd){
return 1;
}
return 0;
}
#Override
public int getCount() {
return drawings.size();
}
};
You will notice in my BaseAdapter, that I have a method called setUpAd:
private void setUpAd(int widthPX, int heightPX, boolean isLandscape){
if (allowShowAd) {
Log.d("draw", "setupAD");
destroyAdView();
adHolderView = new LinearLayout(MainMenuActivity.this);
singleAdView = new NativeExpressAdView(this);
adLoaded = false;
drawingsGridAdapter.notifyDataSetChanged();
singleAdView.setId(R.id.googleIdentify);
singleAdView.setAdUnitId("ca-app-pub-xxxxxxxxxxxxxx/xxxxxxxxxxxxxx");
int wh = Tools.convertPixelsToDp(widthPX - 1, MainMenuActivity.this);
singleAdView.setAdSize(new AdSize(Tools.convertPixelsToDp(widthPX - 1, MainMenuActivity.this),
Tools.convertPixelsToDp(heightPX - 1, MainMenuActivity.this)));
adRequest = new AdRequest.Builder()
.addTestDevice("TEST DEV ID")
.build();
singleAdView.setAdListener(new AdListener() {
#Override
public void onAdFailedToLoad(int errorCode) {
super.onAdFailedToLoad(errorCode);
adLoaded = false;
drawingsGridAdapter.notifyDataSetChanged();
}
#Override
public void onAdLoaded() {
super.onAdLoaded();
adLoaded = true;
drawingsGridAdapter.notifyDataSetChanged();
}
});
singleAdView.loadAd(adRequest);
adHolderView.setLayoutParams(new LinearLayout.LayoutParams(widthPX, heightPX));
adHolderView.setBackgroundColor(Color.WHITE);
adHolderView.addView(singleAdView);
}
}
From that you need a few other methods:
private void destroyAdView()
{
if (singleAdView != null)
{
adRequest = null;
singleAdView.removeAllViews();
singleAdView.setAdListener(null);
singleAdView.destroy();
singleAdView.setEnabled(false);
adHolderView.removeView(singleAdView);
singleAdView = null;
adHolderView = null;
}
}
public static int convertPixelsToDp(float px, Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = px / ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
return (int) dp;
}
And Finally, If you end up using a GridView, here is one last thing to add in order to load the right size ad after orientation changes:
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
allowShowAd = false;
gridView.postDelayed(new Runnable() {
#Override
public void run() {
drawingsGridAdapter.notifyDataSetChanged();
gridView.invalidateViews();
gridView.requestLayout();
gridView.postDelayed(new Runnable() {
#Override
public void run() {
drawingsGridAdapter.notifyDataSetChanged();
}
},500);
gridView.postDelayed(new Runnable() {
#Override
public void run() {
allowShowAd = true;
Log.d("Ads","allow ads now");
drawingsGridAdapter.notifyDataSetChanged();
}
},1500);
}
}, 100);
}
I do not know the StartApp framework, but basically you have to do the following:
Write your own adapter and do not use ArrayAdapter. Here is a basis class you can use to simplify the adapters view recycling: https://github.com/sockeqwe/appkit/blob/master/adapter/src/main/java/com/hannesdorfmann/appkit/adapter/SimpleAdapter.java
Specify own View cells by overriding getItemViewType(int position) and getViewTypeCount()
Specify a own view type and view holder for your banners that will appear in the ListView.
From what I have seen, the banner needs to save something in onSaveInstanceState() and to restore something in onRestoreInstanceState() . Im not sure if this is needed or makes sence in a ListView. Simply try it with or without this calls. If this is needed, than you have to keep a List of Banner Items in you adapter and you
have to do something like this in your Activities code:
public class MyActivity extends Activity
{
public void onSaveInstanceState(Bundle b){
super.onSaveInstanceState(b);
adapter.saveInstancState(b);
}
public void restoreInstanceState(Bundle b){
super.restoreInstanceState(b);
adapter.restoreInstanceState(b);
}
}

how to create a circular list view or populate values in a circular view? [duplicate]

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);
}
}

Android Loop List Adapter [duplicate]

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);
}
}

Categories

Resources