Saltar al contenido

Cree un control de diapositivas personalizado en la aplicación MDK (utilizando el enfoque de metadatos)

  • I Extensiones carpeta, cree archivos y carpetas adicionales. Escribirás una implementación única para plataformas Android e iOS. La estructura final debe ser la siguiente.

    Extensions
    ├── MySliderModule
    │   └── controls
    │       ├── MySliderPlugin
    │       │   ├── android
    │       │   │   └── MySlider.ts
    │       │   ├── ios
    │       │   │   └── MySlider.ts
    │       │   └── MySlider.ts
    │       └── MySliderExtension.ts
    └── tsconfig.json
    
  • I MySliderPlugin/android/MySlider.ts archivo, copie y pegue el siguiente código.

    import { Observable } from 'tns-core-modules/data/observable';
    import { View } from 'tns-core-modules/ui/core/view';
    import { layout } from 'tns-core-modules/ui/core/view';
    import { device as Device } from 'tns-core-modules/platform';
    /*
      This is a way to keep iOS and Android implementation of your extension separate
      We will encapsulate the MySlider class definition inside a function called GetMySliderClass
      This is so that the class definition won't be executed when you load this javascript
      via require function.
      The class definition will only be executed when you execute GetMySliderClass
    */
    declare var com: any;
    declare var android: any;
    export function GetMySliderClass() {
        /**
         * IMPLEMENT THE ANDROID VERSION OF YOUR PLUGIN HERE
         * In this sample you have 2 controls a label and a seekbar (slider)
         * You extends this control with Observable (View) class so that you can accept listeners
         *  and notify them when UI interaction is triggered
         */
        function getPadding() {
            // Return left & right padding in dp
            // For tablet you want 24dp, for other type you use 16dp
            return Device.deviceType === 'Tablet' ? 24 : 16;
        }
    
        class MySlider extends View {
            private _androidcontext;
            private _label;
            private _labelText = "";
            private _seekbar;
            private _layout;
            private _value = 0;
            private _min = 0; //Used to track min for API 25 or lower
    
            private updateText() {
                this._label.setText(this._labelText + "(" + this._value + ")")
            }
    
            public constructor(context: any) {
                super();
                this._androidcontext = context;
                this.createNativeView();
            }
    
            /**
             * Creates new native controls.
             */
            public createNativeView(): Object {
                //Create an Android label
                this._label = new android.widget.TextView(this._androidcontext);
                const labelBottomPaddingInPx = layout.round(layout.toDevicePixels(8)); // For top & bottom padding, always 16dp
                this._label.setPadding(0, 0, 0, labelBottomPaddingInPx);
                this._label.setLayoutParams(new android.view.ViewGroup.LayoutParams(-1, -2));
    
                //Create an Android seekbar
                this._seekbar = new android.widget.SeekBar(this._androidcontext);
                this._seekbar.setLayoutParams(new android.view.ViewGroup.LayoutParams(-1, -2));
    
                //Create a LinearLayout container to contain the label and seekbar
                this._layout = new android.widget.LinearLayout(this._androidcontext);
                this._layout.setOrientation(android.widget.LinearLayout.VERTICAL);
                this._layout.setLayoutParams(new android.view.ViewGroup.LayoutParams(-1, -1));
    
                const hortPaddingInPx = layout.round(layout.toDevicePixels(getPadding()));
                const vertPaddingInPx = layout.round(layout.toDevicePixels(16)); // For top & bottom padding, always 16dp
                this._layout.setPadding(hortPaddingInPx, vertPaddingInPx, hortPaddingInPx, vertPaddingInPx);
                this._layout.addView(this._label);
                this._layout.addView(this._seekbar);
                this.setNativeView(this._layout);
                return this._layout;
            }
    
            /**
             * Initializes properties/listeners of the native view.
             */
            initNativeView(): void {
                console.log("initNativeView called");
                // Attach the owner to nativeView.
                // When nativeView is tapped you get the owning JS object through this field.
                (<any>this._seekbar).owner = this;
                (<any>this._layout).owner = this;
                super.initNativeView();
    
                //Attach a listener to be notified whenever the native Seekbar is changed so that you can notify the MDK Extension
                this._seekbar.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({
                    onStartTrackingTouch(seekBar: any) {
                        // You do not have any use for this event, so do nothing here
                    },
                    //This handler function will be called when user let go of the handle
                    // This is where you will trigger an event called "OnSliderValueChanged" to the MDK Extension Class
                    onStopTrackingTouch(seekBar: any) {
                        var eventData = {
                            eventName: "OnSliderValueChanged",
                            object: seekBar.owner,
                            value: seekBar.owner._value
                        };
                        seekBar.owner.notify(eventData);
                    },
                    //This handler function will be called whenever the slider's value is changed
                    // i.e. whenever user drag the slider's handle
                    onProgressChanged(seekBar: any, progress: number, fromUser: boolean) {
                        seekBar.owner._value = progress;
                        seekBar.owner.updateText();
                    }
                }));
            }
    
            /**
             * Clean up references to the native view and resets nativeView to its original state.
             * If you have changed nativeView in some other way except through setNative callbacks
             * you have a chance here to revert it back to its original state
             * so that it could be reused later.
             */
            disposeNativeView(): void {
                // Remove reference from native view to this instance.
                (<any>this._seekbar).owner = null;
                (<any>this._layout).owner = null;
    
                // If you want to recycle nativeView and have modified the nativeView
                // without using Property or CssProperty (e.g. outside our property system - 'setNative' callbacks)
                // you have to reset it to its initial state here.
            }
    
            //Must return the native view of the control for MDK FormCell and Section Extension
            public getView(): any {
                return this._layout;
            }
    
            public setText(newText: string): void {
                if (newText != null && newText != undefined) {
                    this._labelText = newText;
                    this._label.setText(newText);
                }
            }
    
            public setValue(newVal: number): void {
                if (newVal != null && newVal != undefined) {
                    this._value = newVal;
                    this.updateText();
                    if (this._seekbar.getProgress() < this._min) {
                        this._seekbar.setProgress(this._min);
                    }
                    else {
                        this._seekbar.setProgress(newVal);
                    }
                }
            }
    
            public setMinValue(newMin: number): void {
                if (newMin != null && newMin != undefined) {
                    if (Device.sdkVersion >= 26) { //setMin is only available in set API Level 26 or newer
                        this._seekbar.setMin(newMin);
                    }
                    else {
                        this._min = newMin;
                        if (this._seekbar.getProgress() < this._min) {
                            this._seekbar.setProgress(this._min);
                        }
                    }
                }
            }
    
            public setMaxValue(newMax: number): void {
                if (newMax != null && newMax != undefined) {
                    this._seekbar.setMax(newMax);
                }
            }
        }
        return MySlider;
    }
    

    En su función de importación, si ve errores relacionados tns-core-modules o mdk-core, puede ignorarlos. Actualmente no hay ninguna referencia a dichas bibliotecas en el editor MDK.

  • Salva el MySliderPlugin/android/MySlider.ts expediente.

  • I MySliderPlugin/iOS/MySlider.ts archivo, copie y pegue el siguiente código.

    import { View } from 'tns-core-modules/ui/core/view';
    
    /*
      This is a way to keep iOS and Android implementation of your extension separate
      You will encapsulate the MySlider class definition inside a function called GetMySliderClass
      This is so that the class definition won't be executed when you load this javascript
      via require function.
      The class definition will only be executed when you execute GetMySliderClass
    */
    export function GetMySliderClass() {
        /**
         * IMPLEMENT THE IOS VERSION OF YOUR PLUGIN HERE
         */
    
        // This is a class that handles the native event callbacks
        class SliderHandler extends NSObject {
    
            //This handler function will be called whenever the slider's value is changed
            // i.e. whenever user drag the slider's handle
            public valueChanged(nativeSlider: UISlider, nativeEvent: _UIEvent) {
                nativeSlider.value = Math.round(nativeSlider.value);
                const owner: MySlider = (<any>nativeSlider).owner;
                if (owner) {
                    owner.setValue(nativeSlider.value);
                }
            }
    
            //This handler function will be called when user let go of the handle
            // This is where you will trigger an event called "OnSliderValueChanged" to the MDK Extension Class
            public afterValueChanged(nativeSlider: UISlider, nativeEvent: _UIEvent) {
                nativeSlider.value = Math.round(nativeSlider.value);
                const owner: MySlider = (<any>nativeSlider).owner;
                if (owner) {
                    owner.setValue(nativeSlider.value);
                    var eventData = {
                        eventName: "OnSliderValueChanged",
                        object: owner,
                        value: nativeSlider.value
                    };
                    owner.notify(eventData);
                }
            }
    
            public static ObjCExposedMethods = {
                "valueChanged": { returns: interop.types.void, params: [interop.types.id, interop.types.id] },
                "afterValueChanged": { returns: interop.types.void, params: [interop.types.id, interop.types.id] }
            };
        }
    
        const handler = SliderHandler.new();
    
        class MySlider extends View {
            private _label;
            private _labelText = "";
            private _slider;
            private _layout;
            private _value = 0;
    
            private updateText() {
                this._label.text = this._labelText + "(" + this._value + ")";
            }
    
            public constructor(context: any) {
                super();
                this.createNativeView();
            }
    
            /**
             * Creates new native controls.
             */
            public createNativeView(): Object {
                //Create the Stack view - this is the main view of this extension
                this._layout = UIStackView.new();
                //Configuring the paddings around the stack view
                this._layout.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth];
                this._layout.layoutMarginsRelativeArrangement = true;
                let inset = new NSDirectionalEdgeInsets();
                inset.top = 8; inset.leading = 16; inset.bottom = 8; inset.trailing = 16;
                this._layout.directionalLayoutMargins = inset;
                // Set the layout stacking to be vertical
                this._layout.axis = UILayoutConstraintAxis.Vertical;
    
                //Create the label view
                this._label = UILabel.new();
                this._label.font = this._label.font.fontWithSize(15); //Set font size
                this._label.textColor = UIColor.colorWithRedGreenBlueAlpha(106 / 255, 109 / 255, 112 / 255, 1.0); //Set text color
                this._layout.setCustomSpacingAfterView(4, this._label); //Set the bottom margin of label
    
                //Create the slider control
                this._slider = UISlider.new();
    
                //Assign a handler for whenever value changed i.e. when user is dragging the slider handle
                this._slider.addTargetActionForControlEvents(handler, "valueChanged", UIControlEvents.ValueChanged);
                //Assign a handler for when user let go of the handle
                this._slider.addTargetActionForControlEvents(handler, "afterValueChanged", UIControlEvents.TouchUpInside | UIControlEvents.TouchUpOutside);
    
                //Add the label and slider to the stack view
                this._layout.addArrangedSubview(this._label);
                this._layout.addArrangedSubview(this._slider);
    
                //store the native view
                this.setNativeView(this._layout);
    
                //return the stack view
                return this._layout;
            }
            /**
             * Initializes properties/listeners of the native view.
             */
            initNativeView(): void {
                // Attach the owner to nativeViews.
                // When nativeViews are tapped you get the owning JS object through this field.
                (<any>this._slider).owner = this;
                (<any>this._layout).owner = this;
                super.initNativeView();
            }
    
            /**
             * Clean up references to the native view and resets nativeView to its original state.
             * If you have changed nativeView in some other way except through setNative callbacks
             * you have a chance here to revert it back to its original state
             * so that it could be reused later.
             */
            disposeNativeView(): void {
                // Remove reference from native view to this instance.
                (<any>this._slider).owner = null;
                (<any>this._layout).owner = null;
    
                // If you want to recycle nativeView and have modified the nativeView
                // without using Property or CssProperty (e.g. outside our property system - 'setNative' callbacks)
                // you have to reset it to its initial state here.
            }
    
            //Must return the native view of the control for MDK FormCell and Section Extension
            public getView(): any {
                return this._layout;
            }
    
            public setText(newText: string): void {
                if (newText != null && newText != undefined) {
                    this._labelText = newText;
                    this._label.text = newText;
                }
            }
    
            public setValue(newVal: number): void {
                if (newVal != null && newVal != undefined) {
                    this._value = newVal;
                    this.updateText();
                    this._slider.value = newVal;
                }
            }
    
            public setMinValue(newMin: number): void {
                if (newMin != null && newMin != undefined) {
                    this._slider.minimumValue = newMin;
                }
            }
    
            public setMaxValue(newMax: number): void {
                if (newMax != null && newMax != undefined) {
                    this._slider.maximumValue = newMax;
                }
            }
        }
    
        return MySlider;
    }
    
  • Salva el MySliderPlugin/iOS/MySlider.ts expediente.

  • I MySliderPlugin/MySlider.ts archivo, copie y pegue el siguiente código.

    import * as application from 'tns-core-modules/application';
    
    export let MySlider;
    let MySliderModule;
    /*
    This is a sample of how to implement iOS and Android codes separately in a metadata extension.
    Because all ts files in metadata Extensions folder will be bundled together using webpack,
    if you execute any iOS codes in Android vice versa, it will likely cause issue such as crash.
    
    By splitting the implementation into different files and encapsulate them in a function, it allows
    us to load only the required module for the platform at runtime.
    */
    if (!MySlider) {
        //Here you will check what platform the app is in at runtime.
        if (application.ios) {
            //if app is in iOS platform, load the MySlider module from ios folder
            MySliderModule = require('./ios/MySlider');
        } else {
            //otherise, assume app is in Android platform, load the MySlider module from android folder
            MySliderModule = require('./android/MySlider');
        }
        // calling GetMySliderClass() will return MySlider class for the correct platform.
        //  See the MySlider.ts in ios/andrid folder for details
        MySlider = MySliderModule.GetMySliderClass();
    }
    
  • Salva el MySliderPlugin/MySlider.ts expediente.

  • I MySliderExtension.ts archivo, reemplace este código.

    import { BaseControl } from 'mdk-core/controls/BaseControl';
    import { MySlider } from './MySliderPlugin/MySlider'
    
    export class MySliderClass extends BaseControl {
        private _slider: MySlider;
        private _minVal: number = 0;
        private _maxVal: number = 10000;
    
        public initialize(props) {
            super.initialize(props);
    
            //Create the Slider plugin control
            this.createSlider();
            //Assign the slider's native view as the main view of this extension
            this.setView(this._slider.getView());
        }
    
        private createSlider() {
            //Create MySlider and initialize its native view
            this._slider = new MySlider(this.androidContext());
            this._slider.initNativeView();
    
            this._slider.setMinValue(this._minVal);
            this._slider.setMaxValue(this._maxVal);
    
            //Set the slider's properties if "ExtensionProperties" is defined
            let extProps = this.definition().data.ExtensionProperties;
            if (extProps) {
                //In here you will use ValueResolver to resolve binding/rules for the properties
                // This will allow the app to use binding/rules to set the properties' value
    
                // Resolve title's value
                this.valueResolver().resolveValue(extProps.Title, this.context, true).then(function (title) {
                    this._slider.setText(title);
                }.bind(this));
    
                // Resolve min value
                this.valueResolver().resolveValue(extProps.MinValue, this.context, true).then(function (minVal) {
                    if (minVal !== null && minVal !== undefined) {
                        this._minVal = minVal;
                        this._slider.setMinValue(this._minVal);
                    }
                }.bind(this));
    
                // Resolve max value
                this.valueResolver().resolveValue(extProps.MaxValue, this.context, true).then(function (maxVal) {
                    if (maxVal !== null && maxVal !== undefined) {
                        this._maxVal = maxVal;
                        this._slider.setMaxValue(this._maxVal);
                    }
                }.bind(this));
    
                // Resolve value
                this.valueResolver().resolveValue(extProps.Value, this.context, true).then(function (value) {
                    this.setValue(value, false, false);
                }.bind(this));
            }
    
            //Set up listener for MySlider's OnSliderValueChanged event that will be triggered when user let of the slider's handle
            // It's eventData object contain a property 'value' that will contain the value of the slider
            this._slider.on("OnSliderValueChanged", function (eventData) {
                //We will call the setValue
                this.setValue(eventData.value, true, false);
            }.bind(this));
        }
    
        // Override
        protected createObservable() {
            let extProps = this.definition().data.ExtensionProperties;
            //Pass ExtensionProperties.OnValueChange to BaseControl's OnValueChange
            if (extProps && extProps.OnValueChange) {
                this.definition().data.OnValueChange = extProps.OnValueChange;
            }
            return super.createObservable();
        }
    
        public setValue(value: any, notify: boolean, isTextValue?: boolean): Promise<any> {
            //Check the value
            if (value != null && value != undefined && !isNaN(value)) {
                if (typeof value == "string" && value.trim() == "") {
                    return Promise.reject("Error: Value is not a number");
                }
                let val = Number.parseInt(value);
                //Don't let value go lower than permitted minimum or higher than permitted maximum
                val = val < this._minVal ? this._minVal : val;
                val = val > this._maxVal ? this._maxVal : val;
    
                if (this._slider) {
                    //Set the slider's value
                    this._slider.setValue(val);
                }
                //Store the value. The observable will trigger "OnValueChange" to the MDK app
                // MDK app can register to this event in the metadata with property "OnValueChange"
                return this.observable().setValue(val, notify, isTextValue);
            } else if (isNaN(value)) {
                return Promise.reject("Error: Value is not a number");
            }
            return Promise.resolve();
        }
    
        public viewIsNative() {
            return true;
        }
    }    
    
  • Salva el MySliderExtension.ts expediente.

  • I tsconfig.json archivo, copie y pegue el siguiente código.

    {
        "compilerOptions": {
            "module": "commonjs",
            "target": "es5",
            "experimentalDecorators": true,
            "emitDecoratorMetadata": true,
            "removeComments": true,
            "inlineSourceMap": false,
            "sourceMap": false,
            "noEmitOnError": false,
            "noEmitHelpers": true,
            "declaration": true,
            "lib": [
                "es6",
                "dom"
            ],
            "baseUrl": ".",
            "paths": {
                "tns-core-modules/*": [
                    "./node_modules/tns-core-modules/*"
                ],
                "mdk-sap": [
                    "./node_modules/mdk-sap"
                ],
                "toolbar-plugin": [
                    "./node_modules/toolbar-plugin"
                ],
                "zip-plugin": [
                    "./node_modules/zip-plugin"
                ]
            }
        },
        "exclude": [
            "node_modules",
            "platforms",
            "modules",
            "plugins",
            "build.definitions"
        ]
    }
    

    Este archivo se utiliza para utilizar tipos definidos en MDK SDK o NativeScript. Puedes encontrar más detalles al respecto en documentación útil.

  • Salva el tsconfig.json expediente.