HarmonyOS 开发自定义组件目前还不是很丰富,在开发过程中常常会有一些特殊效果的组件,这就需要我们额外花一些时间实现。
这里给大家提供了一个 BottomSheet 上拉抽屉的组件,同时通过这个组件示例讲解一下 HarmonyOS 中的几个自定义控件用到的知识,分享一下自己自定义组件的思路。
效果演示如下图:
实现思路
①布局设计
选择的是相对布局,蒙层区来改变内容区随着抽屉的位置调节透明度。如图 1:
②手势判断
先得出 Component 在屏幕的上下左右的坐标,然后手指的坐标是否在 Component 内。
/**
* (x,y)是否在view的区域内
*
* @param component
* @param x
* @param y
* @return
*/
private boolean isTouchPointInComponent(Component component, float x, float y) {
int[] locationOnScreen = component.getLocationOnScreen();
int left = locationOnScreen[0];
int top = locationOnScreen[1];
int right = left + component.getEstimatedWidth();
int bottom = top + component.getEstimatedHeight();
boolean inY = y >= top && y <= bottom;
boolean inX = x >= left && x <= right;
return inY && inX;
}
③抽屉偏移
步骤如下:
这里采用的是整个 component 对 Touch 事件的监听。
手指按下的判断是否在抽屉上,然后记录当前触摸 y 坐标。
移动是算出偏移量 offY。
setTouchEventListener(new TouchEventListener() {
@Override
public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
HiLog.info(logLabel, "onTouchEvent action:" + touchEvent.getAction());
switch (touchEvent.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN:
marginBottom = directionalLayout.getMarginBottom();
MmiPoint position = touchEvent.getPointerScreenPosition(0);
if (isTouchPointInComponent(directionalLayout, position.getX(), position.getY())) {
dragStartPointY = touchEvent.getPointerPosition(0).getY();
return true;
}
break;
case TouchEvent.PRIMARY_POINT_UP:
onTouchUp();
break;
case TouchEvent.POINT_MOVE:
float y = touchEvent.getPointerPosition(0).getY();
float offY = dragStartPointY - y;
setDrawerMarginBottom((int) offY);
break;
}
return false;
}
});
根据偏移量改变抽屉的位置:
private void setDrawerMarginBottom(int offY) {
int bottom = marginBottom + offY;
if (bottom > 0) {
bottom = 0;
listContainer.setEnabled(true);
}
if (bottom < -H / 2) {
bottom = -H / 2;
}
HiLog.info(logLabel, "setDrawerMarginBottom bottom:" + bottom);
float alpha = (0.5f - Math.abs((float) bottom / (float) H)) * 0.5f;
HiLog.info(logLabel, "setDrawerMarginBottom alpha:" + alpha);
bgComponent.setAlpha(alpha);
directionalLayout.setMarginBottom(bottom);
}
④事件冲突解决
首先发现不能按安卓的思想去处理:
HarmonyOS 中是没有事件分发这概念的,只有事件消费,ListContainer 先拿到事件,然后是抽屉布局。
根据抽屉在完全展开的位置,在 ListContainer 收到触摸事件时,把 ListContainer 事件静止掉,不让其消费。
待抽屉完全展开时,解开 ListContainer 的事件。
listContainer.setTouchEventListener(new TouchEventListener() {
@Override
public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
marginBottom = directionalLayout.getMarginBottom();
boolean drag_down = listContainer.canScroll(DRAG_DOWN);
boolean drag_UP = listContainer.canScroll(DRAG_UP);
if (marginBottom == 0 && drag_down) {
component.setEnabled(true);
return true;
}
component.setEnabled(false);
return false;
}
});
这里是抽屉容器定位抽屉时,判断是否打开 ListContainer 事件。
private void setDrawerMarginBottom(int offY) {
int bottom = marginBottom + offY;
if (bottom > 0) {
bottom = 0;
listContainer.setEnabled(true);
}
.......
}
⑤背景亮暗变化
首先我们 XML 布局参照上述布局设计—如图 1。背景亮暗的改变根据抽屉位置按比例设置蒙层的透明度。
float alpha = (0.5f - Math.abs((float) bottom / (float) H)) * 0.5f; bgComponent.setAlpha(alpha);
⑥回弹效果
运用到了数值动画,在手势抬起时,判断上下临界点决定动画的上下。
private void onTouchUp() {
HiLog.info(logLabel, "onTouchUp");
createAnimator();
}
private void createAnimator() {
marginBottom = directionalLayout.getMarginBottom();
HiLog.info(logLabel, "createAnimator marginBottom:" + marginBottom);
//创建数值动画对象
AnimatorValue animatorValue = new AnimatorValue();
//动画时长
animatorValue.setDuration(300);
//播放前的延迟时间
animatorValue.setDelay(0);
//循环次数
animatorValue.setLoopedCount(0);
//动画的播放类型
animatorValue.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
//设置动画过程
animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
@Override
public void onUpdate(AnimatorValue animatorValue, float value) {
HiLog.info(logLabel, "createAnimator value:" + value);
if (marginBottom > -H / 4) { // top
HiLog.info(logLabel, "createAnimator top:" + value);
setDrawerBottomOrToP((int) (marginBottom - value * marginBottom));
} else { // bottom
HiLog.info(logLabel, "createAnimator bottom:" + value);
int top = H / 2 + marginBottom;
setDrawerBottomOrToP((int) (marginBottom - value *top));
}
}
});
//开始启动动画
animatorValue.start();
}
private void setDrawerBottomOrToP(int bottom) {
if (bottom > 0) {
bottom = 0;
listContainer.setEnabled(true);
}
if (bottom < -H / 2) {
bottom = -H / 2;
}
float alpha = (0.5f - Math.abs((float) bottom / (float) H)) * 0.5f;
bgComponent.setAlpha(alpha);
directionalLayout.setMarginBottom(bottom);
}
总结
自定义组件步骤及思考方向:
明确父容器和子 view 的关系。
如何绘制一般采用以下三个方向:已有控件组合;采用画布绘制等;继承控件扩展功能。
若涉及到触摸事件,需要考虑如何处理事件分发与消费。
动画选择,可根据需求选择合适动画(本文采用属性动画)。
计算问题,复杂的需要丰富的数学知识。
性能问题(过度计算,重复绘制,对象重复创建)。
代码地址:
https://gitee.com/guangdong-wangduoyu/touch-event-demo
原文标题:HarmonyOS“上拉抽屉”效果实现!
文章出处:【微信公众号:HarmonyOS技术社区】欢迎添加关注!文章转载请注明出处。
全部0条评论
快来发表一下你的评论吧 !