# TouchEventDemo **Repository Path**: guangdong-wangduoyu/touch-event-demo ## Basic Information - **Project Name**: TouchEventDemo - **Description**: 鸿蒙版java 上拉抽屉demo - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2021-11-26 - **Last Updated**: 2022-03-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HarmonyOS 自定义View之上拉抽屉 ## 简介 HarmonyOS 开发自定义的控件并不多,于是自定义一个抽屉;顺便普及一下鸿蒙中的几个自定义控件用到的知识;分享一下自定义view思路; ## 效果演示 ![up](up.gif) ## 问题思考 1. 如何判断手指在抽屉之内? 2. 如何实现抽屉跟随手势滑动? 3. 抽屉布局内是列表,它们之间的是否有滑动冲突?如何解决? 4. 随便滑动位置,背景颜色变暗,应该采用如何布局? 5. 滑动到一半,抽屉回弹效果实现? ## 实现思路 - **布局** 选择的是相对布局,蒙层区来改变内容区随着抽屉的位置调节透明度; ![草图](草图.png) - **如何判断手指在抽屉之内?** 先得出Component在屏幕的上下左右的坐标;然后手指的坐标是否在Component内; ```java /** * (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; } ``` - **如何实现抽屉跟随手势滑动?** 1,这里采用的是整个component对Touch事件的监听; 2,手指按下的判断是否在抽屉上,然后记录当前触摸y坐标, 3,移动是算出偏移量offY; ```java 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; } }); ``` 根据偏移量改变抽屉的位置; ```java 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); } ``` - **抽屉布局内是列表,它们之间的是否有滑动冲突?如何解决?** 首先发现不能按安卓的思想去处理; ​ 1,鸿蒙中是没有事件分发这概念的,只有事件消费,ListContainer先拿到事件,然后是抽屉布局; ​ 2,根据抽屉在完全展开的位置,在ListContainer收到触摸事件是,把ListContainer事件静止掉,不然其消费; ​ 3,待抽屉完全展开时,解开ListContainer的事件; ```java 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事件 ```java private void setDrawerMarginBottom(int offY) { int bottom = marginBottom + offY; if (bottom > 0) { bottom = 0; listContainer.setEnabled(true); } ....... } ``` - **随便滑动位置,背景颜色变暗,应该采用如何布局?** 1,布局问题,见上图 2,背景改变根据抽屉位置按比例设置的透明度 ```java float alpha = (0.5f - Math.abs((float) bottom / (float) H)) * 0.5f; bgComponent.setAlpha(alpha); ``` - **滑动到一半,抽屉回弹效果实现?** 运用到了数值动画;在手势抬起时,判断上下临界点决定动画的上下; ```java private void onTouchUp() { HiLog.info(logLabel, "onTouchUp"); createAnimator(); } ``` ```java 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(); } ``` ```java 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); } ```