HarmonyOS实现Material风格的下拉刷新
suiw9 2024-11-24 21:40 13 浏览 0 评论
介绍
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。
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的大小,缩放等。代码如下:
- 开始时:
@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
继承Component实现Component.EstimateSizeListener, Component.DrawTask接口,onEstimateSize中获取实际宽高,根据宽高onDraw中绘制圆形笑脸,代码如下:
private void drawCircle(Canvas canvas) {
mCirclePaint.setColor(new Color(mSunColor));
mCirclePaint.setStyle(Paint.Style.FILL_STYLE);
canvas.drawCircle(mWidth / (float) 2, mHeight / (float) 2, mSunRadius, mCirclePaint);
mCirclePaint.setColor(Color.WHITE);
if (isDrawFace) {
mouthRect.left = mWidth / 2 - mSunRadius / 2;
mouthRect.right = mWidth / 2 + mSunRadius / 2;
mouthRect.top = mHeight / 2 - mSunRadius / 2;
mouthRect.bottom = mHeight / 2 + mSunRadius / 2;
canvas.save();
canvas.drawCircle(mWidth / 2 - mSunRadius / 2 + mEyesRadius, mHeight / 2 - mSunRadius / 2 + mEyesRadius, mEyesRadius, mCirclePaint);
canvas.drawCircle(mWidth / 2 + mSunRadius / 2 - mEyesRadius, mHeight / 2 - mSunRadius / 2 + mEyesRadius, mEyesRadius, mCirclePaint);
mCirclePaint.setStyle(Paint.Style.STROKE_STYLE);
mCirclePaint.setStrokeWidth(mouthStro);
canvas.drawArc(mouthRect, new Arc(20, 140, false), mCirclePaint);
canvas.restore();
}
}
- 自定义短线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
相关推荐
- 俄罗斯的 HTTPS 也要被废了?(俄罗斯网站关闭)
-
发布该推文的ScottHelme是一名黑客,SecurityHeaders和ReportUri的创始人、Pluralsight作者、BBC常驻黑客。他表示,CAs现在似乎正在停止为俄罗斯域名颁发...
- 如何强制所有流量使用 HTTPS一网上用户
-
如何强制所有流量使用HTTPS一网上用户使用.htaccess强制流量到https的最常见方法可能是使用.htaccess重定向请求。.htaccess是一个简单的文本文件,简称为“.h...
- https和http的区别(https和http有何区别)
-
“HTTPS和HTTP都是数据传输的应用层协议,区别在于HTTPS比HTTP安全”。区别在哪里,我们接着往下看:...
- 快码住!带你十分钟搞懂HTTP与HTTPS协议及请求的区别
-
什么是协议?网络协议是计算机之间为了实现网络通信从而达成的一种“约定”或“规则”,正是因为这个“规则”的存在,不同厂商的生产设备、及不同操作系统组成的计算机之间,才可以实现通信。简单来说,计算机与网络...
- 简述HTTPS工作原理(简述https原理,以及与http的区别)
-
https是在http协议的基础上加了一层SSL(由网景公司开发),加密由ssl实现,它的目的是为用户提供对网站服务器的身份认证(需要CA),以至于保护交换数据的隐私和完整性,原理如图示。1、客户端发...
- 21、HTTPS 有几次握手和挥手?HTTPS 的原理什么是(高薪 常问)
-
HTTPS是3次握手和4次挥手,和HTTP是一样的。HTTPS的原理...
- 一次安全可靠的通信——HTTPS原理
-
为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。Alice与Bob的通信...
- 为什么有的网站没有使用https(为什么有的网站点不开)
-
有的网站没有使用HTTPS的原因可能涉及多个方面,以下是.com、.top域名的一些见解:服务器性能限制:HTTPS使用公钥加密和私钥解密技术,这要求服务器具备足够的计算能力来处理加解密操作。如果服务...
- HTTPS是什么?加密原理和证书。SSL/TLS握手过程
-
秘钥的产生过程非对称加密...
- 图解HTTPS「转」(图解http 完整版 彩色版 pdf)
-
我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。...
- HTTP 和 HTTPS 有何不同?一文带你全面了解
-
随着互联网时代的高速发展,Web服务器和客户端之间的安全通信需求也越来越高。HTTP和HTTPS是两种广泛使用的Web通信协议。本文将介绍HTTP和HTTPS的区别,并探讨为什么HTTPS已成为We...
- HTTP与HTTPS的区别,详细介绍(http与https有什么区别)
-
HTTP与HTTPS介绍超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的...
- 一文让你轻松掌握 HTTPS(https详解)
-
一文让你轻松掌握HTTPS原文作者:UC国际研发泽原写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。...
- 如何在Spring Boot应用程序上启用HTTPS?
-
HTTPS是HTTP的安全版本,旨在提供传输层安全性(TLS)[安全套接字层(SSL)的后继产品],这是地址栏中的挂锁图标,用于在Web服务器和浏览器之间建立加密连接。HTTPS加密每个数据包以安全方...
- 一文彻底搞明白Http以及Https(http0)
-
早期以信息发布为主的Web1.0时代,HTTP已可以满足绝大部分需要。证书费用、服务器的计算资源都比较昂贵,作为HTTP安全扩展的HTTPS,通常只应用在登录、交易等少数环境中。但随着越来越多的重要...
你 发表评论:
欢迎- 一周热门
-
-
Linux:Ubuntu22.04上安装python3.11,简单易上手
-
宝马阿布达比分公司推出独特M4升级套件,整套升级约在20万
-
MATLAB中图片保存的五种方法(一)(matlab中保存图片命令)
-
别再傻傻搞不清楚Workstation Player和Workstation Pro的区别了
-
Linux上使用tinyproxy快速搭建HTTP/HTTPS代理器
-
如何提取、修改、强刷A卡bios a卡刷bios工具
-
Element Plus 的 Dialog 组件实现点击遮罩层不关闭对话框
-
日本组合“岚”将于2020年12月31日停止团体活动
-
SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索
-
tinymce 号称富文本编辑器世界第一,大家同意么?
-
- 最近发表
- 标签列表
-
- dialog.js (57)
- importnew (44)
- windows93网页版 (44)
- yii2框架的优缺点 (45)
- tinyeditor (45)
- qt5.5 (60)
- windowsserver2016镜像下载 (52)
- okhttputils (51)
- android-gif-drawable (53)
- 时间轴插件 (56)
- docker systemd (65)
- slider.js (47)
- android webview缓存 (46)
- pagination.js (59)
- loadjs (62)
- openssl1.0.2 (48)
- velocity模板引擎 (48)
- pcre library (47)
- zabbix微信报警脚本 (63)
- jnetpcap (49)
- pdfrenderer (43)
- fastutil (48)
- uinavigationcontroller (53)
- bitbucket.org (44)
- python websocket-client (47)