Introduction to the

A few days ago, I wrote a React Native component: a very customizable bottom ActionSheet. It works with React Native’s features: it supports both iOS and Android, and a copy of the same code will display almost exactly the same style on both platforms.

Let’s take a look at the effects (iOS emulator on the top row, Android emulator on the bottom row) :

The figure above shows the default style for this component. Because this component is highly customizable, you can get more different styles by setting a few properties.

GitHub: React-naive -highly-customizable-action-sheet

Introduction to Customization

In this component: the title at the top, the selection in the middle, and the cancel at the bottom are optional, and the font, color, height, distance, dividing line color, rounded corners, etc. of each part are also customizable.

Let’s start with a few default styles:

Default style:

The default style is the style that the user presents when the user sets only the data (literal) related properties without setting the style related properties. This style is the style used in wechat and Weibo, which is also the style I like very much.

Looks like the iOS native ActionSheet

Users can set certain properties to achieve the default iOS ActionSheet style:

In addition, the user can implement various other styles by setting certain properties:

Here’s a look at how to customize these styles in code together:

Method of use

Installation:

npm install react-naive-highly-customizable-action-sheet

Reference component:

import ActionSheet from 'react-naive-highly-customizable-action-sheet'

It then passes in a header, an array of option literals, an array of callback methods, and so on to implement an ActionSheet component.

The following code and demo screenshot to explain:

An example of a default style:

The code for this style is:

<ActionSheet
   mainTitle="There are three ways to contact. Please choose one to contact."
   itemTitles = {["By phone"."By message"."By email"]}
   selectionCallbacks = {[this.clickedByPhone,this.clickedByMessage,this.clickedByEmail]}
   mainTitleTextAlign = 'center'
   ref={(actionsheet)=>{this.actionsheet = actionsheet}}
/>
  
// Pop up the bottom menu
showActionSheet(){
	this.actionsheet.show();  
}

// The callback function
clickedByPhone(){
   alert('By Phone');
}

// The callback function
clickedByMessage(){
    alert('By Message');
}

// The callback function
clickedByEmail(){
    alert('By Email');
}
Copy the code

Here,

  • mainTitle: is the headline at the top.
  • itemTitles: An array of option text.
  • selectionCallbacks: Array of callback functions after clicking options.

Note that the array of option literals and the elements in the callback array should correspond one to one. However, even if the number of elements in the callback function array is less than the number of elements in the option literal array, it will not cause a crash.

An example of the iOS ActionSheet style:

The code for this style is:

 <ActionSheet
    mainTitle="There are three ways to contact. Please choose one to contact."
    itemTitles = {["By phone"."By message"."By email"]}
    selectionCallbacks = {[this.clickedByPhone,this.clickedByMessage,this.clickedByEmail]}
    mainTitleTextAlign = 'center'
    contentBackgroundColor = '#EFF0F1'
    bottomSpace = {10}
    cancelVerticalSpace = {10}
    borderRadius = {5}
    sideSpace = {6}
    itemTitleColor = '#006FFF'
    cancelTitleColor = '#006FFF'
    ref={(actionsheet)=>{this.actionsheet = actionsheet}}
/>


// Pop up the bottom menu
showActionSheet(){
	this.actionsheet.show();  
}

// The callback function
clickedByPhone(){
   alert('By Phone');
}

// The callback function
clickedByMessage(){
    alert('By Message');
}

// The callback function
clickedByEmail(){
    alert('By Email');
}
Copy the code

For more styles, see the Example in the demo.

With an overview of what this component does and how to use it, let’s look at how it is encapsulated.

React Native component encapsulation

What to encapsulate

For a view component in GUI programming, there are three things:

  1. data
  2. style
  3. interaction

As for the encapsulation of view components, my personal understanding is that it encapsulates the form of received data, the transformation rules between data and style, and the logic of interaction. And it all starts with the receipt of data. Without receiving data, there is no UI presentation, let alone interaction.

So it’s a good idea to start with the React Native view component.

Data reception

In iOS development, providing data to a view is done by setting properties or implementing data source methods. In React Native development, however, you can usually only set properties to pass in data that the component needs to implement a style. For example, in the two examples above, the title and option text are passed in by setting specific properties.

Also, to ensure that the property is of the correct type, it is a good idea to do a type check on the property:

import React, {Component, PropTypes} from 'react';

static propTypes = {
 
    mainTitle:PropTypes.string.isRequired,// The type is a string and must be passed in
    mainTitleFont:PropTypes.number,// The type is numeric
    mainTitleColor:PropTypes.string,// The type is a string
    mainTitleTextAlign:PropTypes.oneOf(['center'.'left']),// Choose one or the other
    hideCancel:PropTypes.bool,// The type is Boolean. }Copy the code

Notice the mainTitle property on the first line, where it is set as a must-pass property. So if the attribute is not passed in in this case, a warning will appear.

The above is just my example; no attributes in the component I’ve encapsulated are necessarily passed in. For customization, all attributes are passable or not.

Now we know how to pass data into the component. But this is only the first step. The data required by the component may include not only the data passed in by the user, but also other data calculated from the data passed in by the user, such as the total height of the popover. It is easy to understand that the total height of the popover depends on the height of the title, the height of the options and the number of options, and the sum of the height of the cancellations. This data is obviously computed by passing in headings, options, etc.

Also, for data that may not be passed in by the user, the component itself may need to provide default values for the corresponding properties.

To sum up, data processing can be divided into two types:

  1. Calculate additional data.
  2. Provides default values for the corresponding properties.

Take two examples of the code in this component.

The data processing

1. Additional data to be calculated

componentWillMount(){
    
     ...
    //Calculate Title Height
    if (!this.props.mainTitle){
        this.real_titleHeight = 0
    }else {
        this.real_titleHeight = this.state.mainTitleHeight;
    }

    //Calculate Items height
    if (!this.props.itemTitles){
        this.real_itemsPartHeight = 0;
    }else {
        this.real_itemsPartHeight = (this.state.itemHeight + this.state.itemVerticalSpace) * this.props.itemTitles.length;
    }

    //Calculate Cancel part height
    if (this.props.hideCancel){
        this.real_cancelPartHeight = 0;
    }else {
        this.real_cancelPartHeight = this.state.cancelVerticalSpace + this.state.cancelHeight;
    }

    // total content height
    this.totalHeight = this.real_titleHeight +  this.real_itemsPartHeight + this.real_cancelPartHeight + this.state.bottomSpace; . }Copy the code

Here, enclosing real_titleHeight, enclosing real_itemsPartHeight, enclosing real_cancelPartHeigh, enclosing totalHeight is after get properties, need additional calculation data. I put this work in the componentWillMount() method.

2. Provide default values for corresponding attributes

If the user does not pass in a title text color, a default title color is provided:

 constructor(props) {
	super(props);
    this.state = {
      ...
      mainTitleColor:this.props.mainTitleColor?this.props.mainTitleColor:'gray'.// The main title color
      cancelTitle:this.props.cancelTitle?this.props.cancelTitle:'Cancel'.// Cancel the text. }}Copy the code

As you can see, the component provides default values for mainTitleColor and cancelTitle if the user has not set them.

The data show

In React Native, the component’s render() function is responsible for rendering the component. So this function will render the component using the previously calculated data:

render() {
   retrun(  
     <View>
        {this._renderTitleItem()}
        {this._renderItemsPart()}
        {this._renderCancelItem()}
    </View>)}//render title part
_renderTitleItem(){
    if(!this.props.mainTitle){
        return null;
    }else {
        return (
            <TouchableWithoutFeedback>
                <View style={[styles.contentViewStyle]}>
                    <Text>{this.props.mainTitle}</Text>
                </View>
            </TouchableWithoutFeedback>)}}//render selection items part
_renderItemsPart(){
    var itemsArr = new Array(a);let title = this.state.itemTitles[i];
    let itemView =
        <View key={i}>
            {/* Seperate Line */}
            {this._renderItemSeperateLine(showItemSeperateLine)}
            {/* item for selection*/}
            <TouchableOpacity onPress={this._didSelect.bind(this, i)} >
                <View style={[styles.contentViewStyle]} key={i}>
                    <Text style={[styles.textStyle]}>{title}</Text>
                </View>
            </TouchableOpacity>
        </View>
        itemsArr.push(itemView);

    return itemsArr;
}


//render cancel part
_renderCancelItem(){
    return (
      <View style={{width:this.contentWidth,height: this.real_cancelPartHeight}} >
          {/* Seperate Line */}
          {this._renderCancelSeperateLine(showCancelSeperateLine)}
          {/* Cancel Item */}
            <TouchableOpacity onPress={this._dismiss.bind(this)}>
                <View style={[styles.contentViewStyle]}>
                    <Text style={[styles.textStyle]}>{this.state.cancelTitle}</Text>
                </View>
            </TouchableOpacity>
      </View>
    );
}
Copy the code

interaction

Component interactions can be divided into two types: those with and without external callbacks. This external callback refers to the function that needs to be executed outside of the component. Take the bottom menu component: if the user clicks on an item, the menu falls back and calls functions outside the component (such as logging out, clearing the cache, and so on). Similarly, in iOS development, callbacks can be implemented using proxies or blocks. React Native implements callbacks in a similar way to iOS blocks.

Interaction with callbacks

In React Native, if you need to call an external function, you need to pass that function as a property into the component at the beginning. Then intercepts the user’s click and invokes the corresponding callback function. There are three steps:

  1. Pass in the callback function
  2. Intercepting user actions
  3. Call the callback function

1. Pass in the callback function:

static propTypes = {
  
    //selection items callback
    selectionCallbacks:PropTypes.array,
}
Copy the code

In this case, selectionCallbacks are an array property of callbacks that correspond to the selection. An array is used to hold the callback function because the number of options is uncertain.

2. Blocking user actions (click) :

<TouchableOpacity onPress={this._didSelect.bind(this, Animation-opacity ={0.9}> <View style={styles.contentviewstyle} key={I}> <Text style={styles.textstyle}>{title}</Text> </View> </TouchableOpacity>Copy the code

Here, we use the TouchableOpacity component to enable the View component to be clicked, and bind the _SELECT (index) function.

3. Call the callback function:

// Retrieve the corresponding callback function and call it
_select(i) {
    let callback = this.state.selectionCallbacks[i];
    if(callback){
        {callback()}
    }
}
Copy the code

In this case, the _didSelect(index) function is called when an option is clicked. This function takes the index value passed in, gets the corresponding index callback from the callback array and calls it. And to avoid crashes, it also determines whether the callback is empty.

There is no callback interaction

This interaction would be easier if there were no callbacks, just doing it inside the component. For example, the fall back event after clicking cancel:

<TouchableOpacity onPress={this._dismiss. Bind (this)} activeOpacity ={0.9}> <View style={styles.contentviewstyle}> <Text  style={styles.textStyle}>{this.state.cancelTitle}</Text> </View> </TouchableOpacity> //dismiss ActionSheet _dismiss() {  if (! this.state.hide) { this._fade(); }}Copy the code

In addition to making the menu drop back, the user is also given feedback when clicking cancel: the opacity of the background color changes when clicking. Animation-opacity = {0.9}

OK, now that we’re done with data and interaction, let’s take a look at how React Native supports animations.

Animation effects

In general, the bottom menu is Animated when it pops up and falls back down, and React Native’s Animated effects can be implemented using its Animated library.

Here’s an example of a menu popup:

//animation of showing
_appear() {
    Animated.parallel([
        Animated.timing(
            this.state.opacity, // Animation adaptation variables
            {
                easing: Easing.linear,
                duration: 200.// Animation duration, in milliseconds
                toValue: 0.7./ / the end value
            }
        ),
        Animated.timing(
            this.state.offset,
            {
                easing: Easing.linear,
                duration: 200.toValue: 1,
            }
        )
    ]).start();
    }
Copy the code

Here,

  • The Animated. Parallel function is responsible for executing simultaneous composite animations. Since it is a composite animation, it should be passed an array of animations. If you look closely, you’ll see that there are two Animated. Timing functions.

  • The Animated. Timing function is responsible for executing the animation in time. As you can see from the comments, the two animations executed at the same time here are:

    • this.state.opacityValue in 200 ms, from 0 to 0.7 gradient animation.
    • this.state.offsetValue in 200 milliseconds, gradient from 0 to 1 animation.
  • The start() function at the bottom triggers the composite animation.

No starting point value is provided because the current value of the passed variable is directly fetched here.

As opposed to the bottom menu’s pop-up animation, take a look at the bottom menu’s drop animation:

//animation of fading
_fade() {
    Animated.parallel([
        Animated.timing(
            this.state.opacity,
            {
                easing: Easing.linear,
                duration: 200.toValue: 0,
            }
        ),
        Animated.timing(
            this.state.offset,
            {
                easing: Easing.linear,
                duration: 200.toValue: 0,
            }
        )
    ]).start((finished) = > this.setState({hide: true}));
}
Copy the code

For more information on animations, see the official document React Native: Animations

In fact, here, for the component packaging is basically finished, explain the content or focus on the data this piece, how to draw the component will not explain. After all, every component has different code to convert data to style, so learning how to draw a pop-up menu is not much of a reference for drawing other components. But for a generic component, the customization must be up to a certain standard. So instead of “how components are drawn”, I thought it would be more practical to talk about “improving component customization”.

Work done to improve customization:

In the beginning, this control could only set the title, options and callback functions, and only this one style:

But in order to improve the customization, support more styles, and get a better understanding of React Native, I decided to take the challenge and see how much customization could be improved.

As mentioned above, in React Native, components transfer data by setting their properties. So if you want to improve the customization of a component, you need to add properties to that component.

Take a look at all the properties of this component:

  • ItemTitles (Array): An Array of titles to select items

  • SelectionCallbacks (Array) : Click on the callback Array of options

  • MainTitle (String): title text

  • MainTitleFont (Number): title font

  • MainTitleColor (String): title color

  • MainTitleHeight (Number): title bar height

  • MainTitleTextAlign (String): Title alignment

  • MainTitlePadding (Number): Inside title margin

  • ItemTitleFont (Number): Select item font

  • ItemTitleColor (String): Select item color

  • ItemHeight (Number): Select bar height

  • CancelTitle (String): Cancels item title, defaults to ‘Cancel’

  • CancelTitleFont (Number): Cancels the title font

  • CancelTitleColor (String): Cancels the title color

  • CancelHeight (Number): Cancels column height

  • HideCancel (Bool): whether to hideCancel items (default not hidden)

  • FontWeight (String): Font size for all text (set title, select item, unweight item)

  • TitleFontWeight (String): the font size of the title, default to ‘normal’

  • ItemFontWeight (String): The font size of the selection, default is ‘normal’

  • CancelFontWeight (String): Cancels the font size of the item, defaults to ‘bold’

  • ContentBackgroundColor (String): Background color for all items (set title, select item, cancel item background color)

  • TitleBackgroundColor (String): The background color of the title (white by default)

  • ItemBackgroundColor (String): The background color of the selection (white by default)

  • CancelBackgroundColor (String): Cancels the item’s background color (white by default)

  • ItemSpaceColor (String): Dividing line color between selections (default is light gray)

  • CancelSpaceColor (String): Dividing line color between cancelSpaceColor and last option (default is light gray)

  • ItemVerticalSpace (Number): The height of the dividing line between selections

  • CancelVerticalSpace (Number): The height of the dividing line between the cancelVerticalSpace and the last selection

  • BottomSpace (Number): distance from the bottom of the screen to the bottom of the cancel item

  • SideSpace (Number): the distance between the left and right sides of the pop-up box and the left and right sides of the screen

  • BorderRadius (Number): Rounded corners of the pop-up box

  • MaskOpacity (Number): Mask transparency (default: 0.3)

As you can see, each of the three parts of this component (title, option, cancel) has its own properties that can be set. This component was designed with these three parts highly decoupled: each part is independent of each other, has its own data (except for a few that can be used in common), and is drawn separately.

For example, we could set:

Each part of the text content, font size, height

Background color (can be set uniformly or individually)

The dividing line height, the height from the bottom, the distance from the side of the screen

The color of the dividing line

The code corresponding to the above images is provided in the demo (see the Example folder for details).

The component also supports some extreme cases that may be rare on demand, but still provide support.

Extreme cases:

The degree of decoupling can be seen in this final figure: the main heading, selection, and cancellation can all be displayed according to the attributes passed in, without affecting each other. And without any Settings, only the gray bottom mask is displayed.

The last word

It took three days to write the component, and the default style was developed in the first day. The last two days are about customization. Because the customization work is inseparable from data processing and application, and I do not understand JavaScript syntax very well, so I wrote a lot of bugs during the period. Fortunately, React Native is very efficient in building UI, so the workload is not much after data processing.

After all, it is the first React Native component packaged by myself. I believe it still has a lot of room for improvement. For example, there may be some problems in data processing, and we still need your valuable opinions and suggestions.


How to package a React Native component with an open source bottom-menu component

— — — — — — — — — — — — — — — — — — — — — — — — — — — — on July 17, 2018 update — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Pay attention!!

The author recently opened a personal public account, mainly to share programming, reading notes, thinking articles.

  • Programming articles: including selected technical articles published by the author before, and subsequent technical articles (mainly original), and gradually away from iOS content, will shift the focus to improve the direction of programming ability.
  • Reading notes: Share reading notes on programming, thinking, psychology, and career books.
  • Thinking article: to share the author’s thinking on technology and life.

Because the number of messages released by the official account has a limit, so far not all the selected articles in the past have been published on the official account, and will be released gradually.

And because of the various restrictions of the major blog platform, the back will also be released on the public number of some short and concise, to see the big dry goods article oh ~

Scan the qr code of the official account below and click follow, looking forward to growing with you