I'm Settings up a Custom ListView.
The pull-to-refresh feature comes straight from https://github.com/chrisbanes/Android-PullToRefresh
The ListView displayes Images, so i created a custom Adapter:
class mAdapter extends BaseAdapter{
public mAdapter(Context context){
// nothing to do
}
#Override
public int getCount() {
return mValues.size();
}
#Override
public Object getItem(int position) {
return mValues.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public boolean areAllItemsEnabled()
{
return false;
}
#Override
public boolean isEnabled(int position)
{
return false;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if(v == null){
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.list_item, null);
}
ImageView iv = (ImageView) v.findViewById(R.id.imageView);
if(iv != null){
displayImageInView(iv);
iv.setClickable(true);
iv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, "ImageView", Toast.LENGTH_SHORT).show();
}
});
}
return v;
}
}
in onCreate(), i get the listView and assign the adapter:
mListView = (PullToRefreshListView) findViewById(R.id.listView);
mListView.setAdapter(new mAdapter(context));
After that i add an image to mValues (url for image to load from web) and call notifiyDataSetChanged on the adapter.
in mListView.onRefresh(), i add an image to mValues.
This works smoothly for adding the first image, or even the first bunch of images (before calling mAdapter.notifyDataSetChanged()).
The refresh indicator shows and hides as intended.
The weird things start happening when i try to add another image (or bunch) after that.
The refresh indicator shows, the image is displayed in the list view.
BUT : the refresh indicator never hides again after that. "onRefreshComplete()" gets called, but seems not to work properly the second time.
The UI Thread is not blocking, so operation is still possible.
If i delete all items in mValues, notify the adapter and pull to refresh again, the image is added properly, and the refresh indicator is hidden properly.
Conclusion: The pull-to-refresh only hides properly if the list was empty before refreshing.
I really don't know where to look for a solution for this weird error.
Maybe someone familiar with the Pull-To-Refresh Library from Chirs Banes can help me out here.
Thank You !
I just figured it out myself -.-
For anyone interested:
You have to set onRefreshComplete from the UI Thread.
Use a Handler to .post it from inside onRefresh(). <- which by the way runs on a separate thread.
Have a nice day.
I've found 2 ways:
Dynamically, when you need pulltorefreshview to stop do task on pull up, you can set a custom AsyncTask, for example:
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
#Override
protected String[] doInBackground(Void... params) {
return null;
}
#Override
protected void onPostExecute(String[] result) {
lv.onRefreshComplete();
showToast(getResources().getString(R.string.no_more));
super.onPostExecute(result);
}
}
Dynamically call setMode to the pulltorefreshView
ptrlv.setMode(Mode.Both); // both direction can be used
ptrlv.setMpde(Mode.PULL_FROM_START); // only pull down can be used.
Related
Hi I got frustrated while searching solution for my problem.My problem is that i have a gridview to show all images which i selected from gallery.I want to display progressbar on each images in gridview.and while uploading images to server using multipart i want too update progressbar..
I displayed progressbar on each imageview but i am unable to show progress on each progressbar.
SO please help me to show how to show progress bar and their respective process on each imageview.
thanks in advance
Create a interface for an observer:
interface ProgressListener {
void onProgressUpdate(String imagePath, int progress);
}
Let the view holder implement that observer and know the image path:
public class ViewHolder implements ProgressListener {
ImageView imgQueue;
ProgressBar pb;
TextView tv;
String imagePath; //set this in getView!
void onProgressUpdate(String imagePath, int progress) {
if (!this.imagePath.equals(imagePath)) {
//was not for this view
return;
}
pb.post(new Runnable() {
pb.setProgress(progress);
});
}
//your other code
}
The adapter shall hold an map of observers for a particular image path/uri whatever and have an method that is called by the upload/download task. Also add methods to add and remove observer:
public class SelectedAdapter_Test extends BaseAdapter {
private Map<String, ProgressListener> mProgressListener = new HashMap<>();
//your other code
synchronized void addProgressObserver(String imagePath, ProgressListener listener) {
this.mListener.put(imagePath, listener);
}
synchronized void removeProgressObserver(String imagePath) {
this.mListener.remove(imagePath);
}
synchronized void updateProgress(String imagePath, int progress) {
ProgressListener l = this.mListener.get(imagePath);
if (l != null) {
l.onProgressUpdate(imagePath, progress);
}
}
//other code
}
In getView of the adapter register the view holder as an observer:
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//other code
holder.imagePath = data.get(i).getSdcardPath();
this.addProgressObserver(holder.imagePath, holder);
return convertView;
}
The problem right now is, that we register the observer but don't unregister. So let the adapter implement the View.addOnAttachStateChangeListener:
public class SelectedAdapter_Test extends BaseAdapter implements View.addOnAttachStateChangeListener {
//other code
void onViewAttachedToWindow(View v) {
//We ignore this
}
void onViewDetachedFromWindow(View v) {
//View is not visible anymore unregister observer
ViewHolder holder = (ViewHolder) v.getTag();
this.removeProgressObserver(holder.imagePath);
}
//other code
}
Register that observer when you return the view.
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//other code
convertView.addOnAttachStateChangeListener(this);
return convertView;
}
Finally you are able to tell the views what the progress is:
#Override
public void transferred(long num) {
int progress = (int) ((num / (float) totalSize) * 100);
selectedAdapter.onProgressUpdate(listOfPhotos.get(i).getSdcardPath(), progress);
}
One final problem remains, what if the activity is gone while the upload is in progress? You need to check if the activity is still alive. Maybe setting a flag in the adapter to true in onCreate and to false in onDestroy would do the trick. Then the last code fragment could check that flag and not notify the adapter on changes anymore.
So thats basically the idea of how to solve this. Does it work? I don't know I wrote it from scratch without any testing. And even if it does, you still have to manage the states when the progress is 0 or 100. But I leave that to you. Also you might want to change the BaseAdapter for an recyclerView so that we can get rid of the View.addOnAttachStateChangeListener.
add boolean in adapter class
public SelectedAdapter_Test(Context c, ArrayList<CustomGallery> data, boolean showProgress) {
mContext = c;
inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.data = data;
this.showProgress = showProgress;
}
changes in Adapter class getView
holder.pb = (ProgressBar) convertView.findViewById(R.id.progressbar);
if (showProgress)
holder.pb.setVisibility(View.VISIBLE);
else
holder.pb.setVisibility(View.GONE);
make changes in doFileUpload
private void doFileUpload(View v) {
View vi = v;
for (i = 0; i < listOfPhotos.size(); i++) {
<--your task-->
}
//**important**
SelectedAdapter_Test mTest = new SelectedAdapter_Test(context,data,false);
// set above adapter object respectively;
mList.setadapter(mTest);
}
FYI. pass showProgress value as true for the first time when you set adapter.
I have a custom Listview with an ImageView for drawing. In my Mainactivity I start a thread which redraws the ImageView in my Listview every 20ms.
The ImageView is only refreshed when I call adapter.notifyDataSetChanged(); in my Listfragment.
This works fine, but my problem is, that onListItemClick only fires sometimes in this case. When I remove the adapter.notifyDataSetChanged(), onListItemClick fires always but now, my ImageViews are not refreshed.
Here the important parts of my code:
public class FragmentOscilloscope extends ListFragment
{
private ListViewAdapter adapter;
private List<ListViewItem> rowItems;
private Handler sampleUpdateHandler = null;
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
sampleUpdateHandler = new Handler();
}
public void InitFragment()
{
adapter = new ListViewAdapter(getActivity(), rowItems);
setListAdapter(adapter);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
Log.d("FragmentOscilloscope", "onListItemClick");
}
public void UpdateOscilloscope(final PositionMarker pos)
{
for (int i = 0; i < listItems; i++);
{
Canvas canvas = rowItems.get(i).getCanvas();
// do the drawings
}
sampleUpdateHandler.post(new Runnable()
{
#Override
public void run()
{
adapter.notifyDataSetChanged();
}
});
}
}
This is my getView() in my ListViewAdapter:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ListViewItem row_pos = rowItem.get(position);
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.oscilloscope_list_item, parent, false);
}
imageView = (ImageView) convertView.findViewById(R.id.osc_image);
imageView.setImageBitmap(row_pos.getBitmap());
row_pos.setImageView(imageView);
return convertView;
}
Can someone help me with this? I´m really frustrated... Thanks!
You can also find the full code of the described behavior here:
Android ListFragment update/refresh and onItemClick
When I remove the adapter.notifyDataSetChanged(), onListItemClick fires always but now, my ImageViews are not refreshed.
when you call notifyDataSetChanged(), items in your listview will be init and draw again. The main cause the onListItemClick fires sometimes because at that time your UI thread was VERY BUSY, it's processing other tasks and onListItemClick command will be put on the task queue to process.
I guess that in the getView() from adapter you do very heavy tasks, Try to improve it or create Thread/AsynTask for heavy processes. Hope it help.
Any way, if you provide more details in your code (getView() is a good point) I think some guys can help a lot.
background
When choosing an item from a listView, I change its data and call notifyDataSetChanged.
The problem
Since it's the same listView, when I click the item, the effect stays for the view that will be used after the notifyDataSetChanged.
This is especially noticeable on Android Lollipop, where the ripple can continue after the listView gets refreshed with new data.
The code
Here's a sample code showing the problem:
public class MainActivity extends ActionBarActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView=(ListView)findViewById(R.id.listView);
listView.setAdapter(new BaseAdapter()
{
int pressCount=0;
#Override
public int getCount()
{
return 100;
}
#Override
public Object getItem(int position)
{
return null;
}
#Override
public long getItemId(int position)
{
return 0;
}
#Override
public View getView(int position,View convertView,ViewGroup parent)
{
View rootView=convertView;
if(rootView==null)
{
rootView=LayoutInflater.from(MainActivity.this).inflate(android.R.layout.simple_list_item_1,parent,false);
rootView.setBackgroundResource(getResIdFromAttribute(MainActivity.this,android.R.attr.selectableItemBackground));
rootView.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
pressCount++;
notifyDataSetChanged();
}
});
}
TextView tv=(TextView)rootView;
tv.setText("text:"+(pressCount+position));
return rootView;
}
});
}
public static int getResIdFromAttribute(final Activity activity,final int attr)
{
if(attr==0)
return 0;
final TypedValue typedvalueattr=new TypedValue();
activity.getTheme().resolveAttribute(attr,typedvalueattr,true);
return typedvalueattr.resourceId;
}
}
The question
How can I temporarily stop the selection effect till the next time anything is clicked on the listView (but also resume allowing it for the next time the user clicks an item) ?
OK, I've found the answer. It seems it's a known issue, and the solution is quite simple (shown here) :
ViewCompat.jumpDrawablesToCurrentState(view);
Weird thing is, it works for me only when I call it via Handler.post(...) .
Wonder why (as the view is already during animation), and if there's a better solution.
I've got some troubles with notifyDataSetChanged() of a BaseAdapter. This method is called in refreshItems() and shall update the BaseAdapter of my ListActivity. On calling notifyDataSetChanged() nothing happens until I scroll down the ListView for example with the arrow keys. Somehow the modified getView() method also is not called. Maybe you can give me a hint - thanks! :)
public class WinampControlClientPlaylist extends ListActivity {
static WinampControlClientPlaylist activity = null;
static EfficientAdapter adapter = null;
static class EfficientAdapter extends BaseAdapter {
public EfficientAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
private LayoutInflater mInflater;
#Override
public int getCount() {
return Settings.playlistlength;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null)
{
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.listview, null);
holder.text = (TextView) convertView.findViewById(R.string.playlist_title);
holder.image = (ImageView) convertView.findViewById(R.string.playlist_play);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(Settings.playlist[position]);
if (position == Settings.playlistPosition)
{
holder.text.setTypeface(null, Typeface.ITALIC);
holder.image.setVisibility(0);
}
else
{
holder.text.setTypeface(null, Typeface.NORMAL);
holder.image.setVisibility(4);
}
return convertView;
}
static class ViewHolder {
TextView text;
ImageView image;
}
#Override
public Object getItem(int position) {
return Settings.playlist[position];
}
}
void initialize()
{
adapter = new EfficientAdapter(this);
setListAdapter(adapter);
//registerForContextMenu(getListView());
}
#Override
public void onResume()
{
super.onResume();
// REFRESH PLAYLIST
if (getListAdapter() == null && Settings.playlist != null)
initialize();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.playlist);
activity = this;
}
static void refreshItems()
{
try {
adapter.notifyDataSetChanged();
} catch (Exception e) {}
}
}
I had the same problem (ListView updates only when i scroll it, even notifyDataSetChanged didn't help). i solve it this way: just try to do your "view modifications" in thread which creates your user interface i.e.
activity.runOnUiThread(new Runnable() {
public void run() {
//do your modifications here
// for example
adapter.add(new Object());
adapter.notifyDataSetChanged()
}
});
Try calling invalidate() on your ListView.
As Franco pointed out, notifyDataSetChanged() is used to tell the ListView that the contents of its adapter have changed, not that it needs to redraw itself. You are just changing a setting that affects how something is rendered. Try calling refreshDrawableState to tell the list to redraw.
I had the same issue, and the solution for me was to call requestLayout() on the ListView.
I think there may be some problems with the adapter;
maybe it's not set.
In my experience, there was always some kind of reason which prevented the listview (and adapter) to update.
call AbsListView.invalidateViews() on your listview after BaseAdapter.notifyDataSetChanged()
I encountered the same problem, and I tried to call notifyDataSetChanged() on the adapter. Besides, I also tried to call refreshDrawableState(), invalidateViews() on the view and none of those worked. All these methods are called in the UI thread. Then I found this How to clear the views which are held in the ListView's RecycleBin? . Finally setAdapter() worked, only if I create a new adapter.
The main reason behind this is the wrong reference of the adapter on which you are calling notifyDataSetChanged();
I think you need to make sure that you are creating adapter object once and call notifyDataSetChanged() on the same object.
You can debug the object reference value at creating time of adapter object and when you are calling notifyDataSetChanged() method.
Why it works in first code ?
--- Because you are setting the values to temp List and passing it the adapter and it shows it into listview.
Why not work in second code ?
--- Because you are setting temp to adapter far before you set value into temp
second,your adapter class might not getting the updated value when you set new value to temp ..that because temp is not public or not at class level or not static.. Put the temp declaration at root level and try.
And please show your full code as much as required and Logcat if you getting any warnings than also.
From this answer in stack overflow and the sample project referred there, i got the Idea of RotationAsync, where a progress bar work fine with device rotation.
But my problem is, i have a listview with each row there is progress bar. And is there any way to retain the progress while rotation for reach row.
Me creating onclicklistener object for the button click listener in getview function of my adapter class. Where its onClick function call the AsyncTask class
Since each getview (row) is calling different instant of my AsyncTask, i cannot make it static of single ton class.
Any Idea on this.
Thanks.
So you have a ListView which I assume you have some adapter which in it's get view hosts the progress bars. However that progress must be backed by something right? So just save that data. Like I am assuming an adapter like so:
public class MyProgressBarAdapter extends BaseAdapter {
private ArrayList<Integer> mProgessValues;
private SparseArray<AsyncTask<?,?,?>> mTasks;
// No stored reference to a Context
private MyProgressBarAdapter() {
}
public void saveState(Bundle bundle) {
bundle.putIntegerArrayList(getClass().getName() + ".progressValues", mProgressValues);
}
public Object exportLiveState() {
return mTasks;
}
public static MyProgressBarAdapter restore(Bundle bundle, Object rawState) {
MyProgressBarAdapter adapter = new MyProgressBarAdapter();
Class<MyProgressBarAdapter> c = adapter.getClass();
ArrayList<Integer> progresses = null;
if (bundle != null) {
progresses = bundle.getIntegerArrayList(c.getName() + ".progressValues");
}
if (progresses == null) {
progresses = new ArrayList<Integer>();
}
adapter.mProgressValues = progresses;
if (rawState != null && rawState instanceof SparseArray) {
adapter.mTasks = (SparseArray<AsyncTask<?,?,?>>) rawState;
}
return adapter;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = getViewWithHolder(convertView, parent);
ViewHolder holder = convertView.getTag();
// set the appropriate things on the view elements.
holder.position = position;
holder.taskContainer = mTasks;
holder.progressBar.setProgress(mProgressValues.get(position));
convertView.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
ViewHolder holder = view.getTag();
int pos = holder.position;
SparseArray<AsyncTask> tasks = holder.taskContainer;
AsyncTask task = tasks.get(pos);
if (task == null) {
// Create your task
task = new AsyncTask<?, ?, ?> (...);
tasks.put(pos, task);
task.execute();
}
}
return convertView;
}
/// You can write the rest of the adapter I believe.
...
}
and then you don't really need onConfigurationChanged. Just read and save your data accordingly.
public class MyActivity extends Activity {
ListView mListView;
MyProgressBarAdapter mAdapter;
#Override
public void onCreate(Bundle savedState) {
super.onCreate();
Object[] liveState = getLastNonConfigurationInstance();
setContentView(R.layout.mylistview_with_progressbars);
mListView = findViewById(R.id.listview);
// Be consistent with the index
MyProgressBarAdapter adapter = MyProgressBarAdapter.restore(savedState, liveState[0]);
mListView.setAdapter(adapter);
mAdapter = adapter;
...
}
#Override
public void onSaveInstanceState(Bundle bundle) {
mAdapter.save(bundle);
}
#Override
public Object[] onRetainNonConfigurationInstance () {
// size to be whatever live state you need to store other than the adapter
Object[] objects = new Object[1];
// This reference will be retained between onCreate() and onDestroy() calls.
objects[0] = mAdapter.exportLiveState();
// Any other things that can't be serialized
return objects;
}
#Override
public Object[] getLastNonConfigurationInstance() {
Object[] live = (Object[]) super.getLastNonConfigurationInstance();
if (live == null) {
live = new Object[1];
}
return live;
}
// The rest of your activity
...
}
That will make it so that when you flip the orientation, the adapter will be recreated but it will be reinitialized to the same state it was in before. I made some assumptions about the way you store your progress and the nature of your asyncTasks but I hope you can adjust as needed.
You could even, if you don't store a reference to any context, you might be able to get away with just storing the entire adapter itself inside the onRetainNonConfigurationInstance() and using that in the getLastRetainedNonConfigurationInstance()
You can set android:configChanges="orientation" in manifest file to make your activity does not restart when rotating
One solution that i used
If we have only one layout for both landscape and portrait mode, then we can we can solve this by
1. Set the activity asandroid:configChanges="orientation" in manifest file
2. Override the onConfigurationChanged like this
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
But the problem is still the if i need to use different layout for both landscape and portrait mode, each have a listview with progress bar in each row. there i need to retain the progress while rotate which use same AsyncTask class.
How can you set percentage value for each row item? Why don't you update that value to the data item. You can have some thing like below. Since you have the data item you can store whatever you want :) Ps: I wonder that I can format text in comment to not add new answer.
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView,
ViewGroup parent)
public Object getChild(int groupPosition, int childPosition)