作者:狼哥
团队:坚果派
团队介绍:坚果派由坚果等人创建,团队拥有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及以上。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。