Harmony パーソナル センター (ページ インタラクション、ジャンプ、ナビゲーション、コンテナ コンポーネント)

序文

  今日は 1024 年です。すべてのプログラマーに、もっとお金があり、やることは減り、家の近くにいて、ハゲにならずにバグにも強いことを願っています。前回の記事では、DevEco StudioArkTS の主要な開発言語について学習し、簡単な例を作成しました。この記事では、Hongmeng アプリケーション開発についての理解を深めるために、別の例を学習します。

ここに画像の説明を挿入します

文章

  この記事の例も HarmonyOS Academy から引用したものですが、ソースコードの内容をもとに、開発プロセスを逆にたどり、開発プロセスでどのようなナレッジポイントを学べるかを見ていきます。

1. プロジェクトを作成する

  まず、DevEco Studio で下図のように MyCenter という関数を作成します。

ここに画像の説明を挿入します
クリックFinishしてプロジェクトを作成します。プロジェクトの作成後、プレビューHello Worldで確認できます。これを開発したパーソナル センター アプリについて説明しましょう含まれるコンテンツは最初にログイン ページです。ログイン後、下部のナビゲーションからページ コンテンツ (ホームページと個人コンテンツ) を切り替えることができます。最初にログイン ページを作成しましょう。

2. ログイン

プロジェクトを作成すると、ページ (表示されている Index.ets) が付属するため、ログイン ページを作成する必要があります。右クリックpages→< /span>NewPage

ここに画像の説明を挿入します

ポップアップ ウィンドウが表示されるので、ページの名前を「Login」と入力します。

ここに画像の説明を挿入します
[完了] をクリックしてページの作成を完了します。作成が完了すると、resource/main/base/profile/main_pages.json で追加したログイン ページの構成が表示されます。

ここに画像の説明を挿入します

①起動ページを変更する

現在、IndexとLoginの2つのページがありますが、アプリを開くとすぐにログインページに入るため、2つのコンテンツの位置を変更するコードは次のとおりです。

{
    
    
  "src": [
    "pages/Login",
    "pages/Index"
  ]
}

このように、アプリを実行すると、最初のページはログインです。非常に簡単ですよね? 次にしなければならないことは、このログイン ページの UI と機能を記述することです。まず UI の効果を見てみましょうこのページの。

ここに画像の説明を挿入します

  この UI に基づいて、ページの全体的な効果は垂直レイアウト、中央の 2 つのテキストは青いテキスト、下部の 3 つのログイン方法は水平であると結論付けることができます。使いやすさのために、まずテキストとアイコンの色を追加し、resources/base/element/color.json のコードを次のように変更します。

{
    
    
  "color": [
    {
    
    
      "name": "start_window_background",
      "value": "#FFFFFF"
    },
    {
    
    
      "name": "white",
      "value": "#FFFFFF"
    },
    {
    
    
      "name": "background",
      "value": "#F1F3F5"
    },
    {
    
    
      "name": "title_text_color",
      "value": "#182431"
    },
    {
    
    
      "name": "login_more_text_color",
      "value": "#99182431"
    },
    {
    
    
      "name": "placeholder_color",
      "value": "#99182431"
    },
    {
    
    
      "name": "line_color",
      "value": "#33182431"
    },
    {
    
    
      "name": "login_button_color",
      "value": "#007DFF"
    },
    {
    
    
      "name": "login_blue_text_color",
      "value": "#007DFF"
    },
    {
    
    
      "name": "other_login_text_color",
      "value": "#838D97"
    },
    {
    
    
      "name": "loading_color",
      "value": "#182431"
    },
    {
    
    
      "name": "mainPage_selected",
      "value": "#1698CE"
    },
    {
    
    
      "name": "mainPage_normal",
      "value": "#6B6B6B"
    },
    {
    
    
      "name": "mainPage_backgroundColor",
      "value": "#F1F3F5"
    },
    {
    
    
      "name": "home_grid_fontColor",
      "value": "#99182431"
    },
    {
    
    
      "name": "setting_button_backgroundColor",
      "value": "#E5E8EA"
    },
    {
    
    
      "name": "setting_button_fontColor",
      "value": "#FA2A2D"
    }
  ]
}

色を完成させたら、ソース コードの resources/base/element/meida の下にあるアイコンをプロジェクトにコピーします。

ここに画像の説明を挿入します

  次に、ログイン ページの作成を開始できます。まず build() 関数のコンテンツを変更し、垂直レイアウトを定義して、色、コンテンツ サイズ、コンテンツ パディングを設定します。コードは次のとおりです:

@Entry
@Component
struct Login {
    
    

  build() {
    
    
    // 页面纵向布局
    Column() {
    
    

    .backgroundColor($r('app.color.background'))
    .height('100%')
    .width('100%')
    .padding({
    
    
      left: 12,
      right: 12,
      bottom: 24
    })
  }
}

この時点では、プレビュー効果を保存すると空白になります。次に、Column() にアイコンと 2 つのテキストを追加します。コードは次のとおりです:あ>

	  //Logo
      Image($r('app.media.logo'))
        .width(78)
        .height(78)
        .margin({
    
     top: 100, bottom: 16 })
      Text('登录')
        .fontSize(24)
        .fontWeight(FontWeight.Medium)
        .fontColor($r('app.color.title_text_color'))
      Text('登录帐号以使用更多服务')
        .fontSize(16)
        .fontColor($r('app.color.login_more_text_color'))
        .margin({
    
     top: 16, bottom: 30 })

次に、図に示すように効果をプレビューします。

ここに画像の説明を挿入します
次に、アカウント番号とパスワードの入力ボックスを作成し、次のコードを追加します。

	  //账号输入框
      TextInput({
    
     placeholder: '账号' })
        .maxLength(11)
        .type(InputType.Number)
        .placeholderColor($r('app.color.placeholder_color'))
        .height(45)
        .fontSize(18)
        .backgroundColor($r('app.color.background'))
        .width('100%')
        .padding({
    
     left: 0 })
        .margin({
    
     top: 12 })
        .onChange((value: string) => {
    
    
          //获取输入的内容
          
        })
      //下划线
      Line().width('100%')
        .height(2)
        .backgroundColor($r('app.color.line_color'))
      //密码输入框
      TextInput({
    
     placeholder: '密码' })
        .maxLength(8)
        .type(InputType.Password)
        .placeholderColor($r('app.color.placeholder_color'))
        .height(45)
        .fontSize(18)
        .backgroundColor($r('app.color.background'))
        .width('100%')
        .padding({
    
     left: 0 })
        .margin({
    
     top: 12 })
        .onChange((value: string) => {
    
    
          //获取输入的内容
          
        })
      //下划线
      Line().width('100%')
        .height(2)
        .backgroundColor($r('app.color.line_color'))

まずプレビューしてみましょう:
ここに画像の説明を挿入します

次に、説明できる点について説明します。1 つ目は、TextInput の入力タイプです。Password の場合、ボタンが表示されます。これをクリックすると、パスワードの平文の内容が表示されます。Iこれは今でもとても気に入っています。自分で書く必要はありません。また、入力したコンテンツをリアルタイムで同期する onChange() もあることを確認しました。入力ボックスの場合、それを受け取る変数を定義する必要があります。これは、ログインで変数を定義することができます。コードは次のとおりです。

  @State account: string = '';
  @State password: string = '';

これはアカウントとパスワードの入力値です。コンテンツに順番に値を割り当てます。アカウント入力ボックスの下のコンテンツをaccountに割り当て、次にコンテンツを割り当てます。パスワード入力ボックスをpassword に入力する場合、コードは次のとおりです:

	  //账号输入框
      TextInput({
    
     placeholder: '账号' })
        ...
        .onChange((value: string) => {
    
    
          //获取输入的内容
          this.account = value;
        })
      ...
      //密码输入框
      TextInput({
    
     placeholder: '密码' })
        ...
        .onChange((value: string) => {
    
    
          //获取输入的内容
          this.password = value;
        })
      ...

②拡張修飾子

  展開修飾子を導入しましょう. まず、次の図に示すように、先ほど作成したコードを見てみましょう。

ここに画像の説明を挿入します

マークしたコードの一部に一貫性があることがわかりますが、これは実際に入力ボックスと行に必要な設定です。その後、展開修飾子を使用して、繰り返されるコンテンツ スタイルをスタイル関数にカプセル化して使用できます。同様のスタイルで、次のようにします。 Login{} の外側に inputStyle() 関数を追加します。コードは次のとおりです。

/**
 * 输入框的通用样式
 */
@Extend(TextInput) function inputStyle() {
    
    
  .placeholderColor($r('app.color.placeholder_color'))
  .height(45)
  .fontSize(18)
  .backgroundColor($r('app.color.background'))
  .width('100%')
  .padding({
    
     left: 0 })
  .margin({
    
     top: 12 })
}

ここでは、変更されたタイプを含む@Extend 修飾子を使用します。ここでの入力は TextInput コンポーネントで、内部のコードは次と同じです。 before マークされたコードが上に移動され、別の lineStyle() 関数を作成します。コードは次のとおりです:

@Extend(Line) function lineStyle() {
    
    
  .width('100%')
  .height(2)
  .backgroundColor($r('app.color.line_color'))
}

次の図に示すように、これら 2 つの関数が追加される場所に注意してください。
ここに画像の説明を挿入します

コンポーネント内に記述するとエラーが報告されるので、先ほどの入力ボックスと行のコードを次の図のように更新します。

ここに画像の説明を挿入します

上記のオリジナルの書き方に比べて非常にシンプルでコードの繰り返しも少なくなります。入力ボックスの下の2つの青い文字も同じスタイルなので拡張スタイルを追加します。コードは以下の通りです:

@Extend(Text) function blueTextStyle() {
    
    
  .fontColor($r('app.color.login_blue_text_color'))
  .fontSize(14)
  .fontWeight(FontWeight.Medium)
}

次のように、アンダースコアの後にコードを追加します。

      Row() {
    
    
        Text('短信验证码登录').blueTextStyle()
        Text('忘记密码').blueTextStyle()
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .width('100%')
      .margin({
    
     top: 8 })

水平レイアウトで 2 つのテキストを読み込み、justifyContent(FlexAlign.SpaceBetween) でレイアウト内のコンテンツを設定し、左右の両方を横に移動して、プレビュー効果を確認します。

ここに画像の説明を挿入します

次に、ログイン ボタンと登録 UI を記述し、以下に示すように、上記のコードの後に​​コードを追加し続けます。

      //登录按钮
      Button('登录', {
    
     type: ButtonType.Capsule })
        .width('90%')
        .height(40)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .backgroundColor($r('app.color.login_button_color'))
        .margin({
    
     top: 80, bottom: 12 })
        .onClick(() => {
    
    
          // 登录

        })
      Text('注册账号')
        .fontColor($r('app.color.login_blue_text_color'))
        .fontSize(16)
        .fontWeight(FontWeight.Medium)

ここでのコードは実際には説明するのが簡単ではありません。一目瞭然です。焦点は、ログイン ボタンをクリックした後に onClick() 関数で実行されるコードにあります。

③ ページジャンプ

次に、Login{} にログイン関数を追加します。コードは次のとおりです。

  /**
   * 登录
   */
  login(): void {
    
    
    if (this.account === '' || this.password === '') {
    
    
      //显示Toast
      promptAction.showToast({
    
     message: '账号或密码为空' })
      return
    }
    router.replaceUrl({
    
     url: 'pages/Index' });
  }

ここのコードでは promptActionrouter を使用しています。どちらも Honmeng 内のプラグインです。入力後に赤い下線付きの波線が表示された場合は、次に、パッケージをインポートし、波線の上にマウスを置き、ショートカット キー Alt + Enter を使用すると、ポップアップ ウィンドウが表示されます。
ここに画像の説明を挿入します

最初の項目を選択すると、必要なプラグインが現在のコンポーネントにインポートされます。インポート後にエラーは報告されません。インポートされたコンテンツは次の図に示されています。

ここに画像の説明を挿入します

次に、次の図に示すように、ログイン ボタンのクリック イベントでログイン関数を呼び出します。

ここに画像の説明を挿入します

以下に示すように、もう一度プレビューし、ログイン ボタンをクリックして試してください。

ここに画像の説明を挿入します
アカウントとパスワードを入力して「ログイン」をクリックするだけで、インデックスページにジャンプします。試してみることはできますが、処理する必要がある詳細がいくつかあるため、現在のログインページのコンテンツはまだ完成していません最初に行うことは、「完了」ページで、登録済みアカウントのテキストの後に次のコードを追加します。

      //空白填充组件,具有自动填充容器空余部分的能力。仅当父组件为Row/Column时生效。
      Blank()
      Text('其他登录方式')
        .fontColor($r('app.color.other_login_text_color'))
        .fontSize(12)
        .fontWeight(FontWeight.Medium)
        .margin({
    
     top: 48, bottom: 12 })
      Row({
    
     space: 44 }) {
    
    
        this.imageButton($r('app.media.login_method1'))
        this.imageButton($r('app.media.login_method2'))
        this.imageButton($r('app.media.login_method3'))
      }

ここでは、3 つのボタンのスタイルは同じですが、画像リソースが異なるため、@Builder 修飾子を使用してボタンを作成し、Login{} コード:

  /**
   * 其他登录方式按钮
   * @param src
   */
  @Builder
  imageButton(src: Resource) {
    
    
    Button({
    
     type: ButtonType.Circle, stateEffect: true }) {
    
    
      Image(src)
    }
    .height(48)
    .width(48)
    .backgroundColor($r('app.color.background'))
  }

プレビュー効果は次のようになります。
ここに画像の説明を挿入します

④ プログレスバーが表示されるまで待ちます

  一般に、ログインは一度に成功するわけではありません。ログイン後のデータ処理とネットワーク遅延が発生します。通常、ログインをクリックした後に待機中の進行状況が表示され、ログインが進行中であることがユーザーに通知されます。何も起こらない場合はお待ちください。ログイン時に変更され、ログインに時間がかかると、ユーザーはアプリが停止していると考え、アプリを直接アンインストールします。

  次に、いくつかの変数を定義し、Login{} に次のコードを追加することもできます。

  //是否显示加载条
  @State isShowProgress: boolean = false;
  //超时标识
  private timeOutId: number = -1;

このisShowProgress変数を使用して、読み込みの進行状況を表示するかどうかを設定します。このtimeOutIdは、読み込みにかかる時間などのタイムアウト処理に使用されます。ログインしてメイン ページに入ります。簡単にシミュレートします。最初に行うことは、 isShowProgress 変数を true に変更し、 login() 関数。コードは次のとおりです: a>

  login(): void {
    
    
    if (this.account === '' || this.password === '') {
    
    
      //显示Toast
      promptAction.showToast({
    
     message: '账号或密码为空' })
      return
    }
    //内容不为空则显示加载进度条
    this.isShowProgress = true;

    if (this.timeOutId === -1) {
    
    
      //设置超时处理,两秒后执行页面跳转到主页
      this.timeOutId = setTimeout(() => {
    
    
        this.isShowProgress = false;
        this.timeOutId = -1;
        router.replaceUrl({
    
     url: 'pages/Index' });
      }, 2000);
    }
  }

ここでは、isShowProgresstrue に割り当てます。これにより UI の更新がトリガーされるため、build() 関数内の適切な位置を判断する必要があります。 isShowProgress 値。この読み込みの進行状況はポップアップ ウィンドウではなく、ページのスペースを占有するので、前に作成した Blank() コンポーネントを覚えておいてください。 ?その上に次のコードを追加します。

      //是否显示等待进度条
      if (this.isShowProgress) {
    
    
        LoadingProgress()
          .color($r('app.color.loading_color'))
          .width(30)
          .height(30)
          .margin({
    
     top: 20 })
      }

以下に示すように場所を追加します。

ここに画像の説明を挿入します
ここでは isShowProgress の判断で待機中のプログレスバーが表示されていますが、表示をキャンセルする箇所がまだ残っています。 login() 関数に戻りましょう次のコード スニペットを確認してください。

    if (this.timeOutId === -1) {
    
    
      //设置超时处理,两秒后执行页面跳转到主页
      this.timeOutId = setTimeout(() => {
    
    
        this.isShowProgress = false;
        this.timeOutId = -1;
        router.replaceUrl({
    
     url: 'pages/Index' });
      }, 2000);
    }

まず、このパラメータが -1 であるかどうかを確認します。その場合は、2 秒のタイムアウトを追加します。タイムアウトの最後に isShowProgress を false に設定すると、UI が以前に表示されていた読み込み進行状況バーを削除し、 timeOutId を -1 に設定して、最後にそのページにジャンプします。ライフサイクル処理を追加して、Login{} に次のコードを追加することもできます。

  /**
   * 组件的生命周期,组件销毁时执行
   */
  aboutToDisappear() {
    
    
    clearTimeout(this.timeOutId);
    this.timeOutId = -1;
  }

ページにジャンプすると現在のコンポーネントが破棄され、このライフサイクル関数がトリガーされるため、ここで破棄タイムアウト処理を行っています。

ここに画像の説明を挿入します

3. ナビゲーションバー

  ログイン後、メインページであるインデックスページに入りますが、まずはメインページの内容を見てみましょう。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

これら 2 つの図から、メイン ページはタブとタブ コンテンツの 2 つの部分で構成されていることがわかります。下部のタブをクリックすると切り替えることができます。では、このページを作成するときはどのように始めればよいでしょうか?まず、下部のナビゲーション部分であるタブを作成する必要があります。

Index.ets のコードを次のように変更してみましょう:

@Entry
@Component
struct Index {
    
    
  @State currentIndex: number = 0

  private tabsController: TabsController = new TabsController()

  @Builder TabBuilder(title: string, index: number, selectedImg: Resource, normalImg: Resource) {
    
    
    Column() {
    
    
      Image(this.currentIndex === index ? selectedImg : normalImg)
        .width(24)
        .height(24)
      Text(title)
        .margin({
    
     top: 4 })
        .fontSize(10)
        .fontColor(this.currentIndex === index ? $r('app.color.mainPage_selected') : $r('app.color.mainPage_normal'))
    }
    .justifyContent(FlexAlign.Center)
    .height(26)
    .width('100%')
    .onClick(() => {
    
    
      this.currentIndex = index
      this.tabsController.changeIndex(this.currentIndex)
    })
  }

  build() {
    
    
    Tabs({
    
    
      barPosition: BarPosition.End,
      controller: this.tabsController
    }) {
    
    
      TabContent() {
    
    
        // 首页内容
      }
      .padding({
    
     left: 12, right: 12 })
      .backgroundColor($r('app.color.mainPage_backgroundColor'))
      .tabBar(this.TabBuilder('首页', 0, $r('app.media.home_selected'), $r('app.media.home_normal')))

      TabContent() {
    
    
      	// 我的内容
      }
      .padding({
    
     left: 12, right: 12 })
      .backgroundColor($r('app.color.mainPage_backgroundColor'))
      .tabBar(this.TabBuilder('我的', 1, $r('app.media.mine_selected'), $r('app.media.mine_normal')))
    }
    .width('100%')
    .backgroundColor(Color.White)
    .barHeight(56)
    .barMode(BarMode.Fixed)
    .onChange((index: number) => {
    
    
      this.currentIndex = index
    })
  }
}

  このコードを分析してみましょう。まず、 currentIndex 変数を定義して現在のタブの添え字を記録し、次に tabsController を定義します。タブを制御します。次に、@Builder デコレータを使用してタブのコンテンツを構築し、垂直レイアウトを使用してアイコンとテキストを中央に配置します。currentIndex および現在の Index は、タブが選択されているかどうかを判断するために使用されます。 currentIndex のデフォルトは 0 です。これは、最初のタブ (ホームページ タブ) がデフォルトで選択されることを意味します。タブのクリック イベントで、 currentIndex の値を更新します。 、this.tabsController.changeIndex(this.currentIndex) を使用してタブ オプションを切り替えます。

  build() 関数のコードを見てみましょう。ここでは、タブを介してコンテンツ ビューを切り替えるためのコンテナ コンポーネントである Tabs() コンポーネントを使用します。タブはコンテンツ ビューに対応します。内部に渡されるパラメータを見てみましょう. ここでは最初のパラメータに焦点を当てています. この barPosition は添え字を意味するものではなく, Tabs のタブ位置を設定します.デフォルト値: BarPosition.Start。ここでのデフォルト値は、実際には Tabs コンポーネントの vertical 属性と組み合わせて使用​​されます。

  vertical は水平タブの場合は false に設定され、true に設定すると垂直タブになります。デフォルト値: false。コード内でこの属性を設定していないため、デフォルトは垂直です。BarPosition の値と組み合わせて見てみましょう:

  • Start、垂直属性メソッドが true に設定されている場合、タブはコンテナーの左側に配置され、垂直属性メソッドが false に設定されている場合、タブはコンテナーの上部に配置されます。
  • End、垂直属性メソッドが true に設定されている場合、タブはコンテナーの右側に配置され、垂直属性メソッドが false に設定されている場合、タブはコンテナーの下部に配置されます。

  ということで、タブが画面の下部にあり、画面の上下左右にタブを配置できるようになりました。

  Tabs() には TabContent という 2 つの TabContent() が配置されていますが、これはタブ内でのみ使用され、タブを切り替えるためのコンテンツ ビューに相当します。後で書きます。このコンポーネントには tabBar() 属性があり、タブのコンテンツをロードするために使用されます。ここでは、前に作成した TabBuilder() 関数を使用します。

  最後に、Tabs() コンポーネントの他の 2 つのプロパティを見てみましょう。

  1. BarMode2 つの属性があります。 1. スクロール可能: 各 TabBar は実際のレイアウト幅を使用し、全長 (水平タブの barWidth、垂直タブの barHeight) を超えるとスライドできます。 2. 修正: すべての TabBar は barWidth 幅で均等に分散されます (barHeight の高さは縦向きで均等に分散されます)。
  2. onChange、タブ切り替え後にイベントがトリガーされます。 index: 現在表示されているインデックス。インデックスは 0 から計算されます。このイベントをトリガーする条件: 1. TabContent がスライドをサポートしている場合、コンポーネントがスライドをトリガーするとイベントがトリガーされます。 2. コントローラー API インターフェイスを通じて呼び出されます。 3. 状態変数によって構築された属性値を通じて変更します。 4. タブをクリックしてトリガーします。

これらの手順を通じて、Tabs() の使用方法はすでに理解されていると思います。インデックスを保存してプレビューしましょう。デフォルトはホームで、次の図に示すように [マイ] をクリックします。< /span>

ここに画像の説明を挿入します

4. ホームページ

このホームページの内容を書く前に、図に示すようにページ全体のレイアウトを見てみましょう。

ここに画像の説明を挿入します
ホーム ページのコンテンツは縦に配置されており、画面サイズを考慮する必要があるため、スライド コントロールを追加して、その中のコンテンツを確認できます。最初にタイトルがあり、タイトルの下にカルーセルがあり、次にその下にあります。は 2 つのグリッド リストです。このようにページの内容を紹介しましたが、このページの内容はどのように書けばよいのでしょうか?

①カルーセル画像

  まず、タイトルとカルーセル画像を完成させ、 ets の下に viewmodel パッケージを作成し、そのパッケージの下に IndexViewModel.ets

export class IndexViewModel {
    
    

  /**
   * 获取轮播图数据
   */
  getSwiperImages(): Array<Resource> {
    
    
    let swiperImages: Resource[] = [
      $r('app.media.fig1'),
      $r('app.media.fig2'),
      $r('app.media.fig3'),
      $r('app.media.fig4')
    ]
    return swiperImages
  }
}

export default new IndexViewModel()

これを通じてカルーセル グラフ データを取得しますgetSwiperImages()。これでメイン ページのコンポーネントを構築できます。ets< の下にコンポーネントを作成します。 i=3> パッケージの場合、パッケージの下に新しい Home.ets ファイルを作成します。内部のコードは次のとおりです。view

import mainViewModel from '../viewmodel/IndexViewModel';

/**
 * 首页
 */
@Component
export default struct Home {
    
    

  private swiperController: SwiperController = new SwiperController();

  build() {
    
    
    Scroll() {
    
    
      Column({
    
     space: 12 }) {
    
    
        //首页
        Column() {
    
    
          Text('首页')
            .fontWeight(FontWeight.Medium)
            .fontSize(24)
            .margin({
    
     top: 12 })
            .padding({
    
     left: 12 })
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        //轮播图
        Swiper(this.swiperController) {
    
    
          ForEach(mainViewModel.getSwiperImages(), (img: Resource) => {
    
    
            Image(img).borderRadius(16)
          }, (img: Resource) => JSON.stringify(img.id))
        }
        .margin({
    
     top: 24 })
        .autoPlay(true)
      }
    }
    .height('100%')
  }
}

ここのコードは上記の考え方に従って設計されています. スクロール バーにはタイトルとカルーセル画像があり、カルーセル画像は自動的に回転するように設定されています. スクロール コンポーネント内のコンテンツがページの高さを満たさない場合、コンテンツは中央に表示されるので、下図のようにIndexにHomeを入れておきます。
ここに画像の説明を挿入します

次に、インデックスをプレビューし、プレビュー レンダリングを確認します。

ここに画像の説明を挿入します

②グリッドリスト

次に、グリッド リストを作成します。最初にデータを作成します。まずデータ Bean を作成し、 を作成します。 1> パッケージを作成するには、このパッケージの下に ファイルを作成します。コードは次のとおりです。 etsbeanItemData.ets

export default class ItemData {
    
    

  title: Resource|string;
  img: Resource;
  others?: Resource|string;

  constructor(title: Resource|string, img: Resource, others?: Resource|string) {
    
    
    this.title = title;
    this.img = img;
    this.others = others;
  }
}

この Bean には、タイトル、写真などの 3 つのデータしかありません。次に、 IndexViewModel に偽のデータを作成し、2 つの関数を作成します。コードは次のとおりです。

  /**
   * 获取第一个网格数据
   */
  getFirstGridData(): Array<ItemData> {
    
    
    let firstGridData: ItemData[] = [
      new ItemData('我的最爱', $r('app.media.love')),
      new ItemData('历史记录', $r('app.media.record')),
      new ItemData('消息', $r('app.media.message')),
      new ItemData('购物车', $r('app.media.shopping')),
      new ItemData('我的目标', $r('app.media.target')),
      new ItemData('圈子', $r('app.media.circle')),
      new ItemData('收藏', $r('app.media.favorite')),
      new ItemData('回收站', $r('app.media.recycle'))
    ]
    return firstGridData
  }


  /**
   * 获取第二个网格数据
   */
  getSecondGridData(): Array<ItemData> {
    
    
    let secondGridData: ItemData[] = [
      new ItemData('排行榜', $r('app.media.top'), '当前热品尽在掌握'),
      new ItemData('新品首发', $r('app.media.new'), '最新潮牌,马上发布'),
      new ItemData('大牌闪购', $r('app.media.brand'), '更多大牌敬请期待'),
      new ItemData('发现好物', $r('app.media.found'), '更多内容等您探索')
    ]
    return secondGridData
  }

ここでは、ItemData をインポートする必要があります。インポート方法を覚えていますか? others?: Resource|string; を作成するときに ? が使用されたため、空であってもかまいません。次に、これら 2 つのグリッドの UI 表示をホームに追加します。コードは次のとおりです。次のように表示:

import mainViewModel from '../viewmodel/IndexViewModel';
import ItemData from '../bean/ItemData';

/**
 * 首页
 */
@Component
export default struct Home {
    
    

  private swiperController: SwiperController = new SwiperController();

  build() {
    
    
    Scroll() {
    
    
      Column({
    
     space: 12 }) {
    
    
        //首页
        ...
        //轮播图
        ...
        //第一个网格布局
        Grid() {
    
    
          ForEach(mainViewModel.getFirstGridData(), (item: ItemData) => {
    
    
            GridItem() {
    
    
              Column() {
    
    
                Image(item.img)
                  .width(24)
                  .height(24)
                Text(item.title)
                  .fontSize(12)
                  .margin({
    
     top: 4 })
              }
            }
          }, (item: ItemData) => JSON.stringify(item))
        }
        .columnsTemplate('1fr 1fr 1fr 1fr')
        .rowsTemplate('1fr 1fr')
        .columnsGap(8)
        .rowsGap(12)
        .padding({
    
     top: 12, bottom: 12 })
        .height(124)
        .backgroundColor(Color.White)
        .borderRadius(24)

        Text('列表')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .width('100%')
          .margin({
    
     top: 12 })
        //第二个网格布局
        Grid() {
    
    
          ForEach(mainViewModel.getSecondGridData(), (secondItem: ItemData) => {
    
    
            GridItem() {
    
    
              Column() {
    
    
                Text(secondItem.title)
                  .fontSize(16)
                  .fontWeight(FontWeight.Medium)
                Text(secondItem.others)
                  .margin({
    
     top: 4 })
                  .fontSize(12)
                  .fontColor($r('app.color.home_grid_fontColor'))
              }
              .alignItems(HorizontalAlign.Start)
            }
            .padding({
    
     top: 8, left: 8 })
            .borderRadius(12)
            .align(Alignment.TopStart)
            .backgroundImage(secondItem.img)
            .backgroundImageSize(ImageSize.Cover)
            .width('100%')
            .height('100%')
          }, (secondItem: ItemData) => JSON.stringify(secondItem))
        }
        .width('100%')
        .height(260)
        .columnsTemplate('1fr 1fr')
        .rowsTemplate('1fr 1fr')
        .columnsGap(8)
        .rowsGap(12)
        .margin({
    
     bottom: 55 })
      }
    }
    .height('100%')
  }
}

なお、以前に書いたコードの一部を省略しているので、コピー&ペーストする必要はありません。実は、グリッドリストのデータ描画方法は通常のリストと同じですが、グリッド リストには他にもいくつかの属性があるので、知っておく必要があります。

  • columnsTemplate: 文字列タイプ。現在のグリッド レイアウトの列数を設定します。設定されていない場合、デフォルトは 1 列です。たとえば、「1fr 1fr 1fr 1fr」は、親コンポーネントを 4 つの列に分割し、親コンポーネントの許容幅を 4 つの等しい部分に分割します。最初の列は 1 シェアを占有し、2 番目の列は 1 シェアを占有し、3 番目の列は 1 シェアを占有します。 、3 番目の列が 1 シェアを占め、4 つの列が 1 シェアを占めます。 「0fr」に設定すると、列幅は 0 になり、GridItem は表示されません。他の不正な値に設定すると、GridItem には固定の 1 列が表示されます。
  • rowsTemplate: 文字列タイプ。現在のグリッド レイアウトの行数を設定します。設定されていない場合、デフォルトは 1 行です。たとえば、「1fr 1fr」は親コンポーネントを 2 つの行に分割し、親コンポーネントの許容高さを 2 等分します。最初の行は 1 つの部分を占め、2 番目の行は 1 つの部分を占めます。「0fr」に設定すると、の場合、この行の高さは になります。行幅は 0 で、この行の GridItem は表示されません。それ以外の不正な値を設定した場合は固定回線として処理されます。
  • columnsGap: 長さタイプ、列間の間隔を設定します。デフォルト値: 0。0 未満の値を設定すると、デフォルト値が表示されます。
  • rowsGap:長さタイプ、行間の間隔を設定します。デフォルト値: 0。0 未満の値を設定すると、デフォルト値が表示されます。

残りの属性については何も言うことはありません。次の図に示すように、インデックスをプレビューしてみましょう。

ここに画像の説明を挿入します

このとき私のものをクリックすると何もないことがわかりますので、次に私のものを書きましょう。

5. 私のもの

まずは私のページの写真を見てみましょう

ここに画像の説明を挿入します

コンテンツも縦に配置されています。上が個人情報、中が機能リスト、下が終了ボタンです。次に、最初にリストのデータを<に提供します。 a i=1> に関数を記述します。コードは次のとおりです。IndexViewModel

  /**
   * 获取设置列表数据
   */
  getSettingListData(): Array<ItemData> {
    
    
    let settingListData: ItemData[] = [
      new ItemData('推送通知', $r('app.media.news'), '开关'),
      new ItemData('数据管理', $r('app.media.data'), null),
      new ItemData('菜单设置', $r('app.media.menu'), null),
      new ItemData('关于', $r('app.media.about'), null),
      new ItemData('清除缓存', $r('app.media.storage'), null),
      new ItemData('隐私协议', $r('app.media.privacy'), null)
    ]
    return settingListData
  }

次に、ビュー パッケージの下に最初に 1 つを構築しますMine.ets。コードは次のとおりです。

import router from '@ohos.router';
import promptAction from '@ohos.promptAction';
import ItemData from '../bean/ItemData';
import mainViewModel from '../viewmodel/IndexViewModel';

/**
 * 我的
 */
@Component
export default struct Mine {
    
    
  @Builder settingCell(item: ItemData) {
    
    
    Row() {
    
    
      Row({
    
     space: 12 }) {
    
    
        Image(item.img)
          .width(22)
          .height(22)
        Text(item.title)
          .fontSize(16)
      }

      // 设置功能item最右侧的功能项
      if (item.others === null) {
    
    
        //可以进入下一级页面
        Image($r('app.media.right_grey'))
          .width(12)
          .height(24)
      } else {
    
    
        //开关
        Toggle({
    
     type: ToggleType.Switch, isOn: false })
      }

    }
    .justifyContent(FlexAlign.SpaceBetween)
    .width('100%')
    .padding({
    
    
      left: 8,
      right: 22
    })
  }

  build() {
    
    
    Scroll() {
    
    
      Column({
    
     space: 12 }) {
    
    
        Column() {
    
    
          Text('我的')
            .fontWeight(FontWeight.Medium)
            .fontSize(24)
            .margin({
    
     top: 12 })
            .padding({
    
     left: 12 })
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        // 个人信息
        Row() {
    
    
          Image($r('app.media.account'))
            .width(48)
            .height(48)
          Column() {
    
    
            Text('李先生')
              .fontSize(20)
            Text('[email protected]')
              .fontSize(12)
              .margin({
    
     top: 4 })
          }
          .alignItems(HorizontalAlign.Start)
          .margin({
    
     left: 24 })
        }
        .margin({
    
     top: 24 })
        .alignItems(VerticalAlign.Center)
        .width('100%')
        .height(96)
        .backgroundColor(Color.White)
        .padding({
    
     left: 24 })
        .borderRadius(16)

        // 功能列表
        List() {
    
    
          ForEach(mainViewModel.getSettingListData(), (item: ItemData) => {
    
    
            ListItem() {
    
    
              //构建每一个item
              this.settingCell(item)
            }
            .height(48)
          }, (item: ItemData) => JSON.stringify(item))
        }
        .backgroundColor(Color.White)
        .width('100%')
        .height('42%')
        // 为列表增加分隔线
        .divider({
    
    
          strokeWidth: 1,
          color: Color.Grey,
          startMargin: 42,
          endMargin: 42
        })
        .borderRadius(16)
        .padding({
    
     top: 4, bottom: 4 })

        Blank()

        Button('退出登录', {
    
     type: ButtonType.Capsule })
          .width('90%')
          .height(40)
          .fontSize(16)
          .fontColor($r('app.color.setting_button_fontColor'))
          .fontWeight(FontWeight.Medium)
          .backgroundColor($r('app.color.setting_button_backgroundColor'))
          .margin({
    
     bottom: 55 })
          .onClick(() => {
    
    
            promptAction.showToast({
    
     message: '退出登录' })
            router.replaceUrl({
    
     url: 'pages/Login' })
          })
      }
      .height('100%')
    }
  }
}

このコードは一見すると非常に多くのコードに見えます。以下で分析してみましょう。上から順に、最初はタイトルと個人情報です。この部分は UI 効果です。言うことはありません。次に、最も重要な関数です。 list はここに渡されます。 @BuildersettingCell() 関数を装飾します。もう一方の項目を使用して、さまざまな効果を表示する必要があるかどうかを決定します。コードは次のとおりです:

	  if (item.others === null) {
    
    
        //可以进入下一级页面
        Image($r('app.media.right_grey'))
          .width(12)
          .height(24)
      } else {
    
    
        //开关
        Toggle({
    
     type: ToggleType.Switch, isOn: false })
      }

が null の場合は、右向きのアイコンです。null でない場合は、スイッチです。デフォルトは false です。中央のリストの読み込みについては何も言うことはありません。最後のログアウト ボタンをクリックすると、 router.replaceUrl({ url: 'pages/Login' }) が呼び出され、ログイン ページに戻ります。ここで、 replaceUrl は現在のページをアプリケーション内のページに置き換え、置き換えられたページを破棄します。

Index を使用して効果をプレビューしてみましょう。

ここに画像の説明を挿入します

①パラメータでジャンプ

  これで、ログイン アカウントには他の機能がなくなりました。アカ​​ウントを Li さんに置き換えることができます。まず、ログイン ボタンのクリック イベントのコードを以下に示すように変更する必要があります。

		router.replaceUrl({
    
    
          url: 'pages/Index',
          params: {
    
    
            account: this.account
          }
        });

ページにジャンプするときに params 属性を追加し、キーと値を入力して、コード行を Mine コンポーネントに追加するだけです。

  //接收传递过来的参数
  @State account: string = router.getParams()?.['account'];

このようにして、渡されたパラメーター値を取得し、それを Text に設定できます。

ここに画像の説明を挿入します

以下で実行して効果を確認してください

ここに画像の説明を挿入します

  この記事はここで終わります。Hongmeng が提供する学習教材の中には非常に包括的なものもあります。実践的なプロセスで読んだりテストしたりすることで、すぐにアプリケーション開発を始めることができます。

6. ソースコード

お役に立てば、スター または フォーク、山は高く、川は長いです、また会いましょう~

源码地址:MyCenter

おすすめ

転載: blog.csdn.net/qq_38436214/article/details/134016817