暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

鸿蒙下拉刷新组件,这个最好用!

鸿蒙技术社区 2021-09-06
824

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


该控件一般配合 ListContainer 使用,因涉及事件分发操作,本库中使用了三方控件 NestedListContainer、事件分发等方便处理事件拦截分发事件。


效果图如下:

自定义控件结构


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


下拉圆形转动风格 MaterialRefreshLayout:

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

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

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

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


下拉自定义笑脸风格 MaterialRefreshLayout:

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

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

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

  • 当手势向下滑动时,自定义笑脸图形 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() / (float2, 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(0new 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 - (doublevalue) * 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 - (doublevalue) * 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() / (float2,
                    mCirclePaint);
        }
    }


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


⑦自定义头部 SunLayout 布局


自定义头部 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 / (float2, mHeight / (float2);
        canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);
        canvas.restore();
    }
}


代码参考:

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


作者:卢经纬


👇点击关注鸿蒙技术社区👇

了解鸿蒙一手资讯


“阅读原文”了解更多

文章转载自鸿蒙技术社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论