前言:
DevEco Studio版本:4.0.0.600
API:10
在安卓中实现仿微信朋友圈这样的效果是通过RecyclerView+自定义ItemTouchHelper来实现,下面文章就介绍如何通过鸿蒙的方式实现这样的效果
效果:
实现原理分析
1、实现3x3九宫格布局:
可以通过Arkts中的Grid/GridItem组件来实现,设置Grid().columnsTemplate('1fr 1fr 1fr')
2、实现拖拽效果
通过查看Grid/GridItem组件相关api文档,发现存在editMode属性方法设置为true可以拖动内部GridItem子控件。并且通过onItemDragStart、onItemDragMove、onItemDrop等事件方法监听拖动的过程。
参考链接:OpenHarmony Grid
3、拖动后将数组内数据对换
新建一个temp用于数组元素实现数据对换
4、监听拖动事件,实现底部删除效果
通过onItemDragStart、onItemDrop拖动开始和拖动结束事件监听方法来控制底部删除效果的显示和隐藏
代码实现:
1、实现九宫格布局
Grid() {
ForEach(this.numbers, (imageBean: ImageSelectBean) => {
GridItem() {
this.pixelMapBuilder(imageBean)
}
})
}
.columnsTemplate('1fr 1fr 1fr')//三等分
.columnsGap(10) //列之间间距
.rowsGap(10) //行之间间距
.supportAnimation(true)
.width('90%')
.height(450)
.margin({ top: 20 })
.backgroundColor(0xFAEEE0)
pixelMapBuilder实现
@Builder
pixelMapBuilder(image: ImageSelectBean) { //拖拽过程样式
Image(image.imageUrl)
.width(130)
.height(130)
.onClick(() => {
if (image.isAdd) {
this.getFileAssetsFromType()
}
})
}
2、实现拖拽效果,并设置监听
Grid() {
//...省略的代码
}
.editMode(true) //设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => { //第一次拖拽此事件绑定的组件时,触发回调。
if (!this.numbers[itemIndex].isAdd) {
animateTo({ duration: 600 }, () => {
this.offsetY = 0
})
return this.pixelMapBuilder(this.numbers[itemIndex]) //设置拖拽过程中显示的组件。
}
})
.onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => { //绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调。
// isSuccess=false时,说明drop的位置在grid外部;insertIndex > length时,说明有新增元素的事件发生,this.numbers[insertIndex].isAdd==true,说明是添加按钮
console.info('1111111111 onItemDrop ' + itemIndex + ' insertIndex:', insertIndex + ' isSuccess: ' + isSuccess + " event x: " + event.x + " y: " + event.y) //itemIndex拖拽起始位置,insertIndex拖拽插入位置
if (!isSuccess || insertIndex >= this.numbers.length || this.numbers[insertIndex].isAdd) {
return
}
})
3、在拖拽结束,对换数组内元素
.onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
this.changeIndex(itemIndex, insertIndex)
})
changeIndex(index1: number, index2: number) { //交换数组位置
let temp: ImageSelectBean;
temp = this.numbers[index1];
this.numbers[index1] = this.numbers[index2];
this.numbers[index2] = temp;
}
4、实现底部删除效果
给底部删除布局设置偏移量(offset方法),来动态控制隐藏和显示效果
UI布局
Stack() {
Text('拖到此处删除')
.fontSize(15)
.fontColor(Color.White)
}
.width('100%')
.height(60)
.flexShrink(1)
.backgroundColor(Color.Pink)
.offset({ x: 0, y: this.offsetY })
动态设置偏移量
//拖拽开始,显示删除布局
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
if (!this.numbers[itemIndex].isAdd) {
animateTo({ duration: 600 }, () => {
this.offsetY = 0
})
return this.pixelMapBuilder(this.numbers[itemIndex]) //设置拖拽过程中显示的图片。
}
})
//拖拽结束,隐藏删除布局
.onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
animateTo({ duration: 600 }, () => {
this.offsetY = 60
})
if (!isSuccess || insertIndex >= this.numbers.length || this.numbers[insertIndex].isAdd) {
return
}
this.changeIndex(itemIndex, insertIndex)
})
5、选择图片方法实现
//选择图片
getFileAssetsFromType() {
let selectNumber = this.maxSelectNumber - this.numbers.length + 1
let photoViewPicker = new picker.PhotoViewPicker();
const photoSelectOptions = new picker.PhotoSelectOptions(); // 创建图库对象实例
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; // 选择媒体文件类型为Image
photoSelectOptions.maxSelectNumber = selectNumber < 0 ? 0 : selectNumber; // 选择媒体文件的最大数目
photoViewPicker.select(photoSelectOptions)
.then((photoSelectResult) => {
this.imgDatas = photoSelectResult.photoUris; // select返回的uri权限是只读权限,需要将uri写入全局变量@State中即可根据结果集中的uri进行读取文件数据操作。
console.info('1111111111 选择图片: ' + JSON.stringify(this.imgDatas));
for (let i = this.imgDatas.length - 1; i >= 0; i--) {
console.info('1111111111 forEach 选择图片: ' + this.imgDatas[i]);
let imageBean = new ImageSelectBean(this.imgDatas[i], false)
let unShiftLength = this.numbers.unshift(imageBean)
if (unShiftLength > 9) {
this.numbers.pop()
}
}
})
.catch((err: BusinessError) => {
console.info('1111111111 选择图片出错: ' + err.message);
})
}
因为图片选择涉及到'ohos.permission.READ_MEDIA', 'ohos.permission.WRITE_MEDIA'两个权限,需要在module.json5文件中配置。另外这两个权限是隐私权限需要通过requestPermissionsFromUser方法动态申请权限。
动态申请权限参考链接:OpenHarmony 权限申请介绍
在aboutToAppear初始化方法中申请权限
ImageSelectBean类
export class ImageSelectBean {
imageUrl: string//图片地址
isAdd: boolean//是否是添加按钮
constructor(imageUrl: string, isAdd: boolean) {
this.imageUrl = imageUrl
this.isAdd = isAdd
}
}
6、实现数据删除
1、给删除布局设置onAreaChange监听获取Y轴坐标位置。
2、给最外层父控件设置触摸监听,在手指滑动到删除区域时,改变文字为:‘松手即可删除’,手指抬起时执行数据元素删除,并判断是否添加按钮是否显示,未显示显示添加按钮。
onAreaChange监听:
Stack()
.onAreaChange((oldValue: Area, newValue: Area) => {
console.info("1111111111 oldValue----height: " + oldValue.height + " oldValue: y:" + oldValue.position.y
+ " newValue y:" + newValue.position.y)
this.stackY = newValue.position.y as number
})
父控件设置onTouch触摸监听
父控件().onTouch((event) => this.touchEvent(event))
touchEvent(event: TouchEvent) {
switch (event.type) {
case TouchType.Move: // 手指移动
console.info('1111111111 手指移动: ' + event.touches[0].y)
if (event.touches[0].y + 65 > this.stackY) {
this.stackContent = '松手即可删除'
} else {
this.stackContent = '拖到此处删除'
}
break
case TouchType.Up: // 手指抬起
case TouchType.Cancel: // 触摸意外中断
console.info('1111111111 手指抬起: ' + event.touches[0].y)
if (event.touches[0].y + 65 > this.stackY) {
this.numbers.splice(this.startIndex, 1)
if (!this.showAdd && this.numbers.length < 9) {
let imageSelectBean = new ImageSelectBean('images/add.png', true)
this.numbers.push(imageSelectBean)
this.showAdd = true
}
}
break
}
}