Modifying ListView's smoothScrollToPosition transition - java

The default behavior for a ListView when calling smoothScrollToPosition on it, it to move with linear speed to the specified position.
Digging into ListView's and AbsListView's code, I can see that this behavior takes place because AbsListView uses a PositionScroller object (implementing AbsPositionScroller) that in turn uses a FlingRunnable object on which the method startScroll gets called with linear = true (which ends up having its OverScroller object use a LinearInterpolator).
I want to modify this behavior, and have it use for example the Scroller.ViscousFluidInterpolator class that the OverScroller class would use by default, but I'm not finding a way to do it.
I see that AbsListView defines a AbsPosScroller interface (that himself implements with a PositionScroller class), that I could try to implement with my own class to have it end up using the ViscousFluidInterpolator, but for some reason this interface is private to the package android.widget...
Am I missing something, or does it look like this has been written in a way that prevents it to have a behavior like that one be customized? Why would they bother writing up a AbsPosScroller interface in first place?
Any leads on how could I get the behavior I want without having to write my entire ListView class from scratch?

While I still don't know why would they write these components in a way that their behavior can't be customized easily when it would've been pretty easy to do it, I came up with an alternative implementation of smoothScrollToPosition (awesomeScrollToPosition in the code below) that does what I needed.
This solution makes use of an OverScroller object (that internally uses the ViscousInterpolator unless a different one is specified) to provide the effect I was looking for, for scrolling to elements within the visible page (the solution to achieve scrolling across pages is more convoluted, but this works for the problem I needed to solve).
I basically implemented a Runnable class private to my own ListView subclass (MyListView) that deals with the scrolling animation, re-posting itself to the UI thread for as long as the animation needs to run, using scrollingListBy in every frame (this method is only available since KitKat [19] though).
public class MyListView extends ListView {
private MyScroller mScroller;
/* MyListView constructors here */
public void awesomeScrollToPosition(int position, int duration) {
if (getChildCount() == 0) {
// Can't scroll without children (visible list items)
return;
}
if (mScroller == null) {
mScroller = new MyScroller();
}
if (mScroller.isRunning()) {
mScroller.stop();
}
int firstPos = getFirstVisiblePosition();
int lastPos = getLastVisiblePosition();
if (!(firstPos <= position && position <= lastPos)) {
// Can't scroll to an item outside of the visible range this easily
return;
}
int targetPosition = position - firstPos;
int targetTop = getChildAt(targetPosition).getTop();
mScroller.start(targetTop, duration);
}
private class MyScroller implements Runnable {
OverScroller mScroller;
boolean mRunning;
int mLastY;
MyScroller() {
mScroller = new OverScroller(getContext());
mRunning = false;
}
void start(int y, int duration) {
// start scrolling
mLastY = 0;
mScroller.startScroll(0, 0, 0, y, duration);
mRunning = true;
postOnAnimation(this);
}
boolean isRunning() {
return mRunning;
}
#Override
public void run() {
boolean more = mScroller.computeScrollOffset();
final int currentY = mScroller.getCurrY();
// actual scrolling
scrollListBy(currentY - mLastY);
if (more) {
mLastY = currentY;
// schedule next run
postOnAnimation(this);
} else {
stop();
}
}
public void stop() {
mRunning = false;
removeCallbacks(this);
}
}
}

Related

How notify current layer for position change

I'm currently using osmdroid to display current positioning.
Based on the following example i tried to optimize the system a little bit by not constructing the ItemizedOverlay<OverlayItem> and ArrayList<OverlayItem> each time my location is changed, but construct them only once in the constructor, and later on simply add points to my ArrayList variable.
Here's how it looks now:
private void InitializeMarkersOverlay() {
mOverlayItemArrayList = new ArrayList<OverlayItem>();
ItemizedOverlay<OverlayItem> locationOverlay =
new ItemizedIconOverlay<OverlayItem>(this, mOverlayItemArrayList, null);
mMapView.getOverlays().add(locationOverlay);
}
and when a new location arrives:
private void AddPointToOverlay(GeoPoint gPt, boolean bShouldClearList) {
OverlayItem overlayItem = new OverlayItem("", "", gPt);
Drawable markerDrawable = ContextCompat.getDrawable(this, R.drawable.pin);
overlayItem.setMarker(markerDrawable);
// first time initializer
if(bShouldClearList) {
mOverlayItemArrayList.clear();
}
mOverlayItemArrayList.add(overlayItem);
}
Since my mMapView already has a pointer to mOverlayItemArrayList i was hoping that my mapview's layer would be automatically notified regarding the change. but nothing actually happens. Only by recreating the objects, i get to see the pin.
Adding to the list does not work because ItemizedIconOverlay need to do some operations on addition. You can check source code for ItemizedIconOverlay.
You can see there is call to populate() in addItem method (and all other methods which are manipulating with items).
public boolean addItem(final Item item) {
final boolean result = mItemList.add(item);
populate();
return result;
}
But populate() is an implementation detail and is marked as protected so you cannot call it directly.
Correct solution would be:
Don't keep reference to the list but to ItemizedIconOverlay
instance.
Use mLocationOverlay.addItem(overlayItem)
You may need to call mapView.invalidate() after adding new point.
I got it working by accessing the overlay directly from the mapview object, not sure why exactly, as i was hoping mMapView.getOverlays() would hold a reference to the ItemizedIconOverlay and its itimized array
if(mMapView.getOverlays().size() > 0) {
((ItemizedIconOverlay<OverlayItem>)mMapView.getOverlays().get(0)).removeAllItems();
((ItemizedIconOverlay<OverlayItem>)mMapView.getOverlays().get(0)).addItem(overlayItem);
}
}

Espresso - How to check if one of the view is displayed

In my test, after one action, there are two possible views which can appear and both of them are correct. How can I check if one of the view is displayed. For a single view I can check with is Displayed(). But that would fail if other view is visible instead. I want to pass the test if any one of those two views are displayed.
onMyButton.perform(click());
onMyPageOne.check(matches(isDisplayed())); //view 1
or
onMyPageTwo.check(matches(isDisplayed())); //view 2
After, perform click on MyButton, any one of the view (1 or 2) is expected to appear but not both. It is not fixed that which one would be displayed.
How can I check if any one of them is displayed?
It's possible to catch the exceptions raised by Espresso like this:
If you want to test if a view is in hierarchy:
try {
onView(withText("Button")).perform(click());
// View is in hierarchy
} catch (NoMatchingViewException e) {
// View is not in hierarchy
}
This exception will be thrown if the view is not in the hierarchy.
Sometimes the view can be in the hierarchy, but we need to test if it is displayed, so there is another exception for assertions, like this:
try {
onView(withText("Button")).check(matches(isDisplayed()));
// View is displayed
} catch (AssertionFailedError e) {
// View not displayed
}
There are two cases here that you could be trying to cover. The first is if you are checking if the view "is displayed on the screen to the user" in which case you would use isDisplayed()
onView(matcher).check(matches(isDisplayed()));
or the negation
onView(matcher).check(matches(not(isDisplayed())));
The other case is if you are checking if the view is visible but not necessarily displayed on the screen (ie. an item in a scrollview). For this you can use withEffectiveVisibility(Visibility)
onView(matcher).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
You can use Matchers.anyOf to check if any of the two views are displayed:
onView(
anyOf(withId(R.id.view_1), withId(R.id.view_2))
).check(matches(isDisplayed()));
For the ones looking to check the visibility status for a view; here are some utility functions I use.
fun ViewInteraction.isGone() = getViewAssertion(ViewMatchers.Visibility.GONE)
fun ViewInteraction.isVisible() = getViewAssertion(ViewMatchers.Visibility.VISIBLE)
fun ViewInteraction.isInvisible() = getViewAssertion(ViewMatchers.Visibility.INVISIBLE)
private fun getViewAssertion(visibility: ViewMatchers.Visibility): ViewAssertion? {
return ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(visibility))
}
And can be used as follows
onView(withId(R.id.progressBar)).isVisible()
onView(withId(R.id.progressBar)).isGone()
I researched Espresso a bit, and I found this # Espresso Samples.
Search text "Asserting that a view is not displayed". It says "The above approach works if the view is still part of the hierarchy." So I think your code should work but you need to use ViewAssertions also. Using your code, perhaps do this:
if (ViewAssertions.doesNotExist()) == null) {
return;
}
onMyPageOne.check(matches(isDisplayed()));
Another technique is check for UI existence. Search for text "Asserting that a view is not present".
Using your code, my best suggestion is:
onMyPageOne.check(doesNotExist());
Note: This calls doesNotExist method.
Their sample code is: onView(withId(R.id.bottom_left)).check(doesNotExist());
Utility class which allows to check if view is visible, gone or invisible:
public class ExtraAssertions {
public static ViewAssertion isVisible() {
return new ViewAssertion() {
public void check(View view, NoMatchingViewException noView) {
assertThat(view, new VisibilityMatcher(View.VISIBLE));
}
};
}
public static ViewAssertion isGone() {
return new ViewAssertion() {
public void check(View view, NoMatchingViewException noView) {
assertThat(view, new VisibilityMatcher(View.GONE));
}
};
}
public static ViewAssertion isInvisible() {
return new ViewAssertion() {
public void check(View view, NoMatchingViewException noView) {
assertThat(view, new VisibilityMatcher(View.INVISIBLE));
}
};
}
private static class VisibilityMatcher extends BaseMatcher<View> {
private int visibility;
public VisibilityMatcher(int visibility) {
this.visibility = visibility;
}
#Override public void describeTo(Description description) {
String visibilityName;
if (visibility == View.GONE) visibilityName = "GONE";
else if (visibility == View.VISIBLE) visibilityName = "VISIBLE";
else visibilityName = "INVISIBLE";
description.appendText("View visibility must has equals " + visibilityName);
}
#Override public boolean matches(Object o) {
if (o == null) {
if (visibility == View.GONE || visibility == View.INVISIBLE) return true;
else if (visibility == View.VISIBLE) return false;
}
if (!(o instanceof View))
throw new IllegalArgumentException("Object must be instance of View. Object is instance of " + o);
return ((View) o).getVisibility() == visibility;
}
}
}
And usage could look like this:
onView(withId(R.id.text_message)).check(isVisible());
Another view assertion which could help to check extra visibility properties of a view and its parents: it checks visibility, isAttachedToWindow, alpha:
class IsVisible : ViewAssertion {
override fun check(view: View, noViewFoundException: NoMatchingViewException?) {
ViewMatchers.assertThat(
"View is not visible. " +
"visibility: ${view.visibility}, " +
"isAttachedToWindow: ${view.isAttachedToWindow}, " +
"alpha: ${view.alpha}",
true, `is`(isViewTreeVisible(view)))
}
private fun isViewTreeVisible(view: View?): Boolean {
return if (view != null) {
val viewVisible = view.isAttachedToWindow && view.visibility == View.VISIBLE && view.alpha == 1.0f
if (view.parent !is View) viewVisible
else viewVisible && isViewTreeVisible(view.parent as View)
} else {
true
}
}
}
The problem is that all assertoin() and check() methods return Assertion that stops test flow if failed.
One simple way to check for a View or its subclass like a Button is to use method
getVisibility from View class. I must caution that visibility attribute is not clearly defined in the GUI world. A view may be considered visible but may be overlapped with another view, for one example, making it hidden.
Another way but more accurate (I have not tried) is to check for the rectangular bounds of the View. Not so simple.
Is that clear enough? cannot give you specific examples since you did not post code.
final AtomicBoolean view1Displayed = new AtomicBoolean(true);
Espresso.onView(ViewMatchers.withId(viewId1)).inRoot(RootMatchers.withDecorView(Matchers.is(intentsTestRule.getActivity().getWindow().getDecorView()))).withFailureHandler(new FailureHandler() {
#Override
public void handle(Throwable error, Matcher<View> viewMatcher) {
view1Displayed.set(false);
}
}).check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
if (view1Displayed.get()) {
try {
Espresso.onView(ViewMatchers.withId(viewId2)).inRoot(RootMatchers.withDecorView(Matchers.is(intentsTestRule.getActivity().getWindow().getDecorView()))).check(ViewAssertions.matches(Matchers.not(ViewMatchers.isDisplayed())));
} catch (NoMatchingViewException ignore) {
}
} else {
Espresso.onView(ViewMatchers.withId(viewId2)).inRoot(RootMatchers.withDecorView(Matchers.is(intentsTestRule.getActivity().getWindow().getDecorView()))).check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
}
When I face this situation I generally split into multiple tests. One test sets the conditions for view #1 to be displayed and the other test sets the conditions for view #2 to be displayed.
But let's say that you can't really control the conditions. For example, what if it depends on a random number or it depends on a third-party resource such as a calculation on a server? In that case, I usually solve the problem mocking. That way I can control the conditions so I know exactly which view to expect. I use Dependency Injection to set the mock I need for each test.

Android/Java and multiple "view states"

Does anyone recognize this pattern and know of a tidy solution?
I've got a view that can be in certain states. Let's call them Neutral, Success, Error, InProgress. In the view I've got multiple elements (Buttons, TextViews and a ProgressBar) that should either be visible/enabled depending on the state the view is in.
Currently I've got methods that represent the states that do the necessary .setEnabled() and .setVisibility() calls. With 4 states and a couple of elements this becomes messy quite fast.
I also feel that the State Pattern is not necessarily a good solution but is something that personally springs to mind.
I would love to hear what any of you think is a simple and tidy solution.
Sample code:
void setIsRegistering() {
isRegistering = true;
isRegistered = false;
progressBar.setVisibility(View.VISIBLE);
successText.setVisibility(View.GONE);
errorText.setVisibility(View.GONE);
setupFooterButton.setEnabled(false);
setupFooterButton.setText("Adding browser");
}
void setIsRegistered() {
isRegistering = false;
isRegistered = true;
progressBar.setVisibility(View.INVISIBLE);
successText.setVisibility(View.VISIBLE);
errorText.setVisibility(View.GONE);
setupFooterButton.setEnabled(true);
setupFooterButton.setText("Next");
}
void setIsNotRegistered() {
isRegistering = false;
isRegistered = false;
progressBar.setVisibility(View.INVISIBLE);
successText.setVisibility(View.INVISIBLE);
errorText.setVisibility(View.GONE);
setupFooterButton.setEnabled(true);
setupFooterButton.setText("Add browser");
}`
You can use a ViewAnimator for this: (http://developer.android.com/reference/android/widget/ViewAnimator.html)
You can then call viewAnimator.setDisplayedChild() to set the selected item.
setDisplayedChild() takes an integer, so I typically create an enum to hold the states I want:
enum ViewStates {
NEUTRAL, SUCCESS, ERROR
}
setDisplayedChild(ViewStates.Neutral.ordinal());
Or if that's too verbose:
enum ViewStates {
NEUTRAL, SUCCESS, ERROR
public static int neutral = NEUTRAL.ordinal();
public static int success = SUCCESS.ordinal();
public static int error = ERROR.ordinal();
}
setDisplayedChild(neutral);

Android: Implicit super constructer undefined for default constructer

Ok, first of all, I know you have seen this problem before, and I'll tell you why this is different. I have a class, DrawView (followed some Canvas tutorials) and it extends View. Ok, but I want a separate class to handle all the animations, so I can just call, for example, mainMenuAnimation() and it will draw it instead of coding it to the actual game loop. Well, if I create a class for holding the animations, Animations.java, and extend DrawView, I get an error from Eclipse:
Implicit super constructor DrawView() is undefined for default constructor. Must define an explicit constructor
The problem is, if I call the DrawView() constructor, it makes a new Animations.java, and so on. (Maybe I should define Animations a = new Animations()? Not sure if I would run into problems later on though). So, if I add an empty constructor in DrawView(), it gives me this error:
Implicit super constructor View() is undefined for default constructor. Must define an explicit constructor
I have no idea what to do, help?
Okay, the reason why I instanced Animations in the DrawView() constructor is because Animations' constructor has to be super(context) and the only way to access the context is through the DrawView() constructor.
DrawView constructor code:
Paint paint; //initialize EVERYTHING
Resources res;
Bitmap title;
Rect titleRect;
boolean inMainMenu, issetBackgroundDrawableSupported;
List<BitmapDrawable> mainMenuAnimation;
int mainMenuAnimationIndex = 0;
public DrawView(Context context) {
super(context);
res = getResources(); //required stuff
title = BitmapFactory.decodeResource(getResources(),R.drawable.title); //title stuff
titleRect = new Rect(res.getDisplayMetrics().widthPixels/2 - title.getWidth()*10 , 100, res.getDisplayMetrics().widthPixels/2 + title.getWidth()*10, 200); //left, top, right, bottom
inMainMenu = false; //main menu stuff
issetBackgroundDrawableSupported = true;
mainMenuAnimation = new ArrayList<BitmapDrawable>();
mainMenuAnimation.add(new BitmapDrawable(getResources(), BitmapFactory.decodeResource(res, R.drawable.mainmenu_background_1)));
mainMenuAnimation.add(new BitmapDrawable(getResources(), BitmapFactory.decodeResource(res, R.drawable.mainmenu_background_2)));
mainMenuAnimation.add(new BitmapDrawable(getResources(), BitmapFactory.decodeResource(res, R.drawable.mainmenu_background_3)));
Animations animations = new Animations(getApplication());
}
And the Animations.java code:
public class Animations extends DrawView {
//define animations
#SuppressLint("NewApi")
public void mainMenuScroll(Canvas canvas) {
inMainMenu = true;
//draw main menu here
if (inMainMenu = true) { //main menu loop
if (issetBackgroundDrawableSupported) { //check if background drawing is supported
try {
setBackgroundDrawable(mainMenuAnimation.get(mainMenuAnimationIndex));
} catch (Exception e){
issetBackgroundDrawableSupported = false; //say it is unsupported
setBackground(mainMenuAnimation.get(mainMenuAnimationIndex));
}
}
else {
setBackground(mainMenuAnimation.get(mainMenuAnimationIndex));
}
mainMenuAnimationIndex++;
if (mainMenuAnimationIndex == 3) { //restart main menu animation
mainMenuAnimationIndex = 0;
}
}
}
}
Ok, I realized another Eclipse notification, might be useful. It says:
Custom view com/spng453/agenericrpg/Animations is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int)
Sounds relevant, but I'm not sure what to do about it.
All Views run within the context of a Context. (I guess that's why it's called that =P). This includes your custom View.
You're going to want to define an Animations constructor that takes a Context, so you can pass it through to the super constructors. This is the cleanest way to get rid of your errors, and will also fix the last problem you mentioned (namely, the Android system is trying to instantiate your class, but it doesn't know what to do with a View that doesn't take a Context in its constructor).
public Animations(Context context) {
super(context);
}

Custom Jslider Initialization Problem

I am working on a custom JSlider that has a custom Track Rectangle. I want the ability to set the color of the track rectangle when first declaring the slider.
Here's a snippet of what I have (The classes are in separate files in the same package):
public class NewSlider extends JSlider {
Color kolor;
public NewSlider (Color k) {
kolor = k;
}
public void updateUI() {
setUI(new NewSliderUI(this, kolor);
updateLabelUIs();
}
}
public class NewSliderUI extends BasicSliderUI {
Color sliderColor = Color.BLACK;
public NewSliderUI (JSlider b, Color k) {
super(b);
sliderColor = k;
}
}
In the above code, "kolor" is initially null and leads to and error when NewSliderUI tries to use it. It appears that the updateUI() method is called before anything else. Then the NewSlider constructor is called. I have tried a variety of things, but because updateUI() appears to run before anything else, nothing I add to the NewSlider class seems to matter.
If I hardcode a Color (ie. setUI(new NewSliderUI(this, Color.BLACK);), then it works, but having a different class for each color seems silly.
Thanks.
I don't see how kolor could be null unless one of the following are happening:
You're passing a null value to the constructor
You're not instantiating NewSlider in the Swing EDT and are having some strange cache issues
NewSlider is being constructed via reflection/deserialization and kolor is not being set.
Have you tried running this in the debugger with some breakpoints? I'd be curious to ensure that the NewSlider constructor is being called (and before the NewSliderUI constructor).
Edit: I see what you mean below. I forgot that the no args constructor for JSlider was being called implicitly. What about doing the following:
public class NewSlider extends JSlider {
Color kolor = Color.BLACK;
public NewSlider (Color k) {
kolor = k;
updateUI();
}
public void updateUI() {
setUI(new NewSliderUI(this, kolor);
updateLabelUIs();
}
}
You end up calling updateUI() twice, but the end result should be what you want.

Categories