6

I'm trying to make a tab bar in android using a RelativeLayout and a RadioGroup that has an indicator which will slide to indicate the active RadioButton within the RadioGroup.

These are customized radio buttons that are effectively rectangles with an icon over text, all contents centered, which uses a custom state-selector for the background.

Here's my current hierarchy:

<RelativeLayout>       // Main view of the tab bar
  <RadioGroup>         // The buttons
    <RadioButton />
    <RadioButton />
    <RadioButton />
  </RadioGroup>
  <ImageView />        // The indicator
</RelativeLayout>

The idea I had was when a radio button was clicked, I would align the indicator to the top & bottom of the selected radio button (and animate it). However, It seems that layout_align<Edge> only works with sibling elements, not with members of another view group, i.e. I can align to the RadioGroup itself, but not to a RadioButton inside it.

I thought of putting the indicator as a member of the RadioGroup, but since RadioGroup is an extension of LinearLayout, there doesn't seem to be a way to place it at the edge of a given RadioButton.

Can anyone think of how I might solve this issue? Or perhaps there's a better solution than my animate-align-to-button technique?

UPDATE With @superjos help, I managed to work this out fairly well.

I had to give each button a known height instead of using wrap_content (not ideal, but for this project it should probably work fine). Make the indicator height match that of the buttons. Make sure the RadioGroup is set to wrap content and be centered vertically in the parent view. Set indicator to alignTop and toRightOf the RadioGroup. Then, for the button click listener:

int prevY, newY;
int prevButtonIndex, newButtonIndex;

public void moveIndicator(button, indicator, index) {
  prevY = newY;
  newY = prevY + (index - prevButtonIndex) * button.getHeight();
  TranslateAnimation animation = new TranslateAnimation(0, 0, prevY, newY);
  animation.setDuration(500);
  animation.setFillAfter(true); // stay at final animation position
  indicator.setAnimation();
  animation.start();
}
Josh Kovach
  • 7,679
  • 3
  • 45
  • 62

2 Answers2

1

Here's my idea about it. Following are some more detailed steps.

The idea is to have the ImageView has a RadioGroup sibling, as it is in your sample. The ImageView is initially positioned on the right of the RadioGroup, and its horizontal middle line is aligned with the one of the first (or whatever chosen) RadioButton.

Then, upon a RadioButton click, a TranslateAnimation is built/configured at runtime in order to let the ImageView shift vertically until its middle line gets aligned with the one of the clicked button. The animation is set with fillAfter so to stay firm after it completes.

Steps:

  • Create your resources so that ImageView and RadioButtons have the same height, or else work with vertical paddings so that they result having the same height.

  • To initially align the ImageView to the first RadioButton, some XML could suffice: in ImageView attach layout_toRightOf and layout_alignTop to the RadioGroup. In order to take into account the top-padding from RadioGroup, you could add that to the initial ImageView top-padding XML attribute. Or better as top-margin.

  • When a RadioButton is clicked, create a TranslateAnimation that will be applied to the ImageVew, changing only the Y coordinate starting from 0 to its destination toY value (while from/toX attribute will be 0). That is given by a formula such as:

    toY = (ClickedRadioButton.Index - LastClickedRadioButton.Index) * RadioButton.Heigth
    

    Here Index is the ordinal position of the RadioButton within the group (e.g. from 0 to 2) (Warning: that is pseudo-code, not necessarily an existing Java field named Index).

  • Apply the animation to the ImageView and start it.

superjos
  • 12,189
  • 6
  • 89
  • 134
  • For the most part, this is looking like it might be the solution to use. The algorithm works for the most part (though need to set `setFillAfter(true)` to make it stick), but using starting coordinate 0 makes the animation always start from the _original_ starting point, not the current starting point. I've tried using `getTop()`, `getBaseline()`, `getLocation()[1]`. But those don't work and cause the starting point to be either the first or the very bottom position. Any idea how to adjust for this? – Josh Kovach Dec 07 '11 at 15:03
  • It looks like the problem stems from only the drawing position changing. To fix this, I apply an actual layout_marginTop change to the indicator that is an offset based on the checked button when the animation completes. This works fine, but the only problem is that it flickers when it's done animating. Is there a way to simply animate a change in layout parameters? – Josh Kovach Dec 07 '11 at 16:18
  • There's the _new_ [property animation](http://developer.android.com/guide/topics/graphics/prop-animation.html) framework, which allows you to animate many (all?) View properties, kind of WPF/Silverlight binding of properties to animations. It has been there since API 11, but I've never used it though. – superjos Dec 07 '11 at 19:26
  • BTW have you tried updating the marginTop (or maybe the paddingTop as an alternative) right after starting the animation, while it's still running? (not sure if this screws completely the effect). Other alternative: do **not** start over and over the animation fromY=0. Store the last animation toY value, and use it as the new animation fromY value. – superjos Dec 07 '11 at 19:29
  • Setting the margin immediately after calling animate seems to set it right away, without waiting for the animation to start. If I put it in `onAnimationStart()` it seems to do roughly the same thing. Interestingly, when another animation is triggered at roughly the same time as this one, the flicker becomes less of a flicker and more of a jump. It jumps to the next position down and then back to it's correct location. – Josh Kovach Dec 07 '11 at 21:47
  • What about the next approach, the one taking the new fromY starting from the last calculated toY ? – superjos Dec 07 '11 at 21:55
  • With the right combination of settings this works. Solution posted above. – Josh Kovach Dec 08 '11 at 16:01
0

You could customise the RadioButton style to include a 9patched version of the indicator image as a background for the selected (might be checked for radios..) state. Or as drawable

However, it depends what you want the animation to look like.

For example.

<style name="TabRadioButton" parent="@android:style/Widget.CompoundButton.RadioButton">
    <item name="android:background">@drawable/tab_bg</item>
    <item name="android:drawableLeft">@drawable/tab_indicator</item>
</style>
FunkTheMonk
  • 10,908
  • 1
  • 31
  • 37
  • The indicator is basically a triangle that points to the center of the button from one of the edges, and will slide from one button-edge center to another in a straight line when the button is clicked. I'm not sure putting it as a drawable on the button itself will get me that effect. – Josh Kovach Dec 02 '11 at 15:23