Create a custom component

Now that we've set up our project, let's create a custom component to represent the pieces of text in our app.

An example text note from the sample app.

Create the Note.qml file

Our poem maker app creates a random, three-part poem each time the button is clicked. Each part of the poem is distinct, but they all share some common properties, such as size, background, and font. In addition, each part uses the same animations; they all appear horizontally from one side of the screen and disappear off a different side of the screen.

To encapsulate these properties, we create a custom component in QML called Note that represents each of the three parts of the poem. This custom component includes layout information for each part (which we call a note from now on), visual properties such as the background and font, and animations. We then use the Note component when we create the rest of the UI for the app.

Start by creating a .qml file called Note.qml in the assets folder of your project. Right-click the assets folder and click New > Other, then expand BlackBerry and select QML File. In the Template drop-down list, click Container and click Finish.

In this file, we add the appropriate import statement so that we can use Cascades controls, and we create a top-level  Container to hold all of the components of our note. This container uses a dock layout to make it easy to center the text on the note. We set the size of the note using using design units so that our UI adapts to different screen sizes and densities. To learn more, see Design units.

import bb.cascades 1.4

// This is the note component template that is used
// to set up the three notes in the poem maker app

Container {
    id: noteContainer
    maxWidth:  ui.du(31.7)

   // All notes use a DockLayout for centering the text
    layout: DockLayout {
    }

Expose properties

There are two properties of a note that we want to be able to specify when we create the rest of our UI. We need to specify the poem text that should appear on each note. We also need to specify the starting point for the animation that occurs when each new note slides onto the screen (we'll talk more about the animations later). We create property aliases that are associated with the properties that we want to expose. For more information about property aliases, see Custom QML components.

In the aliases, poem_part is the id property of a TextArea control that represents the text on the note, and show is the id of the animation that displays a new note on the screen:

    // Create property aliases for the poem text and
    // the animation values of the show animation
    // These properties can be set on the note component
    // from another QML document (main.qml)

    property alias poem: poem_part.text
    property alias showAnimStartX: show.fromX

Our Note component also includes a custom signal called newNote(). This signal is emitted when the animation that hides the note is finished, letting us know that it's time to set up a new note and show it on the screen. We'll handle this signal in our main.qml file later in the tutorial.

signal newNote; 

Add animations

Let's create the animations for our note now. Each note has two possible animations, both of which are simple translation animations. The first one hides the old note by translating it in the y direction off the screen. This translation has a short duration of 500 ms, and it uses an easing curve that starts animating quickly and then slows down as the animation proceeds. When this animation finishes, we want the newNote() signal to be emitted.

    animations: [
        // The hide animation moves the note off the screen in 
        // the y direction
        // Note: There is no "from" value specified because we
        // want to avoid the notes jumping back to the starting
        // position if multiple triggers occur before all notes
        // are back in their original positions.
        TranslateTransition {
            id: hide
            toY: -400
            duration: 500
            easingCurve: StockCurve.CubicOut
            onEnded: {
                // Emit the new note signal
                newNote();
            }
        },

The second animation shows the new note by translating it onto the screen from the x direction. Earlier, we associated this animation's fromX property with the showAnimStartX property alias, which lets us specify the correct starting position of each note's show animation. This animation also has a short duration of 500 ms and uses an easing curve that is similar to the one that the hide animation uses:

        // The show animation is where the note slides in 
        // from the x direction and comes to rest at its
        // original position (the starting point is different  
        // for each note so it is set to a specific
        // location using the property alias showAnimStartX).
        TranslateTransition {
            id: show
            fromX: 0
            toX: 0
            duration: 500
            easingCurve: StockCurve.QuarticOut
        }
    ]    

We need one more component to handle animations for our note: an ImplicitAnimationController. You can use this component to enable or disable implicit animations, which are enabled by default. We need to disable implicit animations at certain times for our app to work correctly, so we create an ImplicitAnimationController now and use it later in the tutorial. We associate the ImplicitAnimationController with our top-level container by using the attachedObjects list. To learn more about this list, see QML and C++ integration.

    // This is the implicit animation controller for the note. 
    // We need this to set the note to a new position before 
    // showing it without triggering implicit animations
    // (see showNote function below).

    attachedObjects: [
        ImplicitAnimationController {
            id: offScreenController
            propertyName: "translationY"
            enabled: false
        }
    ]

Add visual elements

Next, we add the note background to our layout by using an ImageView control:

    // The note background image
    ImageView {
        id: noteBackground
        imageSource: "asset:///images/line_background.png"
    }

To complete the visual elements of our custom component, we add a container to hold the poem text of the note. This container uses padding on its left and right sides to create some space between the text and the edge of the note. The container is also centered inside the note:

    // This container represents the poem part, and the 
    // text is set using the poem property on the note component 

    Container {
        horizontalAlignment: HorizontalAlignment.Center
        verticalAlignment: VerticalAlignment.Center
        rightPadding: ui.du(1) 
        leftPadding: ui.du(1) 

We use a Label for the poem text and specify properties such as a textStyle. We'll define this textStyle later in the tutorial. We set the multiline property to true so that our poem text fits in the label. We also make sure to give the Label an id. We use this id to set the text of the note in our main.qml file.

        Label {
            id: poem_part
            text: "text"
            multiline: true
            textStyle.base: noteStyle.style
        }
    } // end of Container for poem text

Create custom JavaScript functions

Our finished app doesn't actually create new Note components to make a new poem. Instead, we reuse the same Note components by performing the following actions each time the button is pressed:

  • Hide each note by calling a custom JavaScript function called hideNote(). This function starts the hide animation for the notes.
  • Emit the newNote() signal to indicate that we're ready to set up new notes.
  • Handle the newNote() signal by generating new text for each note and calling a custom JavaScript function called showNote(). This function moves the notes to their proper positions so they can be animated and starts the show animation for the notes.

Let's create the hideNote() and showNote() functions now. The hideNote() function is straightforward and starts the hide animation if it's not already running:

    // The function that triggers the hide animation on a note
    function hideNote () {
        hide.play ();
    }

The showNote() function is a bit more complicated. This function is responsible for moving the notes to their correct positions before starting the show animation. However, we don't want this movement itself to be animated; we want the notes to appear immediately in their starting positions. Here's where we use the ImplicitAnimationController that we created earlier. We turn off implicit animations (by setting the translationY property) before we move the notes:

    // The function that triggers the show animation on a note
    function showNote () {

        // Implicit animations are turned off for the
        // translationY property, so the movement of a note
        // to align it will happen immediately 
        translationY = 0;
        
        show.play ();
    }
} // end of top-level Container

It might seem strange that we use custom JavaScript functions to start the hide and show animations, instead of just starting the animations directly. Because we start the animations from our main.qml file, instead of from this Note.qml file, we can't use the id properties of the animations to refer to them. It's not possible to start animations using their id properties if the animations are located in a separate QML document.

We've finished creating the custom Note component for our app, and now we're going to create the main UI and animate the notes.

import bb.cascades 1.4

// This is the note component template that is used
// to set up the three notes in the poem maker app

Container {
    id: noteContainer
    maxWidth:  ui.du(31.7)
    
    // All notes use a DockLayout for centering the text
    layout: DockLayout {
    }
    
    // Create property aliases for the poem text and
    // the animation values of the show animation
    // These properties can be set on the Note component
    // from another QML document (main.qml)
    
    property alias poem: poem_part.text
    property alias showAnimStartX: show.fromX

    signal newNote; 
    
    animations: [
        // The hide animation moves the note off the screen in 
        // the y direction
        // Note: There is no "from" value specified because we
        // want to avoid the notes jumping back to the starting
        // position if multiple triggers occur before all notes
        // are back in their original positions
        TranslateTransition {
            id: hide
            toY: -400
            duration: 500
            easingCurve: StockCurve.CubicOut
            onEnded: {
                // Emit the new note signal
                newNote();
            }
        },
        
        // The show animation is where the note slides in 
        // from the x direction and comes to rest at its
        // original position (the starting point is different  
        // for each note so it is set to a specific
        // location using the property alias showAnimStartX).
        TranslateTransition {
            id: show
            fromX: 0
            toX: 0
            duration: 500
            easingCurve: StockCurve.QuarticOut
        }
    ] 
    
    // This is the implicit animation controller for the note. 
    // We need this to set the note to a new position before 
    // showing it without triggering implicit animations
    // (see showNote function below).
    
    attachedObjects: [
        ImplicitAnimationController {
            id: offScreenController
            propertyName: "translationY"
            enabled: false
        }
    ]        

    // The note background image
    ImageView {
        id: noteBackground
        imageSource: "asset:///images/line_background.png"
    }
        
    // This container represents the poem part, and the
    // text is set using the poem property on the Note component
    Container {
        horizontalAlignment: HorizontalAlignment.Center
        verticalAlignment: VerticalAlignment.Center
        rightPadding: ui.du(1) 
        leftPadding: ui.du(1) 
        
        Label {
            id: poem_part
            text: "text"
            multiline: true
            textStyle.base: noteStyle.style
        }
    } // end of Container for poem text
    
    // The function that triggers the hide animation on a note
    function hideNote () {
        hide.play ();
    }
    
    // The function that triggers the show animation on a note
    function showNote () {
        
        // Implicit animations are turned off for the
        // translationY property, so the movement of a note
        // to align it will happen immediately 
        translationY = 0;
        
        show.play ();
    }
} // end of top-level Container

Last modified: 2015-03-31



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus