作者:狼哥
團隊:堅果派
團隊介紹:堅果派由堅果等人建立,團隊擁有12個華為HDE帶領熱愛HarmonyOS/OpenHarmony的開發者,以及若干其他領域的三十餘位萬粉博主運營。專注於分享HarmonyOS/OpenHarmony、ArkUI-X、元服務、倉頡。團隊成員聚集在北京,上海,南京,深圳,廣州,寧夏等地,目前已開發鴻蒙原生應用,三方庫60+,歡迎交流。
介紹
在社交媒體日益繁榮的今天,九宮格切圖以其獨特的視覺呈現方式,成為了朋友圈中的一股清新之風。透過將一張完整圖片精心切割為九個小方塊,再依次排列釋出,不僅讓圖片內容更加層次分明,還能激發觀者的探索欲,引導他們逐格瀏覽,享受發現新細節的樂趣。
九宮格圖片的用處廣泛而巧妙。它適用於旅行美景的展示,每一格都是一處風景的縮影,串聯起一段完整的旅程記憶;也是美食分享的絕佳選擇,從食材準備到成品呈現,步步精彩,讓人垂涎欲滴;更可用於生活日常的創意記錄,無論是溫馨的家庭瞬間,還是個人的小確幸,都能在九宮格的框架下,被賦予更多故事性和觀賞性。這種創意切圖方式,讓每一次分享都變得更加有趣和生動,是連線你我,傳遞美好情感的新橋樑。
效果預覽
工程目錄
├──entry/src/main/ets // 程式碼區
│ ├──dialog
│ │ └──ImagePicker.ets // 圖片選擇
│ ├──entryability
│ │ └──EntryAbility.ets
│ ├──model
│ │ ├──ImageModel.ets // 圖片操作
│ │ └──PictureItem.ets // 圖片物件
│ └──pages
│ └──Index.ets // 首頁
└──entry/src/main/resources // 應用資源目錄
具體實現
1. 許可權新增
配置檔案module.json5裡新增讀取圖片及影片許可權和修改圖片或影片許可權。
"requestPermissions": [
{
"name": "ohos.permission.WRITE_MEDIA",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
},
"reason": "$string:WRITE_MEDIA"
},
{
"name": "ohos.permission.MEDIA_LOCATION",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
},
"reason": "$string:MEDIA_LOCATION"
},
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
},
"reason": "$string:READ_IMAGEVIDEO"
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
},
"reason": "$string:WRITE_IMAGEVIDEO"
}
]
2. 圖片選擇對話
獲取本地圖片:首先使用getPhotoAccessHelper獲取相簿管理模組例項,然後使用getAssets方法獲取檔案資源,最後使用getAllObjects獲取檢索結果中的所有檔案資產方便展示;
let photoList: Array<photoAccessHelper.PhotoAsset> = [];
let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
let fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [],
predicates: predicates
}
let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> = await this.phAccessHelper.getAssets(fetchOptions);
if (fetchResult != undefined) {
let photoAsset: Array<photoAccessHelper.PhotoAsset> = await fetchResult.getAllObjects();
if (photoAsset != undefined && photoAsset.length > 0) {
for (let i = 0; i < photoAsset.length; i++) {
if (photoAsset[i].photoType === 1) {
photoList.push(photoAsset[i]);
}
}
}
}
自定義對話方塊顯示獲取到的本地圖片
import { photoAccessHelper } from '@kit.MediaLibraryKit';
@CustomDialog
export struct ImagePicker {
@Link index: number;
private imagesData: Array<photoAccessHelper.PhotoAsset> = [];
public controller: CustomDialogController;
@State selected: number = 0;
build() {
Column() {
List({ space: 5 }) {
ForEach(this.imagesData, (item: photoAccessHelper.PhotoAsset, index) => {
ListItem() {
Stack({ alignContent: Alignment.TopEnd }) {...}
}, (item: photoAccessHelper.PhotoAsset) => JSON.stringify(item))
}
.width('95%')
.height(160)
.listDirection(Axis.Horizontal)
Row() {...}
.margin({ bottom: 10, top: 10 })
}
.width('100%')
.padding({ top: 16, left: 16, right: 16 })
}
}
3. 切圖九宮格
使用createImagePacker建立ImagePacker例項,然後使用fs.open開啟檔案,呼叫createImageSource介面建立圖片源例項方便操作圖片,接下來使用getImageInfo方法獲取圖片大小便於分割,最後使用createPixelMap方法傳入每一份的尺寸引數完成圖片裁剪。具體就是根據圖片選擇對話方塊,選擇的下標,到相簿裡獲取到選擇的圖片,然後以只讀方式開啟圖片,獲取開啟圖片資訊,計算出切圖後的寬度和高度,根據引數生成新切圖,並快取到陣列裡,方便顯示切圖後的九宮格,最後呼叫儲存函式把切圖儲存到相簿裡,方便之後使用,比如發朋友圖。
async splitPic(index: number): Promise<Array<PictureItem>> {
// 呼叫上面函式獲取全部圖片
let imagesData: Array<photoAccessHelper.PhotoAsset> = await this.getAllImg();
console.info(`xx testTag splitPic 相簿圖片數量為: ${imagesData.length}`)
let imagePixelMap: Array<PictureItem> = [];
// 建立影像編碼ImagePacker物件
let imagePickerApi = image.createImagePacker();
// 以只讀方式開啟指定下標圖片
await fileIo.open(imagesData[index].uri, fileIo.OpenMode.READ_ONLY).then(async (file: fileIo.File) => {
let fd: number = file.fd;
// 獲取圖片源
let imageSource = image.createImageSource(fd);
// 圖片資訊
let imageInfo = await imageSource.getImageInfo();
// 圖片高度除以3,就是把圖片切為3份
let height = imageInfo.size.height / this.splitCount;
let width = imageInfo.size.width / this.splitCount;
// 切換為 3x3 張圖片
for (let i = 0; i < this.splitCount; i++) {
for (let j = 0; j < this.splitCount; j++) {
// 設定解碼引數DecodingOptions,解碼獲取pixelMap圖片物件
let decodingOptions: image.DecodingOptions = {
desiredRegion: {
size: {
height: height, // 切開圖片高度
width: width // 切開圖片寬度
},
x: j * width, // 切開x起始位置
y: i * height // 切開y起始位置
}
}
// 根據引數重新九宮格圖片
let img: image.PixelMap = await imageSource.createPixelMap(decodingOptions);
// 把生成新圖片放到記憶體裡
imagePixelMap.push(new PictureItem(i * this.splitCount + j, img));
// 儲存到相簿
await this.savePixelMap(img)
}
}
imagePickerApi.release();
fileIo.closeSync(fd);
})
return imagePixelMap;
}
4. 儲存相簿
把上面切出來的PixelMap先轉為ArrayBuffer,然後透過PhotoAccessHelper模組提供相簿管理模組能力,包括建立相簿以及訪問、修改相簿中的媒體資料資訊等。把ArrayBuffer儲存到相簿裡。
async savePixelMap(pm: PixelMap) {
if (this.phAccessHelper === null) {
return;
}
const imagePackerApi: image.ImagePacker = image.createImagePacker();
const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 30 };
try {
const buffer: ArrayBuffer = await imagePackerApi.packing(pm, packOpts);
let options: photoAccessHelper.CreateOptions = {
title: new Date().getTime().toString()
};
let photoUri: string = await this.phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg', options);
let file: fileIo.File = fileIo.openSync(photoUri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await fileIo.write(file.fd, buffer);
fileIo.closeSync(file);
} catch (err) {
console.error(err)
}
}
5. 介面佈局
頂部和底部顯示紅色說明文字,上面預設顯示相簿第一張圖上,點選圖片彈出對話方塊選擇相簿裡的其它圖片,下來是個切割九宮格按鈕,點選可以把選擇的圖片切割為九張圖片,並自動儲存到相簿裡。
Column() {
Text(`預設顯示相簿第一張圖片、點選圖片彈出相簿對話方塊、選擇一張希望切九宮格圖片!`)
.fontSize(10)
.fontColor(Color.Red)
Image(this.imgData[this.index]?.uri)
.objectFit(ImageFit.Contain)
.width('100%')
.aspectRatio(1)
.margin(20)
.onClick(async () => {
this.imagePixelMap = [];
this.imgData = await this.imageModel.getAllImg();
setTimeout(() => {
this.dialogController.open();
}, 200);
})
Stack() {
Divider()
.width('100%')
.color(Color.Orange)
Button('切割九宮格')
.onClick(async () => {
this.imagePixelMap = [];
this.imagePixelMap = await this.imageModel.splitPic(this.index);
})
}
.width('100%')
.height(30)
Grid() {
ForEach(this.imagePixelMap, (item: PictureItem, index:number) => {
GridItem() {
Image(item.pixelMap)
.width('99%')
.objectFit(ImageFit.Fill)
.height(90)
}
.backgroundColor(item.pixelMap === undefined ? '#f5f5f5' : '#ffdead')
}, (item: PictureItem) => JSON.stringify(item))
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(2)
.rowsGap(2)
.backgroundColor('#fff')
.width('100%')
.aspectRatio(1)
.margin(20)
.layoutWeight(1)
Text(`上面九宮格圖片已儲存到相簿、請移步到相簿發一個不一樣的朋友圈吧!`)
.fontSize(10)
.fontColor(Color.Red)
}
.height('100%')
.width('100%')
.padding(20)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
6. 許可權申請
在頁面生命週期aboutToAppear函式時,呼叫許可權申請,並獲取相簿資料。
const PERMISSIONS: Array<Permissions> = [
'ohos.permission.READ_MEDIA',
'ohos.permission.WRITE_MEDIA',
'ohos.permission.MEDIA_LOCATION',
'ohos.permission.MANAGE_MISSIONS'
];
async aboutToAppear() {
await abilityAccessCtrl.createAtManager().requestPermissionsFromUser(getContext(this), PERMISSIONS);
this.imgData = await this.imageModel.getAllImg();
}
相關許可權
讀取圖片及影片許可權:ohos.permission.READ_IMAGEVIDEO
修改圖片或影片許可權:ohos.permission.WRITE_IMAGEVIDEO
約束與限制
1.本示例僅支援標準系統上執行,支援裝置:華為手機。
2.HarmonyOS系統:HarmonyOS NEXT Developer Beta1及以上。
3.DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上。
4.HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上。