Android app has some very long preference screens, which always open at the top of the preference menu. I have some idea where the user wants to be in the preference menu. How can I force the preference screen to open scrolled to a specific preference item?
I know this is an old one, so this answer is just for reference.
To auto-select a given screen, all you have to do is setPreferenceScreen() (this is for a pre-Honeycomb non-Fragment PreferenceActivity).
Once you're on the correct PreferenceScreen, you can indeed use getListView().smoothScrollToPosition(position) (but this is a Froyo+ method), or you can use getListView.setSelection(position).
But how to get the position?
First, watch out for the trap: PreferenceActivity.getListAdapter() does not return the actual ListAdapter, but a local instance variable which is disconcertingly not in sync with PreferenceActivity.getListView().getAdapter() (and usually null).
Second, trying to use Preference.getOrder() returns the order of the Preference object within its parent, which is what you want to use for the position only if you're not using PreferenceCategories since what you need is its order within the PreferenceScreen.
If you are using PreferenceCategories, you need to iterate over the items in the adapter (for (int i = 0; i < adapter.getCount(); i++)until you find the right one, and use its position.
Another corner of the Android SDK that is in dire need of some attention…
You can just use scrollToPreference :
https://developer.android.com/reference/androidx/preference/PreferenceFragmentCompat#scrollToPreference(androidx.preference.Preference)
Example:
scrollToPreference(preferenceKey)
or:
scrollToPreference(preference)
Add this function to your PreferenceFragment
public void scrollToItem(String preferenceName) {
ListView listView = ButterKnife.findById(getView(),android.R.id.list);
Preference preference = findPreference(preferenceName);
if (preference != null && listView != null) {
for (int i = 0; i < listView.getAdapter().getCount(); i++) {
Preference iPref = (Preference) listView.getAdapter().getItem(i);
if (iPref == preference) {
listView.setSelection(i);
break;
}
}
}
}
Lets say you have settings.xml with this
<Preference
android:icon="#drawable/ic_action_email"
android:key="emailSupport"
android:title="#string/email_support" />
You can call
scrollToItem("emailSupport");
Note: You may need to replace listView.setSelection(i) with listView.smoothScrollToPosition(i)
Since PreferenceActivity extends ListActivity, you can call getListView() to get the ListView containing your preferences, and then use listView.smoothScrollToPosition() to scroll to a specific row in the list. I haven't actually tried this before, but it should work.
Related
I have a ListView which is being filled by a BaseAdapter.
The items in this ListView are checkboxes and the last item is a TextView and a Button which allow the user to add new checkboxes to this list. (Four of these are pre-defined and come from an ArrayList, when the user clicks the button in the list he can add a new item into the list through a DialogBox this new item is also a CheckBox)
I noticed that when the orientation of the screen is changed, the new CheckBoxes and the states of the previous ones are lost.
I read about the Activity life cycle and found how this is happening. I also read about the onSaveInstanceState and onRestoreInstanceState methods can be used to save information about the activity in a Bundle and later use this to restore everything.
However, I couldn't think of a way to save all the information about the states & text of the old CheckBoxes & the new CheckBoxes and also how would I add the information of my last item which is a TextView and a Button.
Thanks.
Use a ListFragment instead of a ListView.
Tell the fragment to setRetainInstance and then reattach the fragment instead of creating a new one when you switch orientation.
Sam_D is incorrect when he says that you cannot use config changes if you use this method. getView is still called normally after which you can restore your listview back to the state you require (assuming you kept hold of your adapter in a private field within the fragment)
Try overriding the onRetainCustomNonConfigurationInstance in your activity. And in your onCreate() use getLastCustomNonConfigurationInstance() for getting the saved objects..
see below code for reference...
#Override
public Object onRetainCustomNonConfigurationInstance() {
//stop = true;
// if (pd != null) pd.dismiss();
List l = new ArrayList();
if(yourColl !=null)
{
l.add(rezColl);
}
l.add(true); // save boolean
return l;
}
and in oncreate()
List al = (ArrayList) getLastCustomNonConfigurationInstance();
if(al!=null){
Iterator itr=al.iterator();
while(itr.hasNext())
{
try{
Object o=itr.next();
if(o instanceof ArrayList)
{
yourColl=(ArrayList<HashMap<String, String>>)o;
}
if(o instanceof Boolean)
{
//do something
}
}catch(Exception e){
e.printStackTrace();
}
}
}
setRetainInstance can be useful, but I try to never use it for a Fragment (or a ListFragment) which will expose a View. What this does is essentially tell your Fragment to ignore configuration changes, such as orientation change (there are many others...), which means you can't take advantage of configuration qualifiers in your resource files for this Fragment (for example, having a different layout xml for landscape vs. portrait).
Much better would be to mutate the array that you originally used to populate the adapter whenever a user interacts with the UI (ie checking a checkbox you can represent by a boolean), creating something like a wrapper class for each item. Save that array during onSaveInstanceState, and then re-populate your adapter when the Fragment restarts. Of course this is a little annoying because your class/array will have to implement the Parcelable interface - but an easy way out of having to do that would be to just serialize it into a String using the Gson library, and likewise de-serializing it back to an array, also using Gson.
I want to open some preferences (which are made by extending DialogPreference) on first app startup. Also, these preferences are used as usual preferences.
Is there a way of accomplishing this?
EDIT:
I have my custom preference, made like this:
public class CitySelectPreference extends DialogPreference {
// Some code here
}
And as the solution I want it to be shown from the code, without the need of user getting to preference screen.
Just do this :
CitySelectPreference preference = (CitySelectPreference) findPreference("city_pref_key")
//You have to set a key to yout PreferenceScreen
PreferenceScreen screen = (PreferenceScreen) findPreference("screen_pref_key");
//Retrieve the index of the preference in preferenceScreen
int index = preference.getOrder();
//Perform a click
screen.onItemClick(null, null, index, 0);
I have PreferenceActivity with plenty of PreferenceCategories defined in it. If I have the android:key of a given category.
Is it possible programmatically to scroll the Activity to this category?
I know this is an old answered question, but I found a better way than iterating through all the categories.
PreferenceCategory myCat = (PreferenceCategory) findPreference("myKey");
int position = myCat.getOrder();
getListView().setSelection(position);
You can iterate through the preferences in the activity like this:
PreferenceScreen screen = getPreferenceScreen();
int i;
for(i = 0; i < screen.getPreferenceCount(); i++) {
String key = screen.getPreference(i).getKey();
// be careful, because key will be null if no android:key is specified
// (as is often the case for PreferenceCategory elements)
if("myKey".equals(key))
break;
}
// PreferenceActivity extends ListActivity, so the ListView is accessible...
getListView().setSelection(i);
Tested out with Android SDK 14 and it works fine.
Caution though, calling getListView().setSelection(i) inside onCreate or onResume has no effect. It has to be called after the activity is drawn.
The getPreferenceCount() method counts all PreferenceCategories and their nested preferences. Not sure what it does for PreferenceScreens, although I'm sure a little experimentation there would be revealing.
I have an unusual issue with my ListView. I currently have a "deselectAll()" method which iterates through the items in my ListView and sets them to unchecked (the items implement the Checkable interface). The "checked" variable gets changed correctly (the view reports as not being checked), but the visual indicator (in this case, a background change) does not show the view as unchecked (the background stays the color of a checked item).
I am iterating and deselecting through my listview like so (I also added my declerations):
private ListView vw_entryList;
private void deselectAll() {
for (int i = 0; i < sAdapter.getCount(); i++) {
((Entry)vw_entryList.getItemAtPosition(i)).setChecked(false);
}
}
The code for my implemented setChecked() is as follows:
public void setChecked(boolean checked) {
_checked = checked;
if (checked) {
setBackgroundResource(R.drawable.listview_checked);
}
else {
setBackgroundResource(R.drawable.listview_unchecked);
}
invalidate();
}
It should be noted that when the items are clicked, they are toggled between checked and unchecked in the OnItemClickListener, and this works ok, with the background change and everything. The code for toggling is very similar:
public void toggle() {
_checked = !_checked;
setBackgroundResource(_checked ?
R.drawable.listview_checked : R.drawable.listview_unchecked);
invalidate();
}
The only difference I can see is where the methods are called from. toggle() is called from within the OnItemClickListener.onClick() method, while my deselectAll() is called from within a button's standard OnClickListener, both in the same class. Does anyone have any ideas as to why the background doesn't change when I call my deselectAll() function?
Do you have custom, non-standard color for the background? If so you might take a look at http://www.curious-creature.org/2008/12/22/why-is-my-list-black-an-android-optimization/ - it boils down to setting android:cacheColorHint attribute of your list to the background color. Maybe that will help.
Edited after further discussion:
I think you need to call getAdapter().notifyDataSetChanged() on the List rather than invalidate(). List is really build in the way that it is relying on adapter to provide the data. What you are doing in fact you have an implicit adapter - Entry is really kept in the adapter and by setting checked, you are changing the data model really, but if you do not call notifyDataSetChanged() the list does not really know that the model has changed and will not recreate the views (invalidate() will only redraw the existing ones).
After trying everything (thanks for your help Jarek), I found a solution that works for my purposes. Instead of implicitly calling the setChecked() within the view that was clicked, I leave it up to the setItemChecked() method within the ListView class.
My updated code:
private void deselectAll() {
for (int i = 0; i < sAdapter.getCount(); i++) {
vw_entryList.setItemChecked(i, false);
}
}
My best guess is that the ListView knows that its items implement the Checkable class, and thus requires itself to be the handler of all item operations. Something along those lines. If anyone can explain in more detail why this solution works while the others did not, I'll reward them with the answer and an upvote.
I am currently building out a list of rows with checkboxes dynamically using content from a web service. However, this ListView will need to do pretty much what a PreferenceActivity would accomplish.
I don't know the number of rows as the content is dynamic so I can't create each CheckBoxPreference in XML. How do I go about building a PreferenceActivity that will display an unknown number rows with a CheckBoxPreference dynamically?
I think you're looking for something like this:
public class MyPreferenceActivity extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.my_preference_activity);
//fetch the item where you wish to insert the CheckBoxPreference, in this case a PreferenceCategory with key "targetCategory"
PreferenceCategory targetCategory = (PreferenceCategory)findPreference("targetCategory");
//create one check box for each setting you need
CheckBoxPreference checkBoxPreference = new CheckBoxPreference(this);
//make sure each key is unique
checkBoxPreference.setKey("keyName");
checkBoxPreference.setChecked(true);
targetCategory.addPreference(checkBoxPreference);
}
}
Well #Jodes, actually both of you are right, but the correct way of doing this would be using a ListPreference.
I would use a entire programmatic approach, from my experience it's easier to be consistent; either create an entire XML layout via code, or via XML, but mixing the 2 can be weird and you cannot alter everything set via XML...
onCreate(){
this.setPreferenceScreen(createPreferenceHierarchy());
}
public PreferenceScreen createPreferenceHierarchy(){
PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this);
// category 1 created programmatically
PreferenceCategory cat1 = new PreferenceCategory(this);
cat1.setTitle("title");
root.addPreference(cat1);
ListPreference list1 = new ListPreference(this);
list1.setTitle(getResources().getString(R.string.some_string_title));
list1.setSummary(getResources().getString(R.string.some_string_text));
list1.setDialogTitle(getResources().getString(R.string.some_string_pick_title));
list1.setKey("your_key");
CharSequence[] entries = calendars.getCalenders(); //or anything else that returns the right data
list1.setEntries(entries);
int length = entries.length;
CharSequence[] values = new CharSequence[length];
for (int i=0; i<length; i++){
CharSequence val = ""+i+1+"";
values[i] = val;
}
list1.setEntryValues(values);
cat1.addPreference(list1);
return root;
}//end method
However, using this approach you will run into the platform's limitations of not having a multiple select ListPreference, and you'll probably want to implement something else.
I found this solution, which works great. You'll have to read the comments to find clues about how to debug the code though...
You need a ListView for that, a PreferenceActivity. As discussed in this link, PreferenceActivity should only be used for actually saving preferences.
Instead you could either create a simple dialog with single or multiple choice options:
http://developer.android.com/guide/topics/ui/dialogs.html
Or use a ListView as in the API examples Google provides, they give a simple example:
http://hi-android.info/docs/resources/samples/ApiDemos/src/com/example/android/apis/view/List10.html
Use PreferenceFragmentCompat from Preference Compat Library
compile 'com.android.support:preference-v7:23.4.0'
Check this article for the implementation details https://medium.com/#arasthel92/dynamically-creating-preferences-on-android-ecc56e4f0789#.71ssvjses