0

I'm developing an Android app and there is a feature I want to implement but because of lacking experience I don't know how to do it and unfortunately googling it didn't help.

So, there is a ViewPager with two tabs and a FloatingActionButton with an arrow image and what I want to do is to rotate the arrow depending on the scrolling state of pager (on first tab it should be a left arrow, then between the tabs it should smoothly rotate to face up or down (doesn't really matter), and finally on the second tab it should become a right arrow, and vice versa).

I've been trying RotateAnimation class, but I didn't manage to make it work.

Please notice that my app's minSdkVersion is 14 (Jelly Bean +), so Vector drawables and other just-Lollipop or even KitKat things wouldn't work for me.

Thanks in advance.

Roman Kotenko
  • 489
  • 7
  • 16
  • You can look into making a canvas and calling rotate function on ViewPager's setOnPageChangeListener ( assuming you are using ViewPager for the 2 fragments) – Karan Mar 26 '15 at 20:07
  • see http://stackoverflow.com/questions/7413710/android-how-can-i-rotate-an-arrow-image-around-a-fixed-point – Karan Mar 26 '15 at 20:08

2 Answers2

1

You'll want to use PageChangedListener on a view pager. Then listening to onPageScrolled(int position, float positionOffset, int positionOffsetPixels) will tell you what you need to calculate the rotation angle.

Note that this is not an animation--you are going to explicitly set the rotation of the button based on the scroll of the viewpager. You need to keep track of the current page and compare that to the page you're getting in the position argument of onPageScrolled. To do this, you can use my code as an example:

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    if (currentPagePosition != position && (int) positionOffset == 0 && positionOffsetPixels == 0) {
        // New page is set!
        currentPagePosition = position;
        onNewPage(currentPagePosition);
    } else {
        if (positionOffset == 0 && positionOffsetPixels == 0) {
            // User scrolled a little, then let go 
            // and it went back without changing position
            onNewPage(position);
            return;
        }
        if (position == currentPagePosition) {
            onPageScrolledToLeft(position, positionOffset);
        } else if (position < currentPagePosition) {
            onPageScrolledToRight(position, positionOffset);
        }
    }
}

With this, now you can implement those two methods to use positionOffset to calculate the rotation angle. Note that onPageScrolledToLeft means that the current page is moving left, or the user is scrolling to the right. When this happens, values of positionOffset will go from values of 1.0 to 0.0. When onPageScrolledToRight triggers, positionOffset will go from 0.0 - 1.0. Now after calculating the proper rotation (ie, 0.5 should probably be 0 degrees) you can use a view's setRotation(float rot) method.

Nicklas Jensen
  • 1,424
  • 12
  • 19
Ashton Engberg
  • 5,949
  • 2
  • 19
  • 14
  • I've tried _setRotation_ as well, what's weird it rotated the whole button but its drawable. I think that's the problem of _FloatingActionButton_ implementation, isn't it? – Roman Kotenko Mar 26 '15 at 20:48
  • How did you implement the floating action button? Did you use a 3rd party library? – Nicklas Jensen Mar 26 '15 at 20:50
  • @Nicklas yeah, it's [this](https://github.com/futuresimple/android-floating-action-button) one. I think it's the best one out there. – Roman Kotenko Mar 26 '15 at 20:52
  • Looking through the source, It is probably because of how the button is composed. One way to get around this would be to simply subclass the `FloatingActionButton`, add methods like `rotateTo(float angle)`` then override `onDraw()` such that you rotate the canvas before calling super to draw the button as it normally would. – Ashton Engberg Mar 26 '15 at 21:05
  • In my experiments I didn't find any issues with using `setRotation`, except that the shadow of the FAB is rotated as well, which looks a little weird, and it's not the intended behaviour of a shadow. – Nicklas Jensen Mar 26 '15 at 21:22
1

The library you're using includes the shadow in it's view, which means it's rotated along with the rest of the view when using setRotation. You can get around this by wrapping your Floating Action Button in a FrameLayout, remove the fab_icon attribute from your FAB, and add an ImageView on top of your FAB like so:

<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"
    android:layout_marginBottom="16dp"
    android:layout_marginRight="16dp"
    android:layout_marginEnd="16dp">

    <com.getbase.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:fab_colorNormal="?attr/colorAccent"
        app:fab_colorPressed="?attr/colorAccentHighlight"/>

    <ImageView
        android:id="@+id/fab_icon_overlay"
        android:layout_width="@dimen/fab_icon_size"
        android:layout_height="@dimen/fab_icon_size"
        android:layout_gravity="center"
        android:layout_marginTop="-3dp"
        android:src="@drawable/ic_content_add"
        android:tint="@android:color/white"/>

</FrameLayout>

Then, instead of rotating the FAB, you rotate the ImageView. The result is that the FAB appears to rotate, because it's a circle and the icon inside it is rotating. Please note that the value of android:layout_marginTop must be the negative value of fab_shadow_offset for the icon to be perfectly centered. The default value is 3dp.

What you want to do now is combine Ashtons answer with a call to setRotation(float rotation), please note that the rotation is in degrees (0.0f - 360.0f).

A solution might look like this:

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    // positionOffset ranges from 0.0f to 1.0f, multiply it by 180.0f to rotate the 
    // icon clockwise, making the icon appear flipped when scrolled to the last page.
    // Multiply by -180.0f to rotate counter-clockwise.
    fabIconOverlay.setRotation(positionOffset * 180.0f);
}
Nicklas Jensen
  • 1,424
  • 12
  • 19