Image Saving, Scrolling Issues, Dialog Page Effect, Child Components Throwing Custom Events to Parent Components



This content originally appeared on DEV Community and was authored by kouwei qing

[Daily HarmonyOS Next Knowledge] Image Saving, Scrolling Issues, Dialog Page Effect, Child Components Throwing Custom Events to Parent Components, Interface Grayscale

1、Image Saving in HarmonyOS

The background returns the image address, and the front end needs to save it to the album after loading.

The SaveButton security control can temporarily obtain storage permissions without requiring permission pop-up authorization confirmation. You can also save images after obtaining the user’s media authorization according to normal procedures. Refer to the documentation: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/photoaccesshelper-resource-guidelines-V5

Developers can query media resources based on specific conditions, such as specified types, dates, albums, etc.

The application calls PhotoAccessHelper.getAssets to obtain media resources and passes in a FetchOptions object to specify retrieval conditions. Unless otherwise stated, the resources involved in the documentation that need to be obtained are considered to be pre-installed and have corresponding data in the database. If the example code executes and the obtained resources are empty, please confirm whether the file has been pre-installed and whether the file’s data exists in the database.

If you only want to obtain an object at a specific position (such as the first, last, specified index, etc.), you can use the interface in FetchResult to obtain the media resource object at the corresponding position.

Prerequisites:

  • Obtain the photoAccessHelper instance of the album management module.
  • Apply for the album management module read permission ‘ohos.permission.READ_IMAGEVIDEO’.
  • Import the dataSharePredicates module.

Saving User Files: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/save-user-file-V5

To add images to the user album, refer to this document: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/photoaccesshelper-useralbum-guidelines-V5

Adding Images and Videos to the User Album:

First, obtain the user album object and the array of images or video objects to be added to the album, then call the MediaAlbumChangeRequest.addAssets and PhotoAccessHelper.applyChanges interfaces to add images or videos to the user album.

Prerequisites:

  • Obtain the photoAccessHelper instance of the album management module.
  • Apply for the album management module permissions ‘ohos.permission.READ_IMAGEVIDEO’ and ‘ohos.permission.WRITE_IMAGEVIDEO’.
  • The following takes adding an image to the user album named ‘albumName’ as an example.

Development Steps:

  1. Establish album retrieval conditions to obtain user albums.
  2. Establish image retrieval conditions to obtain images.
  3. Call the PhotoAccessHelper.getAlbums interface to obtain user album resources.
  4. Call the FetchResult.getFirstObject interface to obtain the first user album.
  5. Call the PhotoAccessHelper.getAssets interface to obtain image resources.
  6. Call the FetchResult.getFirstObject interface to obtain the first image.
  7. Call the MediaAlbumChangeRequest.addAssets interface to add images to the user album.
  8. Call the PhotoAccessHelper.applyChanges interface to submit the album change request.
import { dataSharePredicates } from '@kit.ArkData';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
const context = getContext(this);
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);

async function example() {
  let albumPredicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
  let albumName: photoAccessHelper.AlbumKeys = photoAccessHelper.AlbumKeys.ALBUM_NAME;
  albumPredicates.equalTo(albumName, 'albumName');
  let albumFetchOptions: photoAccessHelper.FetchOptions = {
    fetchColumns: [],
    predicates: albumPredicates
  };

  let photoPredicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
  let photoFetchOptions: photoAccessHelper.FetchOptions = {
    fetchColumns: [],
    predicates: photoPredicates
  };

  try {
    let albumFetchResult: photoAccessHelper.FetchResult<photoAccessHelper.Album> = await phAccessHelper.getAlbums(photoAccessHelper.AlbumType.USER, photoAccessHelper.AlbumSubtype.USER_GENERIC, albumFetchOptions);
    let album: photoAccessHelper.Album = await albumFetchResult.getFirstObject();
    console.info('getAlbums successfully, albumName: ' + album.albumName);
    let photoFetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> = await phAccessHelper.getAssets(photoFetchOptions);
    let photoAsset: photoAccessHelper.PhotoAsset = await photoFetchResult.getFirstObject();
    console.info('getAssets successfully, albumName: ' + photoAsset.displayName);
    let albumChangeRequest: photoAccessHelper.MediaAlbumChangeRequest = new photoAccessHelper.MediaAlbumChangeRequest(album);
    albumChangeRequest.addAssets([photoAsset]);
    await phAccessHelper.applyChanges(albumChangeRequest);
    console.info('succeed to add ' + photoAsset.displayName + ' to ' + album.albumName);
    albumFetchResult.close();
    photoFetchResult.close();
  } catch (err) {
    console.error('addAssets failed with err: ' + err);
  }
}

2、Scrolling Issues in HarmonyOS

Operation Steps:

  1. Pass a title when navigating from another page to the all-apps page.
  2. If the title is not empty, scroll to the title’s position.
  3. This this.bgScroller.scrollTo({ xOffset: 0, yOffset: -100 }); has no effect.

You can use the component’s scrollToIndex method to jump to a specified position. Refer to the link: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-container-scroll-V5#scrolltoindex

Refer to the demo:

@Entry
@Component
struct CategoryPage3 {
  @State categoryTabIndex: number = 1;
  @State scrollerIndex: number = 1;
  @State sideClickFlag: boolean = false;
  @State tabTitle:string[] = ['页签1','页签2','页签3','页签4','页签5'];
  scroller: Scroller = new Scroller();
  build() {
    Column() {
      Tabs({
        index: this.categoryTabIndex,
        barPosition: BarPosition.End
      })
      {
        ForEach(this.tabTitle, (item: string, index?: number) => {
          TabContent() {
            GridRow({
              columns: 4
            }) {
              GridCol() {
                SideListComponent({
                  sideListIndex: $scrollerIndex,
                  sideClickFlag: $sideClickFlag,
                  scroller: this.scroller = new Scroller()
                })
              }
              GridCol({
                span: 3
              }) {
                DetailListComponent({
                  sideListIndex: $scrollerIndex,
                  sideClickFlag: $sideClickFlag,
                  scroller: this.scroller
                })
                  .margin( { top: "29vp" })
              }
            }
            .margin({
              left: "24vp",
              right: "24vp"
            })
          }
          .tabBar(this.TabBottom(item, index))
        }, (item: string, index?: number) => index + JSON.stringify(item))
      }
      .layoutWeight(1)
      .barWidth('100%')
      .barHeight("56vp")
      .barMode(BarMode.Fixed)
      .onChange((index: number) => {
        this.categoryTabIndex = index;
      })
    }
    .backgroundColor("#F1F3F5")
  }
  @Builder TabBottom(item: string, index?: number) {
    Column() {
      Text(item)
        .width('100%')
        .height("14vp")
        .fontSize("10fp")
        .fontWeight(500)
        .fontColor(this.categoryTabIndex === index ? "#007DFF" : "#99182431")
        .textAlign(TextAlign.Center)
        .margin({ bottom: "7vp" })
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}
@Component
export struct DetailListComponent {
  scroller: Scroller = new Scroller();
  @State subTitle:string[] = ['分类1子标题1','分类2子标题2','分类3子标题3','分类4子标题4','分类5子标题5','分类6子标题6'];
  @Link sideListIndex: number;
  @Link sideClickFlag: boolean;
  build() {
    List({ scroller: this.scroller }) {
      ForEach(this.subTitle, (item: string) => {
        ListItem() {
          Column() {
            Text(item)
              .height("22vp")
              .fontSize("16fp")
              .fontColor("#FF182431")
              .fontWeight(500)
              .align(Alignment.Start)
            Text('内容')
              .width('100%')
              .height("72vp")
              .textAlign(TextAlign.Center)
              .fontSize("14fp")
              .fontWeight(500)
              .align(Alignment.Center)
              .borderRadius("16vp")
              .backgroundColor("#0C000000")
              .margin({top:10})
            Text('内容')
              .width('100%')
              .height("72vp")
              .textAlign(TextAlign.Center)
              .fontSize("14fp")
              .fontWeight(500)
              .align(Alignment.Center)
              .borderRadius("16vp")
              .backgroundColor("#0C000000")
              .margin({top:10})
            Text('内容')
              .width('100%')
              .height("72vp")
              .textAlign(TextAlign.Center)
              .fontSize("14fp")
              .fontWeight(500)
              .align(Alignment.Center)
              .borderRadius("16vp")
              .backgroundColor("#0C000000")
              .margin({top:10})
          }
          .height(300)
          .alignItems(HorizontalAlign.Start)
        }
        .height(300)
      }, (item: string, index?: number) => index + JSON.stringify(item))
    }
    .width('100%')
    .onScrollIndex((firstIndex: number) => {
      if(!this.sideClickFlag) {
        this.sideListIndex = firstIndex;
      }
      this.sideClickFlag = false;
      this.scroller.scrollToIndex(this.sideListIndex);
    })
  }
}

@Component
export struct SideListComponent {
  scroller: Scroller = new Scroller();
  @Link sideListIndex: number;
  @Link sideClickFlag: boolean;
  @State listData:string[] = ['分类1','分类2','分类3','分类4','分类5','分类6'];

  build() {
    Flex({ justifyContent: FlexAlign.SpaceBetween }) {
      List() {
        ForEach(this.listData, (item: string, index?: number) => {
          ListItem() {
            Text(item)
              .fontSize("14fp")
              .fontWeight('400')
              .fontColor(this.sideListIndex === index ? '#007DFF' : '#182431')
              .textAlign(TextAlign.Start)
              .margin({
                top: '12',
                bottom: '16',
                left: '0'
              })
              .width('100%')
              .onClick(() => {
                this.sideClickFlag = true;
                if (index != undefined) {
                  this.sideListIndex = index;
                }
                this.scroller.scrollToIndex(this.sideListIndex);
              })
          }.animation({
            duration: 1200,
            curve: Curve.Friction,
            delay: 500,
            iterations: -1, // Set to -1 for infinite loop
            playMode: PlayMode.Alternate,
            expectedFrameRateRange: {
              min: 20,
              max: 120,
              expected: 90,
            }
          })
        })
      }

      .constraintSize({
        minWidth: (100)
      })
      .width('100%')
      .height('100%')
      .margin({ top: '12vp' })

      Divider()
        .vertical(true)
        .color("#0C000000")
        .strokeWidth(1)
        .margin({ bottom: '8vp' })
    }
  }}

3、How to Achieve a DialogActivity-like Effect in HarmonyOS?

Need to navigate to a Page, but hope this Page has a Dialog-style layout.

You can use a custom pop-up component to achieve the effect. Refer to the address: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-methods-custom-dialog-box-V5#%E7%A4%BA%E4%BE%8B

PageB is directly an empty page. In the page’s aboutToAppear() method, open the custom pop-up. Add the router.back() method to the pop-up’s internal buttons to return to PageA after processing the logic.

PageA in the entry package:

import { router } from '@kit.ArkUI';

import('library_hsp/src/main/ets/pages/PageB'); // Import the named route page in the shared package

@Entry({ routeName: "PageA" })
@Component
struct PageA {
  @State message: string = 'PageA';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(32)
          .fontWeight(FontWeight.Bold)

        Button("进入B页面").onClick(() => {
          router.pushNamedRoute({
            name: 'PageB'
          })
        })
      }
      .width('100%')
    }
    .height('100%')
  }
}

4、How Do Child Components Throw Custom Events to Parent Components for Operations in HarmonyOS?

For child components to call parent component methods, refer to this demo:

@Entry
@Component
struct Index12 {
  clickFunc (data:number){
    console.log(data.toString())

  }

  build() {
    Row() {
      Column() {
        child({click:(data:number):void=>this.clickFunc(data)})
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
export struct child {

  click?=(data:number)=>{};


  build() {
    Row() {
      Column() {
        Text('点击子组件')
          .onClick(()=>{
            if (this.click != undefined ) {
              this.click(123);
            }
          })

      }
      .width('100%')
    }
    .height('100%')
  }

}

5、Is There a Way to Grayscale the Interface in HarmonyOS?

You need to configure the grayscale attribute of the root component of each page to 1. Refer to the documentation: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-universal-attributes-image-effect-V5

Sample code is as follows:

@Entry
@Component
struct ArkUIClubTest {
  @State saturateValue: number = 0;

  build() {
    Column({ space: 10 }) {
      Row({ space: 10 }) {
        Text('Red Text')
          .fontColor(Color.Red)
          .fontSize(22)
        Text('Blue Text')
          .fontColor(Color.Blue)
          .fontSize(22)
      }

      Row({ space: 10 }) {
        Button('White Text')
          .fontColor(Color.White)
      }

      Flex()
        .width("100%")
        .height(50)
        .backgroundColor(Color.Pink)

      Image($r("app.media.startIcon"))
        .height(150)

      Row({ space: 10 }) {
        Button('页面置灰')
          .onClick(() => {
            this.saturateValue = 1; // Grayscale the page
          })
        Button('恢复彩色')
          .onClick(() => {
            this.saturateValue = 0; // Restore the page to color
          })
      }
    }
    .width("100%")
    .height("100%")
    .padding(10)
    .grayscale(this.saturateValue) // Set the color saturation of the root component
  }
}


This content originally appeared on DEV Community and was authored by kouwei qing