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);
}
}
@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
代码如下:
@Override
public void onBegin(MaterialRefreshLayout materialRefreshLayout) {
if (materialWaveView != null) {
materialWaveView.onBegin(materialRefreshLayout);
}
if (circleProgressBar != null) {
circleProgressBar.onBegin(materialRefreshLayout);
circleProgressBar.setScaleX(1);
circleProgressBar.setScaleY(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);
}
}
⑤自定义 MaterialWaveView
代码如下:
@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);
}
下拉时:
@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
代码如下:
@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);
}
代码如下:
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);
}
}
⑦自定义头部 SunLayout 布局
开始时:
@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);
}
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
👇点击关注鸿蒙技术社区👇
了解鸿蒙一手资讯

点“阅读原文”了解更多




