I have a listview with each item having a ProgressBar on top. My own created thread running and downloading the data in background and have to show download progress in getView part for each item, but i'm not able to update the setProgressbar in getView method of adapter.
But when user scrolls up and down(away from particular view) then its update the progress and freezes and not increasing based on download percentage.
My thread code
downloading = Ion.with(LoadTumblrActivity.this)
.load(item.getGifUrl())
// can also use a custom callback
// can also use a custom callback
.progress(new ProgressCallback() {
#
Override
public void onProgress(long downloaded, long total) {
float progressVal = ((float) downloaded / (float) total);
item.setProgress((int)(100 * progressVal));
}
})
.write(
new File(Environment.getExternalStorageDirectory().getPath() + "/sdcard" + fileName))
.setCallback(new FutureCallback < File > () {
#
SuppressLint("SdCardPath")# Override
public void onCompleted(Exception e, File file) {
{
if (e != null) {
Toast.makeText(LoadTumblrActivity.this, "Error downloading file", Toast.LENGTH_LONG).show();
System.out.println(e);
return;
}
}
}
});
My BaseAdapter
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
// reuse views
if (rowView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
rowView = inflater.inflate(R.layout.feed_item, parent, false);
// configure view holder
ViewHolder viewHolder = new ViewHolder();
viewHolder.progressBar = (ProgressBar) rowView.findViewById(R.id.progress);
rowView.setTag(viewHolder);
}
holder.progressBar.setProgress(item.getProgress());
}
Your Problem is that you call
holder.progressBar.setProgress(item.getProgress());
inside your getView and getView is only called for the items visible on the Screen. That's why your ProgressBar only updates when you scroll for the Items which Comes to Screen due to scrolling! You Need to call this line outside of the getView for example in an AsyncTask when you call publishProgress(); in onProgressUpdate to give user a real experience of how Long it takes till the item appears/finished downloading.
Another Approach would be to set the progressBar to intermediate= "false" and only Show a spinning progressbar! ;)
Hope it helps
Use runOnuiThread to update progress bar:
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
progressDialog.setMessage(ii+" Apps Loaded...");
ii++;
}
});
Source code from Helper+
Related
I have a list view and items have an image button and a ProgressBar.
I what the ProgressBar to be updated(Progress set) from a Thread.
Whenever getView in list adapter be invoked list view items will be instantiated
and the reference to the ProgressBar will change.so i put the last reference of ProgressBar in a HashMap in the adapter and when i want to update ProgressBar i use it as the last reference.
But it works differently depending on number of items.when the size of items is too much and list view is in scroll mode it works properly but with one or two items that fit in one page without scrolling, ProgressBar will get stuck but with selecting one item, ProgressBar continues with no problem.
It seems after last getView and next notifyDataSetChanged() that may be caused by list selection or Scrolling, ProgressBar's reference had been changed out of getView() so i can not have the last one(or no,i don't know!).
I also tried notifyDataSetChanged() on every setProgress but it is not efficient and also blocks imageButton OnClickListener.
here is adapter code:
public class ChatAdapter extends BaseAdapter {
private List<Message> messagesItems;
private Map<Message,ProgressWheel> ProgressBars =new HashMap<>();//to have last reference to the ProgressBar corresponding to each listItem
public Map<Message,Boolean> showProgress=new HashMap<>();//for some controlling
public Map<Message,Boolean> active=new HashMap<>();//for some controlling
public Map<Message,View> layouts=new HashMap<>();//for some controlling
public Map<Message,View> views=new HashMap<>();//for some controlling
final static android.os.Handler mHandler=new android.os.Handler();
#SuppressLint("InflateParams")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Message currentMessage = (Message) getItem(position);
LayoutInflater mInflater = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final ViewHolder viewHolder;
if(convertView==null) {
convertView = mInflater.inflate(R.layout.chat_row_right, parent, false);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
}else {
viewHolder=(ViewHolder) convertView.getTag();
}
ImageButton sendButton= viewHolder.SkipButton;
View progressLayout= convertView.findViewById(R.id.proressLayout);
//ProgressWheel is a custom progressBar
ProgressWheel progressWheel = viewHolder.progressBar;
ProgressBars.put(currentMessage, progressWheel);
layouts.put(currentMessage,progressLayout);
views.put(currentMessage, convertView);
if(showProgress.get(currentMessage)!=null) {
if (showProgress.get(currentMessage)) {
progressLayout.setVisibility(View.VISIBLE);
sendButton.setTag(currentMessage);
//ProgressBarHandler.start just runs a thread and increments progress in background by calling setDelayProgress from this class
if (active.get(currentMessage) == null) {
active.put(currentMessage, true);
ProgressBarHandler progressBarHandler = new ProgressBarHandler(currentMessage, this,this);
progressBarHandler.start(10);
notifyDataSetChanged();
} else if (active.get(currentMessage) == false) {
active.put(currentMessage, true);
ProgressBarHandler progressBarHandler = new ProgressBarHandler(currentMessage, this,this);
progressBarHandler.start(10);
notifyDataSetChanged();
}
} else {
progressLayout.setVisibility(View.GONE);
}
}else
progressLayout.setVisibility(View.GONE);
return convertView;
}
public void setDelayProgress(final Message message, final int progress, final long durationMillis){
mHandler.post(new Runnable() {
#Override
public void run() {
ProgressBars.get(message).setProgress(progress);
// notifyDataSetChanged();// is cause the code extremely inefficient But working !
}
});
}
final static class ViewHolder {
public TextView message;
public ProgressWheel progressBar;
public ImageButton SkeepButton;
public ViewHolder(View convertView){
this.message = (TextView) convertView
.findViewById(R.id.chatMessage);
this.progressBar = (ProgressWheel) convertView
.findViewById(R.id.delayProgress);
this.SkeepButton = (ImageButton) convertView
.findViewById(R.id.sendb);
}
}
}
Your suggestions would be appreciated,Thanks
I'd like to change the locale on my app (runtime).It works fine so far. The only thing left is refreshing the current activity.
The documentation says that updating the locale Configuration should restart the running activity (same behavior as the screen orientation) but it does restart it in my case. (I did not use the android:configChanges="locale" to prevent this behavior).
Here are the key parts :
I have my activity :
public class SettingsActivity extends Activity {
ExpandableListAdapter listAdapter;
//....
}
An Adapter where I bind a click (inside the getChildView function) :
public View getChildView(final int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
//...
convertView = inflater.inflate(R.layout.list_item, null);
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
I18N.setLang(context, "fr");
};
});
}
And the setLang method inside my I18N class :
public class I18N {
public static void setLang(final Context context, String langCode)
{
if (langCode.equals(""))
return;
try {
final Locale newLocale;
final String[] languageRegion = langCode.split("\\-(r)?");
I18N.saveLocale(context, langCode);
newLocale = languageRegion.length > 1 ? new Locale(languageRegion[0], languageRegion[1]) : new Locale(languageRegion[0]) ;
current = newLocale;
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
android.content.res.Configuration conf = res.getConfiguration();
conf.locale = new Locale(languageRegion[0]);
res.updateConfiguration(conf, dm);
}
catch (Exception e) {
Log.e("exception", e.getMessage() + e.getClass());
}
}
}
I have tried this. It kind of works but I don't like this idea of refreshing the activity manually.
The main issue is that despite using the flag FLAG_ACTIVITY_NO_HISTORY, the history (using the back button of the mobile device) does not work properly (each click on the list to change the language still create an history) :
Intent refresh = new Intent(context, context.getClass());
refresh.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY|Intent.FLAG_ACTIVITY_NO_ANIMATION);
context.startActivity(refresh);
What am I doing wrong ? How can I either make the updateConfiguration restarts the activity or make the manual intent not create an history ?
Instead of relaunching the activity, you want to setContentView() once again after you change locale to change activity layout, or reinflate the fragment
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
I18N.setLang(context, "fr");
//setContentView(R.layout.act_layout); // for activity
convertView = inflater.inflate(R.layout.list_item, null)
};
});
edit
Your convertView would be final so you can refresh it through a method
protected View refresh()
{
//inflate
View convertView = inflater.inflate(R.layout.list_item, null);
//setup ui controls
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
I18N.setLang(context, "fr");
refresh();
};
});
return convertView ;
}
public View getChildView(final int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent)
{
return refresh();
}
I have added android:noHistory="true" to my settings activity in the AndroidManifest.xml
This way, i can go back to the main screen after changing the language.
I'm trying to fix an issues that I've seen previously and fixed on iOS, but am unable to fix on android. In iOS, I used the SDWebImage library to download and cache images. However, when scrolling through a long list of cells, the images would come up in the wrong cells. I was able ot fix this by doing the following:
#property (weak) id <SDWebImageOperation> imageOperation;
...
- (void)setFriend:(TAGUser *)friend {
...
self.imageOperation = [[SDWebImageManager sharedManager] downloadWithURL:[NSURL URLWithString:friend.profileImageUrl] options:SDWebImageRetryFailed
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if(!error) {
self.profileImageView.image = image;
}
}];
...
}
- (void)prepareForReuse {
[super prepareForReuse];
if (self.imageOperation) {
[self.imageOperation cancel];
}
self.imageOperation = nil;
}
On Android, I am using the Picaso Libray and trying to achieve the same result like so:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (vi == null) {
vi = inflater.inflate(R.layout.friend_row, parent, false);
ViewHolder holder = new ViewHolder();
holder.friendImage = (ImageView) vi.findViewById(R.id.friend_image);
}
final ViewHolder holder = (ViewHolder) vi.getTag(); //Bad naming convention in my project I know, but it's a built-in method
//THIS SHOULD IN THEORY CANCEL THE REQUEST OF THE OLD FRIEND URL
Picasso.with(mActivity.getBaseContext()).cancelRequest(holder.friendImage);
holder.friendImage.setImageDrawable(mActivity.getResources().getDrawable(R.drawable.background_circle));
final TagUser user = (TagUser) getItem(position);
Picasso.with(mActivity.getBaseContext()).load(user.getProfileUrl()).transform(new RoundedTransformation(ViewUtility.convertPxToDp(mActivity, 23), 0)).fit().into(holder.friendImage,
new Callback() {
#Override
public void onSuccess() {
holder.friendInitials.setVisibility(View.INVISIBLE);
}
#Override
public void onError() {
holder.friendInitials.setVisibility(View.VISIBLE);
}
});
}
Even with cancelRequeset getting called, the profile images are still getting mismatched. Any help would be greatly appreciated!
You don't need to call cancel. Picasso automatically sees when views are being re-used and will cancel old downloads for that image view.
I would also recommend using Picasso's .placeholder API for the background circle.
You seem to be missing a call to setTag when the layout is inflated. Hopefully that's just an error in copying to the post.
Lastly, create the RoundedTransformation once and re-use the same instance for all calls to Picasso.
In the end your code should look like this:
private final Transformation roundTransform;
// Set the following in constructor:
// roundTransform = new RoundedTransformation(ViewUtility.convertPxToDp(mActivity, 23), 0)
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (vi == null) {
vi = inflater.inflate(R.layout.friend_row, parent, false);
ViewHolder holder = new ViewHolder();
vi.setTag(holder)
holder.friendImage = (ImageView) vi.findViewById(R.id.friend_image);
}
final ViewHolder holder = (ViewHolder) vi.getTag(); //Bad naming convention in my project I know, but it's a built-in method
final TagUser user = (TagUser) getItem(position);
Picasso.with(mActivity)
.load(user.getProfileUrl())
.placeholder(R.drawable.background_circle)
.transform(roundTransform)
.fit()
.into(holder.friendImage,
new Callback() {
#Override
public void onSuccess() {
holder.friendInitials.setVisibility(View.INVISIBLE);
}
#Override
public void onError() {
holder.friendInitials.setVisibility(View.VISIBLE);
}
});
}
I would advice to use Volley by Google. I have used it in many projects and it never gave mismatched results even in case I had nearly 1000 images in 3 column gridView. Volley
I have a trouble with clickable elements, for example I can click two items in ListView at the same time with two fingers.
code for listview smth like this:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, null);
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//open some Activity here
}
}
}
}
So with two fingers Activty opens twice.
The same behaviour if I click to several buttons.
The same if click on button and some tab, and so on...
It is some global solution without using boolean flag?
You'll need to set an OnItemClickListener via setOnItemClickListener(...) in order to get the correct clicking behavior.
The easy way of handle this is:
long clickedTime;
#Override
public void onItemClick(View v) {
//open some Activity here
if (System.currentTimeMillis() - clickedTime > 100) {
clickedTime = System.currentTimeMillis();
// ... your stufff
}
}
but you should be using OnItemClickListener
since you look to a global solution, you can add :
android:launchMode="singleTop"
to the properties of your activity in the manifest file,
it allow the activity to be launched only one time.
I'm talking about the activity that you aim to open in onClick of course
I am using following code as Cusotm Array adapter of a ListView in android
In View new MyAsyncTask(url, callback); will be run again and again, How can i make it so that the query to the server will be performed only once.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
final Question question = quesions.get(position);
final OpenionUser myUser = question.getUser();
View view = convertView;
ViewHolder viewHolder = new ViewHolder();
if (convertView == null)
{
view = inflator.inflate(R.layout.question_adapter_layout, parent, false);
viewHolder.firstPhotoPercent = (TextView) view.findViewById(R.id.firstPhotoProgressText);
viewHolder.secondPhotoPercent = (TextView) view.findViewById(R.id.secondPhotoProgressText);
viewHolder.comments = (LinearLayout) view.findViewById(R.id.commentsListView);
viewHolder.profilePic = (ImageButton) view.findViewById(R.id.profile);
viewHolder.leftPic = (ImageButton) view.findViewById(R.id.photo1);
viewHolder.rightPic = (ImageButton) view.findViewById(R.id.photo2);
viewHolder.firstPhotoBg = (RelativeLayout) view.findViewById(R.id.firstPhotoProgress);
viewHolder.secondPhotoBg = (RelativeLayout) view.findViewById(R.id.secondPhotoProgress);
view.setTag(viewHolder);
}
else
viewHolder = (ViewHolder) view.getTag();
// //imageLoader.displayImage(myUser.getProfilePicture(), viewHolder.profilePic, options);
//
viewHolder.username.setText(myUser.getUserName());
viewHolder.question.setText(question.getQuestion());
imageLoader.displayImage(question.getRightThumbnailLink(), viewHolder.rightPic, options);
imageLoader.displayImage(question.getLeftThumbnailLink(), viewHolder.leftPic, options);
String url = String.format(Constants.GET_QUESTION_COMMENTS,
question.getId(),
0,
2);
ResponseCallback callback = new ResponseCallback()
{
#Override
public void onSuccess(HttpResponse response)
{
try {
JSONObject obj = new JSONObject(Utilities.convertStreamToString(response.getEntity().getContent()));
JSONArray array = obj.getJSONArray("comments");
ArrayList<Comment> comments = new ArrayList<Comment>();
for (int i = 0; i < array.length(); i ++)
{
Comment comment = Utilities.getCommentFromJSON(array.getJSONObject(i));
comments.add(comment);
}
addFeaturedViews(comments, viewHolder.comments);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(HttpResponse exception)
{
}
};
new MyAsyncTask(url, callback);
"there is absolutely no guarantee on the order in which getView() will be called nor how many times" (Romain Guy # here). This method is called for example, when an element goes off-screen and then appears again after scrolling the ListView. So you should never make a call to your server from getView: instead, you should execute your asynctask outside the adapter (in a fragment or activity, for example, before instantiating the adapter), and then call notifyDataSetChanged on your list.
you could try this:
if(position==0)
{
new MyAsyncTask(url, callback);
}
*Modifying the answer after seeing the information given by Dhanesh Budhrani in the comment below *
If you want to run new MyAsyncTask(url, callback); only once, you could try this:
if(firsttime)
{
firsttime = false;
new MyAsyncTask(url, callback);
}
here firsttime is a boolean variable initialized to true in the adapter's constructor.
As already mentioned in the comments, the adapter's task is to provide views for the ListView to render. You should handle the data requests in a different class, provide the data to the Adapter and then call notifyDataSetChanged on the ArrayAdapter.
Since the adapter class is directly interacting with ListView, the correct way is not to call any threads directly on it. As you will scroll your ListView/View, the getView() method will be called up again and again and AsyncTask will be called up hindering the smooth flow of ListView.
In order to deal with the situation, call the AsyncTask in your Activity class and as soon as the result is fetched in onSuccess(), define the CustomAdapterClass and call the listView.setAdapter(customAdapter) in it and pass the data in ArrayList as a Constructor of CustomArrayAdapter(ArrayList arrayListJsonResponse) and inflate the data.
Hope it helps. Thanks.