|
| 1 | +# RxRefreshLayout |
| 2 | +RxRefreshLayout延伸了Google的SwipeRefreshLayout的思想,不在列表控件上动刀,而是使用一个ViewGroup来包含列表控件,以保持其较低的耦合性和较高的通用性。其主要特性有: |
| 3 | + |
| 4 | +1. 支持RecyclerView、ScrollView、AbsListView系列(ListView、GridView)、WebView以及其它可以获取到scrollY的控件 |
| 5 | +2. 支持加载更多 |
| 6 | +3. 默认支持 **越界回弹**,随手势速度有不同的效果 |
| 7 | +4. 可开启没有刷新控件的纯净越界回弹模式 |
| 8 | +5. setOnRefreshListener中拥有大量可以回调的方法 |
| 9 | +6. 将Header和Footer抽象成了接口,并回调了滑动过程中的系数,方便实现个性化的Header和Footer |
| 10 | +7. 支持NestedScroll,嵌套CoordinatorLayout |
| 11 | + |
| 12 | + |
| 13 | +## tip |
| 14 | +欢迎朋友们多多star支持 |
| 15 | + |
| 16 | +## 集成AS |
| 17 | + |
| 18 | +> Step 1.先在 build.gradle(Project:XXXX) 的 repositories 添加: |
| 19 | +
|
| 20 | + allprojects { |
| 21 | + repositories { |
| 22 | + ... |
| 23 | + maven { url "https://jitpack.io" } |
| 24 | + } |
| 25 | + } |
| 26 | +> Step 2. 然后在 build.gradle(Module:app) 的 dependencies 添加: |
| 27 | +
|
| 28 | + dependencies { |
| 29 | + //基础工具库 |
| 30 | + implementation 'com.github.zhangi789:RxRefreshLayout:3.1.2' |
| 31 | + } |
| 32 | + |
| 33 | +#### 2.在xml中添加RxRefreshLayout |
| 34 | +```xml |
| 35 | +<?xml version="1.0" encoding="utf-8"?> |
| 36 | +<com.xfresh.cn.RxRefreshLayout |
| 37 | +xmlns:android="http://schemas.android.com/apk/res/android" |
| 38 | + xmlns:app="http://schemas.android.com/apk/res-auto" |
| 39 | + android:id="@+id/refreshLayout" |
| 40 | + android:layout_width="match_parent" |
| 41 | + android:layout_height="match_parent" |
| 42 | + app:tr_wave_height="180dp" |
| 43 | + app:tr_head_height="100dp"> |
| 44 | + |
| 45 | + <android.support.v7.widget.RecyclerView |
| 46 | + android:id="@+id/recyclerview" |
| 47 | + android:layout_width="match_parent" |
| 48 | + android:layout_height="match_parent" |
| 49 | + android:overScrollMode="never" |
| 50 | + android:background="#fff" /> |
| 51 | +</com.xfresh.cn.RxRefreshLayout> |
| 52 | +``` |
| 53 | + |
| 54 | +Android系统为了跟iOS不一样,当界面OverScroll的时候会显示一个阴影。为了达到更好的显示效果,最好禁用系统的overScroll,如上给RecyclerView添加`android:overScrollMode="never"`。 |
| 55 | + |
| 56 | +#### 3.在Activity或者Fragment中配置 |
| 57 | +##### RxRefreshLayout不会自动结束刷新或者加载更多,需要手动控制 |
| 58 | +```java |
| 59 | +refreshLayout.setOnRefreshListener(new RefreshListenerAdapter(){ |
| 60 | + @Override |
| 61 | + public void onRefresh(final TwinklingRefreshLayout refreshLayout) { |
| 62 | + new Handler().postDelayed(new Runnable() { |
| 63 | + @Override |
| 64 | + public void run() { |
| 65 | + refreshLayout.finishRefreshing(); |
| 66 | + } |
| 67 | + },2000); |
| 68 | + } |
| 69 | + |
| 70 | + @Override |
| 71 | + public void onLoadMore(final TwinklingRefreshLayout refreshLayout) { |
| 72 | + new Handler().postDelayed(new Runnable() { |
| 73 | + @Override |
| 74 | + public void run() { |
| 75 | + refreshLayout.finishLoadmore(); |
| 76 | + } |
| 77 | + },2000); |
| 78 | + } |
| 79 | + }); |
| 80 | + } |
| 81 | +``` |
| 82 | +使用finishRefreshing()方法结束刷新,finishLoadmore()方法结束加载更多。此处OnRefreshListener还有其它方法,可以选择需要的来重写。 |
| 83 | + |
| 84 | +如果你想进入到界面的时候主动调用下刷新,可以调用startRefresh()/startLoadmore()方法。 |
| 85 | + |
| 86 | +##### setWaveHeight、setHeaderHeight、setBottomHeight、setOverScrollHeight |
| 87 | +- setMaxHeadHeight 设置头部可拉伸的最大高度。 |
| 88 | +- setHeaderHeight 头部固定高度(在此高度上显示刷新状态) |
| 89 | +- setMaxBottomHeight |
| 90 | +- setBottomHeight 底部高度 |
| 91 | +- setOverScrollHeight 设置最大的越界高度 |
| 92 | + |
| 93 | +#### setEnableRefresh、setEnableLoadmore |
| 94 | +灵活的设置是否禁用上下拉。 |
| 95 | + |
| 96 | +##### setHeaderView(IHeaderView headerView)、setBottomView(IBottomView bottomView) |
| 97 | +设置头部/底部个性化刷新效果,头部需要实现IHeaderView,底部需要实现IBottomView。 |
| 98 | + |
| 99 | +#### setEnableOverScroll |
| 100 | +是否允许越界回弹。 |
| 101 | + |
| 102 | +##### setOverScrollTopShow、setOverScrollBottomShow、setOverScrollRefreshShow |
| 103 | +是否允许在越界的时候显示刷新控件,默认是允许的,也就是Fling越界的时候Header或Footer照常显示,反之就是不显示;可能有特殊的情况,刷新控件会影响显示体验才设立了这个状态。 |
| 104 | + |
| 105 | +##### setPureScrollModeOn() |
| 106 | +开启纯净的越界回弹模式,也就是所有刷新相关的View都不显示,只显示越界回弹效果 |
| 107 | + |
| 108 | +##### setAutoLoadMore |
| 109 | +是否在底部越界的时候自动切换到加载更多模式 |
| 110 | + |
| 111 | +##### addFixedExHeader |
| 112 | +添加一个固定在顶部的Header(效果还需要优化) |
| 113 | + |
| 114 | +##### startRefresh、startLoadMore、finishRefreshing、finishLoadmore |
| 115 | + |
| 116 | +##### setFloatRefresh(boolean) |
| 117 | +支持切换到像SwipeRefreshLayout一样的悬浮刷新模式了。 |
| 118 | + |
| 119 | +##### setTargetView(View view) |
| 120 | +设置滚动事件的作用对象。 |
| 121 | + |
| 122 | +##### setDefaultHeader、setDefaultFooter |
| 123 | +现在已经提供了设置默认的Header、Footer的static方法,可在Application或者一个Activity中这样设置: |
| 124 | +```java |
| 125 | +RxRefreshLayout.setDefaultHeader(SinaRefreshView.class.getName()); |
| 126 | +RxRefreshLayout.setDefaultFooter(BallPulseView.class.getName()); |
| 127 | +``` |
| 128 | + |
| 129 | + |
| 130 | +#### 4.扩展属性 |
| 131 | +- tr_max_head_height 头部拉伸允许的最大高度 |
| 132 | +- tr_head_height 头部高度 |
| 133 | +- tr_max_bottom_height |
| 134 | +- tr_bottom_height 底部高度 |
| 135 | +- tr_overscroll_height 允许越界的最大高度 |
| 136 | +- tr_enable_refresh 是否允许刷新,默认为true |
| 137 | +- tr_enable_loadmore 是否允许加载更多,默认为true |
| 138 | +- tr_pureScrollMode_on 是否开启纯净的越界回弹模式 |
| 139 | +- tr_overscroll_top_show - 否允许顶部越界时显示顶部View |
| 140 | +- tr_overscroll_bottom_show 是否允许底部越界时显示底部View |
| 141 | +- tr_enable_overscroll 是否允许越界回弹 |
| 142 | +- tr_floatRefresh 开启悬浮刷新模式 |
| 143 | +- tr_autoLoadMore 越界时自动加载更多 |
| 144 | +- tr_enable_keepIView 是否在开始刷新之后保持状态,默认为true;若需要保持原来的操作逻辑,这里设置为false即可 |
| 145 | +- tr_showRefreshingWhenOverScroll 越界时直接显示正在刷新中的头部 |
| 146 | +- tr_showLoadingWhenOverScroll 越界时直接显示正在加载更多中的底部 |
| 147 | + |
| 148 | +## 其它说明 |
| 149 | +### 1.默认支持越界回弹,并可以随手势越界不同的高度 |
| 150 | +这一点很多类似SwipeRefreshLayout的刷新控件都没有做到(包括SwipeRefreshLayout),因为没有拦截下来的时间会传递给列表控件,而列表控件的滚动状态很难获取。解决方案就是给列表控件设置了OnTouchListener并把事件交给GestureDetector处理,然后在列表控件的OnScrollListener中监听View是否滚动到了顶部(没有OnScrollListener的则采用延时监听策略)。 |
| 151 | + |
| 152 | +### 2.setOnRefreshListener大量可以回调的方法 |
| 153 | +- onPullingDown(TwinklingRefreshLayout refreshLayout, float fraction) 正在下拉的过程 |
| 154 | +- onPullingUp(TwinklingRefreshLayout refreshLayout, float fraction) 正在上拉的过程 |
| 155 | +- onPullDownReleasing(TwinklingRefreshLayout refreshLayout, float fraction) 下拉释放过程 |
| 156 | +- onPullUpReleasing(TwinklingRefreshLayout refreshLayout, float fraction) 上拉释放过程 |
| 157 | +- onRefresh(TwinklingRefreshLayout refreshLayout) 正在刷新 |
| 158 | +- onLoadMore(TwinklingRefreshLayout refreshLayout) 正在加载更多 |
| 159 | + |
| 160 | +其中fraction表示当前下拉的距离与Header高度的比值(或者当前上拉距离与Footer高度的比值)。 |
| 161 | + |
| 162 | +### 3.Header和Footer |
| 163 | +##### BezierLayout(pic 4) |
| 164 | +- setWaveColor |
| 165 | +- setRippleColor |
| 166 | + |
| 167 | +##### GoogleDotView(pic 5) |
| 168 | +##### SinaRefreshView(pic 3) |
| 169 | +- setArrowResource |
| 170 | +- setTextColor |
| 171 | +- setPullDownStr |
| 172 | +- setReleaseRefreshStr |
| 173 | +- setRefreshingStr |
| 174 | + |
| 175 | +##### ProgressLayout(SwipeRefreshLayout pic 6) |
| 176 | +- setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) |
| 177 | +- setProgressBackgroundColorSchemeColor(@ColorInt int color) |
| 178 | +- setColorSchemeResources(@ColorRes int... colorResIds) |
| 179 | + |
| 180 | +####Footer |
| 181 | +##### BallPulseView(pic 2) |
| 182 | +- setNormalColor(@ColorInt int color) |
| 183 | +- setAnimatingColor(@ColorInt int color) |
| 184 | + |
| 185 | +##### LoadingView(pic 3) |
| 186 | +更多动效可以参考[AVLoadingIndicatorView](https://github.com/81813780/AVLoadingIndicatorView)库。 |
| 187 | + |
| 188 | + |
| 189 | +### 3.实现个性化的Header和Footer |
| 190 | +相关接口分别为IHeaderView和IBottomView,代码如下: |
| 191 | +```java |
| 192 | +public interface IHeaderView { |
| 193 | + View getView(); |
| 194 | + |
| 195 | + void onPullingDown(float fraction,float maxHeadHeight,float headHeight); |
| 196 | + |
| 197 | + void onPullReleasing(float fraction,float maxHeadHeight,float headHeight); |
| 198 | + |
| 199 | + void startAnim(float maxHeadHeight,float headHeight); |
| 200 | + |
| 201 | + void reset(); |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +其中getView()方法用于在TwinklingRefreshLayout中获取到实际的Header,因此不能返回null。 |
| 206 | + |
| 207 | +**实现像新浪微博那样的刷新效果**(有部分修改,具体请看源码),实现代码如下: |
| 208 | + |
| 209 | +1.首先定义SinaRefreshHeader继承自FrameLayout并实现IHeaderView方法 |
| 210 | + |
| 211 | +2.getView()方法中返回this |
| 212 | + |
| 213 | +3.在onAttachedToWindow()或者构造函数方法中获取一下需要用到的布局 |
| 214 | + |
| 215 | +4. 在onFinish()方法中调用listener.onAnimEnd()。此方法的目的是为了在finish之前可以执行一段动画。 |
| 216 | + |
| 217 | +```java |
| 218 | +private void init() { |
| 219 | + View rootView = View.inflate(getContext(), R.layout.view_sinaheader, null); |
| 220 | + refreshArrow = (ImageView) rootView.findViewById(R.id.iv_arrow); |
| 221 | + refreshTextView = (TextView) rootView.findViewById(R.id.tv); |
| 222 | + loadingView = (ImageView) rootView.findViewById(R.id.iv_loading); |
| 223 | + addView(rootView); |
| 224 | + } |
| 225 | +``` |
| 226 | + |
| 227 | +4.实现其它方法 |
| 228 | +```java |
| 229 | +@Override |
| 230 | + public void onPullingDown(float fraction, float maxHeadHeight, float headHeight) { |
| 231 | + if (fraction < 1f) refreshTextView.setText(pullDownStr); |
| 232 | + if (fraction > 1f) refreshTextView.setText(releaseRefreshStr); |
| 233 | + refreshArrow.setRotation(fraction * headHeight / maxHeadHeight * 180); |
| 234 | + |
| 235 | + |
| 236 | + } |
| 237 | + |
| 238 | + @Override |
| 239 | + public void onPullReleasing(float fraction, float maxHeadHeight, float headHeight) { |
| 240 | + if (fraction < 1f) { |
| 241 | + refreshTextView.setText(pullDownStr); |
| 242 | + refreshArrow.setRotation(fraction * headHeight / maxHeadHeight * 180); |
| 243 | + if (refreshArrow.getVisibility() == GONE) { |
| 244 | + refreshArrow.setVisibility(VISIBLE); |
| 245 | + loadingView.setVisibility(GONE); |
| 246 | + } |
| 247 | + } |
| 248 | + } |
| 249 | + |
| 250 | + @Override |
| 251 | + public void startAnim(float maxHeadHeight, float headHeight) { |
| 252 | + refreshTextView.setText(refreshingStr); |
| 253 | + refreshArrow.setVisibility(GONE); |
| 254 | + loadingView.setVisibility(VISIBLE); |
| 255 | + } |
| 256 | + |
| 257 | + @Override |
| 258 | + public void onFinish(OnAnimEndListener listener) { |
| 259 | + listener.onAnimEnd(); |
| 260 | + } |
| 261 | +``` |
| 262 | + |
| 263 | +5.布局文件 |
| 264 | +```xml |
| 265 | +<?xml version="1.0" encoding="utf-8"?> |
| 266 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 267 | + android:orientation="horizontal" android:layout_width="match_parent" |
| 268 | + android:layout_height="match_parent" |
| 269 | + android:gravity="center"> |
| 270 | + <ImageView |
| 271 | + android:id="@+id/iv_arrow" |
| 272 | + android:layout_width="wrap_content" |
| 273 | + android:layout_height="wrap_content" |
| 274 | + android:src="@drawable/ic_arrow"/> |
| 275 | + |
| 276 | + <ImageView |
| 277 | + android:id="@+id/iv_loading" |
| 278 | + android:visibility="gone" |
| 279 | + android:layout_width="34dp" |
| 280 | + android:layout_height="34dp" |
| 281 | + android:src="@drawable/anim_loading_view"/> |
| 282 | + |
| 283 | + <TextView |
| 284 | + android:id="@+id/tv" |
| 285 | + android:layout_width="wrap_content" |
| 286 | + android:layout_height="wrap_content" |
| 287 | + android:layout_marginLeft="16dp" |
| 288 | + android:textSize="16sp" |
| 289 | + android:text="下拉刷新"/> |
| 290 | +</LinearLayout> |
| 291 | +``` |
| 292 | + |
| 293 | +注意fraction的使用,比如上面的代码`refreshArrow.setRotation(fraction * headHeight / maxHeadHeight * 180)`,`fraction * headHeight`表示当前头部滑动的距离,然后算出它和最大高度的比例,然后乘以180,可以使得在滑动到最大距离时Arrow恰好能旋转180度。 |
| 294 | + |
| 295 | + |
| 296 | +onPullingDown/onPullingUp表示正在下拉/正在上拉的过程。 |
| 297 | +onPullReleasing表示向上拉/下拉释放时回调的状态。 |
| 298 | +startAnim则是在onRefresh/onLoadMore之后才会回调的过程(此处是显示了加载中的小菊花) |
| 299 | + |
| 300 | +如上所示,轻而易举就可以实现一个个性化的Header或者Footer。(更简单的实现请参考Demo中的 **TextHeaderView(图四)**)。 |
| 301 | + |
| 302 | +### NestedScroll |
| 303 | +#### RxRefreshLayout嵌套CoordinatorLayout |
| 304 | +---layout |
| 305 | +```xml |
| 306 | +<?xml version="1.0" encoding="utf-8"?> |
| 307 | +<com.xfresh.cn.RxRefreshLayout |
| 308 | +xmlns:android="http://schemas.android.com/apk/res/android" |
| 309 | + xmlns:app="http://schemas.android.com/apk/res-auto" |
| 310 | + android:id="@+id/refresh" |
| 311 | + android:layout_width="match_parent" |
| 312 | + android:layout_height="match_parent"> |
| 313 | + |
| 314 | + <android.support.design.widget.CoordinatorLayout |
| 315 | + android:id="@+id/coord_container" |
| 316 | + android:layout_width="match_parent" |
| 317 | + android:layout_height="match_parent" |
| 318 | + android:addStatesFromChildren="true" |
| 319 | + android:fitsSystemWindows="true"> |
| 320 | + |
| 321 | + <android.support.design.widget.AppBarLayout |
| 322 | + android:id="@+id/appbar_layout" |
| 323 | + android:layout_width="match_parent" |
| 324 | + android:layout_height="wrap_content" |
| 325 | + android:clipChildren="false"> |
| 326 | + |
| 327 | + <!--...--> |
| 328 | + |
| 329 | + </android.support.design.widget.AppBarLayout> |
| 330 | + |
| 331 | + <android.support.v7.widget.RecyclerView |
| 332 | + android:id="@+id/recyclerview" |
| 333 | + android:layout_width="match_parent" |
| 334 | + android:layout_height="match_parent" |
| 335 | + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> |
| 336 | + |
| 337 | + </android.support.design.widget.CoordinatorLayout> |
| 338 | +</com.xfresh.cn.RxRefreshLayout> |
| 339 | +``` |
| 340 | + |
| 341 | +--- 代码1 |
| 342 | +``` |
| 343 | +refreshLayout.setTargetView(rv); |
| 344 | +``` |
| 345 | +让refreshLayout能够找到RecyclerView/ListView |
| 346 | + |
| 347 | +--- 代码2 |
| 348 | +```java |
| 349 | +AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout); |
| 350 | +appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { |
| 351 | + @Override |
| 352 | + public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { |
| 353 | + if (verticalOffset >= 0) { |
| 354 | + refreshLayout.setEnableRefresh(true); |
| 355 | + refreshLayout.setEnableOverScroll(false); |
| 356 | + } else { |
| 357 | + refreshLayout.setEnableRefresh(false); |
| 358 | + refreshLayout.setEnableOverScroll(false); |
| 359 | + } |
| 360 | + } |
| 361 | +}); |
| 362 | +``` |
| 363 | +设置AppBarLayout的移动监听器,需要下拉显示AppBarLayout时需设置setEnableRefresh(false),setEnableOverScroll(false);AppBarLayout隐藏后还原为原来设置的值即可。 |
| 364 | + |
| 365 | +####CoordinatorLayout嵌套RxRefreshLayout |
| 366 | +--- layout |
| 367 | +```xml |
| 368 | +<?xml version="1.0" encoding="utf-8"?> |
| 369 | +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 370 | + xmlns:app="http://schemas.android.com/apk/res-auto" |
| 371 | + android:id="@+id/coord_container" |
| 372 | + android:layout_width="match_parent" |
| 373 | + android:layout_height="match_parent" |
| 374 | + android:addStatesFromChildren="true" |
| 375 | + android:fitsSystemWindows="true"> |
| 376 | + |
| 377 | + <com.xfresh.cn.RxRefreshLayout |
| 378 | + android:id="@+id/refresh" |
| 379 | + android:layout_width="match_parent" |
| 380 | + android:layout_height="match_parent" |
| 381 | + app:layout_behavior="@string/appbar_scrolling_view_behavior"> |
| 382 | + |
| 383 | + <android.support.v7.widget.RecyclerView |
| 384 | + android:id="@+id/recyclerview" |
| 385 | + android:layout_width="match_parent" |
| 386 | + android:layout_height="match_parent" /> |
| 387 | + |
| 388 | + </com.xfresh.cn.RxRefreshLayout> |
| 389 | + |
| 390 | +</android.support.design.widget.CoordinatorLayout> |
| 391 | +``` |
| 392 | +注意给RxRefreshLayout设置一个layout_behavior="@string/appbar_scrolling_view_behavior" |
0 commit comments