Implementing Progress Tracker in React Native

This post shows how I implement a progress tracker (progress bar, progress indicator) in React Native and the lessons I learnt.

First version

At the beginning, the design is like this. There are two buttons. There is an arrow between them.

Original design

Basically, they are two buttons overlapping. The complex part is the arrow. I use 2 overlapping triangles to build the right part of Button 0 (for the gray edge), and 2 smaller triangles to build the left part of Button 1.

It looks good on both iOS and Android. But on Android, when Button 0 is pressed down, it doesn’t look good. The arrow has a different background. (There is no problem on iOS) The reason is the button is TouchableOpacity. When it is pressed down, it makes subviews half transparent. The arrow consists of two overlapping triangles. So the arrow looks darker when the button is pressed down.

Second Version

To remove the extra background of the arrow, I use react-native-svg to draw a polyline as the gray edge of the arrow. It’s easy to draw a polyline with react-native-svg. points property of Polyline is a string of all points like '0,0 1,1 0,2'. 0,0 is the first point. 1,1 is the second point. The code is here.

<Svg width={ARROW_WIDTH} height={HEIGHT}>
<Polyline fill='none' stroke='gray' strokeWidth='2'
points={'0,-2 ' + (ARROW_WIDTH-1) +','+ HEIGHT/2
+ ' 0,' + (HEIGHT+2)} />
</Svg>

No overlapping subviews any more. The progress tracker looks good and works well on both iOS and Android.

Third Version

The design changed. There could be more than 2 buttons. We need to scroll the progress tracker horizontally.

New design

It’s difficult to implement overlapping items in a ScrollView. I wanted to make a button’s subview overflow out of the button by setting negative left value. See pseudo code below.

<ScrollView horizontal={true}>
<TouchableOpacity>
<View style={left: -20}>
</TouchableOpacity>
...
</ScrollView>

Again, it works well on iOS, but doesn’t work on Android. It looks like this on Android.

The reason is overflow: visible does not work on Android. Everything will be clipped off. (It’s true on React Native v0.40 and older. React Native v0.41 will make overflow: visible work on Android. Cool!)

Fourth Version

It’s very frustrating that the third version does not work on Android. I need to use a RelativeLayout instead of a LinearLayout in ScrollView. Finally, I figured out a solution. Add all buttons into a view first, and then add the view into ScrollView. That view is like the content view of UIScrollView. So there is only one item in ScrollView. It works well on both iOS and Android.

You can find the source code here. For the code of the first and second versions, see other branches.

Another possible solution could be set TouchableOpacity’s margin negative value.

Lessons I learnt

  1. iOS is the first-class citizen of React Native. Sometimes code works well on iOS but does not work on Android. We have to spend more time on Android. Now I always run code on Android emulator and run on iPhone simulator occasionally.
  2. Overlapping subviews of TouchableOpacity introduce ugly highlighting effect on Android when the button is pressed down. We should avoid it.
  3. overflow: visible does not work on Android. (It will change in the future.)
  4. If you want to do complex things in ScrollView, you can add items into a content view first. It will be easier to implement.

iOS/Android/Web developer. UX engineering lead at Grab. Singapore/Hong Kong/Shanghai