CustomView

You can create custom views with NUI, following some general guidelines:

  • Derive your view from the Tizen.NUI.BaseComponents.CustomView class, which provides common functionality required by all views.
  • Use properties as much as possible, as views must be data-driven.

    Custom views are used through JavaScript and JSON files.

  • The view can be updated when the properties (such as styles) change.

    Ensure that the view handles property changes gracefully, on both the first and subsequent changes.

  • Use visuals, instead of creating multiple child views, to make the rendering pipeline more efficient.
  • Use events to make the application react to view state changes.
  • Use of gestures instead of analyzing raw touch events.

The Tizen.NUI.BaseComponents.CustomView class is derived from the Tizen.NUI.ViewWrapper class, which in turn is derived from the Tizen.NUI.BaseComponents.View class:

public class CustomView : ViewWrapper

public class ViewWrapper : View

NUI contains predefined custom controls already derived from CustomView objects, including:

Creating a Custom View

To create a custom view:

  1. Create a view with the new operator:

    contactView = new ContactView();
    
  2. Define a static constructor for the view.

    Each custom view must have its static constructor called before any JSON file is loaded. Static constructors for a class are only run once (they are run per view, not per instance). The view must register its type inside the static constructor.

    The Type Registry is used to register your custom view. Type registration allows the creation of the view through a JSON file, as well as registering properties, signals, actions, transitions, and animation effects. Use the Register() method of the Tizen.NUI.CustomViewRegistry class to register the views and any scriptable properties they have:

    static ContactView()
    {
        CustomViewRegistry.Instance.Register(CreateInstance, typeof(ContactView));
    }
    
  3. Define a CreateInstance() method for the custom view.

    Each custom view must provide a CreateInstance() method, which is passed to the Register() method as a parameter. The CreateInstance() method is called if the view is in a JSON file:

    static CustomView CreateInstance()
    {
        return new ContactView();
    }
    
  4. Override the OnInitialize() method, if necessary. It is called after the view has been initialized.
    public override void OnInitialize()
    {
        /// Create a container for the star images
        _container = new FlexContainer();
    
        _container.FlexDirection = FlexContainer.FlexDirectionType.Row;
        _container.WidthResizePolicy = ResizePolicyType.FillToParent;
        _container.HeightResizePolicy = ResizePolicyType.FillToParent;
    
        this.Add(_container);
    }
    

    The following table lists other important custom view methods that you can use to manage the view.

    Table: Custom view methods

    Name Description
    SetBackground() Set the background with a property map.
    EnableGestureDetection() Allow deriving classes to enable any of the gesture detectors that are available.
    RegisterVisual() Register a visual by a property index, linking a view to a visual, when required.
    CreateTransition() Create a transition effect on the view for animations.
    RelayoutRequest() Request a re-layout, which means performing a size negotiation on the view, its parent, and children (and potentially whole scene).
    OnStageConnection() If a notification is required when a custom view is connected to a stage default window, override the OnStageConnection() method. You can use the OnStageDisconnection() method similarly to react to a view getting disconnected from the window.

You can manage the general behavior of your custom view by defining a value for the CustomViewBehaviour enumeration of the Tizen.NUI.BaseComponents.CustomView class during object construction. You can determine how the custom view reacts to size negotiation, style changes, event callbacks, and keyboard navigation:

public VisualView() : base(typeof(VisualView).Name, CustomViewBehaviour.ViewBehaviourDefault)
{
}

public ContactView() : base(typeof(ContactView).Name, CustomViewBehaviour.RequiresKeyboardNavigationSupport)
{
}

Rendering Content

To render content, use or reuse visuals. You can also render content by creating and adding more views to the control itself as its children. However, this solution is not fully optimized and means extra views are added, requiring additional processing.

The following example shows how you can create and register an image visual:

  • Define a scriptable property (in this case, ImageURL) which creates the visual when being set. A scriptable property automatically generates indices.
  • Create the visual with a property map. For more information on the property maps that can be used for each visual type, see Visuals.
  • The RegisterVisual() method of the Tizen.NUI.BaseComponents.CustomView class registers a visual by a property index, linking a view to a visual when required.
  • The GetPropertyIndex() method of the Tizen.NUI.Animatable class gets the generated index corresponding to the property name.

    A range of property indices are provided for ImageVisualPropertyIndex, 0 by default.

/// ContactView.cs

private VisualBase _imageVisual;

[ScriptableProperty()]
public string ImageURL
{
    get
    {
        return _imageURL;
    }
    set
    {
        _imageURL = value;

        /// Create and register an image visual
        PropertyMap imageVisual = new PropertyMap();
        imageVisual.Add(Visual.Property.Type, new PropertyValue((int)Visual.Type.Image))
                   .Add(ImageVisualProperty.URL, new PropertyValue(_imageURL))
                   .Add(ImageVisualProperty.AlphaMaskURL, new PropertyValue(_maskURL));
       _imageVisual = VisualFactory.Get().CreateVisual(imageVisual);

        RegisterVisual(GetPropertyIndex("ImageURL"), _imageVisual);

        /// Set the depth index for the image visual
       _imageVisual.DepthIndex = ImageVisualPropertyIndex;
    }
}

Managing Properties

Properties can be animatable or non-animatable. Examples of animatable Tizen.NUI.BaseComponents.View class properties are Position, Orientation, and Scale. For more information on the NUI animation framework, see Animation.

Properties can be accessed through a unique index. The index can be set manually in code (hard-coded), or calculated automatically. The ContactView.cs file example (in Rendering Content) shows both indexing methods: fixed for depth index, and automatic for registering visuals. The NUI code base is currently being modified (as of July 2017) to utilize property registration based solely on automatic generation of indices.

Property indices are generated automatically in the Tizen.NUI.ScriptableProperty class. With it, you can register a property with the Type Registry. To obtain a unique index for each property, use the GetPropertyIndex() method of the Tizen.NUI.Animatable class, with the name of the property as a parameter.

Add ScriptableProperty to any view-related property that you want to script from JSON:

internal class ScriptableProperty : System.Attribute

Styling Custom Views

The NUI property system allows custom views to be easily styled, changing the look and feel of the view without any code changes. The styling is implemented with JSON stylesheets.

The following table shows an example of a customized style.

Table: Normal and customized style

Normal style Customized style

Popup window, normal style

Popup window, custom style

The format of the JSON stylesheets is under development:

  • The following example (including a visual) shows the current (as of July 2017) JSON stylesheet format:

    "styles":
    {
       "TextField":
       {
          "pointSize": 18,
          "primaryCursorColor": [0.0,0.72,0.9,1.0],
          "secondaryCursorColor": [0.0,0.72,0.9,1.0],
          "cursorWidth": 3,
          "selectionHighlightColor": [0.75,0.96,1.0,1.0],
          "grabHandleImage": "{DALI_STYLE_IMAGE_DIR}cursor_handler_drop_center.png",
          "selectionHandleImageLeft": {"filename": "{DALI_STYLE_IMAGE_DIR}selection_handle_drop_left.png"},
          "selectionHandleImageRight": {"filename": "{DALI_STYLE_IMAGE_DIR}selection_handle_drop_right.png"},
          "enableSelection": true
       },
    
       "TextFieldFontSize0":
       {
          "pointSize": 10
       },
    
       "TextSelectionPopup":
       {
          "popupMaxSize": [656,72],
          "optionDividerSize": [2,0],
          "popupDividerColor": [0.23,0.72,0.8,0.11],
          "popupIconColor": [1.0,1.0,1.0,1.0],
          "popupPressedColor": [0.24,0.72,0.8,0.11],
          "background":
          {
             "visualType": "IMAGE",
             "url": "{DALI_IMAGE_DIR}selection-popup-background.9.png"
          },
          "backgroundBorder":
          {
             "visualType": "IMAGE",
             "url": "{DALI_IMAGE_DIR}selection-popup-border.9.png",
             "mixColor": [0.24,0.72,0.8,1.0]
          },
          "popupFadeInDuration": 0.25,
          "popupFadeOutDuration": 0.25
       }
    }
    
  • The following example (including a visual) shows the new stylesheet format being developed:
    "states":
    {
       "NORMAL":
       {
          "states":
          {
             "UNSELECTED":
             {
                "visuals":
                {
                   "backgroundVisual":
                   {
                      "visualType": "IMAGE",
                      "url": "backgroundUnSelected.png"
                   }
                }
             },
             "SELECTED":
             {
                "visuals":
                {
                   "backgroundVisual":
                   {
                      "visualType": "IMAGE",
                      "url": "backgroundSelected.png"
                   }
                }
             }
          }
       }
    }
    

For more information on building up visuals for various button states using JSON stylesheets and transitioning between the various button states, see Styling Controls with JSON.

Creating Transitions

Controls change states based on user interaction. All controls can move between the NORMAL, FOCUSED, and DISABLED states. Whilst in those states, a button has the SELECTED and UNSELECTED substates. As a control moves between states and substates, transition animations can be used to show visually how the control state changes.

You can create a specific entry and exit animation for each state and substate, or a more common transition animation that is run when a control moves between specific states. You can also use a predefined effect during the transition. Currently, only a CROSSFADE effect is available, animating the opacity of visuals fading in and out.

You can implement transition effects in 2 ways:

  • Using a JSON stylesheet

    The following example uses the CROSSFADE effect:

    "transitions":
    [
       {
          "from": "UNSELECTED",
          "to": "SELECTED",
          "visualName": "*",
          "effect": "CROSSFADE",
          "animator":
          {
             "alphaFunction": "EASE_OUT",
             "duration": 0.2,
             "delay": 0
          }
       }
    ]
    
  • Using the CreateTransition() method of the Tizen.NUI.BaseComponents.CustomView class

    You can animate scriptable properties by using the CreateTransition() method from Tizen.NUI.BaseComponents.CustomView-derived classes. The method creates a transition effect on the view. The transitionData parameter describes the effect to create, and the return value is a handle to an animation defined with the given effect, or an empty handle if no properties match.

    protected Animation CreateTransition(TransitionData transitionData);
    

    The following example is taken from the AnimateVisual() method in the Tizen.NUI.BaseComponents.VisualView class, which is a CustomView-derived class:

    _alphaFunction = "EASE_IN_OUT_SINE";
    
    PropertyMap _animator = new PropertyMap();
    if (_alphaFunction != null)
    {
        _animator.Add("alphaFunction", new PropertyValue(_alphaFunction));
    }
    
    PropertyMap _timePeriod = new PropertyMap();
    _timePeriod.Add("duration", new PropertyValue((endTime - startTime) / 1000.0f));
    _timePeriod.Add("delay", new PropertyValue(startTime / 1000.0f));
    _animator.Add("timePeriod", new PropertyValue(_timePeriod));
    
    string _str1 = property.Substring(0, 1);
    string _str2 = property.Substring(1);
    string _str = _str1.ToLower() + _str2;
    if (_str == "position")
    {
        _str = "offset";
    }
    
    PropertyValue destVal = PropertyValue.CreateFromObject(destinationValue);
    
    PropertyMap _transition = new PropertyMap();
    _transition.Add("target", new PropertyValue(target.Name));
    _transition.Add("property", new PropertyValue(_str));
    if (initialValue != null)
    {
        PropertyValue initVal = PropertyValue.CreateFromObject(initialValue);
        _transition.Add("initialValue", new PropertyValue(initVal));
    }
    
    _transition.Add("targetValue", destVal);
    _transition.Add("animator", new PropertyValue(_animator));
    
    TransitionData _transitionData = new TransitionData(_transition);
    
    return this.CreateTransition(_transitionData);
    

Handling Events and Gestures

You can monitor the following Tizen.NUI.BaseComponents.View class events for your custom view:

  • TouchEvent is triggered when any touch occurs within the bounds of the custom view.
  • HoverEvent is triggered when a pointer moves within the bounds of the custom view (for example, mouse pointer or hover pointer).
  • WheelEvent is triggered when the mouse wheel (or similar) is moved while hovering over the custom view (through a mouse pointer or hover pointer).

NUI has a gesture system which analyses a stream of touch events and attempts to determine the intention of the user. Detectors are provided for the following gestures:

  • Pan: When the user starts panning (or dragging) 1 or more fingers.

    The panning must start from within the bounds of the view.

  • Pinch: Detects when 2 touch points move towards or away from each other.

    The center point of the pinch must be within the bounds of the view.

  • Tap: When the user taps within the bounds of the view.
  • Long press: When the user presses and holds on a certain point within the bounds of the view.

To use gesture detectors:

  1. Specify a gesture detector in the OnInitialize() method:

    public override void OnInitialize()
    {
        /// Enable the tap gesture
        EnableGestureDetection(Gesture.GestureType.Tap);
    }
    

    The EnableGestureDetection() method of the Tizen.NUI.BaseComponents.CustomView class allows deriving classes to enable any available gesture detectors. The above example only enables the default gesture detection for each type. If customization is required for the gesture detection, the gesture detector can be retrieved and set up accordingly in the same method:

    PanGestureDetector panGestureDetector = GetPanGestureDetector();
    panGestureDetector.AddDirection(PanGestureDetector.DIRECTION_VERTICAL);
    
  2. Override the appropriate method for handling the gesture:

    • OnPan(PanGesture& pan) for handling the pan gesture
    • OnPinch(PinchGesture& pinch) for handling the pinch gesture
    • OnTap(TapGesture& tap) for handling the tap gesture
    • OnLongPress(LongPressGesture& longPress) for handling the long-press gesture
    public override void OnTap(TapGesture tap)
    {
        /// Change the color visual to a random color
        Random random = new Random();
        float nextRed   = (random.Next(0, 256) / 255.0f);
        float nextGreen = (random.Next(0, 256) / 255.0f);
        float nextBlue  = (random.Next(0, 256) / 255.0f);
        Animation anim = AnimateBackgroundColor(new Color(nextRed, nextGreen, nextBlue, 1.0f), 0, 2000);
        anim.Play();
    }
    

Managing Size Negotiation

Size negotiation controls the view sizes in a container, based on dependency rules between the views. The Tizen.NUI.ResizePolicyType enumeration specifies a range of options for controlling the way views resize. These options enable automatic resizing.

Table: Resize policy types

ResizePolicyType enumerator Description
DimensionDependency Use this option to make one dimension depend on another. This option covers width-for-height and height-for-width rules.
FillToParent Use this option to maintain a size similar to the parent's size. Aspect ratio is not maintained.
FitToChildren Use this option to scale the size of the view around the size of its children. For example, the height of a pop-up can be resized to fit its content.
Fixed Use this option to maintain a specific size as set by the Size2D property of the Tizen.NUI.BaseComponents.View class. This is the default for all views.
SizeFixedOffsetFromParent Use this option to maintain a size is similar to the parent's size with a fixed offset. Use the SetSizeModeFactor() method of the Tizen.NUI.BaseComponents.View class to specify the offset.
SizeRelativeToParent Use this option to maintain a size is similar to the parent's size with a relative scale. Use the SetSizeModeFactor() method of the Tizen.NUI.BaseComponents.View class to specify the ratio.
UseAssignedSize Use this option to make the view use a size assigned to it. THis option is not an actual resize policy, but more of an implementation detail.
UseNaturalSize Use this option for objects, such as images or text, to get their natural size. This can mean the dimensions of an image or the size of text with no wrapping. You can also use this option with table views when the size of the table depends on its children.

To set a resize policy for a custom view:

contactView = new ContactView();
contactView.WidthResizePolicy = ResizePolicyType.FillToParent;
contactView.HeightResizePolicy = ResizePolicyType.FillToParent;

The view is positioned and resized (relaid out) automatically when a view property or stage hierarchy changes. Although you do not usually need to request for relayouting manually, you can use the RelayoutRequest() method of the Tizen.NUI.BaseComponents.CustomView class for deriving views when the derived view wants to be relaid out.

The following overridable methods of the Tizen.NUI.BaseComponents.CustomView class provide customization points for the size negotiation algorithm:

  • The GetNaturalSize() method returns the natural size of the view.
  • The GetHeightForWidth() method returns the height for a given width. It is invoked by the size negotiation algorithm if the width is fixed.
  • The GetWidthForHeight() method returns the width for a given height. It is invoked by the size negotiation algorithm if the height is fixed.
  • The OnRelayout() method is called during the relayout process at the end of the frame, immediately after size negotiation is complete and the new size has been set on the view. The method can be overridden to position and resize the view.
  • The OnSetResizePolicy() method is called when a resize policy is set on a view, and it allows deriving views to respond to changes in the resize policy. The method can be overridden to receive notice that the resize policy has changed on the view and action can be taken.

Size negotiation is enabled on views by default. To disable size negotiation, pass the DisableSizeNegotiation behavior flag into the view constructor.