Currently, I'm pulling 15 tweets into an ArrayList using a Handler and then passing the completed ArrayList to a updateUI method. It's working great so far, but when my ListView is populated, the scrolling experience is very choppy. When I remove the images reference from my TwitterAdapter the ListView has a fast/smooth user experience. I suspect my Adapter is attempting to re-dowload each graphic when I'm scrolling, but can't get the debugger to hitch on that method to prove it.
Here is the key code within the Activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.Theme_BambooZen);
this.setContentView(R.layout.twitter_layout);
myListView = (ListView) findViewById(R.id.TwitterListView);
/*
* Remove any existing callbacks to the handler before adding the new handler,
* to make absolutely sure we don't get more callback events than we want.
*/
mHandler.removeCallbacks(mUpdateAdapterTask);
mHandler.postDelayed(mUpdateAdapterTask, 1000); //Fire the Handler once, the handler will manage self-calls
}
private Runnable mUpdateAdapterTask = new Runnable() {
public void run() {
try{
//Get Twitter Tweets on separate thread (Requires Network)
ArrayList<Tweet> tweets = getTweets("Formula1", 1);
updateUI(tweets);
//Refresh every 45 seconds = 45000
mHandler.postDelayed(this, 45000);
}
catch (Exception e)
{
e.toString();
}
}
};
/*
* Rebuilds the Tweets on UIThread
*/
public void updateUI(ArrayList<Tweet> tweets){
adapter = new TwitterAdapter(this, R.layout.twitter_listitem, tweets);
myListView.setAdapter(adapter);
}
The Item Adapter:
public class TwitterItemAdapter extends ArrayAdapter<Tweet> {
public TwitterItemAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
// TODO Auto-generated constructor stub
}
public static Bitmap getBitmap(String bitmapUrl) {
try {
URL url = new URL(bitmapUrl);
return BitmapFactory.decodeStream(url.openConnection() .getInputStream());
}
catch(Exception ex) {return null;}
}
}
The Twitter Adapter:
public class TwitterAdapter extends ArrayAdapter<Tweet>{
private ArrayList<Tweet> tweets;
public TwitterAdapter(Context context, int textViewResourceId, ArrayList<Tweet> tweets) {
super(context, textViewResourceId, tweets);
this.tweets = tweets;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.twitter_listitem, null);
}
Tweet tweet = tweets.get(position);
if (tweet != null) {
TextView username = (TextView) v.findViewById(R.id.username);
TextView message = (TextView) v.findViewById(R.id.message);
ImageView image = (ImageView) v.findViewById(R.id.avatar);
if (username != null) {
username.setText(tweet.username);
}
if(message != null) {
message.setText(tweet.message);
}
if(image != null) {
image.setImageBitmap(TwitterItemAdapter.getBitmap(tweet.image_url));
}
}
return v;
}
}
I suspect my Adapter is attempting to re-dowload each graphic when I'm scrolling
Yes.
getView() of TwitterAdapter is called on the main application thread. In there, you are calling getBitmap() (on a completely pointless TwitterItemAdapter class), which is downloading the image from the Internet.
There are probably two dozen implementations of a background image loader available, with varying degrees of sophistication, from an SDK sample to blog posts to whole app frameworks.
Related
I implemented my ListView using custom adapter extended from ArrayAdapter.
My problem is sometimes ListView is loaded slowly. That means a blank activity is loaded first without the ListView, then the ListView comes out. At worst case, I am prompted to "force closed or wait". I like to improve that slow loading as it is annoying to the user.
But sometimes, loading is fast and almost immediate.
But I like to make sure my ListView design is correct and the design does not have any problem with that slow loading. So that this discussion will be useful for other people who are facing the same problem as mine.
My ListView is designed as follow.
Each ListItem has three components, thumbnail image, ID text, and arrow image as shown in the figure attached .
In loading process of the ListView, (1)All ID text are retrieved from the database and populated into a ListArray List<String> listIDs
public class MyListFragment extends ListFragment implements OnItemClickListener {
dbHelper = new TrackerDBAdapter(getActivity());
dbHelpLatLong = new LatLogDBAdapter(getActivity());
dbHelpNotification = new NotificationDatabaseAdapter(getActivity());
dbHelper.open();
Cursor cursor = dbHelper.fetchAllTrackerInTheList();
listIDs = new ArrayList<String>();
activationStatus = new ArrayList<String>();
thisListFragmentContext = getActivity();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
listIDs.add(cursor.getString(1));
}
dbHelper.close();
(2)Then my custom list adapter is called.
adapter = new customList_Adaptor(thisListFragmentContext,
R.layout.list_row, listIDs, this);
}
That is the loading process inside my `ListFragment`.
(3) The following class is my custom ArrayAdapter and I implemented to load thumbnail ImageView using AsyncTask. My query are
(i)I still have retrieving ID text from database, and loading arrow image. Should I put those processes into AsyncTask as well?
(ii)If I need it, should I implement another AsyncTask or use the same AsyncTask used for thumbnail image loading?
(iii)Among these, which aspect of the program design I still can improve that is suspicious to my slow loading?
public class customList_Adaptor extends ArrayAdapter<String>{
protected static final int CAMERA_REQUEST = 0;
private TrackerDBAdapter dbHelper;
private Context context;
private List<String> listIDs = new ArrayList<String>();
private List<String> activationState = new ArrayList<String>();
public MyListFragment mMyListFragment;
public customList_Adaptor(Context context, int textViewResourceId,
List<String> objects, List<String> activationStatus, MyListFragment mMyListFragment) {
super(context, textViewResourceId, objects);
this.setContext(context);
this.listIDs = objects;
this.activationState = activationStatus;
this.mMyListFragment= mMyListFragment;
dbHelper = new TrackerDBAdapter(context);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
if(listIDs != null)
return listIDs.size();
else
return 0;
}
#Override
public String getItem(int arg0) {
// TODO Auto-generated method stub
if(listIDs != null)
return listIDs.get(arg0);
else
return null;
}
#Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
ViewHolder viewHolder=new ViewHolder();
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(vi==null){
vi = inflater.inflate(R.layout.list_row, parent, false);
viewHolder.id=(TextView)vi.findViewById(R.id.title);
viewHolder.thumbnailImage=(ImageView)vi.findViewById(R.id.list_image);
viewHolder.activationStatus = (TextView)vi.findViewById(R.id.activated);
//lazy load image
BitmapWorkerTask task = new BitmapWorkerTask(viewHolder.thumbnailImage);
task.execute(position);
viewHolder.arrow=(ImageView)vi.findViewById(R.id.list_arrow);
vi.setTag(viewHolder);
}
else
viewHolder=(ViewHolder) vi.getTag();
viewHolder.thumbnailImage.setOnClickListener(new onMyClick(position));
// Setting all values in listview
viewHolder.id.setText(listIDs.get(position));
if(activationState.get(position).equals("Not activated yet")){
viewHolder.activationStatus.setText(activationState.get(position));
viewHolder.activationStatus.setTextColor(android.graphics.Color.GRAY);
}
else if(activationState.get(position).equals("Activated"))
viewHolder.activationStatus.setText("");
return vi;
}
public class onMyClick implements OnClickListener {
private final int pos;
public onMyClick(int pos) {
this.pos = pos;
}
#Override
public void onClick(View v) {
MyListFragment.clickedimageView = (ImageView) v.findViewById(R.id.list_image);
mMyListFragment.imagepos(pos);
}
}
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
//Lazy image update
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(Integer... params) {
setData(params[0]);
Bitmap bitmap = null;
dbHelper.open();
Cursor mCursor = dbHelper.getImagebyIDnumber(getData());
byte[] img_bytes = mCursor.getBlob(13);
bitmap = BitmapFactory.decodeByteArray(img_bytes, 0, img_bytes.length);
dbHelper.close();
return bitmap;
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
}
public class ViewHolder {
TextView id;
TextView activationStatus;
ImageView thumbnailImage;
ImageView arrow;
}
I did a few things to make it faster in loading the app.
I am not sure which one is the solution.
(1) I load all data from sql database including text and thumbnail images using AsyncTask.
(2) I change thumbnail image format from png to jpg.
(3) Then I clear the cache manually.
The app looks like faster in loading, but sometimes it is still slow. Most of the times, it is faster than before.
I am still making improvement to my app.
Thanks
I'm having some troubles with correct identification of items in a ListView.
There are 4 classes that matter, it's a lot of code so at first I'm going to explain the logic of those classes.
Enter the ListActivity and initialize its ListView
execute an AsyncTask that downloads JSON response from the server, parses it, populates the ListView with Objects and sets the adapter while showing a ProgressDialog
the PlaylistItem class includes methods which simply get the data from a single JSONObject. It is used to parameterize the ArrayList with its Objects
after the AsyncTask is done the list is filled with items and looks like |Button| Artist(TextView) - Title(TextView)
UPDATE
resolved 1st issue but still can't figure out what's wrong with buttons
2). I set an OnClickListener to my buttons in the Adapter's getView() method. To find out if the button is identified correctly I did nothing but just changed its background. BUT a click on a certain button forces the background of every 11th or 12th button to be changed. Can't figure it out so far.
I can't proceed to getting url and streaming audio until those problems are resolved, so any help is greatly appreciated. My classes go below, please ask if something appears unclear.
AudioList
public class AudioList extends ListActivity {
private ListView lv;
private PlaylistLoader loader;
private AudioListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_list);
init(); // initialize the ListView
/*--- populate the list with user's audio in case network connection is available ---*/
loader = new PlaylistLoader(this, lv, adapter);
if (Utils.isNetworkAvailable(this)) {
loader.execute();
} else {
APP_CONSTANTS.NO_DATA_CONNECTION(this);
}
}
#Override
protected void onResume() {
super.onResume();
lv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
Toast.makeText(getApplicationContext(), Integer.toString(arg2),
Toast.LENGTH_SHORT).show();
}
});
}
private void init() {
lv = getListView();
lv.setTranscriptMode(0x00000000);
lv.setDividerHeight(1);
lv.setSmoothScrollbarEnabled(true);
lv.setVerticalFadingEdgeEnabled(true);
}
PlaylistLoader
public class PlaylistLoader extends AsyncTask<Void, Void, Void> {
private JSONObject usersPlaylist, singleJSONItem;
private JSONArray responseJSONArray;
private ListView lv;
private ArrayList<PlaylistItem> playlist;
private Activity a;
private PlaylistItem audioList;
private SharedPreferences prefs;
private ProgressDialog pd;
AudioListAdapter adapter;
public PlaylistLoader(Activity a, ListView lv, AudioListAdapter adapter) {
this.lv = lv;
this.a = a;
this.adapter = adapter;
}
#Override
protected Void doInBackground(Void... arg0) {
/*--- create new ArrayList of PlaylistItem Objects ---*/
playlist = new ArrayList<PlaylistItem>();
/*--- get the preferences using context of calling activity ---*/
prefs = PreferenceManager.getDefaultSharedPreferences(a);
try {
/*--- download the response JSONObject from server // access_token and
* user_id come from activity's defaultSharedPreferences ---*/
usersPlaylist = Utils.retrieveJsonObjectFromUrl(new URL(
APP_CONSTANTS.REQUEST_AUDIO_LIST(prefs)), a);
/*--- get the response array from received object ---*/
responseJSONArray = usersPlaylist.getJSONArray("response");
/*--- populate the ArrayList with Objects from the response array ---*/
for (int i = 0; i < responseJSONArray.length(); i++) {
singleJSONItem = responseJSONArray.getJSONObject(i);
audioList = new PlaylistItem(singleJSONItem);
playlist.add(audioList);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
pd = new ProgressDialog(a);
pd.setTitle("Please wait");
pd.setMessage("Retrieving audio list...");
pd.show();
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
lv.setVisibility(View.VISIBLE);
pd.dismiss();
/*--- set the adapter passed in constructor as an adapter for passed ListView ---*/
adapter = new AudioListAdapter(a, R.layout.playlist_item, playlist);
lv.setAdapter(adapter);
}
}
AudioListAdapter
public class AudioListAdapter extends ArrayAdapter<PlaylistItem> {
private PlaylistItem pl;
private Context context;
private int layoutResourceId;
private PlaylistItem aud;
private ArrayList<PlaylistItem> data = null;
public AudioListAdapter(Context context, int layoutResourceId,
ArrayList<PlaylistItem> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
#Override
public PlaylistItem getItem(int position) {
return super.getItem(position);
}
#Override
public int getCount() {
return data.size();
}
#Override
public int getPosition(PlaylistItem item) {
return super.getPosition(item);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
pl = new PlaylistItem();
aud = getItem(position);
if (convertView == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
convertView = inflater.inflate(layoutResourceId, parent, false);
pl.btnPlay = (Button) convertView.findViewById(R.id.btn_list_play);
pl.imgSaved = (ImageView) convertView
.findViewById(R.id.img_list_audio_saved);
pl.tvArtist = (TextView) convertView
.findViewById(R.id.tvListItemArtist);
pl.tvTitle = (TextView) convertView
.findViewById(R.id.tvListItemSong);
convertView.setTag(pl);
} else {
pl = (PlaylistItem) convertView.getTag();
pl.btnPlay.setBackgroundResource(R.drawable.list_button_play);
}
pl.tvArtist.setText(aud.getArtist() + " " + "-");
pl.tvTitle.setText(aud.getTitle());
pl.btnPlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
/*--- vibrate if this option is enabled in the preferences ---*/
if (APP_CONSTANTS.isHapticFeedbackEnabled(getContext())) {
APP_CONSTANTS.doVibrate(getContext());
}
pl.btnPlay.setBackgroundResource(R.drawable.list_button_pause);
}
});
return convertView;
}
PlayListItem
public class PlaylistItem {
private String artist, title;
private JSONObject obj;
public Button btnPlay;
public TextView tvArtist, tvTitle;
public ImageView imgSaved;
public int duration;
public int audio_id;
public String url;
/*--- the constructor takes a single JSONObject from the response array ---*/
public PlaylistItem(JSONObject obj) {
this.obj = obj;
}
public PlaylistItem() {
// default constructor
}
/*--- the methods below return values by key from the passed JSONObject ---*/
public String getArtist() {
try {
artist = obj.getString("artist");
} catch (JSONException e) {
e.printStackTrace();
}
return artist;
}
public String getTitle() {
try {
title = obj.getString("title");
} catch (JSONException e) {
e.printStackTrace();
}
return title;
}
public int getID() {
try {
audio_id = obj.getInt("aid");
} catch (JSONException e) {
e.printStackTrace();
}
return audio_id;
}
public String getURL() {
try {
url = obj.getString("url");
} catch (JSONException e) {
e.printStackTrace();
}
return url;
}
}
Edit:
Try this
Take a custom Selector in your drawable
button_play.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/pause_button"
android:state_selected="true" />
<item android:drawable="#drawable/play_button" />
</selector>
Modifty your adapter like this
public class AudioListAdapter extends ArrayAdapter<PlaylistItem> {
private PlaylistItem pl;
private Context context;
private int layoutResourceId;
private PlaylistItem aud;
private ArrayList<PlaylistItem> data = null;
Button previous;
public AudioListAdapter(Context context, int layoutResourceId,
ArrayList<PlaylistItem> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
previous=new Button(context);
this.context = context;
this.data = data;
}
....
....
#Override
public View getView(int position, View convertView, ViewGroup parent) {
pl = new PlaylistItem();
aud = getItem(position);
if (convertView == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
convertView = inflater.inflate(layoutResourceId, parent, false);
pl.btnPlay = (Button) convertView.findViewById(R.id.btn_list_play);
pl.btnPlay.setBackGroundResouce(R.drawable.button_play); //you can set here or in xml
pl.imgSaved = (ImageView) convertView
.findViewById(R.id.img_list_audio_saved);
pl.tvArtist = (TextView) convertView
.findViewById(R.id.tvListItemArtist);
pl.tvTitle = (TextView) convertView
.findViewById(R.id.tvListItemSong);
convertView.setTag(pl);
} else {
pl = (PlaylistItem) convertView.getTag();
}
pl.tvArtist.setText(aud.getArtist() + " " + "-");
pl.tvTitle.setText(aud.getTitle());
pl.btnPlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
/*--- vibrate if this option is enabled in the preferences ---*/
if (APP_CONSTANTS.isHapticFeedbackEnabled(getContext())) {
APP_CONSTANTS.doVibrate(getContext());
}
//for some reason, the background gets changed for every 11th or 12th button in the list
Button current=((Button)v);
current.setSelected(true);
previous.setSelected(false);
previous=current;
}
});
return convertView;
}
}
The reason why your button and listitem not clickable is because Your list have a focus item button, so you need to setFocusable=false for your button.
Try setting focusable=false for your button in the xml. If it is not worked for you than do like this
In your row xml file
1.set focusable=true for your button.
2.In the same set android:descendantFocusability="blocksDescendants" for your parent item.(i.e parent layout in which your views lie).
In getView() method after setting the onclickListener for the button, set focusable false for the button.
It will work for sure. I hope this will help you..
BUT a click on a certain button forces the background of every 11th or 12th button to be changed. Can't figure it out so far.
You are fighting the way ListViews recycle the row layouts.
Think of it this way: if you have a ListView with 10,000 rows but can only fit 9 of them on the screen, then it doesn't make sense to create 10,000 unique layouts. This just waste resources, instead ListView only creates ~10 layouts and reuses them.
Solution: return each row to it's default state when it is reused. In getView() add:
} else {
pl = (PlaylistItem) convertView.getTag();
pl.btnPlay.setBackgroundResource(R.drawable.list_button_play);
// I guessed at the resource's name ^^^^^^^^^^^^^^^^
}
(Also you can make a few small changes to speed up your code. For instance, you only need one OnClickListener since they all contain the same code, make this a class variable and pass this to each play Button. There are more.)
I'm putting together an app which has a listview in a relativeLayout with a couple LinearLayouts inside of it. So I have my adapter, and a function to get the items into the list. This function is only called in the OnCreate function of the apps main process and is called in a runnable on a thread. Can you all see any reason why this thing might be calling adding into my array so many times? (the for loop gets called thousands of times when I've only added one thing to the array.
In the OnCreate method for the main process:
array = new ArrayList<Item>(); //The misbehaving array
ListView lv = (ListView)findViewById(R.id.ItemList);
this.m_adapter = new ItemAdapter(this, R.layout.Itemlist_item, array);
lv.setAdapter(this.m_adapter);
viewItems = new Runnable(){
#Override
public void run() {
getItems();
}
};
Thread thread = new Thread(null, viewItems, "ListThread");
thread.start();
The getItems function and the second Runnable:
private void getItems(){
try{
//Try Some Stuff
}
catch (Exception e) {
Log.e("BACKGROUND_PROC", e.getMessage());
}
runOnUiThread(returnRes);
}
private Runnable returnRes = new Runnable() {
#Override
public void run() {
if(array != null && array.size() > 0){
listAdapter.notifyDataSetChanged();
//This loop is where I'm seeing the problem
for(int i=0;i<array.size();i++) {
listAdapter.add(array.get(i));
}
}
}
};
And the list adapter:
public class ItemAdapter extends ArrayAdapter<Item> {
public ArrayList<Item> items;
public ItemAdapter(Context context, int textViewResourceId, ArrayList<Item> items) {
super(context, textViewResourceId, items);
this.items = items;
}
#Override
public View getView(int position, View conView, ViewGroup parent) {
View v = conView; //The content view
if (v == null){
LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.itemlist_item, null);
}
Item o = items.get(position);
if (o != null){
TextView name = (TextView)v.findViewById(R.id.item_name);
if(name != null){
name.setText("name:"+o.getItemName());
}
}
return v;
}
}
Any help is MUCH appreciated.
Thanks,
-Chris
The problem is that ListView and other such UI widgets that work with an Adapter do not allow the contents of the adapter to change unexpectedly on them. You need to tell the ListView about the change you make before it next tries to interact with your adapter. If you allow another thread to modify the adapter, or modify it on the main thread but don't tell ListView about the change before allowing anything else to happen, you will randomly (due to races) get exceptions thrown by ListView about the adapter changing unexpectedly.
Instead, something like this should work (from HackBod's post here):
public class MyAdapter extends BaseAdapter<Item> {
private ArrayList<Item> mItems;
public int getCount() {
return mItems != null ? mItems.size() : 0;
}
public MyItem getItem(int position) {
return mItems.get(i);
}
/* ... */
}
and then populate the ListView with,
private Handler mHandler = new Handler();
private void setDataFromThread(ArrayList<Item> data) {
// Enqueue work on mHandler to change the data on
// the main thread.
mHandler.post(new Runnable() {
#Override
public void run() {
mItems = newData;
notifyDataSetChanged();
}
});
}
I try to implement homeline pagination via Twitter4j to pull-to-refresh list from Chris Banes. However, I have problems of that how to realize it. I have some notes how it should work but it isn't so, my pull refresh list doesn`t refresh. Have any ideas how to upload next 40 tweet to list on refresh?
Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tweetlist);
initializeVars();
paging = new Paging(1, 40);
try {
ListView actualListView = pullToRefreshView.getRefreshableView();
tweets = twitter.getHomeTimeline(paging);
tweetAdapter = new TweetListAdapter(this, R.layout.customtweetlist, tweets);
actualListView.setAdapter(tweetAdapter);
} catch (TwitterException e) {
e.printStackTrace();
}
}
public void onRefresh() {
new GetDataTask().execute();
}
private class GetDataTask extends AsyncTask<Void, Void, List<Status>> {
protected List<twitter4j.Status> doInBackground(Void... params) {
paging.setPage(2);
try {
tweets = twitter.getHomeTimeline(paging);
} catch (TwitterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return tweets;
}
protected void onPostExecute(List<twitter4j.Status> result) {
tweetAdapter.notifyDataSetChanged();
// Call onRefreshComplete when the list has been refreshed.
pullToRefreshView.onRefreshComplete();
super.onPostExecute(result);
}
}
My TweetAdapter
public class TweetListAdapter extends ArrayAdapter<Status> {
private final Context context;
private final List<Status> values;
public TweetListAdapter(Context context,int textViewResourceId, List<Status> tweets) {
super(context, textViewResourceId, tweets);
this.context = context;
this.values = tweets;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.customtweetlist, parent, false);
TextView textView = (TextView) rowView.findViewById(R.id.tvText);
ImageView imageView = (ImageView) rowView.findViewById(R.id.ivImage);
Status tweet = values.get(position);
textView.setText(tweet.getText());
// imageView.setImageBitmap(getBitmap(tweet.getProfileImageUrl()));
rowView.invalidate();
return rowView;
}
public static Bitmap getBitmap(String bitmapUrl) {
try {
URL url = new URL(bitmapUrl);
return BitmapFactory.decodeStream(url.openConnection() .getInputStream());
}
catch(Exception ex) {return null;}
}
}
So what's happening is that you're updating the tweets variable, which will update the Adapter's this.values value, HOWEVER that list is not what the adapter is using to render the list (if you want to know why, just dig into the ArrayAdapter code). The easiest way to fix the problem (and prevent further confusion) is to extend from BaseAdapter. It's a bit more work for you but you will have full control of what the adapter does (and will understand what's going on better).
To clean the code some more, you should also add a metod to your adapter that updates the value of this.values, you shouldn't depend on it referring to the same list as tweets.
In my application, to populating a ListView i am using custom adapter, because one listitem consists of 3 TextViews and 1 ImageView.
Everytime images are fetched from url.
So when i am launching this activity its taking much time,because its downloading all images then populating the lists.
So i want without images list should populate first having only Textviews and after that only images should come.
How can i do that?
By loading your images with an AsyncTask
example directly from documentation:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
you have to create a lazy image loader using async task.
by doing that, all your list view will be populated. and when the images are fetched, they are updated into the list views asynchronously.
Here is a link - http://iamvijayakumar.blogspot.com/2011/06/android-lazy-image-loader-example.html
You can use lazy load images like this:
https://github.com/thest1/LazyList
Lazy load of images in ListView
The basic Idea is to have a loading image already within your app.
Then use an asyncTask or a thread to load image.
Some code to start with :
Adapter
public class ImageAdapter extends BaseAdapter {
private static final String TAG = "Image Adapter";
int mGalleryItemBackground;
private Context mContext;
private GridView mView;
/** URL-Strings to some remote images. */
private String[] mRemoteImagesURL ;
private Bitmap[] loadedImages;
public ImageAdapter(Context c,String[] remoteImagesURL,GridView v) {
mContext = c;
TypedArray attr = mContext.obtainStyledAttributes(R.styleable.HelloGallery);
mGalleryItemBackground = attr.getResourceId(R.styleable.HelloGallery_android_galleryItemBackground, 0);
attr.recycle();
mView = v;
mRemoteImagesURL=remoteImagesURL;
loadedImages = new Bitmap[mRemoteImagesURL.length];
}
public int getCount() {
return mRemoteImagesURL.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.gallery_item, null);
}
ImageView imageView = (ImageView) convertView.findViewById(R.id.FrontImageView);
/* when image is already down-loaded then load that image */
if(loadedImages[position]!=null)
imageView.setImageBitmap(loadedImages[position]);
else
imageView.setImageResource(R.drawable.loading);
imageView.setBackgroundResource(mGalleryItemBackground);
return convertView;
}
public void loadImage(int position){
Bitmap bm;
try {
/* Open a new URL and get the InputStream to load data from it. */
URL aURL = new URL(mRemoteImagesURL[position]);
URLConnection conn = aURL.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
/* Buffered is always good for a performance plus. */
BufferedInputStream bis = new BufferedInputStream(is);
/* Decode url-data to a bitmap. */
bm = BitmapFactory.decodeStream(bis);
bis.close();
is.close();
loadedImages[position] =bm;
} catch (Exception e) {
Log.e(TAG, "Remote Image Load Exception"+e);
}
}
public void setLoadedImage(int position)
{
Log.d(TAG, "Position "+position);
View childView= mView.getChildAt(position);
if(loadedImages[position]!=null && childView != null)
{
ImageView imageView= (ImageView) childView.findViewById(R.id.FrontImageView);
imageView.setImageBitmap(loadedImages[position]);
}
}
}
private void updateImagesAsThread() {
Thread t = new Thread()
{
public void run()
{
try {
for(int i=0;i<imageAdapter.getCount();i++)
{
imageAdapter.loadImage(i);
listAdapterHandler.sendEmptyMessage(i);
}
}
catch (Exception e) {
// TODO: handle exception
Log.e(TAG,"UpdateImageAsThread "+e);
}
}
};
t.start();
}
private Handler listAdapterHandler = new Handler()
{
#Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case -1:
Log.d(TAG, "here in the handle...");
break;
default:
Log.d(TAG, "here in the handle default...");
imageAdapter.setLoadedImage(msg.what);
//imageAdapter.notifyDataSetChanged();
break;
}
}
};