NativeScript: отключите все элементы управления, пока отображается ActivityIndicator

Допустим, есть страница входа в систему с текстовыми полями имени пользователя и пароля и кнопкой входа. Когда кнопка нажата, запрос устанавливается на сервер и отображается ActivityIndicator. В настоящее время я помещаю StackLayout поверх всех других элементов управления, чтобы не дать пользователю возможность нажимать на них во время обработки запроса. Но в некоторых случаях TextField остается в фокусе, и пользователь может там печатать.

Я уже использую компонент для обертывания всех текстовых полей, чтобы показать ошибки проверки:

@Component({
  selector: "field",
  template: "<grid-layout><ng-content></ng-content>...</grid-layout>"
})
export class FieldComponent {
  @ContentChild(NgModel) private input: NgModel;
  ...
}

Мой вопрос: могу ли я установить для свойства isEnabled значение false в TextField внутри ng-content из FieldComponent с NgModel или каким-либо другим способом? Если невозможно, как лучше всего отключить ввод, когда приложение занято?


person Teddy Bo    schedule 06.12.2016    source источник
comment
В зависимости от требований к дизайну вы должны иметь возможность использовать плагин nativescript-loading-indicator для отображения модального индикатора. Когда отображается модальное окно, вы не сможете взаимодействовать с элементами на экране. github.com/NathanWalker/nativescript-loading-indicator   -  person rrjohnson85    schedule 07.12.2016
comment
спасибо, проверю. Но похоже, что он просто добавляет дополнительный слой, и я не уверен, что он отключает ввод, если элемент уже сфокусирован.   -  person Teddy Bo    schedule 08.12.2016


Ответы (5)


Есть несколько способов сделать это;

  1. Вы можете использовать ngIf или привязку к isEnabled, чтобы отключить его на основе значения привязки данных.

  2. Вы можете создать простую процедуру, которую вы вызываете (мой предпочтительный метод).

require("nativescript-dom"); 
function screenEnabled(isEnabled) {
       runAgainstTagNames('TextEdit', function(e) { e.isEnabled = isEnabled; });
       runAgainstTagNames('Button', function(e) { e.isEnabled = isEnabled; }); 
}

Плагин nativescript-dom имеет обертки runAgainst * или getElementBy * для взаимодействия с собственным уровнем, как если бы вы разговаривали с HTML-доменом.

Полное раскрытие информации, я являюсь автором nativescript-dom, это один из плагинов, который я использую почти в каждом приложении / демонстрации, которое я использую.

person Nathanael    schedule 07.12.2016

Вот мое решение для NativeScript + Angular:

  1. setControlInteractionState () является рекурсивным.
  2. курсор TextField скрыт (с использованием собственного API Android).

XML:

<GridLayout #mainGrid rows="*" columns="*">  
  <!-- Main page content here... -->
  <GridLayout *ngIf="isBusy" rows="*" columns="*">
     <GridLayout rows="*" columns="*" style="background-color: black; opacity: 0.35">
     </GridLayout>
     <ActivityIndicator width="60" height="60" busy="true">
     </ActivityIndicator>
  </GridLayout>
</GridLayout>

or

<GridLayout #mainGrid rows="*" columns="*">  
  <!-- Main page content here... -->      
</GridLayout>
<GridLayout *ngIf="isBusy" rows="*" columns="*">
   <GridLayout rows="*" columns="*" style="background-color: black; opacity: 0.35">
   </GridLayout>
   <ActivityIndicator width="60" height="60" busy="true">
   </ActivityIndicator>
</GridLayout>

TypeScript:

import { Component, ViewChild, ElementRef } from "@angular/core";
import { View } from "ui/core/view";
import { LayoutBase } from "ui/layouts/layout-base";
import { isAndroid, isIOS } from "platform";

@Component({
    templateUrl: "./SignIn.html"
})
export class SignInComponent {

    @ViewChild("mainGrid")
    MainGrid: ElementRef;

    isBusy: boolean = false;

    submit() : void {
        try {
            this.isBusy = true;
            setControlInteractionState(<View>this.MainGrid.nativeElement, false);
            //sign-in here...
        }
        finally {
            this.isBusy = false;
            setControlInteractionState(<View>this.MainGrid.nativeElement, true);
        }
    }

    setControlInteractionState(view: View, isEnabled: boolean) : void {
        view.isUserInteractionEnabled = isEnabled;
        if (isAndroid) {
            if (view.android instanceof android.widget.EditText) {
                let control = <android.widget.EditText>view.android;
                control.setCursorVisible(isEnabled);
            }
        }
        if (view instanceof LayoutBase) {
            let layoutBase = <LayoutBase>view;
            for (let i = 0, length = layoutBase.getChildrenCount(); i < length; i++) {
                let child = layoutBase.getChildAt(i);
                setControlInteractionState(child, isEnabled);
            }
        }
    }

}

NS 2.5.0

person KTCO    schedule 19.02.2017
comment
Это прекрасно работает. Было бы идеально, если бы сетка, содержащая ActivityIndicator, могла точно перекрывать #mainGrid. Любые идеи? - person leoncc; 20.06.2018

Я нашел самое простое решение в Angular, поэтому публикую здесь для справок в будущем. Сначала в app.component.html файле я добавил сетку и ScrollView, как показано ниже:

 <GridLayout>
    <page-router-outlet></page-router-outlet>
    <!-- hack to block UI -->
    <ScrollView isUserInteractionEnabled="false" *ngIf="isLoading">
        <ActivityIndicator busy="true"></ActivityIndicator>     
    </ScrollView>
</GridLayout>

Обратите внимание на page-router-outlet, который находится внутри сетки. По умолчанию он будет помещен в row = "0". Следующим шагом является ScrollView, для которого isUserInteractionEnabled установлено значение false. Теперь в вашем app.component.ts файле добавьте переменную с именем isLoading и переключите ее, используя какие-то события, например, наблюдаемые события RxJs.

person Idrees Khan    schedule 21.10.2019

Расширение ответа @KTCO, чтобы получить размер наложения точно такой же, как у основной сетки:

import { Size, View } from "tns-core-modules/ui/core/view";
import { GridLayout } from "tns-core-modules/ui/layouts/grid-layout/grid-layout";
...
...
dialogSize: Size;
mainGrid: GridLayout;
...
submit() {
  this.mainGrid = <GridLayout>this.MainGrid.nativeElement;
  this.dialogSize = this.mainGrid.getActualSize();
  .....
  .....

<GridLayout *ngIf="isBusy" rows="auto" columns="auto">
  <GridLayout rows="*" columns="*" [width]="dialogSize.width" [height]="dialogSize.height" style="background-color: black; opacity: 0.35">
  </GridLayout>
  <ActivityIndicator width="50" height="50" busy="true">
  </ActivityIndicator>
</GridLayout>
person leoncc    schedule 10.07.2018

Я знаю, что это немного старовато, но иногда мне просто нужно делать что-то по-своему. Если вы хотите сделать это программно:


const excludedView = someViewToBeExcluded;

const enabler = (parentView:View, enable:boolean) => {
  parentView.eachChildView(childView => {
    if (childView != excludedView) {
      enabler(childView, enable);
      childView.isEnabled = enable;
    }          
    return true;
  });
};

enabler(page, false);

Примечание. Это не приведет к отключению / включению начального parentView (т.е. page в этом примере)

person WeezyKrush    schedule 09.05.2019