I've spent over a week trying to figure out a way to do a Limited Multi Selection Preference list. Nothing I've tried works. I'm ready to give up on Android if something seemingly simple is so hard. I've been programming a long time and don't remember being beaten up this badly by something like this. I have to assume I am not understanding something basic. I hope someone can point me in the right direction.
Here is the simplest code I can think off that should work. It does not clear the checkbox even when setting it to false, I've tried true as well. Why doesn't that work? If that will not work, what will?
Any help would be most appreciated.
#Override
protected void onPrepareDialogBuilder(Builder builder)
{
CharSequence[] entries = getEntries();
CharSequence[] entryValues = getEntryValues();
if (entries == null || entryValues == null || entries.length != entryValues.length ) {
throw new IllegalStateException(
"ListPreference requires an entries array and an entryValues array which are both the same length");
}
// Added by WJT since we are loading the entries values after instantiation
// we need the clicked indexes to be setup now, they would not have been
// set up in the constructor
if ((mClickedDialogEntryIndices == null) || (mClickedDialogEntryIndices.length == 0))
mClickedDialogEntryIndices = new boolean[getEntries().length];
restoreCheckedEntries();
builder.setMultiChoiceItems(entries, mClickedDialogEntryIndices,
new DialogInterface.OnMultiChoiceClickListener()
{
public void onClick(DialogInterface dialog, int which, boolean val)
{
mDlg = (AlertDialog)getDialog();
mListView = (ListView)mDlg.getListView();
if (val)
{
if (mSelectedCount < mLimit)
{
mClickedDialogEntryIndices[which] = val;
mSelectedCount++;
}
else
{
mListView.setItemChecked(which, false);
Toast.makeText(getContext(),
R.string.newsLimitExceededMessage,
Toast.LENGTH_LONG).show();
} // (mSelectedCount < mLimit)
}
else
{
mClickedDialogEntryIndices[which] = val;
mSelectedCount--;
} // (val)
} // void onClick(DialogInterface dialog, int which, boolean val)
}); // DialogInterface.OnMultiChoiceClickListener()
} // void onPrepareDialogBuilder(Builder builder)
Thanks,
\ ^ / i l l
Here's how I would approach the problem:
Step #1: Get this working in a standalone throwaway test activity. Forget preferences. Forget dialogs. Just focus on the functionality of having a CHOICE_MODE_MULTIPLE ListView where, after a certain number of items are checked, the unchecked items become disabled.
Step #2: Get the functionality from Step #1 working in the form of a custom widget. By this, I mean you would implement a subclass of ListView (I guess...might be some container if there's more to it than a ListView) that bakes in all of what you need from Step #1.
Step #3: Create a custom DialogPreference subclass that uses the custom widget from Step #2.
For example, here is a sample project where I have a custom ColorMixer widget, rolled into a ColorPreference.
Related
I've created an AccessibilityService in Android where I want to display an AlertDialog after focusing on an EditText on the page (Note: The EditText could be from another app, like a login screen, so I won't always know the ID). When you click on a 'confirm' button within the AlertDialog it populates that EditText with text.
I have all of the steps complete except for the last part...I cannot figure out how to populate the EditText with text. I'm guessing there is a way to cast a findViewByID() method somewhere, but I don't know how to find the ID of the EditText (see above, the EditText could be from another app). See code below, am I way off? The code below always errors out telling me about a sealed instance problem (Cannot perform this action on a not sealed instance.).
public void onAccessibilityEvent(final AccessibilityEvent event) {
final AccessibilityNodeInfo source = event.getSource();
if ((event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED || event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) && CLASS_NAME_EDIT_TEXT.equals(event.getClassName())) {
AlertDialog.Builder mSuspendDialog = new AlertDialog.Builder(getApplicationContext())
.setTitle(R.string.str_have_password_question)
.setPositiveButton(R.string.str_decision_use_password, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
if (event.getSource() != null & event.getClassName().equals("android.widget.EditView")) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo
.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "newtexttopopulateedittext");
event.getSource().performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
}
})
.setNegativeButton(R.string.str_decision_close, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
AlertDialog alert11 = mSuspendDialog.create();
// Ensure we can show the dialog from this service.
alert11.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
alert11.show();
1) When any activity starts, create a List(or whatever) of ID's of all the EditText's on the screen.
2) While creating a list (or later on) iterate through the list again and check if a value is available for that view in your SQLite database.
3) If a value is available, use ACTION_PASTE to inject text in that EditText.
ArrayList<AccessibilityNodeInfo> inputViewsList = new ArrayList<AccessibilityNodeInfo>();
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
refreshChildViewsList(rootNode);
for(AccessibilityNodeInfo mNode : inputViewsList){
String viewId = mNode.getViewIdResourceName();
Cursor cr = db.rawQuery(-----your database parameters----);
if(cr.moveToFirst()){
//this means you have a value for that ET
ClipboardManager clipboardManager = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
ClipData clipData =ClipData.newPlainText("MyAppName", cr.getString(--Your column number--));
clipboardManager.setPrimaryClip(clipData);
mNode.performAction(AccessibilityNodeInfo.ACTION_PASTE);
}
}
Database has to be used so that you can remember the values permanently. The ID which i'm getting above is a fully qualified ID with App package name and view ID. So, it remains unique system-wide.
Now about refreshChildViews() method:
private void refreshChildViews(AccessibilityNodeInfo rootNode){
int childCount = rootNode.getChildCount();
for(int i=0; i<childCount ; i++){
AccessibilityNodeInfo tmpNode = rootNode.getChildAt(i);
int subChildCount = tmpNode.getChildCount();
if(subChildCount==0){
if(tmpNode.getClassName().toString().contentEquals("android.widget.EditText"){
inputViewsList.add(tmpNode);
}
return;
}
if(subChildCount>0){
refreshChildViews(tmpNode);
}
}
}
Don't forget to clear the inputViewsList on TYPE_WINDOW_STATE_CHANGED event!
Let me know if you need anything else.
I'm working on dictionary application. I have a listview with fast scroll enabled and adapter which implements SectionIndexer. When I'm working with chinese dictionary I have much more sections then when working with west-european languages and have a small issue:
if I stop fast scrolling and while scroll bars are visible begin using
default scrolling my "fast scroll scroller" immideatly moves to
another position (somewhere at the top) and only when I'll get almost
to the end of the list it'll start moving to my position too with much
greater speed.
Is there a reason for such behaviour? If there any way to hide fast scroll bars when using default scrolling (but without disabling the fast scroll)?
thought I'd post here in the hopes that it's not too late.
Note: This is not using an AlphabetIndexer, and I don't think using three collections to manage a list is a good idea, though it is simple, and explains the concept.
Below is a basic example of how to use the callbacks:
public LinkedHashMap<Integer,String> sectionList = new LinkedHashMap<Integer,String>();
public HashMap<Integer,Integer> sectionPositions = new HashMap<Integer, Integer>();
public HashMap<Integer,Integer> positionsForSection = new HashMap<Integer, Integer>();
When you have your "locations" array (pre-ordered), this will create three hashmaps to track things, really simple implementation, really easy to read :
if( locations != null && locations.size() > 0 ) {
//Iterate through the contacts, take the first letter, uppercase it, and use that as a key to reference the alphabetised list constructed above.
for( int i = 0; i < locations.size(); i++ ) {
String startchar =locations.get(i).getStartCharacterForAlphabet();
if( startchar != null ) {
if( sectionList.containsValue(startchar) == false ) {
sectionList.put(Integer.valueOf(i),startchar);
positionsForSection.put(Integer.valueOf(sectionList.size() - 1), Integer.valueOf(i));
}
}
sectionPositions.put(Integer.valueOf(i), sectionList.size() - 1);
}
}
And here are the three callbacks:
#Override
public int getPositionForSection(int section) {
return positionsForSection.get(Integer.valueOf(section)).intValue();
}
#Override
public MyLocation getItem(int position) {
if( locations.size() > position ) {
return locations.get(position);
}
return null;
}
#Override
public int getSectionForPosition(int position) {
return sectionPositions.get(Integer.valueOf(position)).intValue();
}
Hope it helps!
I would like to check a number of Spinner controls in the Onclick event of a few buttons (if the user moves away from the Activity), and if one or more were not filled out, display a warning message with 2 options to the user:
#Override
public void onClick(View v) {
int i1 = spinner1.getSelectedItemPosition();
(..)
if ((i1 == 0) | (i2 == 0) | (i3 == 0) | (i4 == 0) | (i5 == 0)) {
AlertDialog.Builder alertbox = new AlertDialog.Builder(this);
alertbox.setMessage("Answers missing, what do you want to do?");
final CharSequence[] items = {
"Oops.. let me fix that…",
"I want to leave the app now" };
alertbox.setTitle("Pick an item");
alertbox.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
if (item == 1) {
finish();
// should lead back to the spinner controls on the page
}else if (item == 2){
finish();
android.os.Process.killProcess(android.os.Process
.myPid());
}
}
}).create();
alertbox.show();
However, I just see an empty box with the title, but the message set with setMessage() is not displayed, and I don't get any list items for selection - the box is empty! Any idea what I can do to fix that?
I have been having the same issue, and I found that if I remove the .setMessage line, then the list appears with the title. Not sure why this is though. If you use the .setpositive button, .negative, etc, then you can have a message. I also noticed that google examples all don't use .setMessage with an item list. They only use .setTitle, which doesn't give you that much space.
So, while this doesn't help you if you want a long message, it will get your list visible if that is your main goal.
I've an android application with preferences declared in XML, loaded with addPreferencesFromResource. The user can open preferences, click on each item and edit them, all works.
One preference I have is:
<ListPreference android:key="abc"
android:title="#string/abc"
android:summary="#string/cde"
android:persistent="true"/>
How can I show the preference dialog to a user automatically (without the need for the user to go to the preference screen and click on it?).
I tried ( (android.preference.DialogPreference) prefMgr.findPreference( "abc" )).showDialog(null), but is says it is a protected method...? Called it from my main activity (which is a PreferenceActivity), that's why it obviously cannot work. But how else?
EDIT
I just found two threads (1, and 2) with the idea to use findViewById to access the preference, but with no success. It always returns null (does for me, too).
It looks like there is really no possibility to do this from code.
See the new accepted answer for a much cleaner approach! This was working, but not really the clean way of doing it.
Damn it, it got me several hours, but it finally works.
The solution is the undocumented call public void onItemClick (...). It takes several arguments, and as pointed out by this question it can be used to simulate a click according to the index of the element you want to call.
My problem was the item I want to call is deeply nested in an XML-structure. But the solution is very easy: add a key to the PreferenceScreen the item you want to open is in:
<PreferenceScreen
android:key="pref_key"
....
/>
<ListPreference android:key="abc"
android:title="#string/abc"
android:summary="#string/cde"
android:persistent="true"/>
</PreferenceScreen>
And the you can just to the following:
// the preference screen your item is in must be known
PreferenceScreen screen = (PreferenceScreen) findPreference("pref_key");
// the position of your item inside the preference screen above
int pos = findPreference("abc").getOrder();
// simulate a click / call it!!
screen.onItemClick( null, null, pos, 0 );
And the Dialog pops up!
It would be nice to get the PreferenceScreen a Preference is in (so you would not have to know where your Preference is in), because moving the preference/changing the XML could break the automatic dialog silently and might not get noticed (if not tested).
For this I wrote a function which will search through all preferences and return the PreferenceScreen your preference is on, so you don't need to have your PreferenceScreen a key!
private PreferenceScreen findPreferenceScreenForPreference( String key, PreferenceScreen screen ) {
if( screen == null ) {
screen = getPreferenceScreen();
}
PreferenceScreen result = null;
android.widget.Adapter ada = screen.getRootAdapter();
for( int i = 0; i < ada.getCount(); i++ ) {
String prefKey = ((Preference)ada.getItem(i)).getKey();
if( prefKey != null && prefKey.equals( key ) ) {
return screen;
}
if( ada.getItem(i).getClass().equals(android.preference.PreferenceScreen.class) ) {
result = findPreferenceScreenForPreference( key, (PreferenceScreen) ada.getItem(i) );
if( result != null ) {
return result;
}
}
}
return null;
}
private void openPreference( String key ) {
PreferenceScreen screen = findPreferenceScreenForPreference( key, null );
if( screen != null ) {
screen.onItemClick(null, null, findPreference(key).getOrder(), 0);
}
}
// With this, you can call your `Preference` like this from code, you do
// not even have to give your PreferenceScreen a key!
openPreference( "abc" );
You could have extended ListPreference to create your dialog, then included your own public method that calls the protected showDialog method of ListPreference. Something like:
public void show()
{
showDialog(null);
}
This way you won't run into the issue of getOrder() not working when there are PreferenceGroups as several people have pointed out in the comments your answer.
This can be done with any preference types that has a protected showDialog method.
If you use the support library you can open a dialog easily with PreferenceManager.showDialog(Preference).
In your PreferenceFragmentCompat:
getPreferenceManager().showDialog(findPreference("pref_name"));
Note that support preference package has many issues:
non-material styling and
it crashes when rotated with an open dialog.
PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference("pref_key");
final ListAdapter listAdapter = preferenceScreen.getRootAdapter();
EditTextPreference editPreference = (EditTextPreference) findPreference("set_password_preference");
final int itemsCount = listAdapter.getCount();
int itemNumber;
for (itemNumber = 0; itemNumber < itemsCount; ++itemNumber) {
if (listAdapter.getItem(itemNumber).equals(editPreference)) {
preferenceScreen.onItemClick(null, null, itemNumber, 0);
break;
}
}
}
}
Improving deepak goel's answer:
private void openPreference(String key) {
PreferenceScreen preferenceScreen = getPreferenceScreen();
final ListAdapter listAdapter = preferenceScreen.getRootAdapter();
final int itemsCount = listAdapter.getCount();
int itemNumber;
for (itemNumber = 0; itemNumber < itemsCount; ++itemNumber) {
if (listAdapter.getItem(itemNumber).equals(findPreference(key))) {
preferenceScreen.onItemClick(null, null, itemNumber, 0);
break;
}
}
}
If you're using AndroidX Preference library, it is quite simple.
public class CustomPreferenceFragment extends PreferenceFragmentCompat {
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.your_preference);
DialogPreference dialogPreference = (DialogPreference) findPreference("your_preference_key");
onDisplayPreferenceDialog(dialogPreference);
}
}
wait, u can do something like this as well
Preference p=findPreference("settings_background_color");
p.setOnPreferenceClickListener(new OnPreferenceClickListener() {
#Override
public boolean onPreferenceClick(Preference preference) {
int color=PreferenceManager.getDefaultSharedPreferences(ALifePatternsWallpaperSettings.this).getInt("settings_background_color", Color.BLACK);
new ColorPickerDialog(ALifePatternsWallpaperSettings.this, ALifePatternsWallpaperSettings.this, "settings_background_color", color, Color.BLACK).show();
return true;
}
});
hi friends try this code in works fine
getPreferenceManager().findPreference("YOUR PREF_KEY").setOnPreferenceClickListener(new OnPreferenceClickListener()
{
public boolean onPreferenceClick(Preference preference)
{
//your code here
return true;
}
});
I am currently using the AlertDialog.builder to create a multichoice list for the user (checkboxes). This works great, except we want one of the buttons to deselect all of the others in the list.
builder.setMultiChoiceItems(list, checked,
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog,
int item, boolean isChecked) {
if(item == ANY_ITEM_BUT_0)
{
((AlertDialog) dialog).getListView().setItemChecked(0, false);
}
}
});
When using "true" it will successfully check the box, but when using false it does not uncheck it (unless i have manually set it to true before hand.) Is there a separate listener I should be using to detect when a user clicks these? It seems to me that there are two checkmarks set, one by the "setItemChecked(0, true);", and one by actually selecting it.
This has been driving me nuts for a couple days now, any help would be greatly appreciated.
OH!!! I forget it to ensure deselect you must change checked to null ;), I had the same issue.
builder.setMultiChoiceItems(list, null, new DialogInterface.OnMultiChoiceClickListener() {
...
To deselect the other items it works fine::
if(item == ANY_ITEM_BUT_0){
for(int i=0; i<items.length;i++){
if (item != ANY_ITEM_BUT_0)
((AlertDialog)dialog).getListView().setItemChecked(i, false);
}
}
Dont think you can change the values in the list since the list-items (checkboxes) are controlled by the builder-object. However, you could simply make the dialog re-initiate when the first item is clicked... by dismissing the dialog that is showing, and create a new one....
If you want to set a check box to not be checked and you need to set the checkedItems array on the call to setMultiChoiceItems(), you need to set the checked array items to false as well. Make sure your checked array is final so you can access it in the listener.
builder.setMultiChoiceItems(list, checked,
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog,
int item, boolean isChecked) {
if(isChecked && item == ANY_ITEM_BUT_0)
{
for(int i=0; i<list.length;i++){
if (i != ANY_ITEM_BUT_0) {
checked[i] = false;
((AlertDialog)dialog).getListView().setItemChecked(i, false);
}
}
}
}
});