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.
Related
I want my recycler view rows to be highlighted after a specific interval of time, say after 2 seconds.
Searched all over the internet but no luck so far.
How about, in the recycler adapter OnBindViewHolder method, put a [Handler.postDelayed](https://developer.android.com/reference/android/os/Handler.html#postDelayed(java.lang.Runnable, long)) in which you set the specific time where you want the item to change.
Inside the runner that you pass in the handler, you put in a boolean flag to check if the row will have a different colour/behaviour + a notifyDataSetChanged() in the adapter. (You will have to change your data object to accomodate this new variable)
The question is not very clear. I had two questions in mind that I mentioned in the comment of the question.
Do you want to highlight some specific rows?
Do you want to toggle the highlight after each two seconds?
So I'm going for a general solution for both.
Let us assume the object you're populating in your each row is like the following.
public class ListItem {
int value;
boolean highlight = false;
}
The list of ListItem object can be inserted in an ArrayList to be populated in the RecyclerView. Here is your adapter which may look like this.
// Declare the yourListItems globally in your Activity
List<ListItem> yourListItems = new ArrayList<ListItem>();
populateYourListItems();
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public class YourViewHolder extends RecyclerView.ViewHolder {
private final TextView valueTextView;
private final LinearLayout background;
public YourViewHolder(final View itemView) {
super(itemView);
valueTextView = (TextView) itemView.findViewById(R.id.value_text_view);
background = (LinearLayout) itemView.findViewById(R.id.background);
}
public void bindView(int pos) {
int value = yourListItems.get(pos).value;
boolean isHighlighted = yourListItems.get(pos).hightlight;
valueTextView.setText(value);
// Set the background colour if the highlight value is found true.
if(isHighlighted) background.setBackgroundColor(Color.GREEN);
else background.setBackgroundColor(Color.WHITE);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_activity_log, parent, false);
return new YourViewHolder(v);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
try {
if (holder instanceof YourViewHolder) {
YourViewHolder vh = (YourViewHolder) holder;
vh.bindView(position);
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public int getItemCount() {
if (yourListItems == null || yourListItems.isEmpty())
return 0;
else
return yourListItems.size();
}
#Override
public int getItemViewType(int position) {
return 1;
}
}
Now when you want to highlight some specific items of your RecyclerView you need to just set the highlight value to true and then call notifyDataSetChanged() to bring into the change in effect.
So you might need a timer like the following which will highlight your rows as per your demand in every two seconds.
// Declare the timer
private Timer highlightTimer;
private TimerTask highlightTimerTask;
highlightTimer = new Timer();
highlightTimerTask = new TimerTask() {
public void run() {
highLightTheListItems();
}
};
highlightTimer.schedule(highlightTimerTask, 2000);
Now implement your highLightTheListItems function as per your need.
public void highLightTheListItems() {
// Modify your list items.
// Call notifyDataSetChanged in your adapter
yourAdapter.notifyDataSetChanged();
}
Hope that helps. Thanks.
Do you mean highlight as in colour the row's background? If so, you could do this in your listViewAdapter
#Override
public View getView(final int position, View row, ViewGroup parent){
if (row==null){
row = LayoutInflater.from(getContext()).inflate(mResource, parent, false);
}
if(foo){
row.setBackgroundColor(getResources().getColor(R.color.translucent_green));
}
else row.setBackgroundColor(Color.TRANSPARENT);
return row;
}
Then in colours.xml
<color name="translucent_green">#667cfc00</color>
The first 2 numbers(66) is the alpha value, ie opacity. The next 6 are RBG in hexadecimal.
I've started working on a small project not to long ago, the main goal is to forge a way for me to keep track of my actions during the course of 100 weeks.
I'm still a rookie android developer and I've encountered an issue that I couldn't explain.
Basically I've populated a ListView using the ArrayAdapter with a list containing 100 strings (Week1, Week2, Week3 ... Week100)
Setting up an onclicklistener on each of the TextViews so that when a user performs a click on a textview, the background color would change to red.
However; whenever I click a single textview - more than a single textview is being colored.
Notes:
I'm using a ScrollView to scroll through the entire list. (Once populated, the 100 week list fills up the entire screen, the scroll view is used to access the entire list.)
I also saved a reference to the currently painted textview so I could make sure that when a user clicks a different textview, the previous one would lose its red background.
MainActivity initialization:
public class MainActivity extends ActionBarActivity
{
TextView selectedWeek; // Reference to the selected week.
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
populateWeekList(); // Populating the ListView
initWeekClick(); // Initializing click listener
}
Populating the ListView:
public void populateWeekList()
{
String[] weeks = new String [100]; // 100 weeks
for (int i=0; i<100;i++)
{
weeks[i] = "Week"+(i+1);
}
ArrayAdapter<String> weekAdapter = new ArrayAdapter<String>(
this,
R.layout.weeksview,
weeks
);
// R.id.weekTypeList is just a normal TextView.
ListView weekList=(ListView) findViewById(R.id.weekTypeList);
weekList.setAdapter(weekAdapter);
}
Code for initializing onClickListener and saving the selectedWeek reference:
public void initWeekClick()
{
ListView weekList=(ListView) findViewById(R.id.weekTypeList);
weekList.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View viewClicked, int position, long id)
{
if (selectedWeek != null)
{
selectedWeek.setBackgroundColor(0);
}
TextView clicked = (TextView) viewClicked;
// Change clicked TextView color to red.
clicked.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
// Save the selected week reference
selectedWeek = clicked;
}
});
}
Ok, your background is shuffling because when you scroll your ListView getView() is called and it consider your current position of TextView(as current view) and set background on it as it detect setBackground() method at onClick listener on it..
First I recommend to create a Adapter class extends ArrayAdapter<?>
Solution 1 :
Use setTag() at onClick listener on your text view like..
text.setTag(position);
and above it use getTag() and put condition
if(holder.text.getTag().equals(position)){
holder.text.setBackgroundColor(Color.BLUE);
}else{
holder.text.setBackgroundColor(Color.WHITE);
}
Solution 2 :
Added this to onCreate method
ArrayList<String> _array = new ArrayList<String>();
for(int i=0 ; i <1000; i ++){ // 1000 value
_array.add(i+"");
}
list.setAdapter(new MainAdapter(this, _array)); // pass you list here
ArrayAdapter class :
public class MainAdapter extends ArrayAdapter<String> {
ArrayList<String> _st = new ArrayList<String>();
ArrayList<Integer> check = new ArrayList<Integer>();
Context _context;
public MainAdapter(Context context,ArrayList<String> _st) {
super(context,R.layout.main, _st); // your inflate layout
this._context = context;
this._st = _st;
}
#Override
public int getCount() {
return _st.size();
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
//---//
// check if current position is there in arraylist
if(checking(position)){
holder.text.setBackgroundColor(Color.BLUE);
}else{
holder.text.setBackgroundColor(Color.WHITE);
}
holder.text.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// set background and put value in array list
holder.text.setBackgroundColor(Color.BLUE);
check.add(position);
}
});
return convertView;
}
// this will check whether current position is there is array list or not and if it there it will break loop and return true
public boolean checking(int position){
boolean fine = false;
for(int i=0; i<check.size();i++){
if(position == check.get(i)){
fine = true;
break;
}
}
return fine;
}
}
public class ViewHolder{
TextView text;
}
}
I don't how much I am ethical in this code...but as you have specified that you have 100 value.I have tested it on 1000 value and it worked
I am not expert so let me know if I am wrong somewhere
Hope it works !!!
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.
I have an Activity that hosts multiple fragments using the actionbar's tab functionality. One of those fragments contains a ListView. Upon this tab being selected, I'd like to select a certain item.
To do this programmatically, I use the following code (where calls is the ListView)
private void selectItem(int position)
{
long itemId = calls.GetItemIdAtPosition(position);
calls.PerformItemClick(calls, position, itemId);
}
If this ListView has been rendered, and I'm calling this, no problem. However, if I call it from onResume, then the code executes but nothing is selected in the end. I figure this is because at the point where I'm calling selectItem, not all items of the ListView have been rendered yet. If however I start off a background thread, sleep for a couple hundred milliseconds, then run the same code (in the ui thread of course), everything is fine, but this is an ugly hack.
Now you might be wondering, "why isn't he using calls.setSelection"? The thing is, I'm using a custom layout that performs expansion - so I need to actually click on the item I want selected (which in turn triggers the layout expansion for the item selected). However, I can call the code that is performed on PerformItemClick directly, the results will be the same (the layout expansion isn't performed).
Isn't there any way for me to catch the "Listview has finished rendering all viewable items" point in time, and then execute my selectItem call at that point? In ASP.NET, I have an event on every UI item telling me when it is done rendering, so I do item selection at that point but I haven't found anything.
Regards
Stephan
Here's the Adapter I'm using
public class ActiveCallsAdapter: ObservableAdapter<Call>
{
public ActiveCallsAdapter(Activity activity, ObservableCollection<Call> calls)
: base(activity, calls)
{
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
var view = (convertView ?? context.LayoutInflater.Inflate(Resource.Layout.Call, parent, false)) as LinearLayout;
//View view = convertView;
//if (view == null) // no view to re-use, create new
// view = context.LayoutInflater.Inflate(Resource.Layout.Call, null);
SetTextView(view, Resource.Id.CallerName, item.CallerName);
SetTextView(view, Resource.Id.CallerNumber, item.CallerNumber);
SetTextView(view, Resource.Id.CallStatus, item.State.ToString());
SetTextView(view, Resource.Id.CallDuration, item.Duration);
return view;
}
public void Update(LinearLayout view, Call item)
{
SetTextView(view, Resource.Id.CallerName, item.CallerName);
SetTextView(view, Resource.Id.CallerNumber, item.CallerNumber);
string identifier = "callState_" + item.State.ToString();
int resourceId = Application.Context.Resources.GetIdentifier(identifier, "string", Application.Context.PackageName);
string callStateString = item.State.ToString();
if (resourceId != 0)
{
try
{
callStateString = Application.Context.Resources.GetString(resourceId);
}
catch (Exception e)
{
AndroidLogModel.Model.AddLogMessage("ActiveCallsAdapter", "Unable to find call state string with resource id " + resourceId + " state string: " + identifier, 3);
}
}
SetTextView(view, Resource.Id.CallStatus, callStateString);
//SetTextView(view, Resource.Id.CallDuration, item.Duration);
}
public void UpdateDuration(LinearLayout view, Call item)
{
SetTextView(view, Resource.Id.CallDuration, item.Duration);
}
}
And the base class of that adapter
public class ObservableAdapter<T>: BaseAdapter<T>
{
protected readonly Activity context;
protected readonly ObservableCollection<T> items;
public ObservableAdapter(Activity context, ObservableCollection<T> collection)
{
this.context = context;
this.items = collection;
//this.collection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(collection_CollectionChanged);
this.items.CollectionChanged += (sender, e) => NotifyDataSetChanged();
}
void collection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyDataSetChanged();
}
public override T this[int position]
{
get { return items[position]; }
}
public override int Count
{
get { return items.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
var view = (convertView ?? context.LayoutInflater.Inflate(Resource.Layout.Call, parent, false)) as LinearLayout;
// configure view here
return view;
}
protected void SetTextView(LinearLayout view, int id, string text)
{
var textView = view.FindViewById<TextView>(id);
if (textView != null)
textView.SetText(text, TextView.BufferType.Normal);
}
}
My Mono skills are limited so I don't know if I fully understood your adapter, anyway I've adapted some old code and made an adapter that expands a single item when click, also it will move the ListView in onResume to a desired position:
private static class CustomAdapter extends BaseAdapter {
// the data
private ArrayList<String> mData;
// an int pointing to a position that has an expanded layout,
// for simplicity I assume that you expand only one item(otherwise use
// an array or list)
private int mExpandedPosition = -1; // -1 meaning no expanded item
private LayoutInflater mInflater;
public CustomAdapter(Context context, ArrayList<String> items) {
mInflater = LayoutInflater.from(context);
mData = items;
}
public void setExpandedPosition(int position) {
// if the position equals mExpandedPosition then we have a click on
// the same row so simply toggle the row to be gone again
if (position == mExpandedPosition) {
mExpandedPosition = -1;
} else {
// else change position of the row that was expanded
mExpandedPosition = position;
}
// notify the adapter
notifyDataSetChanged();
}
#Override
public int getCount() {
return mData.size();
}
#Override
public String getItem(int position) {
return mData.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.ad_expandedelement,
parent, false);
}
((TextView) convertView.findViewById(R.id.textView1))
.setText(getItem(position));
// see if there is an expanded position and if we are at that
// position
if (mExpandedPosition != -1 && mExpandedPosition == position) {
// if yes simply expand the layout
convertView.findViewById(R.id.button1).setVisibility(
View.VISIBLE);
} else {
// this is required, we must revert any possible changes
// otherwise the recycling mechanism will hurt us
convertView.findViewById(R.id.button1).setVisibility(View.GONE);
}
return convertView;
}
}
The onListItemClick will simply be:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// set the expanded(or collapsed if it's a click on the same row that
// was previously expanded) row in the adapter
((CustomAdapter) getListView().getAdapter())
.setExpandedPosition(position);
}
and in onResume will have:
#Override
protected void onResume() {
super.onResume();
// set the position to the desired element
((CustomAdapter) getListView().getAdapter()).setExpandedPosition(15);
// set the selection to that element so we can actually see it
// this isn't required but has the advantage that it will move the
// ListView to the desired
// position if not visible
getListView().setSelection(15);
}
The R.layout.ad_expandedelement is a simple vertical LinearLayout with a TextView and an initially hidden(visibility set to gone) Button. For this Button I change the visibility to simulate expanding/collapsing a row in the ListView. You should be able to understand my code, if you want I can post on github the full sample.
While I'm not sure of the exact equivalent in C#/Mono, the Android framework provides a callback on Activity called onWindowFocusChanged() that indicates the period when the Window associated with a given Activity is visible to the user. You may have better luck waiting to call your selection method until that time, as the ListView should be measured and laid out by that point. In Java, it would be something like this:
#Override
public void onWindowFocusChanged (boolean hasFocus) {
if (hasFocus) {
selectItem(position);
}
}
You may need to have a bit more logic in there, this callback is directly associated with window focus and isn't a true lifecycle method. I can get called multiple times if you are displaying Dialogs or doing other similar operations.
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)