Ohos-MaterialRefreshLayout 是一個(gè)自定義 Material 風(fēng)格下拉刷新控件,支持設(shè)置水波紋效果,支持下拉刷新侵入式和非侵入式,初始化自動(dòng)刷新及上滑加載更多,支持刷新頭部自定義圖案,上拉加載更多等。
該控件一般配合 ListContainer 使用,因涉及事件分發(fā)操作,本庫(kù)中使用了三方控件 NestedListContainer、事件分發(fā)等方便處理事件攔截分發(fā)事件。
自定義控件結(jié)構(gòu)
MaterialRefreshLayout 控件,首先初始化設(shè)置頭部、腳部布局,在手勢(shì)下滑時(shí)顯示頭部布局,動(dòng)態(tài)設(shè)置頭部高度,展示下拉刷新效果,在頁(yè)面底部向上滑動(dòng)時(shí)顯示腳部布局,展示上拉加載更多效果,松手時(shí)圖形即開(kāi)始旋轉(zhuǎn)動(dòng)畫(huà)。
下拉圓形轉(zhuǎn)動(dòng)風(fēng)格 MaterialRefreshLayout:
MaterialRefreshLayout 包含自定義頭部布局 MaterialHeaderView 和腳部布局 MaterialFooterView。
頭部 MaterialHeaderView 包含圓形轉(zhuǎn)動(dòng)條 CircleProgressBar 和下拉波紋 MaterialWaveView。
腳部布局 MaterialFooterView 同頭部結(jié)構(gòu)一致,包含圓形轉(zhuǎn)動(dòng)條 CircleProgressBar 和下拉波紋 MaterialWaveView。
CircleProgressBar 包含有自定義圖形的 MaterialProgressDrawable,設(shè)置圓形的轉(zhuǎn)動(dòng)圖案。
下拉自定義笑臉風(fēng)格 MaterialRefreshLayout:
MaterialRefreshLayout 包含 SunLayout 頭部布局和腳部布局 MaterialFooterView。
SunLayout 頭部包含滾動(dòng)短線 SunLineView 和笑臉 SunFaceView。
當(dāng)有手勢(shì)下滑時(shí),自定義短線 SunLineView,開(kāi)始旋轉(zhuǎn)動(dòng)畫(huà),監(jiān)聽(tīng)刷新動(dòng)作,在 onSizeChanged 中動(dòng)態(tài)改變圖形大小。
當(dāng)手勢(shì)向下滑動(dòng)時(shí),自定義笑臉圖形 SunFaceView,監(jiān)聽(tīng)刷新動(dòng)作,在 onSizeChanged 中動(dòng)態(tài)改變圖形大小。
代碼實(shí)現(xiàn)解讀
首先在攔截事件中根據(jù)手指的滑動(dòng)距離,設(shè)置自定義頭部布局 MaterialHeaderView 可見(jiàn),底部向上滑動(dòng)時(shí),當(dāng)滑到頁(yè)面底部,設(shè)置腳部布局 MaterialFooterView 可見(jiàn)。
①事件分發(fā) onInterceptTouchEvent 中設(shè)置頭、腳布局可見(jiàn)
在攔截事件 onInterceptTouchEvent 中,手指移動(dòng) TouchEvent.POINT_MOVE 時(shí),根據(jù)滑動(dòng)距離及是否是在頭部的滑動(dòng)。
設(shè)置頭部自定義 headerview 是否顯示,再根據(jù)向上滑動(dòng)距離是否小于 0 及是否滑動(dòng)到底部加載底部 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;
上一步完成后,緊接著就是在觸摸事件中動(dòng)態(tài)設(shè)置頭部布局高度,水波紋高度,滑到最大距離時(shí),設(shè)置為控件本身高度。
②事件觸摸 onTouchEvent 中設(shè)置高度
在觸摸事件 onTouchEvent 中,當(dāng)手指下滑,onTouchEvent 中設(shè)置頭部自定義 headerview 的高度,隨著下滑距離增加,動(dòng)態(tài)設(shè)置水波紋高度,當(dāng)頭部為侵入式時(shí),設(shè)置 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);
}
在松手時(shí),監(jiān)聽(tīng)抬起事件 TouchEvent.PRIMARY_POINT_UP,當(dāng)頭部 headerview 高度大于原有高度時(shí),將頭部設(shè)置為刷新中狀態(tài)。
代碼如下:
if (mMaterialHeaderView.getLayoutConfig().height 》 mHeadHeight) {
updateListener();
mMaterialHeaderView.setHeight((int) mHeadHeight);
mMaterialHeaderView.postLayout();
}
再接下來(lái)就是完成自定義頭部控件的布局,并在下拉接口方法中設(shè)置下拉時(shí)的縮放,透明度等狀態(tài)。
③自定義頭部 MaterialHeaderView
自定義 MaterialHeaderView 由 MaterialWaveView 和 CircleProgressBar 兩個(gè)自定義 Component 組合成,實(shí)現(xiàn) MaterialHeadListener 接口。
onBegin 方法中設(shè)置 materialWaveView 的起始狀態(tài),circleProgressBar 縮放大小,透明度等。
代碼如下:
@Overridepublic 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 方法中設(shè)置 materialWaveView 的下拉狀態(tài),circleProgressBar 縮放大小,透明度等。
代碼如下:
@Overridepublic 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);
}
}
設(shè)置刷新中 onRefreshing 狀態(tài)。代碼如下:
@Overridepublic void onRefreshing(MaterialRefreshLayout materialRefreshLayout) {
if (materialWaveView != null) {
materialWaveView.onRefreshing(materialRefreshLayout);
}
if (circleProgressBar != null) {
circleProgressBar.onRefreshing(materialRefreshLayout);
}
}
onComlete 刷新完成后自定義 Component 的狀態(tài)初始化,代碼如下:
@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);
}
}
頭部布局完成后,接下來(lái)就是實(shí)現(xiàn)自定義腳部布局實(shí)現(xiàn)。
④自定義腳部 MaterialFooterView
自定義 MaterialFooterView 由 MaterialWaveView 和 CircleProgressBar 兩個(gè)自定義 Component 組合成,實(shí)現(xiàn) MaterialHeadListener 接口。基本同 MaterialHeaderView 一致,接口實(shí)現(xiàn)方法設(shè)置內(nèi)容相同。
onBegin 方法中設(shè)置 materialWaveView 的起始狀態(tài),circleProgressBar 縮放 1,透明度等。
代碼如下:
@Overridepublic void onBegin(MaterialRefreshLayout materialRefreshLayout) {
if (materialWaveView != null) {
materialWaveView.onBegin(materialRefreshLayout);
}
if (circleProgressBar != null) {
circleProgressBar.onBegin(materialRefreshLayout);
circleProgressBar.setScaleX(1);
circleProgressBar.setScaleY(1);
}
}
onPull 方法中設(shè)置 materialWaveView 的下拉狀態(tài),circleProgressBar 縮放 1,透明度等。
代碼如下:
@Overridepublic 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);
}
}
設(shè)置刷新中 onRefreshing 狀態(tài)。代碼如下:
@Overridepublic void onRefreshing(MaterialRefreshLayout materialRefreshLayout) {
if (materialWaveView != null) {
materialWaveView.onRefreshing(materialRefreshLayout);
}
if (circleProgressBar != null) {
circleProgressBar.onRefreshing(materialRefreshLayout);
}
}
onComlete 刷新完成后自定義 Component 的狀態(tài)初始化,代碼如下:
@Overridepublic 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);
}
}
頭部、腳部布局都完成后,就開(kāi)始要完成頭部和腳部布局里面的自定義組件,首先從頭部布局中的自定義組件開(kāi)始。
前面講到頭部由圓形轉(zhuǎn)動(dòng)條 CircleProgressBar 和下拉波紋 MaterialWaveView 組成,先開(kāi)始繪制波浪紋 MaterialWaveView,實(shí)現(xiàn) MaterialHeadListener 接口,接口回調(diào)中設(shè)置組件的狀態(tài)。
⑤自定義 MaterialWaveView
初始化畫(huà)筆設(shè)置,添加 addDrawTask 任務(wù),onDraw 方法中繪制下拉區(qū)域圖形,并填充顏色。
代碼如下:
@Overridepublic 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);
}
實(shí)現(xiàn) MaterialHeadListener 接口,監(jiān)聽(tīng)各下拉方法的回調(diào),當(dāng)有下拉的情形時(shí),改變下拉區(qū)域狀態(tài)。下拉時(shí)在 onPull 中,設(shè)置下拉區(qū)域 header 高度及 wave 高度。
刷新中 onRefreshing,加載數(shù)值動(dòng)畫(huà)并動(dòng)態(tài)改變 wave 高度。結(jié)束 onComlete 中,加載數(shù)值動(dòng)畫(huà)動(dòng)態(tài)改變 head 的高度。代碼如下:
下拉時(shí):
@Overridepublic 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();
}
刷新時(shí):
@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();
}
結(jié)束時(shí):
@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();
}
上一步完成后接下來(lái)開(kāi)始實(shí)現(xiàn)頭部圓形轉(zhuǎn)動(dòng)的 CircleProgressBar,并設(shè)置圖案的自定義 ShapeElement 圖形,配合手勢(shì)操作,下拉時(shí)設(shè)置圖形動(dòng)態(tài)大小,松手時(shí)旋轉(zhuǎn)刷新。
⑥自定義 CircleProgressBar
自定義圓形轉(zhuǎn)動(dòng) CircleProgressBar,設(shè)置自定義背景 MaterialProgressDrawable,實(shí)現(xiàn) MaterialHeadListener 接口。
根據(jù)下拉狀態(tài)設(shè)置圓形 MaterialProgressDrawable 旋轉(zhuǎn)角度,釋放手勢(shì)時(shí)開(kāi)始動(dòng)畫(huà),結(jié)束后停止旋轉(zhuǎn)并初始化狀態(tài)等。
代碼如下:
@Overridepublic void onPull(MaterialRefreshLayout materialRefreshLayout, float fraction) {
if (mProgressDrawable != null)
mProgressDrawable.setProgressRotation(fraction);
invalidate();
}
@OverridePublic void onRefreshing(MaterialRefreshLayout materialRefreshLayout) {
if (mProgressDrawable != null) {
mProgressDrawable.onStart();
}
}
@Overridepublic void onComlete(MaterialRefreshLayout materialRefreshLayout) {
if (mProgressDrawable != null) {
mProgressDrawable.onStop();
}
setVisibility(Component.INVISIBLE);
}
自定義 MaterialProgressDrawable 設(shè)置 CircleProgressBar 的背景,首先構(gòu)造方法中初始化圓形 Ring 和旋轉(zhuǎn)動(dòng)畫(huà),設(shè)置畫(huà)筆顏色,寬度,大小,在 drawToCanvas 中繪制圓形 Ring。
當(dāng)有手勢(shì)操作時(shí)調(diào)用 onStart 方法中的旋轉(zhuǎn)動(dòng)畫(huà),開(kāi)始旋轉(zhuǎn)。在 Ring 類 draw 方法中,根據(jù)起始旋轉(zhuǎn)角度繪制圓形圈圈及三角箭頭。
代碼如下:
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 風(fēng)格下拉刷新帶水波紋,帶轉(zhuǎn)動(dòng) progressbar 的實(shí)現(xiàn)步驟,緊接著講一講下拉自定義笑臉的另外一種刷新風(fēng)格,實(shí)際上就是重新定義了刷新頭部的圖形,在這里也可以自己嘗試替換成其它不同的圖形。
⑦自定義頭部 SunLayout 布局
自定義頭部 SunLayout 由 SunFaceView 和 SunLineView 組成,SunFaceView 為自定義笑臉,SunLineView 為自定義笑臉周圍短線。
SunLayout 實(shí)現(xiàn)了 MaterialHeadListener 接口,開(kāi)始狀態(tài) onBegin 時(shí)縮放從零到有,下拉 onPull 時(shí),設(shè)置 SunView 和 LineView 的大小,縮放等。代碼如下:
開(kāi)始時(shí):
@Override
public void onBegin(MaterialRefreshLayout materialRefreshLayout) {
setScaleX(0.001f);
setScaleY(0.001f);
}
下拉時(shí):
@Overridepublic 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 實(shí)現(xiàn) Component.DrawTask, Component.EstimateSizeListener 接口,構(gòu)造方法中初始化 Paint,onEstimateSize 中測(cè)量寬高,onDraw 中繪制線條。代碼如下:
測(cè)量時(shí):
@Overridepublic 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;
}
畫(huà)線條:
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
作者:盧經(jīng)緯
責(zé)任編輯:haq
-
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
7401瀏覽量
129279 -
中軟國(guó)際
+關(guān)注
關(guān)注
0文章
726瀏覽量
8184 -
鴻蒙系統(tǒng)
+關(guān)注
關(guān)注
183文章
2642瀏覽量
69832 -
HarmonyOS
+關(guān)注
關(guān)注
80文章
2153瀏覽量
36039
原文標(biāo)題:鴻蒙下拉刷新組件,這個(gè)最好用!
文章出處:【微信號(hào):gh_834c4b3d87fe,微信公眾號(hào):OpenHarmony技術(shù)社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
如何為 Vision Five 2 編譯自定義 Linux 內(nèi)核?
電能質(zhì)量監(jiān)測(cè)裝置可自定義監(jiān)測(cè)時(shí)段嗎?
電能質(zhì)量在線監(jiān)測(cè)裝置可自定義監(jiān)測(cè)時(shí)段嗎?
無(wú)圖形界面模式下自定義檢查工具的應(yīng)用
采用匯編指示符來(lái)使用自定義指令
【M-K1HSE開(kāi)發(fā)板免費(fèi)體驗(yàn)】相關(guān)源碼之閱讀和分析1-使用XComponent + Vsync 實(shí)現(xiàn)自定義動(dòng)畫(huà)
LOTO示波器自定義解碼功能—CANFD解碼
KiCad 中的自定義規(guī)則(KiCon 演講)
HarmonyOS實(shí)戰(zhàn):自定義時(shí)間選擇器
HarmonyOS實(shí)戰(zhàn):高德地圖自定義定位圖標(biāo)展示
鴻蒙系統(tǒng)如何設(shè)置自定義下拉刷新控件
評(píng)論