I have a listview in the layout, and each item has two part: one is a user avatar (ImageView), and other is a chat content ( TextView). Looks like:
And I have a custom adapter.
I would like to implement : when I click the avatar, I can go to the system gallery and select a photo as the avatar.
So my code about onClickListener in adapter class is:
Intent itent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
((Activity)(context)).startActivityForResult(itent, LOAD_IMAGE_RESULT);
And I also override the onActivityResult method in activity.
However, the solution I mentioned above cannot change the avatar in onActivityResult method, because I don't know how to communicate between adapter and activity.
Hope for a solution.
EDIT - Initially, I have just shown one of the method to update child views only by leaving it's adapter in the inconsistent state (because the original question don't give much information about underlying data structure).
Adapter: An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to
the data items. The Adapter is also responsible for making a View for
each item in the data set.
Note: As the definition itself describes that it's important to maintain the state means (data + view). So you should always have the consistent user experience.
To give the example properly, I have also defined the data model class ItemData based on the assumption of given image in original question.
/**
* Model Class
*/
public class ItemData {
private Uri imageUri;
private String msg;
private Date timeStamp;
public void setImageUri(Uri uri) {
this.imageUri = uri;
}
public Uri getImageUri() {
return this.imageUri;
}
...
}
CustomAdapter
Create the custom adapter for listView which will maintain the child views with it's dataset accordingly. You've to maintain the reference of last selected row index lastSelectedIndexRow which can be used later for updating the view.
Note: To get the view for any index in the listView, we should not call getView() method of the adapter. As calling getView() with null for the convertView causes the adapter to inflate a new view from the adapter's layout resource (does not get the view that is already being displayed).
The AdapterView should always be updated with notifyDataSetChanged() based on the current dataset hold by adapter.
public class CustomAdapter extends BaseAdapter {
private Context context;
private LayoutInflater inflater;
private List<ItemData> dataList;
private int lastSelectedRowIndex = -1;
public static int LOAD_IMAGE_RESULT = 201;
public CustomAdapter(Context context, ArrayList<ItemData> dataList) {
// hold the items
this.context = context;
this.dataList = dataList;
this.inflater = LayoutInflater.from(this.context);
}
#Override
public int getCount() {
return (dataList != null && !dataList.isEmpty()) ? dataList.size() : 0;
}
#Override
public ItemData getItem(int position) {
return (dataList != null && !dataList.isEmpty()) ? dataList.get(position) : null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderItem viewHolder;
if (converView == null) {
// inflate the layout
convertView = inflater.inflate(R.layout.row_item, parent, false);
// well set up the ViewHolder
viewHolder = new ViewHolderItem();
viewHolder.avatar = (ImageButton)view.findById(R.id.avatar);
// store the holder with the view.
convertView.setTag(viewHolder);
}
else {
// we've just avoided calling findViewById() on resource every time
// just use the viewHolder
viewHolder = (ViewHolderItem) convertView.getTag();
}
// Set row data
ItemData data = (ItemData)getItem(position);
if (data != null) {
// set message
viewHolder.message.setText(data.getMessage());
// set formatted timestamp
String formattedTimeStamp = ...; // convert data.getTimeStamp() into formatted version
viewHolder.timeStamp.setTextView(formattedTimeStamp);
// set Image and also it's action.
viewHolder.avatar.setImageUri(data.getImageUri());
viewHolder.avatar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
Intent intent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
((Activity)context).startActivityForResult(intent, LOAD_IMAGE_RESULT);
} catch (Exception e) {
Log.e("Demo application", "Failed to invoke call", e);
}
}
}
}
}
/**
* Get the last selected item index
*
*/
public int getLastSelectedItemIndex() {
return lastSelectedRowIndex;
}
/**
* Update the adapater with new data
*
*/
public void updateItems(List<ItemData> dataList) {
if (this.dataList != dataList)
this.dataList = dataList;
// update the view.
notifyDataSetChanged();
// reset the last selection
lastSelectedRowIndex = -1;
}
/**
* Hold View items
*/
static class ViewHolderItem {
private ImageView avatar;
private TextView message;
private TextView timeStamp;
}
}
ExampleActivity
In activity, you've to defined the reference of custom adapter and handle the image selection results in onActivityResult() method. Once you get the selected image from gallery then update the underlying data holding inside dataList and also update the adapter by calling updateItems() custom method.
The updateItems() method of adapter will take the new data list as argument and invalidate the adapterView by calling notifyDataSetChanged().
public class ExampleActivity extend FragmentActivity {
private ListView listView;
private CustomAdapter adapter;
private List<ItemData> dataList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dataList = ...; // Load the list from database
// Create the custom adapter with filled list items.
adapter = new CustomAdapter(this, dataList);
// List View and set the data adapter
listView = (ListView)findViewById(R.id.listView);
listView.setAdapter(adapter);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Check if it's coming from MediaStore Selection.
if (resultCode == Activity.RESULT_OK &&
requestCode == CustomAdapter.LOAD_IMAGE_RESULT) {
// Get the selected rowIndex
if (adapter != null && dataList != null) {
// check the row index is valid
int rowIndex = adapter.getLastSelectedItemIndex();
if (rowIndex > -1 && rowIndex < dataList.size()) {
// Get the item
ItemData item = dataList.get(rowIndex);
// Update the item with imageUri
item.setImageUri(Uri.parse(data.getData()));
/**
* If you may want to update the information in database,
* then it's the best place, but please do in background thread.
*/
//Now notify the adapter with new changes
adapter.updateItems(dataList);
}
}
}
}
...
}
You should save a reference to the clicked view in the onClickListener method and have a public method in your adapter. When you get the response in the onActivityResult method call the adapter's method and use the
Add a public method in your custom adapter to set the image (like public void setAvatar(Bitmap image))
Then, in your onActivityResult method, get the image selected and use your newly created method to set the new image in your adapter. (see here for more informations: Getting a Result from an Activity
Don't forget to put notifyDataSetChanged(); at the end of your your newly method in your adapter to refresh all the list.
public void setAvatar(Bitmap image)
{
...
imageView.setImageBitmap(image);
...
notifyDataSetChanged();
}
In addition, if you need to know which image was clicked, you can specify an Integer as a parameter and check it in the onActivityResult.
I see you use a constant LOAD_IMAGE_RESULT for this parameter. Instead of a constant, put an integer who identify the clicked item.
In the adapter, (not sure which kind of adapter you using), you set the onClickListener for the ImageView. Hence,
mImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
<INSERT CODE HERE>
mContext.startActivity(intent);
}
});
Where mImageView is the ImageView for that item. Also a good idea might be to use an ImageButton since it allows Images and is a button which is what you want. Then by the add the code you want to select the Image. You may need to pass context through to the Adapter too (but you would generally do that anyway). Then you can process the onActivityResult method in the activity that the adapter is in i.e. setting the Image to the image you picked. There are a couple ways to do this.
Related
When setting an ArrayList of Uris in the gridView, only one item is displayed. Why ?
Adapter:
public class ImageGridAdapter extends BaseAdapter {
private Context mContext;
public ImageGridAdapter(Context c) {
this.mContext = c;
}
#Override
public int getCount() {
try {
return PictureGroupActivity.ALofSelectedImgs.size();
} catch (NullPointerException e) {
return 0;
}
}
#Override
public Object getItem(int position) {
return PictureGroupActivity.ALofSelectedImgs.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(200, 200));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(5, 5, 5, 5);
} else {
imageView = (ImageView) convertView;
}
try {
imageView.setImageURI(PictureGroupActivity.ALofSelectedImgs.get(position));
Toast.makeText(mContext.getApplicationContext(), "Idee: " + PictureGroupActivity.ALofSelectedImgs, Toast.LENGTH_SHORT).show();
}catch (NullPointerException e) {}
return imageView;
}
Setting the adapter:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.picture_group_activity_layout);
GridView gridView = (GridView) findViewById(R.id.picture_group_gridView);
gridView.setAdapter(new ImageGridAdapter(this));
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(PictureGroupActivity.this, "You clicked " + position, Toast.LENGTH_SHORT).show();
}
});
}
From where I take the Image (After choosing from the phones gallery):
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) {
selectedImage = data.getData();
ALofSelectedImgs = new ArrayList<Uri>();
ALofSelectedImgs.add(selectedImage);
Intent restart = getIntent();
finish();
startActivity(restart);
}
}
How do I make it add multiple Images into the ArrayList and make them stay there and not overwrite each other ?
There are a few problems that I can see with your approach here.
The simplest solution (changing the least code). Would be to move ALofSelectedImgs = new ArrayList(); from onActivityResult(int, int, Intent) and put it in onCreate(Bundle).
This still won't persist the data between orientation changes or closing the app. You'll get a new empty ArrayList every time onCreate(Bundle) is called.
And I wouldn't recommend using static fields like that. For starters, you can't use ImageGridAdapter with any other Activity or Fragment. You need to pass the List to it, either in the constructor or a setter method. That way you can reuse it more easily.
private Context mContext;
private List<Uri> mUris;
public ImageGridAdapter(Context context, List<Uri> uris) {
super(context);
mContext = context;
mUris = uris;
}
For a better solution... there are many ways to do this. Here's what I'd do:
Create a class extending SQLiteOpenHelper in order to save the selected Uris in an SQLite table. I learned a lot from this tutorial. I probably would not worry with a ContentProvider for something this simple. If you're interested I could pm you a template I use for keeping many tables' columns and other constants in a contract class.
Create an adapter extending SimpleCursorAdapter to create Views from a Cursor containing a query from your SQLite table. This is explained in the above tutorial.
In PhotoGroupActivity, perform a query of your table and initialize the adapter with the Cursor returned in onCreate(Bundle).
In onActivityResult(int, int, Intent) you need to insert the Uri into the SQLite table, then perform another query and give the adapter the new Cursor.
EDIT:
To answer your second question. This didn't occur to me at first, but you'll want to use thumbnails to display in your GridView. Use Bitmap.createScaledBitmap(Bitmap, int, int, boolean) to create the thumbnail. Store the thumbnail in your app's private storage to avoid it going in your gallery, then add the Uri to the thumbnail to your ArrayList. You'll probably want to keep track of the Uri for the full-size image as well for when the user touches the thumbnail.
Try using HashMap with the thumbnail Uri as the key and the full-size Uri as the value.
// Create the HashMap like this:
HashMap<Uri, Uri> uriMap = new HashMap<>();
// You have the main Uri. Get the bitmap, create a thumbnail and store it.
// Add an entry to the HashMap like this:
uriMap.put(thumbnailUri, fullSizeUri);
// To get the list of thumbnail Uris for the adapter:
List<Uri> thumbnailList = new ArrayList<>(uriMap.keySet());
// When user presses an image in GridView, get the relevant full-size
// Uri like this:
fullSizeUri = uriMap.get(thumbnailUri);
EDIT AGAIN:
I looked again and realised I suggested a CursorAdapter and then gave info on how to get a HashMap of the data.
Instead, just add another column to the SQLite table and store both Uris there. When you obtain the Uri and thumbnail Uri, store them both in the table and query the table for the SimpleCursorAdapter.
I have implemented a custom adapter and listItemView. The adapter sets an onlclick listener to a button that is on the listItemView. The onclick listener simply calls a private method I have in the adapter and passes it the position of the item to be removed. I know the position is correct because the database removes the proper item. I have found similar questions but have not been able to adapt the answers to work for me. Ideas and thoughts are greatly appreciated. Thanks.
Here is the full adapter class
public class FoodListAdapter extends ArrayAdapter<FoodListItem> {
//private
private int type;
public FoodListAdapter(Context context, ArrayList<FoodListItem> _objects) {
super(context, 0, _objects);
type = 0;
}
public FoodListAdapter(Context context, ArrayList<FoodListItem> _objects, int _type) {
super(context, 0, _objects);
type = _type;
}
#Override
public View getView(int position, View reusableView, ViewGroup parent)
{
//Cast the reusable view to a listAdpaterItemView
FoodListItemView listItemView = (FoodListItemView) reusableView;
//Check if the listAdapterItem is null
if(listItemView == null)
{
//If it is null, then create a view.
listItemView = FoodListItemView.inflate(parent, this, type);
}
if (type == 2)
{
Button deleteButton = (Button) listItemView.findViewById(R.id.listItemViewDeleteBTN);
deleteButton.setTag(new Integer(position));
}
//Now we need to set the view to display the data.
listItemView.setData(getItem(position));
return listItemView;
}
}
Here is a portion of my code used in fragment. Note that I have a private variable decalred in the class for listAdapter, though I don't think I need that.
private void displayListForDate(Calendar _date)
{
//get the list view
ListView listView = (ListView) getView().findViewById(1);
//Clear the listview by removing the listadapter and setting it to null.
//listView.setAdapter(null);
//First we must get the items.
Global global = (Global) getActivity().getApplicationContext();
DietSQLiteHelper database = global.getDatabase();
//Create a list to hold the items we ate. This list will then be added to the listView.
final ArrayList<FoodListItem> consumedList;
//Add the items to the array.
consumedList = database.getConsumed(_date.getTimeInMillis());
//Create an adapter to be used by the listView
listAdapter = new FoodListAdapter(getActivity().getBaseContext(), consumedList, 2);
//Add the adapter to the listView.
listView.setAdapter(listAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
consumedList.remove(position);
listAdapter.notifyDataSetChanged();
}
});
}
If you didn't implement "equals" method of FoodListItem, try to implements it.
I would suggest,
that you just update the underlying data, in your case its ArrayList<FoodItems>.
In your Adapter make this simple method and change :
private List<FoodListItem> myList = new ArrayList<FoodListItem>();
public FoodListAdapter(Context context, List<FoodListItem> myList) {
super(context, 0, myList);
type = 0;
this.myList = myList;
}
public FoodListAdapter(Context context, List<FoodListItem> myList, int _type) {
super(context, 0, myList);
type = _type;
this.myList = myList;
}
// Also update your getView() method to use myList!
#Override
public View getView(int position, View reusableView, ViewGroup parent)
{
...
listItemView.setData(myList.get(position));
public void removeItem(int positio){
if(myList != null){
myList.remove(position);
}
}
And then in class, you are creating the adapter (Activity/Fragment), just call the method.
// Update the underlying ArrayAdapter
adapter.removeItem(position);
// Notify the adapter, the data has changed
adapter.notifyDataSetChanged();
Also, you shouldnt open connection to your SQLiteDatabase on UI thread, because you are blocking it. You never know, how fast is the reading from disk going to be. If it takes too long, user can think, that your application froze and therefore, he leaves, which you dont want. I would suggest to use AsyncTask, you will find a lot of examples.
I went through and cleaned up my code and it now works, here is the working code. I really don't know exactly the difference other than I updated the IDs that I was using to assign and get views. If anyone can explain the cause for the issue I was having I would appreciate it.
Here is the snippet from my fragment where I create the list view and assign an adapter.
private void displayListForDate(Calendar _date)
{
//get the list view
ListView listView = (ListView) getView().findViewById(R.id.listView);
//Clear the listview by removing the listadapter and setting it to null.
//listView.setAdapter(null);
//First we must get the items.
Global global = (Global) getActivity().getApplicationContext();
DietSQLiteHelper database = global.getDatabase();
//Create a list to hold the items we ate. This list will then be added to the listView.
ArrayList<FoodListItem> consumedList;
//Add the items to the array.
consumedList = database.getConsumed(_date.getTimeInMillis());
//Create an adapter to be used by the listView
listAdapter = new FoodListAdapter(getActivity().getBaseContext(), consumedList, 2);
//Add the adapter to the listView.
listView.setAdapter(listAdapter);
}
and here is my adapter class.
public class FoodListAdapter extends ArrayAdapter<FoodListItem>
{
//private
private int type;
public FoodListAdapter(Context context, ArrayList<FoodListItem> _objects) {
super(context, 0, _objects);
type = 0;
}
public FoodListAdapter(Context context, ArrayList<FoodListItem> _objects, int _type) {
super(context, 0, _objects);
type = _type;
}
#Override
public View getView(int position, View reusableView, ViewGroup parent)
{
//Cast the reusable view to a listAdpaterItemView
FoodListItemView listItemView = (FoodListItemView) reusableView;
//Check if the listAdapterItem is null
if(listItemView == null)
{
//If it is null, then create a view.
listItemView = FoodListItemView.inflate(parent, type);
}
if (type == 2)
{
Button deleteButton = (Button) listItemView.findViewById(R.id.listItemViewDeleteBTN);
deleteButton.setTag(new Integer(position));
deleteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Integer tag = (Integer) view.getTag();
deleteItem(tag.intValue());
}
});
}
//Now we need to set the view to display the data.
listItemView.setData(getItem(position));
return listItemView;
}
private void deleteItem(int position)
{
FoodListItem item = getItem(position);
Global global = (Global) getContext().getApplicationContext();
DietSQLiteHelper database = global.getDatabase();
database.removeConsumed(item.getID());
remove(getItem(position));
}
}
I have a List Adapter for a listview in my one activity,
Now I want to populate this list view with data I obtain from a web service. Where is the correct place to execute a async request to the server to obtain the json data to populate the adapter?
1: before I instantiate the adapter and passing it through as a constructor?
But then I dont know how refreshing will work.
2: Some where in the adapter?
2.1: in the adapters constructor?
2.2 Some where in the get view?
Here is my adapter so far:
public class MyDevicesAdapter extends BaseAdapter {
private Context mContext;
private String TAG = "MyDevicesAdapter";
// Keep all Objects in array
private ArrayList<MyDevice> devices;
/**
* Constructor for creating a my devices list adapter
*
* #param context
* The context of the calling class
*/
public MyDevicesAdapter(Context context) {
mContext = context;
devices = new ArrayList<MyDevice>();
}
#Override
public int getCount() {
return devices.size();
}
#Override
public Object getItem(int position) {
return devices.get(position);
}
#Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
/**
* Checks if the List is empty
*/
public boolean isEmpty() {
return devices.isEmpty();
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater mInflater = (LayoutInflater) mContext
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.my_device_item, null);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView
.findViewById(R.id.my_device_item_ImageView);
holder.txtName = (TextView) convertView
.findViewById(R.id.my_device_item_textView_name);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// Temp variable to store the current list item
final MyDevice DeviceItem = (MyDevice) getItem(position);
//Set Image
//Set Name
//on click listener for the view
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//Log.d(TAG, "Deleting order in position : " + position);
//deleteOrder(orders.get(position), position, v);
}
});
return null;
}
/**
* Private View holder class Keeps a reference to the View for the certain
* components. Is used to increase performance of the listView
*/
private class ViewHolder {
ImageView imageView;
TextView txtName;
}
}
Here is the Async Request that's going to fetch my data:
Network.login("mydevices",null, new AsyncHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers,
byte[] responseBody) {
}
#Override
public void onFailure(int statusCode, Header[] headers,
byte[] responseBody, Throwable error) {
// Response failed :(
}
});
Somewhere else ;)
Based on your app you can trigger the download from any position. But you should do the work in a seperat thread or by using an AsyncTask. So this will not block your UI. If the data are available just notify the listeners by calling notifyDataSetChanged() and you are done. The UI will been reloaded automatically after that call.
Initialize the adapter and list and then call async task.
In the post method you can call adapter.notifyDataSetChanged() for the changes to list every time you update it.
Get the data in your initialize progress.
Let your adapter know it by a CallBack(Adapter implements the callback interface and register it to the AsyncTask,when work is down,the AsyncTask do the callback).
Create an ArrayList (or any form the way you want to pass your data) depending upon the type of data you have to pass to your adapter. In the onCreate pass that as a parameter to your adapter even if it is blank. You wont get any data at this point.
Then on a separate thread, lets say Asynctask get the data and add it to your ArrayList. Post this simply call
adapter.setNotifyDataSetChanged();
This will automatically refresh your List and populate it with the latest values of your ArrayList. ListView's are designed in this manner, that every time a call is made to notifyDataSetChanged() it will refresh itself depending upon the data it was initialised with.
I have an array of 30 items which i need to display in a listview. However, per my layout I'll be displaying 2 items side by side in one row of listview.
Since there are now going to be only 15 rows to display (30/2), how do i modify the position attribute of Adapter such that i see only 15 rows.
I tried doing position++, in getView and also modify getCount() to return 15, but that does not work either.
Rather than do it this way, a better method may be to simply count two items as one row. The getView() method returns a View that is a representation of each item returned by getItem(). In your case, each item contains two elements. So just put the logic in that would retrieve two elements at a time. May be easier to encapsulate them in a class like for example:
ArrayList<Row> mItems = new ArrayList<Row>();
private class Row {
Object obj1;
Object obj2;
}
public void addItem(Object obj) {
Row useRow;
if(mItems.isEmpty()) {
useRow = new Row();
} else {
useRow = mItems.get(mItems.size() - 1);
if(useRow.obj2 != null) {
useRow = new Row();
}
}
if(useRow.obj1 == null) {
useRow.obj1 = obj;
} else {
useRow.obj2 = obj;
}
mItems.add(useRow);
}
In this case, your BaseAdapter is backed by a List of Row objects. Each Row object contains two of your elements. Every time you add an element, you add it to the first, then to the second, else you create a new Row object and add it.
EDIT:
In order to have clickability, you'll have to implement an OnClickListener to each item's View. Something like this may work:
public interface ItemClickListener {
public void onItemClick(Object obj);
}
private ItemClickListener mClickListener;
public void setItemClickListener(ItemClickListener listener) {
mClickListener = listener;
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewGroup root;
if(convertView == null) {
root = buildRootView();
View item1View = buildFirstView();
View item2View = buildSecondView();
...
item1View.setOnClickListener(mItemListener);
item2View.setOnClickListener(mItemListener);
...
// Put both Views in your top level root view if they are not there already
}
Row row = getItem(position);
item1View.setTag(row.obj1);
item2View.setTag(row.obj2);
}
private View.OnClickListener mItemListener = new View.OnClickListener {
#Override
public void onClick(View v) {
Object obj = (Object) v.getTag();
if(mClickListener != null) {
mClickListener.onItemClick(obj);
}
}
}
So basically, you disable the clicking by overriding "areAllItemsEnabled()" and "isEnabled()" to return "false". Then, the click listener in the adapter will activate each time the user clicks on a row. Since you put the Object of the row in the View's tag, you can retrieve it on click. It will be swapped to a new Object even when the ListView recycles because it calls getView() each time. Then create an object that inherits from the click interface to retrieve the object and do whatever you need.
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)