您现在的位置是:亿华云 > IT科技
HarmonyOS 自定义组件之上拉抽屉
亿华云2025-10-09 13:14:00【IT科技】7人已围观
简介想了解更多内容,请访问:和华为官方合作共建的鸿蒙技术社区https://harmonyos.51cto.com简介HarmonyOS 开发自定义组件目前还不是很丰富,在开发过程中常常会有一些特殊效果的
想了解更多内容,自组件之上请访问:
和华为官方合作共建的定义鸿蒙技术社区
https://harmonyos.51cto.com
简介
HarmonyOS 开发自定义组件目前还不是很丰富,在开发过程中常常会有一些特殊效果的拉抽组件,这就需要我们额外花一些时间实现,自组件之上这里给大家提供了一个BottomSheet上拉抽屉的定义组件,同时通过这个组件示例讲解一下HarmonyOS中的拉抽几个自定义控件用到的知识,分享一下自己自定义组件的自组件之上思路。
效果演示

实现思路
1.布局设计
选择的定义是相对布局,蒙层区来改变内容区随着抽屉的拉抽位置调节透明度。
图1:


2.手势判断
先得出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; }3.抽屉偏移
这里采用的是整个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); }4.事件冲突解决
首先发现不能按安卓的思想去处理:
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); } ....... }5.背景亮暗变化
首先我们XML布局参照上述布局设计—图1; 背景亮暗的改变根据抽屉位置按比例设置蒙层的透明度; float alpha = (0.5f - Math.abs((float) bottom / (float) H)) * 0.5f; bgComponent.setAlpha(alpha);6.回弹效果
运用到了数值动画,在手势抬起时,判断上下临界点决定动画的上下。
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://harmonyos.51cto.com
很赞哦!(4628)
相关文章
- 因为域名解析需要同步到DNS根服务器,而DNS根服务器会不定时刷,只有DNS根服务器刷新后域名才能正常访问,新增解析一般会在10分钟左右生效,最长不会超过24小时,修改解析时间会稍微延长。
- 冰雪奇缘2火爆来袭!Python带你分析4万多条短评,发现了这几个秘密
- 5 个越早知道越好的 Python 特性
- 分布式与集群是一回事儿么?别让这么简单的问题难住你
- 第六:这个圈子里的域名确实是赚钱的一些大玩家,至于小米农,有多少赚钱?几乎没有,也就是说,轿子里只有一个人,而且大多数人都抬着轿子。
- 谷歌ALBERT模型V2+中文版来了,GitHub热榜第二
- 2020年Java程序员应该学习的10大技术
- Hystrix 实现资源隔离的“两把利器”
- 记住那句话,域名向来不属于任何人,谁先买就归谁,购买期过后,域名又不再属于任何人。
- 2019 年女性程序员报告:掌握 C、Java 和 C++ 的人最多