HarmonyOS自定义控件之Material风格的下拉刷新

开发 前端 OpenHarmony
Ohos-MaterialRefreshLayout是一个自定义Material风格下拉刷新控件,支持设置水波纹效果,支持下拉刷新侵入式和非侵入式,初始化自动刷新及上滑加载更多,支持刷新头部自定义图案,上拉加载更多等。

[[420946]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

介绍

Ohos-MaterialRefreshLayout是一个自定义Material风格下拉刷新控件,支持设置水波纹效果,支持下拉刷新侵入式和非侵入式,初始化自动刷新及上滑加载更多,支持刷新头部自定义图案,上拉加载更多等。该控件一般配合ListContainer使用,因涉及事件分发操作,本库中使用了三方控件NestedListContainer、事件分发等方便处理事件拦截分发事件。

效果图:

自定义控件结构

MaterialRefreshLayout控件,首先初始化设置头部、脚部布局,在手势下滑时显示头部布局,动态设置头部高度,展示下拉刷新效果,在页面底部向上滑动时显示脚部布局,展示上拉加载更多效果,松手时图形即开始旋转动画。

下拉圆形转动风格MaterialRefreshLayout

1.MaterialRefreshLayout包含自定义头部布局MaterialHeaderView和脚部布局MaterialFooterView。

2.头部MaterialHeaderView包含圆形转动条CircleProgressBar和下拉波纹MaterialWaveView。

3.脚部布局MaterialFooterView同头部结构一致,包含圆形转动条CircleProgressBar和下拉波纹MaterialWaveView。

4.CircleProgressBar包含有自定义图形的MaterialProgressDrawable,设置圆形的转动图案。

下拉自定义笑脸风格MaterialRefreshLayout

1.MaterialRefreshLayout包含SunLayout头部布局和脚部布局MaterialFooterView。

2.SunLayout头部包含滚动短线SunLineView和笑脸SunFaceView。

3.当有手势下滑时,自定义短线SunLineView,开始旋转动画,监听刷新动作,在onSizeChanged中动态改变图形大小。

4.当手势向下滑动时,自定义笑脸图形SunFaceView,监听刷新动作,在onSizeChanged中动态改变图形大小。

代码实现解读

首先在拦截事件中根据手指的滑动距离,设置自定义头部布局MaterialHeaderView可见,底部向上滑动时,当滑到页面底部,设置脚部布局MaterialFooterView可见。

事件分发onInterceptTouchEvent中设置头、脚布局可见

在拦截事件onInterceptTouchEvent中,手指移动TouchEvent.POINT_MOVE时,根据滑动距离及是否是在头部的滑动,设置头部自定义headerview是否显示,再根据向上滑动距离是否小于0及是否滑动到底部加载底部footerview。代码如下:

  1. case TouchEvent.POINT_MOVE: 
  2.             float currentY = ev.getPointerPosition(0).getY(); 
  3.             Float dy= new BigDecimal(currentY).subtract(new BigDecimal(mTouchY)).floatValue(); 
  4.             if (dy > 0 && !canChildScrollUp()) { 
  5.                 if (mMaterialHeaderView != null) { 
  6.                    mMaterialHeaderView.setVisibility(Component.VISIBLE); 
  7.                     mMaterialHeaderView.onBegin(this); 
  8.                 } else if (mSunLayout != null) { 
  9.                     mSunLayout.setVisibility(Component.VISIBLE); 
  10.                     mSunLayout.onBegin(this); 
  11.                 } 
  12.                 return true
  13.             } else if (dy < 0 && !canChildScrollDown() && isLoadMore) { 
  14.                 if (mMaterialFooterView != null && !isLoadMoreing) { 
  15.                     soveLoadMoreLogic(); 
  16.                 } 
  17.                 return false
  18.             } 
  19.             break; 

上一步完成后,紧接着就是在触摸事件中动态设置头部布局高度,水波纹高度,滑到最大距离时,设置为控件本身高度。

事件触摸onTouchEvent中设置高度

在触摸事件onTouchEvent中,当手指下滑,onTouchEvent中设置头部自定义headerview的高度,随着下滑距离增加,动态设置水波纹高度,当头部为侵入式时,设置component向下平移。代码如下:

  1. case TouchEvent.POINT_MOVE: 
  2.             mCurrentY = e.getPointerPosition(0).getY(); 
  3.             float dy = new BigDecimal(mCurrentY).subtract(new BigDecimal(mTouchY)).floatValue(); 
  4.             dy = Math.min(mWaveHeight * 2, dy); 
  5.             dy = Math.max(0, dy); 
  6.             if (mChildView != null) { 
  7.                 float offsetY = dy / 2; 
  8.                 float fraction = offsetY / mHeadHeight; 
  9.                 if (mMaterialHeaderView != null) { 
  10.                     mMaterialHeaderView.setHeight((int) offsetY); 
  11.                     mMaterialHeaderView.postLayout(); 
  12.                     mMaterialHeaderView.onPull(this, fraction); 
  13.                 } else if (mSunLayout != null) { 
  14.                     mSunLayout.setHeight((int) offsetY); 
  15.                     mSunLayout.postLayout(); 
  16.                     mSunLayout.startSunLineAnim(this); 
  17.                     mSunLayout.onPull(this, fraction); 
  18.                 } 
  19.                 if (!isOverlay) 
  20.                     mChildView.setTranslationY(offsetY); 
  21.             } 

在松手时,监听抬起事件TouchEvent.PRIMARY_POINT_UP,当头部headerview高度大于原有高度时,将头部设置为刷新中状态,代码如下:

  1. if (mMaterialHeaderView.getLayoutConfig().height > mHeadHeight) { 
  2.     updateListener(); 
  3.     mMaterialHeaderView.setHeight((int) mHeadHeight); 
  4.     mMaterialHeaderView.postLayout(); 
  5. }  

再接下来就是完成自定义头部控件的布局,并在下拉接口方法中设置下拉时的缩放,透明度等状态。

自定义头部MaterialHeaderView

自定义MaterialHeaderView由MaterialWaveView和CircleProgressBar两个自定义Component组合成,实现MaterialHeadListener接口。

onBegin方法中设置materialWaveView的起始状态,circleProgressBar缩放大小,透明度等。代码如下:

  1. @Override 
  2. public void onBegin(MaterialRefreshLayout materialRefreshLayout) { 
  3.     if (materialWaveView != null) { 
  4.         materialWaveView.onBegin(materialRefreshLayout); 
  5.     } 
  6.     if (circleProgressBar != null) { 
  7.         circleProgressBar.setScaleX(0.001f); 
  8.         circleProgressBar.setScaleY(0.001f); 
  9.         circleProgressBar.onBegin(materialRefreshLayout); 
  10.     } 
  11.  } 

 onPull方法中设置materialWaveView的下拉状态,circleProgressBar缩放大小,透明度等。代码如下:

  1. @Override 
  2. public void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) { 
  3.     if (materialWaveView != null) { 
  4.         materialWaveView.onPull(materialRefreshLayout, fraction); 
  5.     } 
  6.     if (circleProgressBar != null) { 
  7.         circleProgressBar.onPull(materialRefreshLayout, fraction); 
  8.         float a = Util.limitValue(1, fraction); 
  9.         circleProgressBar.setScaleX(a); 
  10.         circleProgressBar.setScaleY(a); 
  11.         circleProgressBar.setAlpha(a); 
  12.     } 

 设置刷新中onRefreshing状态。代码如下:

  1. @Override 
  2. public void onRefreshing(MaterialRefreshLayout materialRefreshLayout) { 
  3.     if (materialWaveView != null) { 
  4.         materialWaveView.onRefreshing(materialRefreshLayout); 
  5.     } 
  6.     if (circleProgressBar != null) { 
  7.         circleProgressBar.onRefreshing(materialRefreshLayout); 
  8.     } 

 onComlete刷新完成后自定义Component的状态初始化,代码如下:

  1. @Override 
  2.  public void onComlete(MaterialRefreshLayout materialRefreshLayout) { 
  3.    if (materialWaveView != null) { 
  4.        materialWaveView.onComlete(materialRefreshLayout); 
  5.    } 
  6.    if (circleProgressBar != null) { 
  7.        circleProgressBar.onComlete(materialRefreshLayout); 
  8.        circleProgressBar.setTranslationY(0); 
  9.        circleProgressBar.setScaleX(0); 
  10.        circleProgressBar.setScaleY(0); 
  11.   } 

 头部布局完成后,接下来就是实现自定义脚部布局实现。

自定义脚部MaterialFooterView

自定义MaterialFooterView由MaterialWaveView和CircleProgressBar两个自定义Component组合成,实现MaterialHeadListener接口。基本同MaterialHeaderView一致,接口实现方法设置内容相同。

onBegin方法中设置materialWaveView的起始状态,circleProgressBar缩放1,透明度等。代码如下:

  1. @Override 
  2. public void onBegin(MaterialRefreshLayout materialRefreshLayout) { 
  3.     if (materialWaveView != null) { 
  4.         materialWaveView.onBegin(materialRefreshLayout); 
  5.     } 
  6.     if (circleProgressBar != null) { 
  7.         circleProgressBar.onBegin(materialRefreshLayout); 
  8.         circleProgressBar.setScaleX(1); 
  9.         circleProgressBar.setScaleY(1); 
  10.     } 

 onPull方法中设置materialWaveView的下拉状态,circleProgressBar缩放1,透明度等。代码如下:

  1. @Override 
  2. public void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) { 
  3.     if (materialWaveView != null) { 
  4.         materialWaveView.onPull(materialRefreshLayout, fraction); 
  5.     } 
  6.     if (circleProgressBar != null) { 
  7.         circleProgressBar.onPull(materialRefreshLayout, fraction); 
  8.         float a = Util.limitValue(1, fraction); 
  9.         circleProgressBar.setScaleX(1); 
  10.         circleProgressBar.setScaleY(1); 
  11.         circleProgressBar.setAlpha(a); 
  12.     } 

 设置刷新中onRefreshing状态。代码如下:

  1. @Override 
  2. public void onRefreshing(MaterialRefreshLayout materialRefreshLayout) { 
  3.     if (materialWaveView != null) { 
  4.         materialWaveView.onRefreshing(materialRefreshLayout); 
  5.     } 
  6.     if (circleProgressBar != null) { 
  7.         circleProgressBar.onRefreshing(materialRefreshLayout); 
  8.     } 

 onComlete刷新完成后自定义Component的状态初始化,代码如下:

  1. @Override 
  2. public void onComlete(MaterialRefreshLayout materialRefreshLayout) { 
  3.     if (materialWaveView != null) { 
  4.         materialWaveView.onComlete(materialRefreshLayout); 
  5.     } 
  6.     if (circleProgressBar != null) { 
  7.         circleProgressBar.onComlete(materialRefreshLayout); 
  8.         circleProgressBar.setTranslationY(0); 
  9.         circleProgressBar.setScaleX(0); 
  10.         circleProgressBar.setScaleY(0); 
  11.     } 

 头部、脚部布局都完成后,就开始要完成头部和脚部布局里面的自定义组件,首先从头部布局中的自定义组件开始,前面讲到头部由圆形转动条CircleProgressBar和下拉波纹MaterialWaveView组成,先开始绘制波浪纹MaterialWaveView,实现MaterialHeadListener接口,接口回调中设置组件的状态。

自定义MaterialWaveView

初始化画笔设置,添加addDrawTask任务,onDraw方法中绘制下拉区域图形,并填充颜色,代码如下:

  1. @Override 
  2. public void onDraw(Component component, Canvas canvas) { 
  3.     path.reset(); 
  4.     paint.setColor(new Color(color)); 
  5.     path.lineTo(0, headHeight); 
  6.     path.quadTo(getEstimatedWidth() / (float) 2, headHeight + waveHeight, getEstimatedWidth(), headHeight); 
  7.     path.lineTo(getEstimatedWidth(), 0); 
  8.     canvas.drawPath(path, paint); 

实现MaterialHeadListener接口,监听各下拉方法的回调,当有下拉的情形时,改变下拉区域状态。下拉时在onPull中,设置下拉区域header高度及wave高度。刷新中onRefreshing,加载数值动画并动态改变wave高度。结束onComlete中,加载数值动画动态改变head的高度。代码如下:

下拉时:

  1. @Override 
  2. public void onPull(MaterialRefreshLayout br, float fraction) { 
  3.     setHeadHeight((int) (Util.dip2px(getContext(), DefaulHeadHeight) * Util.limitValue(1, fraction))); 
  4.     setWaveHeight((int) (Util.dip2px(getContext(), DefaulWaveHeight) * Math.max(0, new BigDecimal(fraction).subtract(new BigDecimal(1)).floatValue()))); 
  5.     invalidate(); 

 刷新时:

  1. @Override 
  2. public void onRefreshing(MaterialRefreshLayout br) { 
  3.     setHeadHeight((int) (Util.dip2px(getContext(), DefaulHeadHeight))); 
  4.     int waveHeight = getWaveHeight(); 
  5.     AnimatorValue animator = new AnimatorValue(); 
  6.     animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() { 
  7.         @Override 
  8.         public void onUpdate(AnimatorValue animatorValue, float value) { 
  9.             setWaveHeight(getIntValue((1 - (double) value) * waveHeight)); 
  10.             invalidate(); 
  11.         } 
  12.     }); 
  13.     animator.setCurveType(Animator.CurveType.BOUNCE); 
  14.     animator.setDuration(200); 
  15.     animator.start(); 

 结束时: 

  1. @Override 
  2. public void onComlete(MaterialRefreshLayout br) { 
  3.     waveHeight = 0; 
  4.     AnimatorValue animator = new AnimatorValue(); 
  5.     animator.setDuration(200); 
  6.     animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() { 
  7.         @Override 
  8.         public void onUpdate(AnimatorValue animatorValue, float value) { 
  9.             headHeight = getIntValue((1 - (double) value) * headHeight); 
  10.             invalidate(); 
  11.         } 
  12.     }); 
  13.     animator.start(); 

上一步完成后接下来开始实现头部圆形转动的CircleProgressBar,并设置图案的自定义ShapeElement图形,配合手势操作,下拉时设置图形动态大小,松手时旋转刷新。

自定义CircleProgressBar

自定义圆形转动CircleProgressBar,设置自定义背景MaterialProgressDrawable,实现MaterialHeadListener接口,根据下拉状态设置圆形MaterialProgressDrawable旋转角度,释放手势时开始动画,结束后停止旋转并初始化状态等。代码如下:

  1. @Override 
  2. public void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) { 
  3.     if (mProgressDrawable != null
  4.         mProgressDrawable.setProgressRotation(fraction); 
  5.     invalidate(); 
  6.  
  7. @Override 
  8. Public void onRefreshing(MaterialRefreshLayout materialRefreshLayout) { 
  9.     if (mProgressDrawable != null) { 
  10.         mProgressDrawable.onStart(); 
  11.     } 
  12.  
  13. @Override 
  14. public void onComlete(MaterialRefreshLayout materialRefreshLayout) { 
  15.     if (mProgressDrawable != null) { 
  16.         mProgressDrawable.onStop(); 
  17.     } 
  18.     setVisibility(Component.INVISIBLE); 

 自定义MaterialProgressDrawable设置CircleProgressBar的背景

首先构造方法中初始化圆形Ring和旋转动画,设置画笔颜色,宽度,大小,在drawToCanvas中绘制圆形Ring, 当有手势操作时调用onStart方法中的旋转动画,开始旋转。在Ring类draw方法中,根据起始旋转角度绘制圆形圈圈及三角箭头,代码如下:

  1. public void draw(Canvas c, Rect bounds) { 
  2.         final RectFloat arcBounds = mTempBounds; 
  3.         arcBounds.modify(bounds); 
  4.         arcBounds.left = new BigDecimal(arcBounds.left).add(new BigDecimal(mStrokeInset)).floatValue(); 
  5.         arcBounds.top = new BigDecimal(arcBounds.top).add(new BigDecimal(mStrokeInset)).floatValue(); 
  6.         arcBounds.right = new BigDecimal(arcBounds.right).subtract(new BigDecimal(mStrokeInset)).floatValue(); 
  7.         arcBounds.bottom = new BigDecimal(arcBounds.bottom).subtract(new BigDecimal(mStrokeInset)).floatValue(); 
  8.  
  9. ​        final float startAngle = new BigDecimal(mStartTrim).add(new BigDecimal(mRotation)).floatValue() * 360; 
  10. ​        final float endAngle = new BigDecimal(mEndTrim).add(new BigDecimal(mRotation)).floatValue() * 360; 
  11. ​        float sweepAngle = new BigDecimal(endAngle).subtract(new BigDecimal(startAngle)).floatValue(); 
  12.  
  13. ​        mPaint.setColor(Color.RED); 
  14. ​        c.drawArc(arcBounds, new Arc(startAngle, sweepAngle, false), mPaint); 
  15. ​        drawTriangle(c, startAngle, sweepAngle, bounds); 
  16.  
  17. ​        if (mAlpha < 255) { 
  18. ​            mCirclePaint.setColor(new Color(mBackgroundColor)); 
  19. ​            mCirclePaint.setAlpha(255 - mAlpha); 
  20. ​            c.drawCircle(bounds.getCenterX(), bounds.getCenterY(), bounds.getWidth() / (float) 2, 
  21. ​                    mCirclePaint); 
  22. ​        } 
  23. ​    } 

 上述基本上就完成了Material风格下拉刷新带水波纹,带转动progressbar的实现步骤,紧接着讲一讲下拉自定义笑脸的另外一种刷新风格,实际上就是重新定义了刷新头部的图形,在这里也可以自己尝试替换成其它不同的图形。

自定义头部SunLayout布局

自定义头部SunLayout由SunFaceView和SunLineView组成,SunFaceView为自定义笑脸,SunLineView为自定义笑脸周围短线。SunLayout实现了MaterialHeadListener接口,开始状态onBegin时缩放从零到有,下拉onPull时,设置SunView和LineView的大小,缩放等。代码如下:

自定义头部SunLayout由SunFaceView和SunLineView组成,SunFaceView为自定义笑脸,SunLineView为自定义笑脸周围短线。SunLayout实现了MaterialHeadListener接口,开始状态onBegin时缩放从零到有,下拉onPull时,设置SunView和LineView的大小,缩放等。代码如下:

开始时:

  1. @Override 
  2. public void onBegin(MaterialRefreshLayout materialRefreshLayout) { 
  3.     setScaleX(0.001f); 
  4.     setScaleY(0.001f); 

 下拉时:

  1. @Override 
  2. public void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) { 
  3.     float a = Util.limitValue(1, fraction); 
  4.     if (a >= 0.7) { 
  5.         mLineView.setVisibility(VISIBLE); 
  6.     } else { 
  7.         mLineView.setVisibility(HIDE); 
  8.     } 
  9.     mSunView.setPerView(mSunRadius, a); 
  10.     mLineView.setLineWidth(mLineWidth); 
  11.     setScaleX(a); 
  12.     setScaleY(a); 
  13.     setAlpha(a); 
  •  自定义笑脸SunFaceView
  • 自定义短线SunLineView

SunLineView继承Component实现Component.DrawTask, Component.EstimateSizeListener接口,构造方法中初始化Paint,onEstimateSize中测量宽高,onDraw中绘制线条,代码如下:

测量时:

  1. @Override 
  2. public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) { 
  3.     HiLog.info(Contants.LABEL, "onMeasure"); 
  4.     int widthMode = EstimateSpec.getMode(widthMeasureSpec); 
  5.     int widthSize = EstimateSpec.getSize(widthMeasureSpec); 
  6.     int heightMode = EstimateSpec.getMode(heightMeasureSpec); 
  7.     int heightSize = EstimateSpec.getSize(heightMeasureSpec); 
  8.     int width; 
  9.     int height; 
  10.     if (widthMode == EstimateSpec.PRECISE) { 
  11.         width = widthSize; 
  12.     } else { 
  13.         width = (mSunRadius + mFixLineHeight + mLineHeight) * 2 + getPaddingRight() + getPaddingLeft(); 
  14.     } 
  15.     if (heightMode == EstimateSpec.PRECISE) { 
  16.         height = heightSize; 
  17.     } else { 
  18.         height = (mSunRadius + mFixLineHeight + mLineHeight) * 2 + getPaddingTop() + getPaddingBottom(); 
  19.     } 
  20.     setEstimatedSize(width, height); 
  21.     mWidth = width; 
  22.     mHeight = height; 
  23.     return false

画线条:

  1. private void drawLines(Canvas canvas) { 
  2.     for (int i = 0; i <= 360; i++) { 
  3.         if (i % mLineLevel == 0) { 
  4.             mLineLeft = mWidth / 2 - mLineWidth / 2; 
  5.             mLineTop = mHeight / 2 - mSunRadius - mFixLineHeight; 
  6.             mLineBottom = mLineTop + mLineHeight; 
  7.         } 
  8.         canvas.save(); 
  9.         canvas.rotate(i, mWidth / (float) 2, mHeight / (float) 2); 
  10.         canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint); 
  11.         canvas.restore(); 
  12.     } 

代码参考

https://gitee.com/chinasoft5_ohos/Ohos-MaterialRefreshLayout

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-08-16 14:45:38

鸿蒙HarmonyOS应用

2021-08-25 10:14:51

鸿蒙HarmonyOS应用

2021-09-06 14:58:23

鸿蒙HarmonyOS应用

2015-02-11 17:49:35

Android源码自定义控件

2009-06-08 20:13:36

Eclipse自定义控

2009-07-31 10:23:09

ASP.NET源码DateTimePic

2021-08-11 14:29:20

鸿蒙HarmonyOS应用

2009-08-06 09:18:01

ASP.NET自定义控ASP.NET控件开发

2017-02-17 09:37:12

Android自定义控件方法总结

2013-04-19 10:14:24

2021-12-24 15:46:23

鸿蒙HarmonyOS应用

2009-08-06 17:52:45

ASP.NET控件开发自定义控件

2022-06-30 14:02:07

鸿蒙开发消息弹窗组件

2022-07-15 16:45:35

slider滑块组件鸿蒙

2021-10-26 10:07:02

鸿蒙HarmonyOS应用

2009-09-03 13:34:03

.NET自定义控件

2011-08-18 09:44:33

iPhone SDK仪表控件UIDialView

2014-09-24 11:42:46

AndroidButton

2021-03-09 15:23:45

鸿蒙HarmonyOS应用开发

2021-11-01 10:21:36

鸿蒙HarmonyOS应用
点赞
收藏

51CTO技术栈公众号