【中软国际】HarmonyOS自定义控件之Material风格的下拉刷新 原创 精华

发布于 2021-8-24 16:35
浏览
13收藏

介绍

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

效果图:

【中软国际】HarmonyOS自定义控件之Material风格的下拉刷新-开源基础软件社区 【中软国际】HarmonyOS自定义控件之Material风格的下拉刷新-开源基础软件社区 【中软国际】HarmonyOS自定义控件之Material风格的下拉刷新-开源基础软件社区
【中软国际】HarmonyOS自定义控件之Material风格的下拉刷新-开源基础软件社区 【中软国际】HarmonyOS自定义控件之Material风格的下拉刷新-开源基础软件社区

自定义控件结构

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。

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

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

代码实现解读

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

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

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

case TouchEvent.POINT_MOVE:
            float currentY = ev.getPointerPosition(0).getY();
            Float dy= new BigDecimal(currentY).subtract(new BigDecimal(mTouchY)).floatValue();
            if (dy > 0 && !canChildScrollUp()) {
                if (mMaterialHeaderView != null) {
                   mMaterialHeaderView.setVisibility(Component.VISIBLE);
                    mMaterialHeaderView.onBegin(this);
                } else if (mSunLayout != null) {
                    mSunLayout.setVisibility(Component.VISIBLE);
                    mSunLayout.onBegin(this);
                }
                return true;
            } else if (dy < 0 && !canChildScrollDown() && isLoadMore) {
                if (mMaterialFooterView != null && !isLoadMoreing) {
                    soveLoadMoreLogic();
                }
                return false;
            }
            break;

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

事件触摸onTouchEvent中设置高度

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

    case TouchEvent.POINT_MOVE:
                mCurrentY = e.getPointerPosition(0).getY();
                float dy = new BigDecimal(mCurrentY).subtract(new BigDecimal(mTouchY)).floatValue();
                dy = Math.min(mWaveHeight * 2, dy);
                dy = Math.max(0, dy);
                if (mChildView != null) {
                    float offsetY = dy / 2;
                    float fraction = offsetY / mHeadHeight;
                    if (mMaterialHeaderView != null) {
                        mMaterialHeaderView.setHeight((int) offsetY);
                        mMaterialHeaderView.postLayout();
                        mMaterialHeaderView.onPull(this, fraction);
                    } else if (mSunLayout != null) {
                        mSunLayout.setHeight((int) offsetY);
                        mSunLayout.postLayout();
                        mSunLayout.startSunLineAnim(this);
                        mSunLayout.onPull(this, fraction);
                    }
                    if (!isOverlay)
                        mChildView.setTranslationY(offsetY);
                }
    
  • 在松手时,监听抬起事件TouchEvent.PRIMARY_POINT_UP,当头部headerview高度大于原有高度时,将头部设置为刷新中状态,代码如下:

    if (mMaterialHeaderView.getLayoutConfig().height > mHeadHeight) {
        updateListener();
        mMaterialHeaderView.setHeight((int) mHeadHeight);
        mMaterialHeaderView.postLayout();
    } 
    

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

自定义头部MaterialHeaderView

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

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

    @Override
    public void onBegin(MaterialRefreshLayout materialRefreshLayout) {
        if (materialWaveView != null) {
            materialWaveView.onBegin(materialRefreshLayout);
        }
        if (circleProgressBar != null) {
            circleProgressBar.setScaleX(0.001f);
            circleProgressBar.setScaleY(0.001f);
            circleProgressBar.onBegin(materialRefreshLayout);
        }
     }
    
  • onPull方法中设置materialWaveView的下拉状态,circleProgressBar缩放大小,透明度等。代码如下:

    @Override
    public void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) {
        if (materialWaveView != null) {
            materialWaveView.onPull(materialRefreshLayout, fraction);
        }
        if (circleProgressBar != null) {
            circleProgressBar.onPull(materialRefreshLayout, fraction);
            float a = Util.limitValue(1, fraction);
            circleProgressBar.setScaleX(a);
            circleProgressBar.setScaleY(a);
            circleProgressBar.setAlpha(a);
        }
    }
    
  • 设置刷新中onRefreshing状态。代码如下:

    @Override
    public void onRefreshing(MaterialRefreshLayout materialRefreshLayout) {
        if (materialWaveView != null) {
            materialWaveView.onRefreshing(materialRefreshLayout);
        }
        if (circleProgressBar != null) {
            circleProgressBar.onRefreshing(materialRefreshLayout);
        }
    }
    
  • onComlete刷新完成后自定义Component的状态初始化,代码如下:

    @Override
     public void onComlete(MaterialRefreshLayout materialRefreshLayout) {
       if (materialWaveView != null) {
           materialWaveView.onComlete(materialRefreshLayout);
       }
       if (circleProgressBar != null) {
           circleProgressBar.onComlete(materialRefreshLayout);
           circleProgressBar.setTranslationY(0);
           circleProgressBar.setScaleX(0);
           circleProgressBar.setScaleY(0);
      }
    }
    

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

自定义脚部MaterialFooterView

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

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

    @Override
    public void onBegin(MaterialRefreshLayout materialRefreshLayout) {
        if (materialWaveView != null) {
            materialWaveView.onBegin(materialRefreshLayout);
        }
        if (circleProgressBar != null) {
            circleProgressBar.onBegin(materialRefreshLayout);
            circleProgressBar.setScaleX(1);
            circleProgressBar.setScaleY(1);
        }
    }
    
  • onPull方法中设置materialWaveView的下拉状态,circleProgressBar缩放1,透明度等。代码如下:

    @Override
    public void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) {
        if (materialWaveView != null) {
            materialWaveView.onPull(materialRefreshLayout, fraction);
        }
        if (circleProgressBar != null) {
            circleProgressBar.onPull(materialRefreshLayout, fraction);
            float a = Util.limitValue(1, fraction);
            circleProgressBar.setScaleX(1);
            circleProgressBar.setScaleY(1);
            circleProgressBar.setAlpha(a);
        }
    }
    
  • 设置刷新中onRefreshing状态。代码如下:

    @Override
    public void onRefreshing(MaterialRefreshLayout materialRefreshLayout) {
        if (materialWaveView != null) {
            materialWaveView.onRefreshing(materialRefreshLayout);
        }
        if (circleProgressBar != null) {
            circleProgressBar.onRefreshing(materialRefreshLayout);
        }
    }
    
  • onComlete刷新完成后自定义Component的状态初始化,代码如下:####

    @Override
    public void onComlete(MaterialRefreshLayout materialRefreshLayout) {
        if (materialWaveView != null) {
            materialWaveView.onComlete(materialRefreshLayout);
        }
        if (circleProgressBar != null) {
            circleProgressBar.onComlete(materialRefreshLayout);
            circleProgressBar.setTranslationY(0);
            circleProgressBar.setScaleX(0);
            circleProgressBar.setScaleY(0);
        }
    }
    

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

自定义MaterialWaveView

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

    @Override
    public void onDraw(Component component, Canvas canvas) {
        path.reset();
        paint.setColor(new Color(color));
        path.lineTo(0, headHeight);
        path.quadTo(getEstimatedWidth() / (float) 2, headHeight + waveHeight, getEstimatedWidth(), headHeight);
        path.lineTo(getEstimatedWidth(), 0);
        canvas.drawPath(path, paint);
    }
    
  • 实现MaterialHeadListener接口,监听各下拉方法的回调,当有下拉的情形时,改变下拉区域状态。下拉时在onPull中,设置下拉区域header高度及wave高度。刷新中onRefreshing,加载数值动画并动态改变wave高度。结束onComlete中,加载数值动画动态改变head的高度。代码如下:

    • 下拉时:

      @Override
      public void onPull(MaterialRefreshLayout br, float fraction) {
          setHeadHeight((int) (Util.dip2px(getContext(), DefaulHeadHeight) * Util.limitValue(1, fraction)));
          setWaveHeight((int) (Util.dip2px(getContext(), DefaulWaveHeight) * Math.max(0, new BigDecimal(fraction).subtract(new BigDecimal(1)).floatValue())));
          invalidate();
      }
      
    • 刷新时:

      @Override
      public void onRefreshing(MaterialRefreshLayout br) {
          setHeadHeight((int) (Util.dip2px(getContext(), DefaulHeadHeight)));
          int waveHeight = getWaveHeight();
          AnimatorValue animator = new AnimatorValue();
          animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
              @Override
              public void onUpdate(AnimatorValue animatorValue, float value) {
                  setWaveHeight(getIntValue((1 - (double) value) * waveHeight));
                  invalidate();
              }
          });
          animator.setCurveType(Animator.CurveType.BOUNCE);
          animator.setDuration(200);
          animator.start();
      }
      
    • 结束时:

      @Override
      public void onComlete(MaterialRefreshLayout br) {
          waveHeight = 0;
          AnimatorValue animator = new AnimatorValue();
          animator.setDuration(200);
          animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
              @Override
              public void onUpdate(AnimatorValue animatorValue, float value) {
                  headHeight = getIntValue((1 - (double) value) * headHeight);
                  invalidate();
              }
          });
          animator.start();
      }
      

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

自定义CircleProgressBar

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

    @Override
    public void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) {
        if (mProgressDrawable != null)
            mProgressDrawable.setProgressRotation(fraction);
    	invalidate();
    }
    
    @Override
    Public void onRefreshing(MaterialRefreshLayout materialRefreshLayout) {
        if (mProgressDrawable != null) {
            mProgressDrawable.onStart();
        }
    }
    
    @Override
    public void onComlete(MaterialRefreshLayout materialRefreshLayout) {
        if (mProgressDrawable != null) {
            mProgressDrawable.onStop();
        }
        setVisibility(Component.INVISIBLE);
    }
    
  • 自定义MaterialProgressDrawable设置CircleProgressBar的背景

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

public void draw(Canvas c, Rect bounds) {
        final RectFloat arcBounds = mTempBounds;
        arcBounds.modify(bounds);
        arcBounds.left = new BigDecimal(arcBounds.left).add(new BigDecimal(mStrokeInset)).floatValue();
        arcBounds.top = new BigDecimal(arcBounds.top).add(new BigDecimal(mStrokeInset)).floatValue();
        arcBounds.right = new BigDecimal(arcBounds.right).subtract(new BigDecimal(mStrokeInset)).floatValue();
        arcBounds.bottom = new BigDecimal(arcBounds.bottom).subtract(new BigDecimal(mStrokeInset)).floatValue();

​        final float startAngle = new BigDecimal(mStartTrim).add(new BigDecimal(mRotation)).floatValue() * 360;
​        final float endAngle = new BigDecimal(mEndTrim).add(new BigDecimal(mRotation)).floatValue() * 360;
​        float sweepAngle = new BigDecimal(endAngle).subtract(new BigDecimal(startAngle)).floatValue();

​        mPaint.setColor(Color.RED);
​        c.drawArc(arcBounds, new Arc(startAngle, sweepAngle, false), mPaint);
​        drawTriangle(c, startAngle, sweepAngle, bounds);

​        if (mAlpha < 255) {
​            mCirclePaint.setColor(new Color(mBackgroundColor));
​            mCirclePaint.setAlpha(255 - mAlpha);
​            c.drawCircle(bounds.getCenterX(), bounds.getCenterY(), bounds.getWidth() / (float) 2,
​                    mCirclePaint);
​        }
​    }

上述基本上就完成了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的大小,缩放等。代码如下:

  • 开始时:

    @Override
    public void onBegin(MaterialRefreshLayout materialRefreshLayout) {
        setScaleX(0.001f);
        setScaleY(0.001f);
    }
    
  • 下拉时:

    @Override
    public void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) {
        float a = Util.limitValue(1, fraction);
        if (a >= 0.7) {
            mLineView.setVisibility(VISIBLE);
        } else {
            mLineView.setVisibility(HIDE);
        }
        mSunView.setPerView(mSunRadius, a);
        mLineView.setLineWidth(mLineWidth);
        setScaleX(a);
        setScaleY(a);
        setAlpha(a);
    }
    
  • 自定义笑脸SunFaceView

  • 自定义短线SunLineView

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

  • 测量时:

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        HiLog.info(Contants.LABEL, "onMeasure");
        int widthMode = EstimateSpec.getMode(widthMeasureSpec);
        int widthSize = EstimateSpec.getSize(widthMeasureSpec);
        int heightMode = EstimateSpec.getMode(heightMeasureSpec);
        int heightSize = EstimateSpec.getSize(heightMeasureSpec);
        int width;
        int height;
        if (widthMode == EstimateSpec.PRECISE) {
            width = widthSize;
        } else {
            width = (mSunRadius + mFixLineHeight + mLineHeight) * 2 + getPaddingRight() + getPaddingLeft();
        }
        if (heightMode == EstimateSpec.PRECISE) {
            height = heightSize;
        } else {
            height = (mSunRadius + mFixLineHeight + mLineHeight) * 2 + getPaddingTop() + getPaddingBottom();
        }
        setEstimatedSize(width, height);
        mWidth = width;
        mHeight = height;
        return false;
    }
    
  • 画线条:

    private void drawLines(Canvas canvas) {
        for (int i = 0; i <= 360; i++) {
            if (i % mLineLevel == 0) {
                mLineLeft = mWidth / 2 - mLineWidth / 2;
                mLineTop = mHeight / 2 - mSunRadius - mFixLineHeight;
                mLineBottom = mLineTop + mLineHeight;
            }
            canvas.save();
            canvas.rotate(i, mWidth / (float) 2, mHeight / (float) 2);
            canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);
            canvas.restore();
        }
    }
    

代码参考

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

作者:卢经纬

更多原创内容请关注:中软国际 HarmonyOS 技术学院

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-8-25 18:14:03修改
12
收藏 13
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐