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!
Related
I've inherited an app which displays a range of values as buttons (0 - 1000) - layout ViewA.
The buttons get indexed, and stuff happens. When the user clicks on some subset of those values, they change color, and get associated with some data which gets exported to a csv file eventually.
This works.
Now the user wants to be able to change between the original set, and a new set (0 - 1500) - layout ViewB.
I created these layouts, and put in a checkbox, and when it's selected it sets the visibility of ViewA to GONE and ViewB to Visible.
These buttons should be indexed just like the original ones, but the screen doesn't get updated before it crashes at observerPosBtn[i] = 42. 1 past the max index of the original array.
What (I think) I'm running into is that the layout is not getting updated until I leave the onChecked method, so the buttons which exceed the original range don't exist and can't get indexed. Then when stuff happens to an out-of-range button the app crashes. (Each range worked properly before adding the checkbox switch.)
Is there a way to "force" an update before I leave onChecked, or where does the code jump back to after the onChecked handler that I could call the 'updateObsPosButtons' function?
The code is structured (broadly) like:
Main Activity{
//definitions and stuff like
onCreate(){}
#Override
protected void onResume() {
super.onResume();
if(refreshSelectionEntries) {
startupConfig();
refreshSelectionEntries = false;
}
}
....
public void startupConfig(){
//onClick etcs
show1500mRangeCheckBox = findViewById(R.id.show1500mRangeCheckBox);
boolean[] observerPosChecked = new boolean[observerPosCount];
show1500mRangeCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
currentRange = "1500";
observerPosCount = 61;
} else {
currentRange = "1000";
observerPosCount = 41;
}
updateScreenRange(currentRange);
updateObsPosButtons(currentRange);
}
});
updateObsPosButtons(currentRange); //Called once in initial startup?
....
private void updateScreenRange(String range){
...
final View obs1500Incr50LL = findViewById(R.id.obs1500Incr50LL);
final View obs1500Incr25LL = findViewById(R.id.obs1500Incr25LL);
final View obsIncr50LL = findViewById(R.id.obsIncr50LL);
final View obsIncr25LL = findViewById(R.id.obsIncr25LL);
if (range == "1000") {
obs1500Incr50LL.setVisibility(View.GONE);
obs1500Incr25LL.setVisibility(View.GONE);
obsIncr50LL.setVisibility(View.VISIBLE);
obsIncr25LL.setVisibility(View.VISIBLE);
} else {
obsIncr50LL.setVisibility(View.GONE);
obsIncr25LL.setVisibility(View.GONE);
obs1500Incr50LL.setVisibility(View.VISIBLE);
obs1500Incr25LL.setVisibility(View.VISIBLE);
}
}
private void updateObsPosButtons(String range){
if (range == "1000"){
observerPosChecked = new boolean[observerPosCount];
....
populateObserverPos1000Btn();
//iterate through button range then "do stuff"
for (int observerPosIndex = 0; observerPosIndex < observerPosCount; observerPosIndex++) {
final int i = observerPosIndex;
....
}else{
observerPosChecked = new boolean[observerPosCount];
...
populateObserverPos1500Btn();
//iterate through button range - CRASHES AT i = 42
for (int observerPosIndex = 0; observerPosIndex < observerPosCount; observerPosIndex++) {
final int i = observerPosIndex;
// CHEAT HERE
....
}
} //End StartConfig
private void populateObserverPos1000Btn() {
int oPS_index = 0;
View obsIncr50LL = findViewById(R.id.obsIncr50LL);
View obsIncr25LL = findViewById(R.id.obsIncr25LL);
observerPosBtn[oPS_index++] = obsIncr50LL.findViewById(R.id.observerPosBtn_0);
observerPosBtn[oPS_index++] = obsIncr25LL.findViewById(R.id.observerPosBtn_25);
...
observerPosBtn[oPS_index++] = obsIncr50LL.findViewById(R.id.observerPosBtn_1000);
}
private void populateObserverPos1500Btn() {
int oPS_index = 0;
View obs1500Incr50LL = findViewById(R.id.obsIncr50LL);
View obs1500Incr25LL = findViewById(R.id.obs1500Incr25LL);
observerPosBtn[oPS_index++] = obs1500Incr50LL.findViewById(R.id.observerPosBtn_0);
observerPosBtn[oPS_index++] = obs1500Incr25LL.findViewById(R.id.observerPosBtn_25);
...
observerPosBtn[oPS_index++] = obs1500Incr50LL.findViewById(R.id.observerPosBtn_1500);
}
....
} //End MainActivity
If I "cheat" and add (at CHEAT HERE):
if (observerPosBtn[i] == null){
continue;
}
The updObsPosButtons function does finish and the screen updates, but it seems like if I click on anything above index 41 it crashes.
I think I've included the relevant code.
I've tried a few suggestions I've seen here like notifydatasetchanged, and a run handler... not sure I really can implement them in the current configuration though.
Is there anything I can do without a major refactor?
(I apologize in advance for my poor terminology, code architecture, and general ignorance - my software experience is a little firmware (C) - not Java let alone Android!
I'm the only option to even look it over for this "simple" (haha) update. Either it's something I can take a crack at, or I need to explain the roadblock well enough to outsource it! Thanks so much!)
For a hybrid app , I need to scroll till the end of the page. How can I do ?
I am able to scroll to exact element by using driver.scrollTo(); and driver.ScrollToExact();
But I want to scroll the app from top to bottom.
Can anyone tell me please?
#Test
public void testScroll()throws Exception
{
for(int i=0;i<4;i++)
{
Thread.sleep(2000);
if (driver.findElement(By.name("end_item")).isDisplayed())
{
driver.findElement(By.name("end_item")).click();
break;
}
else
{
verticalScroll();
}
}
}
public void verticalScroll()
{
size=driver.manage().window().getSize();
int y_start=(int)(size.height*0.60);
int y_end=(int)(size.height*0.30);
int x=size.width/2;
driver.swipe(x,y_start,x,y_end,4000);
}
This will help you to swipe till end or till whatever position you want.
You can use coordinates to scroll to the end of the page with even locating the String available on the page. Use this:
TouchAction action = new TouchAction(driver).longPress(20,y).moveTo(20, 10).release();
action.perform();
I too had the same problem. What I did is, used the text which is at the bottom of that particular page ("text should be dynamic, it should be constant") then used the following code
String text="ABC";
driver.scrollTo(text);
After this code you can perform any action.. for example see the below code
String text="ABC";
driver.scrollTo(text);
driver.findElement(By.xpath("//*[#text='"+text+"']")).click();
public List<String> getAllStudyMaterialName(List<WebElement> elements) {
List<String> documentTitle = new ArrayList<>();
boolean isStudyMaterialRepeat = false;
while (true) {
List<WebElement> titles = elements;
for (WebElement title : titles) {
documentTitle.add(title.getText());
}
scrollUp(driver);
if (documentTitle.get(documentTitle.size() - 1).toString().equals(elements.get(2).getText())
&& documentTitle.get(documentTitle.size() - 2).toString().equals(elements.get(1).getText())
&& documentTitle.get(documentTitle.size() - 3).toString().equals(elements.get(0).getText())) {
isStudyMaterialRepeat = true;
}
if (isStudyMaterialRepeat) {
break;
}
}
return documentTitle;
}
After scrolling till bottom I am checking it's a same elements
This logic will scroll till the bottom of the page and if it found last 3 elements are same with stored element it will break the loop
I'm building an interface similar to the Google Hangouts chat interface. New messages are added to the bottom of the list. Scrolling up to the top of the list will trigger a load of previous message history. When the history comes in from the network, those messages are added to the top of the list and should not trigger any kind of scroll from the position the user had stopped when the load was triggered. In other words, a "loading indicator" is shown at the top of the list:
Which is then replaced in-situ with any loaded history.
I have all of this working... except one thing that I've had to resort to reflection to accomplish. There are plenty of questions and answers involving merely saving and restoring a scroll position when adding items to the adapter attached to a ListView. My problem is that when I do something like the following (simplified but should be self-explanatory):
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
Then what the user will see is a quick flash to the top of the ListView, then a quick flash back to the right location. The problem is fairly obvious and discovered by many people: setSelection() is unhappy until after notifyDataSetChanged() and a redraw of ListView. So we have to post() to the view to give it a chance to draw. But that looks terrible.
I've "fixed" it by using reflection. I hate it. At its core, what I want to accomplish is reset the first position of the ListView without going through the rigamarole of the draw cycle until after I've set the position. To do that, there's a helpful field of ListView: mFirstPosition. By gawd, that's exactly what I need to adjust! Unfortunately, it's package-private. Also unfortunately, there doesn't appear to be any way to set it programmatically or influence it in any way that doesn't involve an invalidate cycle... yielding the ugly behavior.
So, reflection with a fallback on failure:
try {
Field field = AdapterView.class.getDeclaredField("mFirstPosition");
field.setAccessible(true);
field.setInt(listView, positionToSave);
}
catch (Exception e) { // CATCH ALL THE EXCEPTIONS </meme>
e.printStackTrace();
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
}
Does it work? Yes. Is it hideous? Yes. Will it work in the future? Who knows? Is there a better way? That's my question.
How do I accomplish this without reflection?
An answer might be "write your own ListView that can handle this." I'll merely ask whether you've seen the code for ListView.
EDIT: Working solution with no reflection based on Luksprog's comment/answer.
Luksprog recommended an OnPreDrawListener(). Fascinating! I've messed with ViewTreeObservers before, but never one of these. After some messing around, the following type of thing appears to work quite perfectly.
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
listView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(listView.getFirstVisiblePosition() == positionToSave) {
listView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
else {
return false;
}
}
});
}
Very cool.
As I said in my comment, a OnPreDrawlistener could be another option to solve the problem. The idea of using the listener is to skip showing the ListView between the two states(after adding the data and after setting the selection to the right position). In the OnPreDrawListener(set with listViewReference.getViewTreeObserver().addOnPreDrawListener(listener);) you'll check the current visible position of the ListView and test it against the position which the ListView should show. If those don't match then make the listener's method return false to skip the frame and set the selection on the ListView to the right position. Setting the proper selection will trigger the draw listener again, this time the positions will match, in which case you'd unregister the OnPreDrawlistener and return true.
I was breaking up my head until I found a solution similar to this.
Before adding a set of items you have to save top distance of the firstVisible item and after adding the items do setSelectionFromTop().
Here is the code:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// for (Item item : items){
mListAdapter.add(item);
}
// restore index and top position
mList.setSelectionFromTop(index, top);
It works without any jump for me with a list of about 500 items :)
I took this code from this SO post: Retaining position in ListView after calling notifyDataSetChanged
The code suggested by the question author works, but it's dangerous.
For instance, this condition:
listView.getFirstVisiblePosition() == positionToSave
may always be true if no items were changed.
I had some problems with this aproach in a situation where any number of elements were added both above and below the current element. So I came up with a sligtly improved version:
/* This listener will block any listView redraws utils unlock() is called */
private class ListViewPredrawListener implements OnPreDrawListener {
private View view;
private boolean locked;
private ListViewPredrawListener(View view) {
this.view = view;
}
public void lock() {
if (!locked) {
locked = true;
view.getViewTreeObserver().addOnPreDrawListener(this);
}
}
public void unlock() {
if (locked) {
locked = false;
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
#Override
public boolean onPreDraw() {
return false;
}
}
/* Method inside our BaseAdapter */
private updateList(List<Item> newItems) {
int pos = listView.getFirstVisiblePosition();
View cell = listView.getChildAt(pos);
String savedId = adapter.getItemId(pos); // item the user is currently looking at
savedPositionOffset = cell == null ? 0 : cell.getTop(); // current item top offset
// Now we block listView drawing until after setSelectionFromTop() is called
final ListViewPredrawListener predrawListener = new ListViewPredrawListener(listView);
predrawListener.lock();
// We have no idea what changed between items and newItems, the only assumption
// that we make is that item with savedId is still in the newItems list
items = newItems;
notifyDataSetChanged();
// or for ArrayAdapter:
//clear();
//addAll(newItems);
listView.post(new Runnable() {
#Override
public void run() {
// Now we can finally unlock listView drawing
// Note that this code will always be executed
predrawListener.unlock();
int newPosition = ...; // Calculate new position based on the savedId
listView.setSelectionFromTop(newPosition, savedPositionOffset);
}
});
}
private void setListviewSelection(final ListView list, final int pos) {
list.post(new Runnable() {
#Override
public void run() {
list.setSelection(pos);
for (int i = 0; i < list.getChildCount(); i++) {
View v = list.getChildAt(i);
if (i == pos && v != null)
v.setBackgroundColor(Color.argb(200, 51, 181, 229));
else if (v != null)
v.setBackgroundColor(Color.BLACK);
}
}
});
}
Here is a code I'm using to imitate a selection in my music player. The idea is that when user press Next or Previous button an element is highlighted in the ListView, but this is not working the way I want, because setSelection doesn't scroll smoothly and basically some elements are not highlighted correctly. For better explanation, what I'm actually trying to implement is a Winamp app which has that way of scrolling when you press next/previous button (when viewing your playlist).
Using setSelectionFromTop(), smoothScrollToPosition() didn't work correctly either.
You can only update Views from the main thread. Move your code to the Main thread.
http://bestsiteinthemultiverse.com/2009/12/android-selected-state-listview-example/
Here is the solution which works perfectly.
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.