Programmatically check (select) ListFragment item after screen rotation - android

I want to programmatically (re)highlight selected list item after screen rotation.
public class MyListFragment extends ListFragment {
private static final String tag = MyListFragment.class.getName();
private static final String indexTag = "index";
private int index = -1;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
if (savedInstanceState != null) {
index = savedInstanceState.getInt(indexTag, -1);
Log.d(tag, "Restored index " + index + " from saved instance state.");
}
}
#Override
public void onResume() {
super.onResume();
if (index >= 0) {
showDetails(index);
}
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
private void showDetails(int index) {
this.index = index;
getListView().setItemChecked(index, true);
// update details panel
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(indexTag, index);
}
}
I use CheckedTextView as item view in my custom adapter:
public class MyListAdapter extends BaseAdapter {
private static final String tag = MyListAdapter.class.getName();
#Override
public CheckedTextView getView(int position, View convertView, ViewGroup parent) {
if (convertView == null || !(convertView instanceof CheckedTextView)) {
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.simple_list_item_single_choice, parent, false);
}
((CheckedTextView)convertView).setText("test");
return (CheckedTextView)convertView;
}
}
After screen rotation showDetails() is called and details panel updates but setItemChecked()does nothing and the item is still not highlighted. Also I noticed that when item it clicked by touch event setItemChecked() is not needed and the row highlights anyway.
So how can I programmatically check the item during onResume stage?

put showIndex(index) in your onActivityCreate() because on screen rotation Android destroys current activity and create another one saving current state through Bundle savedInstanceState

I solved the problem. I forgot that I'm setting list adapter through AsyncTask on my activity so when showDetails() is called during onResume stage my fragment still has empty list.
So I removed onResume method from my fragment, made showDetails() public and call it from my activity after setting the adapter:
public void onListLoadDone(...) {
final MyListAdapter adapter = new MyListAdapter(...);
myListFragment.setListAdapter(adapter);
myListFragment.showDetails();
}

Related

Restoring State of Spinners and Their Adapters

I am having issues trying to restore state of Android Spinners in my application. Currently there are multiple Spinners in my Activity's ListView header that depend on one another, as the selection of one Spinner loads data for the subsequent Spinner.
The problem I am experiencing is, restoring state doesn't seem to work when I manually set selections on the Spinners. I have tried in both onRestoreInstanceState and onResume. It appears setting the selections is asynchronous when looking at the LogCat output. How can I reliably restore state of these Spinners when I have to wait for one to be selected before the other can populated and then set?
EDIT: Added code
Activity's onCreate():
mSecondSpinner = mMyListHeader.findViewById(R.id.second_spinner);
mSecondSpinnerArrayAdapter = new SecondArrayAdapter(MyActivity.this, R.layout.second_spinner_item, new ArrayList<MySecondDto>());
mSecondSpinner.setAdapter(mSecondSpinnerArrayAdapter);
mSecondSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
MySecondDto selectedMySecondDto = (MySecondDto) parent.getItemAtPosition(position);
List<MyThirdDto> myThirdDtos = selectedMySecondDto.getMyThirdDtos();
// Load third spinner with dtos....
}
#Override
public void onNothingSelected(AdapterView<?> parent) {}}
);
mFirstSpinner = mMyListHeader.findViewById(R.id.first_spinner);
mFirstSpinnerAdapter= new FirstArrayAdapter(MyActivity.this, R.layout.first_spinner, mResponse.getAllDtos());
mFirstSpinner.setAdapter(mFirstSpinnerArrayAdapter);
mFirstSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mSecondSpinner.setAdapter(null);
MyFirstDto selectedMyFirstDto = (MyFirstDto ) parent.getItemAtPosition(position);
List<MySecondDto> mySecondDtos = selectedMyFirstDto .getMySecondDtos();
mSecondSpinnerArrayAdapter.clearAndReplaceAll(mySecondDtos);
mSecondSpinner.setAdapter(mSecondSpinnerArrayAdapter);
// If there is only one second dto, disable the spinner
if (mySecondDtos== null || mySecondDtos.size() <= 1)
{
disableSpinner(mSecondSpinner);
}
else
{
// Enable second spinner, select the hint element
enableSpinner(mSecondSpinner);
mSecondSpinner.setSelection(mSecondSpinnerArrayAdapter .getHintIndex());
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {}
});
Activity's onRestoreInstanceState():
mFirstSpinner.setSelection(mFirstAdapterPosition);
mFirstSpinnerArrayAdapter.notifyDataSetChanged();
mSecondSpinner.setSelection(mSecondAdapterPosition);
mSecondSpinnerArrayAdapter.notifyDataSetChanged();
Have u tried this
mySpinner.post(new Runnable() { #Override public void run() { mySpinner.setSelection(position); } });
This might work for you.
There could be a bug in it somewhere, so be careful.
I have a similar situation to you but I had 3 spinners and they get populated depending on the selection of the previous one.
The idea is to store the Indexes/Positions of the Spinners in IndexVariables.
Theses Variables have default value of -1.
Store current values in onSaveInstanceState,
Restore values in onActivityCreated.
In onItemSelected check if selected Item = null, check if the IndexVariable was set (i.e. !-= -1)
If so use it to set Spinner then set IndexVariable back to -1;
Here's the class
(I used NothingSelectedSpinnerAdapter from How to make an Android Spinner with initial text "Select One".
Not really important but just giving a shout out to the guy/girl where I got that code.)
public class SpinnerTestFragment extends Fragment {
private MainActivity activity;
private static final String SELECTED_THEME_IDX_STORAGE_KEY = "mSelectedTheme_IDX_StorageKey";
private static final String SELECTED_AIM_IDX_STORAGE_KEY = "mSelectedAim_IDX_StorageKey";
private static final String SELECTED_GOAL_IDX_STORAGE_KEY = "mSelectedGoal_IDX_StorageKey";
private static String TAG = "SpinnerTestFragment";
private Spinner spnrThemes;
private Spinner spnrAims;
private Spinner spnrGoals;
private String mSelectedTheme;
private String mSelectedAim;
private String mSelectedGoal;
private int mSelectedAimIdx = -1;
private int mSelectedThemeIdx = -1;
private int mSelectedGoalIdx = -1;
//----------------------------------------------------------------------------------------//
public SpinnerTestFragment() {
}//ctor
//----------------------------------------------------------------------------------------//
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
activity = (MainActivity) getActivity();
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_photo, container, false);
spnrThemes = view.findViewById(R.id.spnrThemes);
spnrAims = view.findViewById(R.id.spnrAims);
spnrGoals = view.findViewById(R.id.spnrGoals);
setSpinner(spnrThemes, "Select Theme", ThemesAimsGoals.getThemes());
spnrThemes.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Object selectedItem = parent.getItemAtPosition(position);
if (selectedItem != null) {
mSelectedTheme = selectedItem.toString();
setSpinner(spnrAims, "Select Aim", ThemesAimsGoals.getAims(mSelectedTheme));
} else if(mSelectedThemeIdx != -1){
selectedItem = parent.getItemAtPosition(mSelectedThemeIdx);
mSelectedTheme = selectedItem.toString();
setSpinner(spnrAims, "Select Aim", ThemesAimsGoals.getAims(mSelectedTheme));
parent.setSelection(mSelectedThemeIdx);
mSelectedThemeIdx = -1;
}//Else
}//onItemSelected
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
spnrAims.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Object selectedItem = parent.getItemAtPosition(position);
if (selectedItem != null) {
mSelectedAim = selectedItem.toString();
setSpinner(spnrGoals, "Select Goal", ThemesAimsGoals.getGoals(mSelectedTheme, mSelectedAim));
} else if(mSelectedAimIdx != -1){
selectedItem = parent.getItemAtPosition(mSelectedAimIdx);
mSelectedAim = selectedItem.toString();
setSpinner(spnrGoals, "Select Goal", ThemesAimsGoals.getGoals(mSelectedTheme, mSelectedAim));
parent.setSelection(mSelectedAimIdx);
mSelectedAimIdx = -1;
}//Else
}//onItemSelected
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
spnrGoals.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Object selectedItem = parent.getItemAtPosition(position);
if (selectedItem != null) {
mSelectedGoal = selectedItem.toString();
}else if(mSelectedGoalIdx != -1){
selectedItem = parent.getItemAtPosition(mSelectedGoalIdx);
mSelectedGoal = selectedItem.toString();
parent.setSelection(mSelectedGoalIdx);
mSelectedGoalIdx = -1;
}//Else
}//onItemSelected
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
return view;
}//onCreateView
//----------------------------------------------------------------------------------------//
/**
* Populate Spinner
* #param spnr Spinner to populate
* #param prompt What to show at the start
* #param array Items in the spinner
*/
private void setSpinner(Spinner spnr, String prompt, String[] array) {
spnr.setPrompt(prompt);
ArrayAdapter<CharSequence> adapter = new ArrayAdapter(activity, android.R.layout.simple_spinner_item, array);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spnr.setAdapter(
new NothingSelectedSpinnerAdapter(
adapter,
R.layout.contact_spinner_row_nothing_selected,
activity,
prompt));
}//setSpinner
//----------------------------------------------------------------------------------------//
/**
* Some lifecycle callbacks so that the image can survive orientation chang
*
* #param outState current state of fragment
*/
#Override
public void onSaveInstanceState(Bundle outState) {
Log.d(TAG, "onSaveInstanceState");
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_THEME_IDX_STORAGE_KEY, spnrThemes.getSelectedItemPosition());
outState.putInt(SELECTED_AIM_IDX_STORAGE_KEY, spnrAims.getSelectedItemPosition());
outState.putInt(SELECTED_GOAL_IDX_STORAGE_KEY, spnrGoals.getSelectedItemPosition());
}//onSaveInstanceState
//----------------------------------------------------------------------------------------//
/**
* Rebuilds the Activity/Fragment in the image of the last one.
*
* #param savedInstanceState Info from last session or rotation
*/
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, "onActivityCreated");
if (savedInstanceState == null)
return;
mSelectedThemeIdx = savedInstanceState.getInt(SELECTED_THEME_IDX_STORAGE_KEY);
mSelectedAimIdx = savedInstanceState.getInt(SELECTED_AIM_IDX_STORAGE_KEY);
mSelectedGoalIdx = savedInstanceState.getInt(SELECTED_GOAL_IDX_STORAGE_KEY);
}//onActivityCreated
}//Cls

How to keep the checked items of the dynamically created views from a adapter on Orientation change

What i am doing:: I have a horizontal listview as shown below for which i am populating items dynamically
What is happening:: Since its a dynamically created listview onorientation change the checked items are unchecked
Question: How can i collected the checked items from the adapter and recheck the selected things on orientation change
AdpBufTypeSearch.java
public class AdpBufTypeSearch extends BaseAdapter{
private HashMap<String, String> objHashBufType;
SparseBooleanArray mBufTypeArr = new SparseBooleanArray();
private ArrayList<HashMap<String, String>> objListBufType;
Context mContext;
public AdpBufTypeSearch(Context _mContext,ArrayList<HashMap<String, String>> _objListBufType) {
mContext=_mContext;
objListBufType=_objListBufType;
}
#Override
public int getCount() {
return objListBufType.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
//LOGIC:: result will be a set on which ones are selected Ex:: 0,1,2,4
public String getSelectedBuffetType() {
//This final value(strBufTypeId) is returned when we access from class
String strBufTypeId="";
for(int i=0;i<objListBufType.size();i++) {
HashMap<String, String> objHashBufType = objListBufType.get(i);
if(objHashBufType.get("selected")=="1") {
strBufTypeId=strBufTypeId+objHashBufType.get(buf_type_mas.COLUMN_BUF_TYPE_ID);
strBufTypeId=strBufTypeId+",";
}
}
//remove the last "," in the string
if(strBufTypeId.lastIndexOf(",")>0)
strBufTypeId=strBufTypeId.substring(0, strBufTypeId.lastIndexOf(","));
return strBufTypeId;
}
/*LOGIC:: <HashMapObject(objHashBufType)> ==> their value of key(selected) is updated to "1" else key(selected) is updated to 0 */
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View retval = LayoutInflater.from(parent.getContext()).inflate(R.layout.adp_meal_type, null);
final TextView buf_type_name = (TextView) retval.findViewById(R.id.buf_type_name);
TextView buf_type_id=(TextView) retval.findViewById(R.id.buf_type_id);
ImageView buf_type_image=(ImageView) retval.findViewById(R.id.buf_type_image);
final LinearLayout imgBkgSelector=(LinearLayout) retval.findViewById(R.id.imgBkgSelector);
imgBkgSelector.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//LOGIC:: If Selected unselect it and if it is unselected select it
if(mBufTypeArr.get((Integer) imgBkgSelector.getTag())==true){
//INNER-LOGIC:: Background not selected
mBufTypeArr.put((Integer) imgBkgSelector.getTag(), false);
objHashBufType = objListBufType.get((Integer) imgBkgSelector.getTag());
objHashBufType.put("selected", "0");
imgBkgSelector.setBackgroundColor(Color.parseColor(mContext.getString(R.color.cBlack)));
buf_type_name.setTextColor(Color.parseColor(mContext.getString(R.color.cWhite)));
}
else{
//INNER-LOGIC:: Background selected
mBufTypeArr.put((Integer) imgBkgSelector.getTag(), true);
objHashBufType = objListBufType.get((Integer) imgBkgSelector.getTag());
objHashBufType.put("selected", "1");
imgBkgSelector.setBackgroundColor(Color.parseColor(mContext.getString(R.color.cBlue)));
buf_type_name.setTextColor(Color.parseColor(mContext.getString(R.color.cWhite)));
}
}
});
imgBkgSelector.setTag(position);
//Essential code for retain the Background check part on scroll of images
if(mBufTypeArr.get(position)==true){
imgBkgSelector.setBackgroundColor(Color.parseColor(mContext.getString(R.color.cBlue)));
buf_type_name.setTextColor(Color.parseColor(mContext.getString(R.color.cWhite)));
}else{
imgBkgSelector.setBackgroundColor(Color.parseColor(mContext.getString(R.color.cBlack)));
buf_type_name.setTextColor(Color.parseColor(mContext.getString(R.color.cWhite)));
}
// Get the position
objHashBufType = objListBufType.get(position);
// Capture position and set results to the TextViews
//Capitilize the names
String capitalizedBufTypeName = WordUtils.capitalizeFully(objHashBufType.get(buf_type_mas.COLUMN_BUF_TYPE_NAME), ' ');
buf_type_name.setText(capitalizedBufTypeName);
buf_type_id.setText(objHashBufType.get(buf_type_mas.COLUMN_BUF_TYPE_ID));
Picasso.with(mContext)
.load(mContext.getString(R.string.URL_BUFFET_TYPE_IMAGE).trim()+objHashBufType.get(buf_type_mas.COLUMN_BUF_TYPE_IMAGE).trim()).resizeDimen(R.dimen.filter_image_width,R.dimen.filter_image_height).centerCrop().into(buf_type_image);
return retval;
}
}
FrgMdSearch .java
public class FrgMdSearch extends Fragment {
private HashMap<String, String> objHashBufType;
private ArrayList<HashMap<String, String>> objListBufType=null;
private AdpBufTypeSearch bufTypeAdapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//Setting the adapter for buf images<---DYNAMIC VIEWS--->
setAdapterBufImages();
}
private void setAdapterBufImages() {
bufTypeAdapter=new AdpBufTypeSearch(getActivity(),objListBufType);
hListView.setAdapter(bufTypeAdapter);
}
}
You need to save the position of the checked item when orientation change occurs in your onSaveInstanceState method also create a getter method in your AdpBufTypeSearch adapter that returns the current position of the checked item and setter method to set the checked item.
sample:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("position", bufTypeAdapter.getCheckedPosition()); //getCheckedPosition must return the checked item position
}
In oncreateView of the fragment
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//Setting the adapter for buf images<---DYNAMIC VIEWS--->
setAdapterBufImages(savedInstanceState);
}
private void setAdapterBufImages(Bundle savedInstanceState) {
bufTypeAdapter=new AdpBufTypeSearch(getActivity(),objListBufType);
if(savedInstanceState != null)
{
bufTypeAdapter.setCheckedItem(savedInstanceState.getInt("position")); //will set the checked item
}
hListView.setAdapter(bufTypeAdapter);
}

Android: Select ListView Item in onResume

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.

How does a spinner know its value?

I have a spinner which starts a second activity on a value change. This works good.
After a return everything works fine too. But if I rotate now the mobile the OnItemSelectedListener is called! Why? The spinner should be on position -1. Which means that the callback will not be called.
How does android know which spinner item is selected? I tried to prevend to restore this information by sending the base class the a null value for the savedInstanceState.
Here is some code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
setContentView(R.layout.menu);
String db=MyDatabase.getData();
if(db == null)
db="";
Spinner spinner=(Spinner)findViewById(R.id.spinner);
MyAdapter adapter=new MyAdapter(this, db, "ID", "Name");
spinner.setAdapter(adapter);
adapter.setHint(spinner, "Please select...");
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id2) {
int id=((SpinnerItem)parent.getAdapter().getItem(position)).getId();
Intent intent=new Intent();
intent.setClass(MainMenu.this, Other.class);
intent.putExtra("id", id);
startActivity(intent);
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
}
Here is my adapter:
public class MyAdapter extends ArrayAdapter<SpinnerItem> {
private final Context context;
private String hint;
public MyAdapter(Context context, String input, String key, String value) {
super(context, android.R.layout.simple_spinner_item, create(input, key, value));
this.context=context;
}
private static SpinnerItem[] create(String input, String key, String value) {
Vector<SpinnerItem> list=new Vector<SpinnerItem>();
// fill the list
return list.toArray(new SpinnerItem[] {});
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// show the hint
if(position == -1) {
TextView tv=new TextView(context);
tv.setTextColor(Color.DKGRAY);
tv.setText(hint);
return tv;
}
return super.getView(position, convertView, parent);
}
public void setHint(Spinner spinner, String hint) {
if(spinner.getAdapter() == null) {
throw new IllegalStateException("Set your adapter first!");
}
this.hint=hint;
try {
final Method m=AdapterView.class.getDeclaredMethod("setNextSelectedPositionInt", int.class);
m.setAccessible(true);
m.invoke(spinner, -1);
final Method n=AdapterView.class.getDeclaredMethod("setSelectedPositionInt", int.class);
n.setAccessible(true);
n.invoke(spinner, -1);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
When you rotate your device your Activity gets restarted or recreated add this line to your AndroidManifest file in activity tag to avoid it.
android:configChanges="keyboardHidden|orientation"
When you change the orientation the onCreate() of Activity get Called.
and by default as the behaviour of the spinner the method onItemSelectedListener() get called of the spinner when ever the activity gets created.
So this is happening to you in this case
Solution for such issue is given here StackOverflow Question's Link check how he handled the issue by taking two counters checkBoxCounter and checkBoxInitialized and try to implement same in your case.

Maintain/Save/Restore scroll position when returning to a ListView

I have a long ListView that the user can scroll around before returning to the previous screen. When the user opens this ListView again, I want the list to be scrolled to the same point that it was previously. Any ideas on how to achieve this?
Try this:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
// ...
// restore index and position
mList.setSelectionFromTop(index, top);
Explanation:
ListView.getFirstVisiblePosition() returns the top visible list item. But this item may be partially scrolled out of view, and if you want to restore the exact scroll position of the list you need to get this offset. So ListView.getChildAt(0) returns the View for the top list item, and then View.getTop() - mList.getPaddingTop() returns its relative offset from the top of the ListView. Then, to restore the ListView's scroll position, we call ListView.setSelectionFromTop() with the index of the item we want and an offset to position its top edge from the top of the ListView.
Parcelable state;
#Override
public void onPause() {
// Save ListView state # onPause
Log.d(TAG, "saving listview state");
state = listView.onSaveInstanceState();
super.onPause();
}
...
#Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Set new items
listView.setAdapter(adapter);
...
// Restore previous state (including selected item index and scroll position)
if(state != null) {
Log.d(TAG, "trying to restore listview state");
listView.onRestoreInstanceState(state);
}
}
I adopted the solution suggested by #(Kirk Woll), and it works for me. I have also seen in the Android source code for the "Contacts" app, that they use a similar technique. I would like to add some more details:
On top on my ListActivity-derived class:
private static final String LIST_STATE = "listState";
private Parcelable mListState = null;
Then, some method overrides:
#Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
mListState = state.getParcelable(LIST_STATE);
}
#Override
protected void onResume() {
super.onResume();
loadData();
if (mListState != null)
getListView().onRestoreInstanceState(mListState);
mListState = null;
}
#Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
mListState = getListView().onSaveInstanceState();
state.putParcelable(LIST_STATE, mListState);
}
Of course "loadData" is my function to retrieve data from the DB and put it onto the list.
On my Froyo device, this works both when you change the phone orientation, and when you edit an item and go back to the list.
A very simple way:
/** Save the position **/
int currentPosition = listView.getFirstVisiblePosition();
//Here u should save the currentPosition anywhere
/** Restore the previus saved position **/
listView.setSelection(savedPosition);
The method setSelection will reset the list to the supplied item. If not in touch mode the item will actually be selected if in touch mode the item will only be positioned on screen.
A more complicated approach:
listView.setOnScrollListener(this);
//Implements the interface:
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
mCurrentX = view.getScrollX();
mCurrentY = view.getScrollY();
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
//Save anywere the x and the y
/** Restore: **/
listView.scrollTo(savedX, savedY);
I found something interesting about this.
I tried setSelection and scrolltoXY but it did not work at all, the list remained in the same position, after some trial and error I got the following code that does work
final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {
#Override
public void run() {
list.setSelection(0);
}
});
If instead of posting the Runnable you try runOnUiThread it does not work either (at least on some devices)
This is a very strange workaround for something that should be straight forward.
CAUTION!! There is a bug in AbsListView that doesn't allow the onSaveState() to work correctly if the ListView.getFirstVisiblePosition() is 0.
So If you have large images that take up most of the screen, and you scroll to the second image, but a little of the first is showing, the scroll position Won't be saved...
from AbsListView.java:1650 (comments mine)
// this will be false when the firstPosition IS 0
if (haveChildren && mFirstPosition > 0) {
...
} else {
ss.viewTop = 0;
ss.firstId = INVALID_POSITION;
ss.position = 0;
}
But in this situation, the 'top' in the code below will be a negative number which causes other issues that prevent the state to be restored correctly. So when the 'top' is negative, get the next child
// save index and top position
int index = getFirstVisiblePosition();
View v = getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
if (top < 0 && getChildAt(1) != null) {
index++;
v = getChildAt(1);
top = v.getTop();
}
// parcel the index and top
// when restoring, unparcel index and top
listView.setSelectionFromTop(index, top);
For some looking for a solution to this problem, the root of the issue may be where you are setting your list views adapter. After you set the adapter on the listview, it resets the scroll position. Just something to consider. I moved setting the adapter into my onCreateView after we grab the reference to the listview, and it solved the problem for me. =)
private Parcelable state;
#Override
public void onPause() {
state = mAlbumListView.onSaveInstanceState();
super.onPause();
}
#Override
public void onResume() {
super.onResume();
if (getAdapter() != null) {
mAlbumListView.setAdapter(getAdapter());
if (state != null){
mAlbumListView.requestFocus();
mAlbumListView.onRestoreInstanceState(state);
}
}
}
That's enough
Am posting this because I am surprised nobody had mentioned this.
After user clicks the back button he will return to the listview in the same state as he went out of it.
This code will override the "up" button to behave the same way as the back button so in the case of Listview -> Details -> Back to Listview (and no other options) this is the simplest code to maintain the scrollposition and the content in the listview.
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return(true);
}
return(super.onOptionsItemSelected(item)); }
Caution: If you can go to another activity from the details activity the up button will return you back to that activity so you will have to manipulate the backbutton history in order for this to work.
Isn't simply android:saveEnabled="true" in the ListView xml declaration enough?
BEST SOLUTION IS:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
// ...
// restore index and position
mList.post(new Runnable() {
#Override
public void run() {
mList.setSelectionFromTop(index, top);
}
});
YOU MUST CALL IN POST AND IN THREAD!
You can maintain the scroll state after a reload if you save the state before you reload and restore it after. In my case I made a asynchronous network request and reloaded the list in a callback after it completed. This is where I restore state. Code sample is Kotlin.
val state = myList.layoutManager.onSaveInstanceState()
getNewThings() { newThings: List<Thing> ->
myList.adapter.things = newThings
myList.layoutManager.onRestoreInstanceState(state)
}
If you're using fragments hosted on an activity you can do something like this:
public abstract class BaseFragment extends Fragment {
private boolean mSaveView = false;
private SoftReference<View> mViewReference;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mSaveView) {
if (mViewReference != null) {
final View savedView = mViewReference.get();
if (savedView != null) {
if (savedView.getParent() != null) {
((ViewGroup) savedView.getParent()).removeView(savedView);
return savedView;
}
}
}
}
final View view = inflater.inflate(getFragmentResource(), container, false);
mViewReference = new SoftReference<View>(view);
return view;
}
protected void setSaveView(boolean value) {
mSaveView = value;
}
}
public class MyFragment extends BaseFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setSaveView(true);
final View view = super.onCreateView(inflater, container, savedInstanceState);
ListView placesList = (ListView) view.findViewById(R.id.places_list);
if (placesList.getAdapter() == null) {
placesList.setAdapter(createAdapter());
}
}
}
If you are saving/restoring scroll position of ListView yourself you are essentially duplicating the functionality already implemented in android framework. The ListView restores fine scroll position just well on its own except one caveat: as #aaronvargas mentioned there is a bug in AbsListView that won't let to restore fine scroll position for the first list item. Nevertheless the best way to restore scroll position is not to restore it. Android framework will do it better for you. Just make sure you have met the following conditions:
make sure you have not called setSaveEnabled(false) method and not set android:saveEnabled="false" attribute for the list in the xml layout file
for ExpandableListView override long getCombinedChildId(long groupId, long childId) method so that it returns positive long number (default implementation in class BaseExpandableListAdapter returns negative number). Here are examples:
.
#Override
public long getChildId(int groupPosition, int childPosition) {
return 0L | groupPosition << 12 | childPosition;
}
#Override
public long getCombinedChildId(long groupId, long childId) {
return groupId << 32 | childId << 1 | 1;
}
#Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
#Override
public long getCombinedGroupId(long groupId) {
return (groupId & 0x7FFFFFFF) << 32;
}
if ListView or ExpandableListView is used in a fragment do not recreate the fragment on activity recreation (after screen rotation for example). Obtain the fragment with findFragmentByTag(String tag) method.
make sure the ListView has an android:id and it is unique.
To avoid aforementioned caveat with first list item you can craft your adapter the way it returns special dummy zero pixels height view for the ListView at position 0.
Here is the simple example project shows ListView and ExpandableListView restore their fine scroll positions whereas their scroll positions are not explicitly saved/restored. Fine scroll position is restored perfectly even for the complex scenarios with temporary switching to some other application, double screen rotation and switching back to the test application. Please note, if you are explicitly exiting the application (by pressing the Back button) the scroll position won't be saved (as well as all other Views won't save their state).
https://github.com/voromto/RestoreScrollPosition/releases
For an activity derived from ListActivity that implements LoaderManager.LoaderCallbacks using a SimpleCursorAdapter it did not work to restore the position in onReset(), because the activity was almost always restarted and the adapter was reloaded when the details view was closed. The trick was to restore the position in onLoadFinished():
in onListItemClick():
// save the selected item position when an item was clicked
// to open the details
index = getListView().getFirstVisiblePosition();
View v = getListView().getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - getListView().getPaddingTop());
in onLoadFinished():
// restore the selected item which was saved on item click
// when details are closed and list is shown again
getListView().setSelectionFromTop(index, top);
in onBackPressed():
// Show the top item at next start of the app
index = 0;
top = 0;
Neither of the solutions offered here seemed to work for me. In my case, I have a ListView in a Fragment which I'm replacing in a FragmentTransaction, so a new Fragment instance is created each time the fragment is shown, which means that the ListView state can not be stored as a member of the Fragment.
Instead, I ended up storing the state in my custom Application class. The code below should give you an idea how this works:
public class MyApplication extends Application {
public static HashMap<String, Parcelable> parcelableCache = new HashMap<>();
/* ... code omitted for brevity ... */
}
public class MyFragment extends Fragment{
private ListView mListView = null;
private MyAdapter mAdapter = null;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mAdapter = new MyAdapter(getActivity(), null, 0);
mListView = ((ListView) view.findViewById(R.id.myListView));
Parcelable listViewState = MyApplication.parcelableCache.get("my_listview_state");
if( listViewState != null )
mListView.onRestoreInstanceState(listViewState);
}
#Override
public void onPause() {
MyApplication.parcelableCache.put("my_listview_state", mListView.onSaveInstanceState());
super.onPause();
}
/* ... code omitted for brevity ... */
}
The basic idea is that you store the state outside the fragment instance. If you don't like the idea of having a static field in your application class, I guess you could do it by implementing a fragment interface and storing the state in your activity.
Another solution would be to store it in SharedPreferences, but it gets a bit more complicated, and you would need to make sure you clear it on application launch unless you want the state to be persisted across app launches.
Also, to avoid the "scroll position not saved when first item is visible", you can display a dummy first item with 0px height. This can be achieved by overriding getView() in your adapter, like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if( position == 0 ) {
View zeroHeightView = new View(parent.getContext());
zeroHeightView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
return zeroHeightView;
}
else
return super.getView(position, convertView, parent);
}
My answer is for Firebase and position 0 is a workaround
Parcelable state;
DatabaseReference everybody = db.getReference("Everybody Room List");
everybody.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
state = listView.onSaveInstanceState(); // Save
progressBar.setVisibility(View.GONE);
arrayList.clear();
for (DataSnapshot messageSnapshot : dataSnapshot.getChildren()) {
Messages messagesSpacecraft = messageSnapshot.getValue(Messages.class);
arrayList.add(messagesSpacecraft);
}
listView.setAdapter(convertView);
listView.onRestoreInstanceState(state); // Restore
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
and convertView
position 0 a add a blank item that you are not using
public class Chat_ConvertView_List_Room extends BaseAdapter {
private ArrayList<Messages> spacecrafts;
private Context context;
#SuppressLint("CommitPrefEdits")
Chat_ConvertView_List_Room(Context context, ArrayList<Messages> spacecrafts) {
this.context = context;
this.spacecrafts = spacecrafts;
}
#Override
public int getCount() {
return spacecrafts.size();
}
#Override
public Object getItem(int position) {
return spacecrafts.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#SuppressLint({"SetTextI18n", "SimpleDateFormat"})
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.message_model_list_room, parent, false);
}
final Messages s = (Messages) this.getItem(position);
if (position == 0) {
convertView.getLayoutParams().height = 1; // 0 does not work
} else {
convertView.getLayoutParams().height = RelativeLayout.LayoutParams.WRAP_CONTENT;
}
return convertView;
}
}
I have seen this work temporarily without disturbing the user, I hope it works for you
use this below code :
int index,top;
#Override
protected void onPause() {
super.onPause();
index = mList.getFirstVisiblePosition();
View v = challengeList.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
}
and whenever your refresh your data use this below code :
adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);
I'm using FirebaseListAdapter and couldn't get any of the solutions to work. I ended up doing this. I'm guessing there are more elegant ways but this is a complete and working solution.
Before onCreate:
private int reset;
private int top;
private int index;
Inside of the FirebaseListAdapter:
#Override
public void onDataChanged() {
super.onDataChanged();
// Only do this on first change, when starting
// activity or coming back to it.
if(reset == 0) {
mListView.setSelectionFromTop(index, top);
reset++;
}
}
onStart:
#Override
protected void onStart() {
super.onStart();
if(adapter != null) {
adapter.startListening();
index = 0;
top = 0;
// Get position from SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
top = sharedPref.getInt("TOP_POSITION", 0);
index = sharedPref.getInt("INDEX_POSITION", 0);
// Set reset to 0 to allow change to last position
reset = 0;
}
}
onStop:
#Override
protected void onStop() {
super.onStop();
if(adapter != null) {
adapter.stopListening();
// Set position
index = mListView.getFirstVisiblePosition();
View v = mListView.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop());
// Save position to SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
}
}
Since I also had to solve this for FirebaseRecyclerAdapter I'm posting the solution here for that too:
Before onCreate:
private int reset;
private int top;
private int index;
Inside of the FirebaseRecyclerAdapter:
#Override
public void onDataChanged() {
// Only do this on first change, when starting
// activity or coming back to it.
if(reset == 0) {
linearLayoutManager.scrollToPositionWithOffset(index, top);
reset++;
}
}
onStart:
#Override
protected void onStart() {
super.onStart();
if(adapter != null) {
adapter.startListening();
index = 0;
top = 0;
// Get position from SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
top = sharedPref.getInt("TOP_POSITION", 0);
index = sharedPref.getInt("INDEX_POSITION", 0);
// Set reset to 0 to allow change to last position
reset = 0;
}
}
onStop:
#Override
protected void onStop() {
super.onStop();
if(adapter != null) {
adapter.stopListening();
// Set position
index = linearLayoutManager.findFirstVisibleItemPosition();
View v = linearLayoutManager.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
// Save position to SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
}
}
To clarify the excellent answer of Ryan Newsom and to adjust it for fragments and for the usual case that we want to navigate from a "master" ListView fragment to a "details" fragment and then back to the "master"
private View root;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
if(root == null){
root = inflater.inflate(R.layout.myfragmentid,container,false);
InitializeView();
}
return root;
}
public void InitializeView()
{
ListView listView = (ListView)root.findViewById(R.id.listviewid);
BaseAdapter adapter = CreateAdapter();//Create your adapter here
listView.setAdpater(adapter);
//other initialization code
}
The "magic" here is that when we navigate back from the details fragment to the ListView fragment, the view is not recreated, we don't set the ListView's adapter, so everything stays as we left it!

Categories

Resources