How to override the Android ListView fast scroller appearance - android

I'm trying to implement a ListView with a FastScroll mechanism which uses time rather than A-Z
Unfortunately I can't seem to find a way into the layout used by the FastScroller index - it seems determined to show a small black square with very large white text
I've looked at the source:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.0_r1/android/widget/FastScroller.java/
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/widget/AbsListView.java#AbsListView
Both of these seem to show the key fast scroll member variables are private, and that the resource id used is fixed:
mOverlayDrawable = res.getDrawable(com.android.internal.R.drawable.menu_submenu_background);
Is there any way to override this? Ideally I'm targeting 2.2. and above.

You can try with reflection with something like this:
try {
Field scrollerField = AbsListView.class.getDeclaredField("mFastScroller"); //java.lang.reflect.Field
scrollerField.setAccessible(true);
FastScroller instance = scrollerField.get(listViewInstance);
Field overlayField = instance.getClass().getDeclaredField("mOverlayDrawable");
overlayField.setAccessible(true);
overlayField.set(instance, yourValueHere);
} catch (Exception e) {
Log.d("Error", "Could not get fast scroller");
}
I just typed it out so it might or might not compile straight off the bat, but that's the idea. I didn't check if the fields were called the same in all of the versions, you might have to adjust.

Related

UiScrollable scrollIntoView stops just before scrolling into view

I have created the following wrap of UiScrollable for android testing:
protected fun UiScrollable.ensureScrolledIntoView(elementToScrollTo: UiObject) {
val elementPresent = scrollIntoView(elementToScrollTo)
if (!elementPresent) {
Assert.fail("Expected element ${elementToScrollTo.selector} not found in scroll view")
}
}
I believe the wrapper itself is not the problem, but sometimes the scrollIntoView fails to make the last needed scroll swipe. Best is to demonstrate with example:
Gives the error:
java.lang.AssertionError: Expected element
UiSelector[CLASS=android.widget.LinearLayout, DESCRIPTION=May,
CHILD=UiSelector[TEXT=7,
RESOURCE_ID=com.maypackage.android:id/calendar_day_text_view]] not
found in scroll view
And it was, obviously tasked to scroll to May 7th.
The same thing sometimes happens when I search for a date that will be found with up scroll: the method returns just one scroll short and the element is not found.
Has anyone encountered such problem? How to overcome it?
EDIT
After much more experimentation I found out the issue to be elements on the screen consuming certain scrolling attempts. This is most easily experienced in case of floating buttons, but the situation of the question shows more obscure example of the same. In most cases it turned out that setSwipeDeadZonePercentage on the UiScrollable did the trick for me. This methods tells the scrollable that there is certain area in which no swiping attempts should be made. This solved the issues in cases of overflow buttons. For the more obscure cases I use the method below, which is still not guaranteed to work all the time, but is stable in my cases.
OLD, original answer:
Eventually, after much trials I found out that the framework error is more generic and does not necessary trigger a stop one swipe before the element is in the view. Frankly, I believe the problem is with the unstable behavior of scrollForward method which is used internally in the scrollIntoView method. Eventually I re-wrote my method as follows:
protected fun UiScrollable.ensureScrolledIntoView(elementToScrollTo: UiObject) {
// this method used to use scrollIntoView, but it proved unstable
if (elementToScrollTo.exists()) return
while (scrollBackward()) {
// no body needed
}
// very complex construct, because scroll forward seems to return false on first call
var consecutiveFailingScrolls = 0
while (consecutiveFailingScrolls < 2) {
if (elementToScrollTo.exists()) return
if (!scrollForward()) {
consecutiveFailingScrolls++
} else {
consecutiveFailingScrolls = 0
}
}
Assert.fail("Expected element ${elementToScrollTo.selector} not found in scroll view")
}
This is basically trying to address the framework instability and, up to the moment it has not failed me. By the way, I suspect the instability of scrollIntoView manifests only when we are scrolling lazily loading components like recycler views. For the moment I intend to continue using the library methods for non-lazy loading lists and see if more errors are encountered.

Why does the Scroll event unnecessarily fire when a ListView is loaded in (Xamarin.)Android?

I am working on a Xamarin.Android app. I have a ListView that has the following code:
OnCreate
{
//other code here
listView.Scroll += ListView_Scroll;
}
private void ListView_Scroll(object sender, AbsListView.ScrollEventArgs e)
{
throw new NotImplementedException();
}
(I simply added the Scroll += ... and the Empty Project to the Custom Row Views project of Xamarin Android.
Immediately when the list is loaded, it throws the NotImplementedException. Even when Adapter is null, it still scrolls!
Why does it scroll when it does not need to? The ScrollState also changes to Fling.
Can someone explain why this happens? I am working on an app that uses this event and this is very annoying to work around.
I don't know if this also happens in Android but I assume it does, that's why I have tagged Android, as well.
ListView in Android doesn't inherit from Scrollview but is very similar to the same
The list-view module is using android.widget.ListView for its Android implementation while behind ScrollView lies android.widget.HorizontalScrolView (or vertical) so basically we have two different native controls with different implementations. Comparing to HorizontalScrollView, the ListView for Android presets much more functionality for listing data and also has specific optimisations so this is the logical choice to implement in nativeScript as well.
Secondly it throws that exception because you have the following line in your scroll event :
throw new NotImplementedException();
Removing this should solve your problem for the most part.
Why does it scroll when it does not need to?
What actually happens here is that the listview Scrolls To the current Position
Goodluck Happy Coding!

Find multiple elements in web view with espresso

I'm testing a hybrid app, where each view has a web view.
In one of these web views I have a list of elements with the same attribute. They have the same xpath locator that is something like:
//h4[contains(#data-role, 'product-name')]
I want to create a list of these elements and iterate through them, count them, get their attributes.
In the documentation, I found two similar methods:
findElement(locator, value)
and
findMultipleElements(locator, value)
Though it's totally unclear to me how to use it. I tried to find examples on it but with no success.
Could someone help me with this?
Here is the solution that I have found.
#kaqqao is right that findMultipleItems call returns Atom<List<ElementReference>> that is not usable with onWebView() because there you have only withElement() that accepts either Atom<ElementReference> or just ElementReference
What you can do though is perform your action that find multiple items and just get results from your Atom. This is how it works internally if you check the source of doEval method inside Web.java for espresso.
val elements = with(AtomAction(findMultipleElements(
Locator.XPATH,
"YOUR_COMPLEX_XPATH"
), null, null)) {
onView(ViewMatchers.isAssignableFrom(WebView::class.java)).perform(this)
this.get()
}
This code will give you List<ElementMatcher>.
Then just run it as
elements.forEach {
onWebView().forceJavascriptEnabled().withElement(it).perform(webClick())
}
Can you try something like that? Since what you should care about is really the ElementReference and you can iterate the lsit returned from findMultipleElements with simple for/foreach statement:
yourList = findMultipleElements(locator, value);
yourList.size(); //this will get you the count of found elements with that locator
for(Atom<ElementReference> item : yourList ){
item.getAttribute...
//and whatever you want
}

Using espresso, how can I decide which view action to use according to displayed view?

I have a form that displays one component at a time, it may be: EditText, Spinner, ListView, or something else according to some rules.
And each component has a different test code.
Using Espresso, how can I check which component is displayed and do its action?
pseudo code
if( ask to enter the number ){
onView(withId(R.id.et_number)).perform(replaceText("12345"));
}else if(ask to select my country){
onView(withRecyclerView(R.id.rv_country).atPosition(0)).perform(click());
}
What is the best way to implement this case? And if I have to create my custom matcher, how is the implementation for this case?
I would be grateful to anyone who gives me a simple example.
I did something similar before. What I did was to check if a element was displayed inside a try and catch the exception.
Try this:
try { // supposing et_number is displayed
onView(withId(R.id.et_number)).check.matches(isCompletelyDisplayed())); // will throw an exception if its not displayed
onView(withId(R.id.et_number)).perform(replaceText("12345"));
} catch (Exception e) { // et_number is not displayed
onView(withRecyclerView(R.id.rv_country).atPosition(0)).perform(click());
}

Casting an EditText as a TextView

This is a question on performance, not on capability.
When I get a View from findViewById(...), and it's an EditText, but I don't need anything specific to EditText, should I cast the View to TextView or to EditText? TextView is a closer subclass to View, but EditText is what the View actually is.
IMHO, yes you can cast the edittext as textview. But I dont think there is any must-to-follow rule. You can perform operations either by casting to an edittext or a textview.
As you dont need the functionalities of edittext you can surely cast to textview
I can see no problem doing that. If you dont have any EditText specific task then go ahead and cast it to TextView.
P.S.:- I personally do that always.. :D
You should cast it to EditText because otherwise it will through exception and your application will be crashed. And BTW if you are doing such things in your code I don't think you are following good practices. So don't do that.
Here is what I think, for me clarity is more important than any kind of optimization you can think of making in your application. If you have an EditText then leave it as such, otherwise you are only adding confusion to the code.
At the end the object already exists, and it is an EditText, so you will be accessing an EditText, no matter what are you casting it. You are only casting it in order to use it's methods in code, but the object instance doesn't change. So regarding to the inheritance, for example, if the EditText has overriden a method of the TextView, if you cast the object to a TextView and use it, you will still be using the EditText method.
Anyway, the virtual machine should cache the results of method resolution, so it wil be no impact in further use.
Don't worry about premature optimizations. Is this actually a bottleneck in your code? It shouldn't be. Something like this is totally negligible. It shouldn't have any noticeable effect on performance because it's still the same underlying object, just the type is being handled differently.
edit: Also, you may not need the EditText features now, but in the future you may. Just leave it as an EditText to save yourself a maintenance headache in the future.
I've found that there IS benefit in casting an EditText to a TextView in java code.
Doing so can reduce the amount of imports in your code, which can (drastically) reduce the size of your compiled APK.
EditTexts and Buttons can more often than not be referenced as TextViews in code (Buttons could also just be a View). Almost any kind of ViewGroup can be referenced as just ViewGroup. AbsListView instead of ListView (API 11+). AbsSpinner instead of Spinner. Just to name a few easy ones. Doing so has reduced some of my apk sizes by 20-30%.
you can miss the cast. findViewById returns a View object. If you don't need anything specific, work with it as View object
EDIT:
since we are talking about performance, here is a quick test. The results looks a bit random to me, but it looks like there is no significant difference between EditText and TextView. Only the Reflection method is slower:
try {
String text = "";
String result = "";
//Reflection
Long time = SystemClock.elapsedRealtimeNanos();
View v = ctx.findViewById(R.id.hello_text);
Field f = v.getClass().getSuperclass().getDeclaredField("mText");
f.setAccessible(true);
text = f.get(v).toString();
time = SystemClock.elapsedRealtimeNanos() - time;
result = "via Reflection ("+time.toString()+" ms)";
//TextView
time = SystemClock.elapsedRealtimeNanos();
TextView t = (TextView)ctx.findViewById(R.id.hello_text);
text = t.getText().toString();
time = SystemClock.elapsedRealtimeNanos() - time;
result = result + "\nvia TextView ("+time.toString()+" ms)";
//EditText
time = SystemClock.elapsedRealtimeNanos();
EditText e = (EditText)ctx.findViewById(R.id.hello_text);
text = e.getText().toString();
time = SystemClock.elapsedRealtimeNanos() - time;
result = result + "\nvia EditText ("+time.toString()+" ms)";
Toast.makeText(ctx, result, Toast.LENGTH_SHORT).show();
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (NoSuchFieldException e) {
}
}

Categories

Resources