 
 想了解更多内容,自组件之上请访问: 和华为官方合作共建的定义鸿蒙技术社区 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;     } });         这里是WordPress模板抽屉容器定位抽屉时,判断是否打开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  
  |