HarmonyOS 开发自定义组件目前还不是很丰富,在开发过程中常常会有一些特殊效果的组件,这就需要我们额外花一些时间实现。
这里给大家提供了一个 BottomSheet 上拉抽屉的组件,同时通过这个组件示例讲解一下 HarmonyOS 中的几个自定义控件用到的知识,分享一下自己自定义组件的思路。
效果演示如下图:
实现思路
①布局设计
选择的是相对布局,蒙层区来改变内容区随着抽屉的位置调节透明度。如图 1:
②手势判断
先得出 Component 在屏幕的上下左右的坐标,然后手指的坐标是否在 Component 内。
/** *(x,y)是否在view的区域内 * *@paramcomponent *@paramx *@paramy *@return */ privatebooleanisTouchPointInComponent(Componentcomponent,floatx,floaty){ int[]locationOnScreen=component.getLocationOnScreen(); intleft=locationOnScreen[0]; inttop=locationOnScreen[1]; intright=left+component.getEstimatedWidth(); intbottom=top+component.getEstimatedHeight(); booleaninY=y>=top&&y<= bottom; boolean inX = x >=left&&x<= right; return inY && inX; }
③抽屉偏移
步骤如下:
这里采用的是整个 component 对 Touch 事件的监听。
手指按下的判断是否在抽屉上,然后记录当前触摸 y 坐标。
移动是算出偏移量 offY。
setTouchEventListener(newTouchEventListener(){ @Override publicbooleanonTouchEvent(Componentcomponent,TouchEventtouchEvent){ HiLog.info(logLabel,"onTouchEventaction:"+touchEvent.getAction()); switch(touchEvent.getAction()){ caseTouchEvent.PRIMARY_POINT_DOWN: marginBottom=directionalLayout.getMarginBottom(); MmiPointposition=touchEvent.getPointerScreenPosition(0); if(isTouchPointInComponent(directionalLayout,position.getX(),position.getY())){ dragStartPointY=touchEvent.getPointerPosition(0).getY(); returntrue; } break; caseTouchEvent.PRIMARY_POINT_UP: onTouchUp(); break; caseTouchEvent.POINT_MOVE: floaty=touchEvent.getPointerPosition(0).getY(); floatoffY=dragStartPointY-y; setDrawerMarginBottom((int)offY); break; } returnfalse; } });
根据偏移量改变抽屉的位置:
privatevoidsetDrawerMarginBottom(intoffY){ intbottom=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(newTouchEventListener(){ @Override publicbooleanonTouchEvent(Componentcomponent,TouchEventtouchEvent){ marginBottom=directionalLayout.getMarginBottom(); booleandrag_down=listContainer.canScroll(DRAG_DOWN); booleandrag_UP=listContainer.canScroll(DRAG_UP); if(marginBottom==0&&drag_down){ component.setEnabled(true); returntrue; } component.setEnabled(false); returnfalse; } });
这里是抽屉容器定位抽屉时,判断是否打开 ListContainer 事件。
privatevoidsetDrawerMarginBottom(intoffY){ intbottom=marginBottom+offY; if(bottom>0){ bottom=0; listContainer.setEnabled(true); } ....... }
⑤背景亮暗变化
首先我们 XML 布局参照上述布局设计—如图 1。背景亮暗的改变根据抽屉位置按比例设置蒙层的透明度。
floatalpha=(0.5f-Math.abs((float)bottom/(float)H))*0.5f; bgComponent.setAlpha(alpha);
⑥回弹效果
运用到了数值动画,在手势抬起时,判断上下临界点决定动画的上下。
privatevoidonTouchUp(){ HiLog.info(logLabel,"onTouchUp"); createAnimator(); }
privatevoidcreateAnimator(){ marginBottom=directionalLayout.getMarginBottom(); HiLog.info(logLabel,"createAnimatormarginBottom:"+marginBottom); //创建数值动画对象 AnimatorValueanimatorValue=newAnimatorValue(); //动画时长 animatorValue.setDuration(300); //播放前的延迟时间 animatorValue.setDelay(0); //循环次数 animatorValue.setLoopedCount(0); //动画的播放类型 animatorValue.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE); //设置动画过程 animatorValue.setValueUpdateListener(newAnimatorValue.ValueUpdateListener(){ @Override publicvoidonUpdate(AnimatorValueanimatorValue,floatvalue){ HiLog.info(logLabel,"createAnimatorvalue:"+value); if(marginBottom>-H/4){//top HiLog.info(logLabel,"createAnimatortop:"+value); setDrawerBottomOrToP((int)(marginBottom-value*marginBottom)); }else{//bottom HiLog.info(logLabel,"createAnimatorbottom:"+value); inttop=H/2+marginBottom; setDrawerBottomOrToP((int)(marginBottom-value*top)); } } }); //开始启动动画 animatorValue.start(); }
privatevoidsetDrawerBottomOrToP(intbottom){ 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技术社区】欢迎添加关注!文章转载请注明出处。
-
安卓
+关注
关注
5文章
2120浏览量
56990 -
HarmonyOS
+关注
关注
79文章
1967浏览量
29997
原文标题:HarmonyOS“上拉抽屉”效果实现!
文章出处:【微信号:gh_834c4b3d87fe,微信公众号:OpenHarmony技术社区】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论