footers = new ArrayList<>();
233 |
234 | private void showConfigDialog() {
235 | if (dialog == null) {
236 | dialog = new TestConfigDialog(this);
237 | dialog.setTestListener(new TestConfigDialog.TestListener() {
238 | @Override
239 | public void addHeader() {
240 | View header = LayoutInflater.from(MainActivity.this)
241 | .inflate(R.layout.header_test, recyclerView, false);
242 | recyclerView.addHeader(header);
243 | headers.add(header);
244 | }
245 |
246 | @Override
247 | public void removeHeader() {
248 | if (headers.size() == 0) return;
249 | View header = headers.get(headers.size() - 1);
250 | recyclerView.removeHeader(header);
251 | headers.remove(header);
252 | }
253 |
254 | @Override
255 | public void addFooter() {
256 | View footer = LayoutInflater.from(MainActivity.this)
257 | .inflate(R.layout.footer_test, recyclerView, false);
258 | recyclerView.addFooter(footer);
259 | footers.add(footer);
260 | }
261 |
262 | @Override
263 | public void removeFooter() {
264 | if (footers.size() == 0) return;
265 | View footer = footers.get(footers.size() - 1);
266 | recyclerView.removeFooter(footer);
267 | footers.remove(footer);
268 | }
269 |
270 | @Override
271 | public void startRefresh() {
272 | recyclerView.startRefresh(true);
273 | }
274 |
275 | @Override
276 | public void resetAdapter() {
277 | list.clear();
278 | index = 0;
279 | for (int i = 0; i < 15; i++) {
280 | list.add("数据 " + index++);
281 | }
282 | recyclerView.setAdapter(new SRVAdapter(list));
283 | }
284 |
285 | @Override
286 | public void showEmpty() {
287 | reStart();
288 | }
289 |
290 | @Override
291 | public void showError() {
292 | reStart();
293 | }
294 | });
295 | }
296 | dialog.show();
297 | }
298 |
299 | }
300 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/SRVApplication.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | import android.app.Application;
4 | import com.squareup.leakcanary.LeakCanary;
5 |
6 | /**
7 | * author: hzw
8 | * time: 2018/7/11 上午11:08
9 | * description:
10 | */
11 | public class SRVApplication extends Application {
12 |
13 | @Override public void onCreate() {
14 | super.onCreate();
15 | if (LeakCanary.isInAnalyzerProcess(this)) {
16 | // This process is dedicated to LeakCanary for heap analysis.
17 | // You should not init your app in this process.
18 | return;
19 | }
20 | LeakCanary.install(this);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/SRVTestConfig.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | /**
4 | * author: hzw
5 | * time: 2018/8/12 下午2:58
6 | * description:
7 | */
8 | public class SRVTestConfig {
9 |
10 | private volatile static SRVTestConfig instance;
11 |
12 | private SRVTestConfig() {
13 | }
14 |
15 | public static SRVTestConfig getInstance() {
16 | if (instance == null) {
17 | synchronized (SRVTestConfig.class) {
18 | if (instance == null) {
19 | instance = new SRVTestConfig();
20 | }
21 | }
22 | }
23 | return instance;
24 | }
25 |
26 | private boolean refreshError;
27 | private boolean refreshEmpty;
28 | private boolean refresh = true;
29 |
30 | public boolean isRefreshError() {
31 | return refreshError;
32 | }
33 |
34 | public void setRefreshError(boolean refreshError) {
35 | this.refreshError = refreshError;
36 | if (refreshError) {
37 | refreshEmpty = false;
38 | refresh = false;
39 | }
40 | }
41 |
42 | public boolean isRefreshEmpty() {
43 | return refreshEmpty;
44 | }
45 |
46 | public void setRefreshEmpty(boolean refreshEmpty) {
47 | this.refreshEmpty = refreshEmpty;
48 | if (refreshEmpty) {
49 | refreshError = false;
50 | refresh = false;
51 | }
52 | }
53 |
54 | public boolean isRefresh() {
55 | return refresh;
56 | }
57 |
58 | public void setRefresh(boolean refresh) {
59 | this.refresh = refresh;
60 | if (refresh) {
61 | refreshError = false;
62 | refreshEmpty = false;
63 | }
64 | }
65 |
66 |
67 | private boolean loadingNoData;
68 | private boolean loadingError;
69 |
70 | public boolean isLoadingNoData() {
71 | return loadingNoData;
72 | }
73 |
74 | public void setLoadingNoData(boolean loadingNoData) {
75 | this.loadingNoData = loadingNoData;
76 | if (loadingNoData) {
77 | loadingError = false;
78 | }
79 | }
80 |
81 | public boolean isLoadingError() {
82 | return loadingError;
83 | }
84 |
85 | public void setLoadingError(boolean loadingError) {
86 | this.loadingError = loadingError;
87 | if (loadingError) {
88 | loadingNoData = false;
89 | }
90 | }
91 |
92 | public void setLoadingNormal() {
93 | loadingNoData = false;
94 | loadingError = false;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/TestConfigDialog.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.os.Bundle;
6 | import android.support.annotation.NonNull;
7 | import android.util.Log;
8 | import android.view.Gravity;
9 | import android.view.View;
10 | import android.view.Window;
11 | import android.view.WindowManager;
12 | import android.widget.Button;
13 | import android.widget.Toast;
14 |
15 | /**
16 | * author: hzw
17 | * time: 2018/8/12 下午4:51
18 | * description:
19 | */
20 | public class TestConfigDialog extends Dialog implements View.OnClickListener {
21 |
22 | public TestConfigDialog(@NonNull Context context) {
23 | super(context, R.style.dialogStyle);
24 | }
25 |
26 | @Override protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | Window window = getWindow();
29 | if (window == null) return;
30 | window.setContentView(R.layout.dialog_config);
31 | window.setGravity(Gravity.BOTTOM);
32 | window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
33 | initView();
34 | }
35 |
36 | private void initView() {
37 | findViewById(R.id.add_header).setOnClickListener(this);
38 | findViewById(R.id.remove_header).setOnClickListener(this);
39 | findViewById(R.id.refresh).setOnClickListener(this);
40 | findViewById(R.id.add_footer).setOnClickListener(this);
41 | findViewById(R.id.remove_footer).setOnClickListener(this);
42 | findViewById(R.id.reset_adapter).setOnClickListener(this);
43 | findViewById(R.id.refresh_loading).setOnClickListener(this);
44 | findViewById(R.id.footer_loading).setOnClickListener(this);
45 | findViewById(R.id.loading_error).setOnClickListener(this);
46 | findViewById(R.id.empty).setOnClickListener(this);
47 | findViewById(R.id.error).setOnClickListener(this);
48 | findViewById(R.id.loading_nodata).setOnClickListener(this);
49 | }
50 |
51 | @Override public void onClick(View v) {
52 | if (listener == null) return;
53 | switch (v.getId()) {
54 | case R.id.add_header:
55 | listener.addHeader();
56 | break;
57 | case R.id.remove_header:
58 | listener.removeHeader();
59 | break;
60 | case R.id.refresh:
61 | listener.startRefresh();
62 | break;
63 | case R.id.add_footer:
64 | listener.addFooter();
65 | break;
66 | case R.id.remove_footer:
67 | listener.removeFooter();
68 | break;
69 | case R.id.reset_adapter:
70 | listener.resetAdapter();
71 | break;
72 | case R.id.refresh_loading:
73 | SRVTestConfig.getInstance()
74 | .setRefresh(true);
75 | Toast.makeText(getContext(), "再刷新试试看!", Toast.LENGTH_SHORT)
76 | .show();
77 | break;
78 | case R.id.footer_loading:
79 | SRVTestConfig.getInstance()
80 | .setLoadingNormal();
81 | Toast.makeText(getContext(), "加载更多正常", Toast.LENGTH_SHORT)
82 | .show();
83 | break;
84 | case R.id.loading_error:
85 | SRVTestConfig.getInstance()
86 | .setLoadingError(true);
87 | Toast.makeText(getContext(), "加载更多会错误", Toast.LENGTH_SHORT)
88 | .show();
89 | break;
90 | case R.id.loading_nodata:
91 | SRVTestConfig.getInstance()
92 | .setLoadingNoData(true);
93 | Toast.makeText(getContext(), "加载更多无数据", Toast.LENGTH_SHORT)
94 | .show();
95 | break;
96 | case R.id.empty:
97 | SRVTestConfig.getInstance()
98 | .setRefreshEmpty(true);
99 | listener.showEmpty();
100 | break;
101 | case R.id.error:
102 | SRVTestConfig.getInstance()
103 | .setRefreshError(true);
104 | listener.showError();
105 | break;
106 | }
107 | dismiss();
108 | }
109 |
110 |
111 | public interface TestListener {
112 | void addHeader();
113 |
114 | void removeHeader();
115 |
116 | void addFooter();
117 |
118 | void removeFooter();
119 |
120 | void startRefresh();
121 |
122 | void resetAdapter();
123 |
124 | void showEmpty();
125 |
126 | void showError();
127 | }
128 |
129 | private TestListener listener;
130 |
131 | public void setTestListener(TestListener listener) {
132 | this.listener = listener;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/TestEmptyView.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 |
10 | import com.hzw.srecyclerview.AbsStateView;
11 |
12 | /**
13 | * author: hzw
14 | * time: 2018/2/5 下午5:25
15 | * description:
16 | */
17 |
18 | public class TestEmptyView extends AbsStateView {
19 | public TestEmptyView(@NonNull Context context) {
20 | super(context);
21 | }
22 |
23 | public TestEmptyView(@NonNull Context context, @Nullable AttributeSet attrs) {
24 | super(context, attrs);
25 | }
26 |
27 | public TestEmptyView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
28 | super(context, attrs, defStyleAttr);
29 | }
30 |
31 | @Override
32 | public void init() {
33 | View view = LayoutInflater.from(getContext())
34 | .inflate(R.layout.empty_test, this, true);
35 |
36 | //空布局的点击刷新
37 | view.findViewById(R.id.empty_click).setOnClickListener(new OnClickListener() {
38 | @Override
39 | public void onClick(View v) {
40 | retry(true);
41 | }
42 | });
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/TestErrorView.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 |
10 | import com.hzw.srecyclerview.AbsStateView;
11 |
12 | /**
13 | * author: hzw
14 | * time: 2018/2/5 下午5:25
15 | * description:
16 | */
17 |
18 | public class TestErrorView extends AbsStateView {
19 | public TestErrorView(@NonNull Context context) {
20 | super(context);
21 | }
22 |
23 | public TestErrorView(@NonNull Context context, @Nullable AttributeSet attrs) {
24 | super(context, attrs);
25 | }
26 |
27 | public TestErrorView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
28 | super(context, attrs, defStyleAttr);
29 | }
30 |
31 | @Override
32 | public void init() {
33 | View view = LayoutInflater.from(getContext())
34 | .inflate(R.layout.error_test, this, true);
35 |
36 | //空布局的点击刷新
37 | view.findViewById(R.id.empty_click).setOnClickListener(new OnClickListener() {
38 | @Override
39 | public void onClick(View v) {
40 | retry(true);
41 | }
42 | });
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/TestLoadFooter.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 |
11 | import com.hzw.srecyclerview.AbsLoadFooter;
12 |
13 | /**
14 | * 功能:
15 | * Created by 何志伟 on 2017/7/19.
16 | */
17 |
18 | public class TestLoadFooter extends AbsLoadFooter {
19 |
20 | private View load, noMore, error;
21 |
22 | public TestLoadFooter(Context context) {
23 | super(context);
24 | }
25 |
26 | public TestLoadFooter(Context context, @Nullable AttributeSet attrs) {
27 | super(context, attrs);
28 | }
29 |
30 | public TestLoadFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
31 | super(context, attrs, defStyleAttr);
32 | }
33 |
34 | @Override public void init() {
35 | View loadView = LayoutInflater.from(getContext())
36 | .inflate(R.layout.srv_load_footer, this, false);
37 | noMore = loadView.findViewById(R.id.tv_src_loadNoMore);
38 | load = loadView.findViewById(R.id.v_srv_loading);
39 | error = loadView.findViewById(R.id.tv_src_loadError);
40 | error.setOnClickListener(new OnClickListener() {
41 | @Override public void onClick(View v) {
42 | errorRetry();
43 | }
44 | });
45 | addView(loadView);
46 | }
47 |
48 | @Override public void loadingState(int state) {
49 | switch (state) {
50 | case LOAD_SUCCESS:
51 | load.setVisibility(VISIBLE);
52 | noMore.setVisibility(GONE);
53 | break;
54 | case LOAD_ERROR:
55 | load.setVisibility(GONE);
56 | noMore.setVisibility(GONE);
57 | error.setVisibility(VISIBLE);
58 | break;
59 | case LOAD_NO_MORE:
60 | //据说有一种需求是,没数据时,直接不显示无数据的UI,此时可设置高度为0,
61 | //然后重写reset()方法,当列表刷新时会重置加载尾部
62 | ViewGroup.LayoutParams params = getLayoutParams();
63 | params.height = 0;
64 | setLayoutParams(params);
65 | break;
66 | case LOAD_BEGIN:
67 | load.setVisibility(VISIBLE);
68 | noMore.setVisibility(GONE);
69 | error.setVisibility(GONE);
70 | break;
71 | }
72 | }
73 |
74 | /**
75 | * 刷新结束后如果需要重置加载尾部,可重写此方法重置LoadFooter
76 | */
77 | @Override public void reset() {
78 | super.reset();
79 | ViewGroup.LayoutParams params = getLayoutParams();
80 | params.height = dip2px(45);
81 | setLayoutParams(params);
82 | }
83 |
84 |
85 | private int dip2px(float value) {
86 | final float scale = Resources.getSystem()
87 | .getDisplayMetrics().density;
88 | return (int) (value * scale + 0.5f);
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/TestLoadingView.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import com.hzw.srecyclerview.AbsStateView;
10 |
11 | /**
12 | * author: hzw
13 | * time: 2018/2/5 下午5:25
14 | * description:
15 | */
16 |
17 | public class TestLoadingView extends AbsStateView {
18 | public TestLoadingView(@NonNull Context context) {
19 | super(context);
20 | }
21 |
22 | public TestLoadingView(@NonNull Context context, @Nullable AttributeSet attrs) {
23 | super(context, attrs);
24 | }
25 |
26 | public TestLoadingView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
27 | super(context, attrs, defStyleAttr);
28 | }
29 |
30 | @Override public void init() {
31 | LayoutInflater.from(getContext())
32 | .inflate(R.layout.error_loading, this, true);
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/TestRefreshHeader.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.widget.TextView;
10 |
11 | import com.hzw.srecyclerview.AbsRefreshHeader;
12 |
13 | /**
14 | * 功能:
15 | * Created by 何志伟 on 2017/7/17.
16 | */
17 |
18 | public class TestRefreshHeader extends AbsRefreshHeader {
19 |
20 | private TextView refreshText;
21 | private ClockView clockView;
22 |
23 | public TestRefreshHeader(Context context) {
24 | super(context);
25 | }
26 |
27 | public TestRefreshHeader(Context context, @Nullable AttributeSet attrs) {
28 | super(context, attrs);
29 | }
30 |
31 | public TestRefreshHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
32 | super(context, attrs, defStyleAttr);
33 | }
34 |
35 | @Override
36 | public void init() {
37 | View view = LayoutInflater.from(getContext()).inflate(R.layout.refresh_view, this, false);
38 | clockView = view.findViewById(R.id.v_refresh);
39 | refreshText = view.findViewById(R.id.tv_refresh);
40 | addView(view);
41 | }
42 |
43 |
44 | /**
45 | * 如果需要设置刷新动画时间,可以重写此方法
46 | */
47 | @Override
48 | public int getRefreshDuration() {
49 | return 300;
50 | }
51 |
52 | /**
53 | * 如果需要设置头部的Gravity,可以重写此方法
54 | *
55 | * @return HEADER_CENTER,HEADER_BOTTOM
56 | */
57 | @Override
58 | public int getRefreshGravity() {
59 | return AbsRefreshHeader.HEADER_CENTER;
60 | // return AbsRefreshHeader.HEADER_BOTTOM;
61 | }
62 |
63 | /**
64 | * 如果需要设置刷新高度,也就是刷新临界值,可以重写此方法
65 | */
66 | @Override
67 | public int getRefreshHeight() {
68 | return dip2px(70);
69 | }
70 |
71 | private int dip2px(float value) {
72 | final float scale = Resources.getSystem().getDisplayMetrics().density;
73 | return (int) (value * scale + 0.5f);
74 | }
75 |
76 | /**
77 | * 刷新时下拉的灵敏度,数值越大越不灵敏
78 | */
79 | @Override
80 | public int getRefreshSensitivity() {
81 | return 1;
82 | }
83 |
84 | /**
85 | * SRecyclerView的onDetachedFromWindow被调用,可能SRecyclerView所在的界面要被销毁,
86 | * 如果子类中有动画等未完成,可以重写此方法取消动画等耗时操作,避免造成内存泄露
87 | */
88 | @Override
89 | public void srvDetachedFromWindow() {
90 | if (clockView != null) {
91 | clockView.resetClock();
92 | }
93 | }
94 |
95 | @Override
96 | public void refresh(int state, int height) {
97 | switch (state) {
98 | case NORMAL:
99 | refreshText.setText("下拉刷新");
100 | clockView.stopClockAnim();
101 | break;
102 | case REFRESH:
103 | refreshText.setText("正在刷新...");
104 | clockView.startClockAnim();
105 | break;
106 | case PREPARE_NORMAL:
107 | refreshText.setText("下拉刷新");
108 | clockView.setClockAngle(height);
109 | break;
110 | case PREPARE_REFRESH:
111 | refreshText.setText("释放立即刷新");
112 | clockView.setClockAngle(height);
113 | break;
114 | }
115 | }
116 |
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hzw/srecyclerviewproject/TestSRVModule.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerviewproject;
2 |
3 | import android.content.Context;
4 |
5 | import com.hzw.srecyclerview.AbsStateView;
6 | import com.hzw.srecyclerview.AbsLoadFooter;
7 | import com.hzw.srecyclerview.AbsRefreshHeader;
8 | import com.hzw.srecyclerview.SRecyclerViewModule;
9 |
10 | /**
11 | * 功能:
12 | * Created by 何志伟 on 2017/7/17.
13 | */
14 |
15 | public class TestSRVModule implements SRecyclerViewModule {
16 | @Override public AbsRefreshHeader getRefreshHeader(Context context) {
17 | return new TestRefreshHeader(context);
18 | }
19 |
20 | /**
21 | * 也可以只配置其中一项,使用默认的加载UI
22 | */
23 | @Override public AbsLoadFooter getLoadingFooter(Context context) {
24 | return new TestLoadFooter(context);
25 | }
26 |
27 | @Override public AbsStateView getEmptyView(Context context) {
28 | return new TestEmptyView(context);
29 | //return null;
30 | }
31 |
32 | @Override public AbsStateView getErrorView(Context context) {
33 | return new TestErrorView(context);
34 | //return null;
35 | }
36 |
37 | @Override public AbsStateView getLoadingView(Context context) {
38 | return new TestLoadingView(context);
39 | //return null;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/srecyclerview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/drawable/srecyclerview.gif
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
26 |
27 |
36 |
37 |
38 |
49 |
50 |
51 |
52 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
29 |
30 |
46 |
53 |
60 |
74 |
88 |
103 |
117 |
131 |
145 |
159 |
173 |
187 |
201 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/empty_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/error_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/error_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/footer_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/header_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/refresh_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
18 |
19 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/attr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #30000000
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SRecyclerView
3 |
4 |
5 | Hello blank fragment
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven {
7 | url 'https://maven.google.com/'
8 | name 'Google'
9 | }
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.0.1'
13 | classpath 'com.novoda:bintray-release:0.7.0'
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | jcenter()
23 | maven {
24 | url 'https://maven.google.com/'
25 | name 'Google'
26 | }
27 | }
28 | //解决乱码问题
29 | tasks.withType(Javadoc) {
30 | options.addStringOption('Xdoclint:none', '-quiet')
31 | options.addStringOption('encoding', 'UTF-8')
32 | }
33 | }
34 |
35 | task clean(type: Delete) {
36 | delete rootProject.buildDir
37 | }
38 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jul 17 14:01:35 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':srecyclerview'
2 |
--------------------------------------------------------------------------------
/srecyclerview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/srecyclerview/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.novoda.bintray-release'
3 | apply plugin: 'maven'
4 |
5 | android {
6 | compileSdkVersion 26
7 | buildToolsVersion '26.0.2'
8 |
9 | defaultConfig {
10 | minSdkVersion 14
11 | targetSdkVersion 26
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | consumerProguardFiles 'proguard-rules.pro'
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | publish {
24 | userOrg = 'hzwsunshine'
25 | groupId = 'com.github.hzw'
26 | artifactId = 'srecyclerview'
27 | version = '1.2.9'
28 | description = 'refresh and loading recyclerview'
29 | website = "https://github.com/HzwSunshine/SRecyclerView"
30 | }
31 |
32 | task clearJar(type: Delete) {
33 | delete 'build/libs/httputils.jar'
34 | delete 'libs/httputils.jar'
35 | }
36 | }
37 |
38 | dependencies {
39 | compileOnly 'com.android.support:design:26.1.0'
40 | }
41 |
42 |
43 |
--------------------------------------------------------------------------------
/srecyclerview/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\DELL\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
27 |
28 | -keep public class * implements com.hzw.srecyclerview.SRecyclerViewModule
29 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/AbsLoadFooter.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.Gravity;
6 | import android.view.ViewGroup;
7 | import android.widget.LinearLayout;
8 |
9 | /**
10 | * 功能:抽象的加载尾部,可继承并自定义加载尾部
11 | * Created by 何志伟 on 2017/7/10.
12 | */
13 | public abstract class AbsLoadFooter extends LinearLayout {
14 |
15 | /**
16 | * 加载完成,即加载成功
17 | */
18 | protected final static int LOAD_SUCCESS = 0;
19 | /**
20 | * 加载开始
21 | */
22 | protected final static int LOAD_BEGIN = 1;
23 | /**
24 | * 加载无更多数据
25 | */
26 | protected final static int LOAD_NO_MORE = 2;
27 | /**
28 | * 加载失败或错误
29 | */
30 | protected final static int LOAD_ERROR = 3;
31 |
32 | public AbsLoadFooter(Context context) {
33 | super(context);
34 | }
35 |
36 | public AbsLoadFooter(Context context, AttributeSet attrs) {
37 | super(context, attrs);
38 | }
39 |
40 | public AbsLoadFooter(Context context, AttributeSet attrs, int defStyleAttr) {
41 | super(context, attrs, defStyleAttr);
42 | }
43 |
44 | final void initFooter() {
45 | setLayoutParams(new LayoutParams(
46 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
47 | setVisibility(GONE);
48 | setGravity(Gravity.CENTER);
49 | init();
50 | }
51 |
52 | final void loadBegin() {
53 | if (getVisibility() == GONE) {
54 | setVisibility(VISIBLE);
55 | }
56 | loadingState(LOAD_BEGIN);
57 | }
58 |
59 | final void loadSuccess() {
60 | loadingState(LOAD_SUCCESS);
61 | }
62 |
63 | final void loadingNoMoreData() {
64 | loadingState(LOAD_NO_MORE);
65 | }
66 |
67 | final void loadingError() {
68 | loadingState(LOAD_ERROR);
69 | }
70 |
71 | /**
72 | * 刷新结束后如果有需要,可重写此方法重置LoadFooter
73 | */
74 | public void reset() {
75 | setVisibility(GONE);
76 | }
77 |
78 | /**
79 | * SRecyclerView的onDetachedFromWindow被调用,可能SRecyclerView所在的界面要被销毁,
80 | * 如果子类中有动画等未完成,可以重写此方法取消动画等耗时操作,避免造成内存泄露
81 | */
82 | public void srvDetachedFromWindow() {
83 | }
84 |
85 | /**
86 | * 加载尾部初始化
87 | */
88 | public abstract void init();
89 |
90 | /**
91 | * 加载更多的加载状态
92 | *
93 | * @param state 状态
94 | */
95 | public abstract void loadingState(int state);
96 |
97 |
98 | /**
99 | * 错误重试
100 | */
101 | final public void errorRetry() {
102 | if (listener != null) {
103 | listener.errorRetry();
104 | }
105 | }
106 |
107 | interface ErrorRetryListener {
108 | void errorRetry();
109 | }
110 |
111 | private ErrorRetryListener listener;
112 |
113 | final void setErrorRetryListener(ErrorRetryListener listener) {
114 | this.listener = listener;
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/AbsRefreshHeader.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.ValueAnimator;
6 | import android.content.Context;
7 | import android.content.res.Resources;
8 | import android.util.AttributeSet;
9 | import android.view.Gravity;
10 | import android.view.ViewGroup;
11 | import android.view.animation.DecelerateInterpolator;
12 | import android.widget.LinearLayout;
13 |
14 | /**
15 | * 功能:抽象的刷新头部,可继承并自定义刷新头部
16 | * Created by 何志伟 on 2017/7/6.
17 | */
18 | public abstract class AbsRefreshHeader extends LinearLayout {
19 |
20 | protected final static int HEADER_BOTTOM = 1;
21 | protected final static int HEADER_CENTER = 2;
22 | protected final static int NORMAL = 0;//正常状态,或者刷新结束的状态
23 | protected final static int REFRESH = 1;//正在刷新的状态
24 | protected final static int PREPARE_NORMAL = 2;//刷新前的状态,未超过刷新临界值
25 | protected final static int PREPARE_REFRESH = 3;//刷新前的状态,已超过刷新临界值
26 | private ValueAnimator animator;
27 | private boolean isAnimRefresh;
28 | private boolean isRefreshing;
29 | private int refreshHeight;
30 | private int currentHeight;
31 | private int currentState;
32 | private int sensitivity;
33 | private int duration;
34 |
35 | public AbsRefreshHeader(Context context) {
36 | super(context);
37 | }
38 |
39 | public AbsRefreshHeader(Context context, AttributeSet attrs) {
40 | super(context, attrs);
41 | }
42 |
43 | public AbsRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
44 | super(context, attrs, defStyleAttr);
45 | }
46 |
47 | final void initHeader() {
48 | setLayoutParams(new LayoutParams(
49 | ViewGroup.LayoutParams.MATCH_PARENT, 0));
50 | //获取子类的配置
51 | duration = getRefreshDuration();
52 | refreshHeight = getRefreshHeight();
53 | sensitivity = getRefreshSensitivity();
54 | int gravity = getRefreshGravity();
55 | if (gravity == HEADER_BOTTOM) {
56 | setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
57 | } else if (gravity == HEADER_CENTER) {
58 | setGravity(Gravity.CENTER);
59 | }
60 | init();
61 | }
62 |
63 | /**
64 | * 手指拖动时
65 | */
66 | final void move(float delay) {
67 | if (currentState == REFRESH || isRefreshing) return;
68 | delay = delay / sensitivity;
69 | currentHeight += delay;
70 | setHeight(currentHeight);
71 | //拖动时的状态
72 | if (getCurrentHeight() == 0) {
73 | currentState = NORMAL;
74 | refresh(NORMAL, currentHeight);
75 | } else if (getHeight() < refreshHeight) {
76 | currentState = PREPARE_NORMAL;
77 | refresh(PREPARE_NORMAL, currentHeight);
78 | } else if (getHeight() >= refreshHeight) {
79 | currentState = PREPARE_REFRESH;
80 | refresh(PREPARE_REFRESH, currentHeight);
81 | }
82 | }
83 |
84 | /**
85 | * 手指抬起时
86 | */
87 | final void up() {
88 | //手指抬起时,如果当前处于刷新状态或者刷新头部高度为0,则
89 | if (isRefreshing || currentState == NORMAL) return;
90 | //处于将要刷新状态时,松开手指即可刷新,同时改变到刷新高度
91 | if (currentState == PREPARE_REFRESH && loadListener != null) {
92 | isRefreshing = true;
93 | refresh(REFRESH, refreshHeight);
94 | loadListener.refresh();
95 | }
96 | heightChangeAnim();
97 | }
98 |
99 | private void heightChangeAnim() {
100 | if (animator != null && animator.isRunning()) return;
101 | int start, end;
102 | //需要高度动画的状态有:PREPARE_REFRESH,PREPARE_NORMAL,REFRESH
103 | switch (currentState) {
104 | case REFRESH:
105 | //刷新结束变为正常
106 | start = currentHeight;
107 | end = 0;
108 | break;
109 | case PREPARE_NORMAL:
110 | //在低于刷新高度的位置松开拖动,准备回归初始状态
111 | start = currentHeight;
112 | end = 0;
113 | break;
114 | case PREPARE_REFRESH:
115 | //在高于刷新高度的位置松开拖动,准备开始刷新
116 | start = currentHeight;
117 | end = refreshHeight;
118 | break;
119 | case NORMAL:
120 | //代码调用开始刷新,准备开始刷新
121 | start = 0;
122 | end = refreshHeight;
123 | break;
124 | default:
125 | return;
126 | }
127 | if (animator == null) {
128 | animator = ValueAnimator.ofInt(start, end);
129 | animator.setDuration(duration)
130 | .setInterpolator(new DecelerateInterpolator());
131 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
132 | @Override
133 | public void onAnimationUpdate(ValueAnimator animation) {
134 | int height = (int) animation.getAnimatedValue();
135 | setHeight(height);
136 | //高度自动更新的两个状态:刷新结束后的状态,未达到刷新高度而松手的状态
137 | if (currentState == PREPARE_NORMAL || currentState == NORMAL) {
138 | refresh(PREPARE_NORMAL, height);
139 | }
140 | }
141 | });
142 | animator.addListener(new AnimatorListenerAdapter() {
143 | @Override
144 | public void onAnimationEnd(Animator animation) {
145 | heightChangeAnimEnd();
146 | }
147 | });
148 | } else {
149 | animator.setIntValues(start, end);
150 | }
151 | animator.start();
152 | }
153 |
154 | private void setHeight(int height) {
155 | height = height < 0 ? 0 : height;
156 | ViewGroup.LayoutParams params = getLayoutParams();
157 | params.height = height;
158 | setLayoutParams(params);
159 | }
160 |
161 | private int getCurrentHeight() {
162 | return getLayoutParams().height;
163 | }
164 |
165 | final boolean isDelay() {
166 | return getLayoutParams().height > 0 && currentState != REFRESH;
167 | }
168 |
169 | /**
170 | * 高度动画结束时,currentState只可能有四种状态:
171 | * PREPARE_REFRESH,PREPARE_NORMAL,REFRESH和NORMAL
172 | */
173 | private void heightChangeAnimEnd() {
174 | switch (currentState) {
175 | case REFRESH://刷新结束
176 | currentState = NORMAL;
177 | currentHeight = 0;
178 | isRefreshing = false;
179 | refresh(NORMAL, 0);
180 | break;
181 | case PREPARE_NORMAL://在低于刷新高度的位置松开拖动,当前已回归初始状态
182 | currentState = NORMAL;
183 | currentHeight = 0;
184 | break;
185 | case PREPARE_REFRESH://在高于刷新高度的位置松开拖动,当前已开始刷新
186 | currentState = REFRESH;
187 | currentHeight = refreshHeight;
188 | break;
189 | case NORMAL://代码调用自动刷新,当前已开始刷新
190 | if (currentHeight < 0) return;
191 | currentState = REFRESH;
192 | if (isAnimRefresh) {
193 | currentHeight = refreshHeight;
194 | }
195 | refresh(REFRESH, refreshHeight);
196 | loadListener.refresh();
197 | break;
198 | }
199 | }
200 |
201 | /**
202 | * 请求数据,刷新完成,恢复初始状态
203 | */
204 | final void refreshComplete() {
205 | if (currentState == NORMAL) return;
206 | if (animator != null && animator.isRunning()) {
207 | animator.cancel();
208 | }
209 | currentState = REFRESH;
210 | heightChangeAnim();
211 | }
212 |
213 | final void startRefresh(final boolean isAnim) {
214 | if (loadListener == null || currentState == REFRESH || isRefreshing) return;
215 | isRefreshing = true;
216 | isAnimRefresh = isAnim;
217 | int delay = getWidth() == 0 ? 500 : 0;
218 | postDelayed(runnable, delay);
219 | }
220 |
221 | private final Runnable runnable = new Runnable() {
222 | @Override
223 | public void run() {
224 | currentState = NORMAL;
225 | if (isAnimRefresh) {
226 | heightChangeAnim();
227 | } else {
228 | heightChangeAnimEnd();
229 | }
230 | }
231 | };
232 |
233 | interface RefreshListener {
234 | void refresh();
235 | }
236 |
237 | private RefreshListener loadListener;
238 |
239 | final void setRefreshListener(RefreshListener listener) {
240 | loadListener = listener;
241 | }
242 |
243 | /*----------------------------------------获取刷新配置--------------------------------*/
244 | public int getRefreshHeight() {
245 | return dip2px(60);
246 | }
247 |
248 | private int dip2px(float value) {
249 | final float scale = Resources.getSystem()
250 | .getDisplayMetrics().density;
251 | return (int) (value * scale + 0.5f);
252 | }
253 |
254 | public int getRefreshGravity() {
255 | return HEADER_BOTTOM;
256 | }
257 |
258 | public int getRefreshDuration() {
259 | return 300;
260 | }
261 |
262 | /**
263 | * 刷新灵敏度
264 | */
265 | public int getRefreshSensitivity() {
266 | return 3;
267 | }
268 | /*----------------------------------------获取刷新配置--------------------------------*/
269 |
270 | /**
271 | * SRecyclerView的onDetachedFromWindow被调用,可能SRecyclerView所在的界面要被销毁,
272 | * 如果子类中有动画等未完成,可以重写此方法取消动画等耗时操作,避免造成内存泄露
273 | */
274 | public void srvDetachedFromWindow() {
275 | removeCallbacks(runnable);
276 | if (animator != null && animator.isRunning()) {
277 | animator.cancel();
278 | heightChangeAnimEnd();
279 | setHeight(0);
280 | }
281 | }
282 |
283 | /**
284 | * 子类刷新头初始化
285 | */
286 | public abstract void init();
287 |
288 |
289 | /**
290 | * 刷新头部的调用方法
291 | *
292 | * NORMAL:初始化状态或者刷新结束后状态,刷新结束,高度变为0时,会调用此状态
293 | *
294 | * REFRESH:正在刷新状态,高度从松开手指时的高度到刷新变为0的高度之间,都属于刷新状态,
295 | * --------高度变为0时,状态变为NORMAL,会调用NORMAL状态
296 | *
297 | * PREPARE_NORMAL:准备回归初始化的状态,手指移动时当前高度小于刷新高度,此时松开手指,不会调用NORMAL状态,
298 | * ---------------或者自动刷新时当前高度未达到刷新高度的状态,达到刷新高度后会调用REFRESH状态
299 | *
300 | * PREPARE_REFRESH:准备刷新的状态,手指移动时当前高度大于刷新高度,此时松开手指,会立刻调用REFRESH状态,
301 | * ---------------同时高度变为刷新高度
302 | *
303 | * @param state 分别为:NORMAL,REFRESH,PREPARE_NORMAL,PREPARE_REFRESH
304 | * @param height 当前刷新头的高度
305 | */
306 | // switch (state) {
307 | // case NORMAL:
308 | //
309 | // break;
310 | // case REFRESH:
311 | //
312 | // break;
313 | // case PREPARE_NORMAL:
314 | //
315 | // break;
316 | // case PREPARE_REFRESH:
317 | //
318 | // break;
319 | // }
320 | public abstract void refresh(int state, int height);
321 |
322 | }
323 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/AbsStateView.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.view.ViewGroup;
8 | import android.widget.FrameLayout;
9 |
10 | /**
11 | * author: hzw
12 | * time: 2018/2/5 下午5:24
13 | * description:
14 | */
15 | public abstract class AbsStateView extends FrameLayout {
16 |
17 | public AbsStateView(@NonNull Context context) {
18 | this(context, null);
19 | }
20 |
21 | public AbsStateView(@NonNull Context context, @Nullable AttributeSet attrs) {
22 | this(context, attrs, 0);
23 | }
24 |
25 | public AbsStateView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
26 | super(context, attrs, defStyleAttr);
27 | setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
28 | init();
29 | }
30 |
31 | /**
32 | * 状态View的异常时的刷新重新
33 | *
34 | * @param isAnim 是否有刷新动画
35 | */
36 | final public void retry(boolean isAnim) {
37 | if (listener != null) {
38 | listener.retry(isAnim);
39 | }
40 | }
41 |
42 | interface RetryListener {
43 | void retry(boolean isAnim);
44 | }
45 |
46 | private RetryListener listener;
47 |
48 | final void setRetryListener(RetryListener listener) {
49 | this.listener = listener;
50 | }
51 |
52 | public abstract void init();
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/BaseSRVAdapter.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * 功能:SRV的简易适配器,可用于普通的RecyclerView
12 | * Created by 何志伟 on 2017/7/13.
13 | */
14 | public abstract class BaseSRVAdapter extends RecyclerView.Adapter {
15 |
16 | private final int itemLayoutId;
17 | private final List mList;
18 |
19 | public BaseSRVAdapter(List list, int itemLayoutId) {
20 | this.mList = list;
21 | this.itemLayoutId = itemLayoutId;
22 | }
23 |
24 | @Override public SRVHolder onCreateViewHolder(ViewGroup parent, int viewType) {
25 | View view = LayoutInflater.from(parent.getContext())
26 | .inflate(itemLayoutId, parent, false);
27 | SRVHolder holder = SRVHolder.getViewHolder(view);
28 | onCreateView(parent, holder);
29 | return holder;
30 | }
31 |
32 | /**
33 | * 用于初始化view
34 | */
35 | public void onCreateView(ViewGroup parent, SRVHolder holder) {
36 | }
37 |
38 | @Override public void onBindViewHolder(final SRVHolder holder, int position) {
39 | onBindView(holder, mList.get(position), position);
40 | }
41 |
42 | public abstract void onBindView(SRVHolder holder, T data, int i);
43 |
44 | @Override public int getItemCount() {
45 | return mList.size();
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/SRVConfig.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.content.Context;
4 | import android.content.pm.ApplicationInfo;
5 | import android.content.pm.PackageManager;
6 |
7 | /**
8 | * 功能:获取用户的全局SRV配置
9 | * Created by 何志伟 on 2017/7/17.
10 | */
11 | class SRVConfig {
12 |
13 | private static final String SRV_CONFIG_VALUE = SRecyclerViewModule.class.getSimpleName();
14 | private volatile static SRVConfig instance;
15 | private SRecyclerViewModule module;
16 |
17 | private SRVConfig(Context context) {
18 | initConfig(context);
19 | }
20 |
21 | static SRVConfig getInstance(Context context) {
22 | if (instance == null) {
23 | synchronized (SRVConfig.class) {
24 | if (instance == null) {
25 | instance = new SRVConfig(context.getApplicationContext());
26 | }
27 | }
28 | }
29 | return instance;
30 | }
31 |
32 | private void initConfig(Context context) {
33 | String moduleName = null;
34 | try {
35 | ApplicationInfo appInfo = context.getPackageManager()
36 | .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
37 | if (appInfo.metaData != null) {
38 | for (String key : appInfo.metaData.keySet()) {
39 | if (SRV_CONFIG_VALUE.equals(appInfo.metaData.get(key))) {
40 | moduleName = key;
41 | break;
42 | }
43 | }
44 | }
45 | } catch (PackageManager.NameNotFoundException ignored) {
46 | }
47 | if (moduleName != null) {
48 | try {
49 | Class moduleClass = Class.forName(moduleName);
50 | Object cls = moduleClass.newInstance();
51 | if (cls instanceof SRecyclerViewModule) {
52 | module = (SRecyclerViewModule) cls;
53 | }
54 | } catch (Exception ignored) {
55 | }
56 | }
57 | }
58 |
59 | SRecyclerViewModule getConfig() {
60 | return module;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/SRVHolder.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.util.SparseArray;
5 | import android.view.View;
6 | import android.widget.Button;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | /**
11 | * 功能:BaseSRVAdapter的ViewHolder
12 | * Created by 何志伟 on 2017/7/17.
13 | */
14 | public class SRVHolder extends RecyclerView.ViewHolder {
15 | private final SparseArray views;
16 | private final View itemView;
17 |
18 | private SRVHolder(View itemView) {
19 | super(itemView);
20 | this.itemView = itemView;
21 | views = new SparseArray<>();
22 | }
23 |
24 | public static SRVHolder getViewHolder(View itemView) {
25 | SRVHolder holder = (SRVHolder) itemView.getTag(R.id.srv_item_holder);
26 | if (holder == null) {
27 | holder = new SRVHolder(itemView);
28 | itemView.setTag(R.id.srv_item_holder, holder);
29 | }
30 | return holder;
31 | }
32 |
33 | @SuppressWarnings("unchecked") public T getView(int id) {
34 | View childView = views.get(id);
35 | if (childView == null) {
36 | childView = itemView.findViewById(id);
37 | views.put(id, childView);
38 | }
39 | return (T) childView;
40 | }
41 |
42 | //返回ItemView
43 | public View getItemView() {
44 | return itemView;
45 | }
46 |
47 | //封装返回常用的控件
48 | public TextView getTextView(int id) {
49 | return getView(id);
50 | }
51 |
52 | public Button getButton(int id) {
53 | return getView(id);
54 | }
55 |
56 | public ImageView getImageView(int id) {
57 | return getView(id);
58 | }
59 |
60 | //封装设置常用的控件
61 | public SRVHolder setTextView(int id, CharSequence text) {
62 | getTextView(id).setText(text);
63 | return this;
64 | }
65 |
66 | public SRVHolder setButton(int id, CharSequence title) {
67 | getButton(id).setText(title);
68 | return this;
69 | }
70 |
71 | public SRVHolder setImageResource(int id, int resource) {
72 | getImageView(id).setImageResource(resource);
73 | return this;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/SRVLoadFooter.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 |
8 | /**
9 | * 功能:SRecyclerView默认的加载尾部
10 | * Created by 何志伟 on 2017/7/17.
11 | */
12 | class SRVLoadFooter extends AbsLoadFooter {
13 |
14 | private View load, noMore, error;
15 |
16 | public SRVLoadFooter(Context context) {
17 | super(context);
18 | }
19 |
20 | public SRVLoadFooter(Context context, AttributeSet attrs) {
21 | super(context, attrs);
22 | }
23 |
24 | public SRVLoadFooter(Context context, AttributeSet attrs, int defStyleAttr) {
25 | super(context, attrs, defStyleAttr);
26 | }
27 |
28 | @Override public void init() {
29 | View view = LayoutInflater.from(getContext())
30 | .inflate(R.layout.srv_load_footer, this, false);
31 | noMore = view.findViewById(R.id.tv_src_loadNoMore);
32 | load = view.findViewById(R.id.v_srv_loading);
33 | error = view.findViewById(R.id.tv_src_loadError);
34 | error.setOnClickListener(new OnClickListener() {
35 | @Override public void onClick(View v) {
36 | errorRetry();
37 | }
38 | });
39 | addView(view);
40 | }
41 |
42 | @Override public void loadingState(int state) {
43 | switch (state) {
44 | case LOAD_SUCCESS:
45 | load.setVisibility(GONE);
46 | noMore.setVisibility(GONE);
47 | error.setVisibility(GONE);
48 | break;
49 | case LOAD_ERROR:
50 | load.setVisibility(GONE);
51 | noMore.setVisibility(GONE);
52 | error.setVisibility(VISIBLE);
53 | break;
54 | case LOAD_NO_MORE:
55 | load.setVisibility(GONE);
56 | noMore.setVisibility(VISIBLE);
57 | error.setVisibility(GONE);
58 | break;
59 | case LOAD_BEGIN:
60 | load.setVisibility(VISIBLE);
61 | noMore.setVisibility(GONE);
62 | error.setVisibility(GONE);
63 | break;
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/SRVRefreshHeader.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.animation.Animation;
8 | import android.view.animation.RotateAnimation;
9 | import android.widget.TextView;
10 |
11 | /**
12 | * 功能:SRecyclerView默认的刷新头部
13 | * Created by 何志伟 on 2017/7/17.
14 | */
15 | class SRVRefreshHeader extends AbsRefreshHeader {
16 |
17 | private RotateAnimation upAnim, downAnim;
18 | private boolean isUp = true;
19 | private View progress, icon;
20 | private TextView tips;
21 |
22 | public SRVRefreshHeader(Context context) {
23 | super(context);
24 | }
25 |
26 | public SRVRefreshHeader(Context context, AttributeSet attrs) {
27 | super(context, attrs);
28 | }
29 |
30 | public SRVRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
31 | super(context, attrs, defStyleAttr);
32 | }
33 |
34 | @Override public void init() {
35 | View view = LayoutInflater.from(getContext())
36 | .inflate(R.layout.srv_refresh_header, this, false);
37 | tips = view.findViewById(R.id.tv_src_refreshTips);
38 | progress = view.findViewById(R.id.pb_srv_refreshProgress);
39 | icon = view.findViewById(R.id.img_srv_refreshIcon);
40 | addView(view);
41 | upAnim = new RotateAnimation(180, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
42 | downAnim = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
43 | upAnim.setDuration(200);
44 | upAnim.setFillAfter(true);
45 | downAnim.setDuration(200);
46 | downAnim.setFillAfter(true);
47 | }
48 |
49 | @Override public void srvDetachedFromWindow() {
50 | super.srvDetachedFromWindow();
51 | if (upAnim != null) upAnim.cancel();
52 | if (downAnim != null) downAnim.cancel();
53 | if (icon != null) icon.clearAnimation();
54 | }
55 |
56 | @Override public void refresh(int state, int height) {
57 | switch (state) {
58 | case NORMAL:
59 | progress.setVisibility(GONE);
60 | icon.setVisibility(VISIBLE);
61 | tips.setText(getContext().getString(R.string.srv_refresh_normal));
62 | isUp = true;
63 | break;
64 | case REFRESH:
65 | progress.setVisibility(VISIBLE);
66 | icon.clearAnimation();
67 | icon.setVisibility(GONE);
68 | tips.setText(getContext().getString(R.string.srv_refreshing));
69 | break;
70 | case PREPARE_NORMAL:
71 | tips.setText(getContext().getString(R.string.srv_refresh_normal));
72 | if (!isUp) icon.startAnimation(upAnim);
73 | isUp = true;
74 | break;
75 | case PREPARE_REFRESH:
76 | tips.setText(getContext().getString(R.string.srv_refresh_prepare));
77 | if (isUp) icon.startAnimation(downAnim);
78 | isUp = false;
79 | break;
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/SRecyclerView.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Rect;
9 | import android.support.annotation.LayoutRes;
10 | import android.support.design.widget.AppBarLayout;
11 | import android.support.design.widget.CoordinatorLayout;
12 | import android.support.v7.widget.GridLayoutManager;
13 | import android.support.v7.widget.LinearLayoutManager;
14 | import android.support.v7.widget.RecyclerView;
15 | import android.support.v7.widget.StaggeredGridLayoutManager;
16 | import android.util.AttributeSet;
17 | import android.util.SparseArray;
18 | import android.view.LayoutInflater;
19 | import android.view.MotionEvent;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.view.ViewParent;
23 |
24 | import java.util.List;
25 |
26 | /**
27 | * 功能:刷新与加载更多
28 | * Created by 何志伟 on 2017/7/6.
29 | */
30 | public class SRecyclerView extends RecyclerView implements AppBarLayout.OnOffsetChangedListener {
31 |
32 | private AbsRefreshHeader refreshHeader;
33 | private WrapperAdapter wrapperAdapter;
34 | private AbsLoadFooter loadingFooter;
35 | private SRecyclerViewModule config;
36 | private LoadListener loadListener;
37 | private AppBarLayout appBarLayout;
38 | private SRVDivider divider;
39 | private View emptyView;
40 | private View errorView;
41 | private View LoadView;
42 |
43 | private final SparseArray headers = new SparseArray<>();
44 | private final SparseArray footers = new SparseArray<>();
45 | private int HEADER_TYPE = 1314522;
46 | private int FOOTER_TYPE = HEADER_TYPE * 10;
47 |
48 | private boolean isLoadingEnable = true;
49 | private boolean isRefreshEnable = true;
50 | private boolean isAppBarExpand = true;
51 | private boolean isFirstMove = true;
52 | private boolean isLoading = false;
53 | private boolean isPullUp;
54 |
55 | private float dividerHeight;
56 | private float dividerRight;
57 | private float dividerLeft;
58 | private float firstY;
59 | private float lastY;
60 |
61 | private int currentScrollMode;
62 | private int currentState;
63 | private int dividerColor;
64 |
65 |
66 | public SRecyclerView(Context context) {
67 | super(context);
68 | init(null, 0);
69 | }
70 |
71 | public SRecyclerView(Context context, AttributeSet attrs) {
72 | super(context, attrs);
73 | init(attrs, 0);
74 | }
75 |
76 | public SRecyclerView(Context context, AttributeSet attrs, int defStyle) {
77 | super(context, attrs, defStyle);
78 | init(attrs, defStyle);
79 | }
80 |
81 | private void init(AttributeSet attrs, int def) {
82 | TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SRecyclerView, def, 0);
83 | dividerHeight = a.getDimension(R.styleable.SRecyclerView_dividerHeight, 0);
84 | dividerColor = a.getColor(R.styleable.SRecyclerView_dividerColor, Color.TRANSPARENT);
85 | dividerRight = a.getDimension(R.styleable.SRecyclerView_dividerRightMargin, 0);
86 | dividerLeft = a.getDimension(R.styleable.SRecyclerView_dividerLeftMargin, 0);
87 | a.recycle();
88 | currentScrollMode = getOverScrollMode();
89 | LinearLayoutManager manager = new LinearLayoutManager(getContext());
90 | manager.setOrientation(LinearLayoutManager.VERTICAL);
91 | setLayoutManager(manager);
92 | }
93 |
94 | @Override
95 | public boolean onTouchEvent(MotionEvent e) {
96 | if (refreshHeader == null) return super.onTouchEvent(e);
97 | switch (e.getAction()) {
98 | case MotionEvent.ACTION_MOVE:
99 | if (isFirstMove) {
100 | isFirstMove = false;
101 | firstY = e.getRawY();
102 | lastY = firstY;
103 | }
104 | float y = e.getRawY();
105 | float delay = y - lastY;
106 | lastY = y;
107 | if (isRefreshEnable && isTop() && isAppBarExpand) {
108 | refreshHeader.move(delay);
109 | setOverScrollMode(View.OVER_SCROLL_NEVER);
110 | if (refreshHeader.isDelay()) return false;
111 | }
112 | break;
113 | case MotionEvent.ACTION_UP:
114 | isFirstMove = true;
115 | isPullUp = e.getRawY() - firstY < 0;
116 | setOverScrollMode(currentScrollMode);
117 | default:
118 | if (isRefreshEnable && isTop() && isAppBarExpand) {
119 | refreshHeader.up();
120 | }
121 | }
122 | return super.onTouchEvent(e);
123 | }
124 |
125 | private boolean isTop() {
126 | return refreshHeader.getParent() != null;
127 | }
128 |
129 | @Override
130 | protected void onAttachedToWindow() {
131 | super.onAttachedToWindow();
132 | ViewParent parent = getParent();
133 | while (parent != null) {
134 | if (parent instanceof CoordinatorLayout) break;
135 | parent = parent.getParent();
136 | }
137 | if (parent == null) return;
138 | CoordinatorLayout layout = (CoordinatorLayout) parent;
139 | for (int i = 0; i < layout.getChildCount(); i++) {
140 | View child = layout.getChildAt(i);
141 | if (child instanceof AppBarLayout) {
142 | appBarLayout = (AppBarLayout) child;
143 | break;
144 | }
145 | }
146 | if (appBarLayout != null) {
147 | appBarLayout.addOnOffsetChangedListener(this);
148 | }
149 | }
150 |
151 | @Override
152 | protected void onDetachedFromWindow() {
153 | super.onDetachedFromWindow();
154 | clearOnScrollListeners();
155 | if (getAdapter() != null) {
156 | getAdapter().unregisterAdapterDataObserver(mObserver);
157 | }
158 | if (appBarLayout != null) {
159 | appBarLayout.removeOnOffsetChangedListener(this);
160 | }
161 | if (refreshHeader != null) refreshHeader.srvDetachedFromWindow();
162 | if (loadingFooter != null) loadingFooter.srvDetachedFromWindow();
163 | headers.clear();
164 | footers.clear();
165 | clearOnScrollListeners();
166 | loadListener = null;
167 | wrapperAdapter = null;
168 | }
169 |
170 | @Override
171 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
172 | isAppBarExpand = verticalOffset == 0;
173 | }
174 |
175 | private final AdapterDataObserver mObserver = new AdapterDataObserver() {
176 | @Override
177 | public void onChanged() {
178 | wrapperAdapter.notifyDataSetChanged();
179 | checkEmpty();
180 | }
181 |
182 | @Override
183 | public void onItemRangeInserted(int positionStart, int itemCount) {
184 | wrapperAdapter.notifyItemRangeInserted(positionStart + getOffset(), itemCount);
185 | checkEmpty();
186 | }
187 |
188 | @Override
189 | public void onItemRangeRemoved(int positionStart, int itemCount) {
190 | wrapperAdapter.notifyItemRangeRemoved(positionStart + getOffset(), itemCount);
191 | checkEmpty();
192 | }
193 |
194 | @Override
195 | public void onItemRangeChanged(int positionStart, int itemCount) {
196 | wrapperAdapter.notifyItemRangeChanged(positionStart + getOffset(), itemCount);
197 | }
198 |
199 | @Override
200 | public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
201 | super.onItemRangeChanged(positionStart, itemCount, payload);
202 | wrapperAdapter.notifyItemRangeChanged(positionStart + getOffset(), itemCount, payload);
203 | }
204 |
205 | @Override
206 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
207 | wrapperAdapter.notifyItemMoved(fromPosition + getOffset(), toPosition);
208 | }
209 |
210 | private int getOffset() {
211 | return wrapperAdapter.getHeaderCount();
212 | }
213 | };
214 |
215 |
216 | /*-------------------------------setEmptyView-----------------------------------------start--------*/
217 | public void setEmptyView(@LayoutRes int layoutId) {
218 | setEmptyView(LayoutInflater.from(getContext())
219 | .inflate(layoutId, this, false));
220 | }
221 |
222 | public void setEmptyView(View view) {
223 | emptyView = view;
224 | if (emptyView != null) {
225 | checkEmpty();
226 | }
227 | }
228 |
229 | private void checkEmpty() {
230 | if (loadListener != null && wrapperAdapter != null && emptyView != null) {
231 | if (wrapperAdapter.isEmpty()) {
232 | wrapperAdapter.updateStateView(WrapperAdapter.STATE_EMPTY);
233 | } else {
234 | wrapperAdapter.updateStateView(WrapperAdapter.STATE_NORMAL);
235 | }
236 | }
237 | }
238 | /*-------------------------------setEmptyView-----------------------------------------end--------*/
239 |
240 |
241 | /*-------------------------------setErrorView-----------------------------------------start--------*/
242 | public void setErrorView(@LayoutRes int layoutId) {
243 | setErrorView(LayoutInflater.from(getContext())
244 | .inflate(layoutId, this, false));
245 | }
246 |
247 | public void setErrorView(View view) {
248 | errorView = view;
249 | if (errorView != null) {
250 | checkError();
251 | }
252 | }
253 |
254 | private void checkError() {
255 | if (wrapperAdapter != null && wrapperAdapter.isEmpty() && errorView != null) {
256 | wrapperAdapter.updateStateView(WrapperAdapter.STATE_ERROR);
257 | }
258 | }
259 | /*-------------------------------setErrorView-----------------------------------------end--------*/
260 |
261 |
262 | /*-------------------------------setLoadingView-----------------------------------------start--------*/
263 | public void setLoadingView(@LayoutRes int layoutId) {
264 | setLoadingView(LayoutInflater.from(getContext())
265 | .inflate(layoutId, this, false));
266 | }
267 |
268 | public void setLoadingView(View view) {
269 | LoadView = view;
270 | }
271 |
272 | private void checkLoading() {
273 | if (wrapperAdapter != null && LoadView != null) {
274 | wrapperAdapter.updateStateView(WrapperAdapter.STATE_LOADING);
275 | }
276 | }
277 | /*-------------------------------setLoadingView-----------------------------------------end--------*/
278 |
279 |
280 | @Override
281 | public void setAdapter(Adapter adapter) {
282 | if (getAdapter() != null) {
283 | getAdapter().unregisterAdapterDataObserver(mObserver);
284 | }
285 | wrapperAdapter = new WrapperAdapter(adapter);
286 | super.setAdapter(wrapperAdapter);
287 | adapter.registerAdapterDataObserver(mObserver);
288 | if (divider == null) {
289 | setDivider(dividerColor, dividerHeight, dividerLeft, dividerRight);
290 | }
291 | //设置了加载功能时,初始化刷新头和加载尾部
292 | if (config == null && loadListener != null && isInitLoad()) {
293 | initRefresh();
294 | initLoading();
295 | checkLoading();
296 | }
297 | }
298 |
299 | @Override
300 | public Adapter getAdapter() {
301 | if (wrapperAdapter != null) {
302 | return wrapperAdapter.getAdapter();
303 | }
304 | return null;
305 | }
306 |
307 | /**
308 | * 检查LayoutManager以确定是否需要初始化下拉刷新和加载更多,只有当LayoutManager属于
309 | * LinearLayoutManager和GridLayoutManager,并且方向为纵向时才返回true
310 | */
311 | private boolean isInitLoad() {
312 | LayoutManager manager = getLayoutManager();
313 | if (!(manager instanceof LinearLayoutManager) ||
314 | ((LinearLayoutManager) manager).getOrientation() != VERTICAL) {
315 | refreshHeader = null;
316 | loadingFooter = null;
317 | isLoadingEnable = false;
318 | isRefreshEnable = false;
319 | return false;
320 | }
321 | return true;
322 | }
323 |
324 | @Override
325 | public void setLayoutManager(LayoutManager layout) {
326 | super.setLayoutManager(layout);
327 | if (!(layout instanceof LinearLayoutManager) || (layout instanceof GridLayoutManager)) {
328 | if (divider != null) removeItemDecoration(divider);
329 | }
330 | }
331 |
332 | public void setDivider(int color, float height, float dividerLeft, float dividerRight) {
333 | LayoutManager layout = getLayoutManager();
334 | boolean isSetDivider = height != 0 && layout instanceof LinearLayoutManager;
335 | boolean isGridManager = layout instanceof GridLayoutManager;
336 | //只对LinearLayoutManager设置分割线
337 | if (isSetDivider && !isGridManager) {
338 | if (divider != null) removeItemDecoration(divider);
339 | LinearLayoutManager manager = (LinearLayoutManager) layout;
340 | if (manager.getOrientation() == LinearLayoutManager.VERTICAL) {
341 | divider = new SRVDivider(LinearLayoutManager.VERTICAL);
342 | divider.initVerticalDivider(height, color, dividerLeft, dividerRight);
343 | addItemDecoration(divider);
344 | } else if (manager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
345 | divider = new SRVDivider(LinearLayoutManager.HORIZONTAL);
346 | divider.initHorizontalDivider(height, color);
347 | addItemDecoration(divider);
348 | }
349 | }
350 | }
351 |
352 | /**
353 | * 刷新和加载的监听
354 | */
355 | public interface LoadListener {
356 | void refresh();
357 |
358 | void loading();
359 | }
360 |
361 | public void setLoadListener(LoadListener listener) {
362 | loadListener = listener;
363 | //已经调用setAdapter,但是没有调用此方法
364 | if (getAdapter() != null && isInitLoad() && loadListener != null) {
365 | initRefresh();
366 | initLoading();
367 | checkLoading();
368 | }
369 | }
370 |
371 | /*------------------------------------------刷新头部操作----------------------------begin------*/
372 | private void initRefresh() {
373 | //只在此方法中仅仅获取一次用户全局配置
374 | initSRVConfig();
375 | //没有设置刷新头部时,设置默认的刷新头部,否则使用设置的刷新头
376 | if (refreshHeader == null) {
377 | refreshHeader = new SRVRefreshHeader(getContext());
378 | }
379 | refreshHeader.initHeader();
380 | wrapperAdapter.setRefreshHeader(refreshHeader);
381 | refreshHeader.setRefreshListener(new AbsRefreshHeader.RefreshListener() {
382 | @Override
383 | public void refresh() {
384 | loadListener.refresh();
385 | }
386 | });
387 | }
388 |
389 | /**
390 | * 获取用户配置的刷新头和加载尾
391 | * 配置的优先级为:代码设置 > SRVConfig配置
392 | */
393 | private void initSRVConfig() {
394 | config = SRVConfig.getInstance(getContext()).getConfig();
395 | if (config != null) {
396 | if (refreshHeader == null) refreshHeader = config.getRefreshHeader(getContext());
397 | if (loadingFooter == null) loadingFooter = config.getLoadingFooter(getContext());
398 | if (LoadView == null) LoadView = config.getLoadingView(getContext());
399 | if (errorView == null) errorView = config.getErrorView(getContext());
400 | if (emptyView == null) emptyView = config.getEmptyView(getContext());
401 | if (emptyView instanceof AbsStateView) {
402 | ((AbsStateView) emptyView).setRetryListener(new AbsStateView.RetryListener() {
403 | @Override
404 | public void retry(boolean isAnim) {
405 | startRefresh(isAnim);
406 | }
407 | });
408 | }
409 | if (errorView instanceof AbsStateView) {
410 | ((AbsStateView) errorView).setRetryListener(new AbsStateView.RetryListener() {
411 | @Override
412 | public void retry(boolean isAnim) {
413 | startRefresh(isAnim);
414 | }
415 | });
416 | }
417 | }
418 | }
419 |
420 | /**
421 | * 设置单独的刷新头部
422 | * 应在setAdapter之前调用才有效
423 | */
424 | public void setRefreshHeader(AbsRefreshHeader view) {
425 | if (view == null || getAdapter() != null) return;
426 | refreshHeader = view;
427 | }
428 |
429 | public void addHeader(View view) {
430 | if (view == null) return;
431 | checkAddView(view);
432 | headers.put(HEADER_TYPE++, view);
433 | if (wrapperAdapter != null) {
434 | wrapperAdapter.notifyAddHeader();
435 | checkEmpty();
436 | }
437 | }
438 |
439 | public void removeHeader(View view) {
440 | if (wrapperAdapter != null) {
441 | wrapperAdapter.removeHeader(view);
442 | checkEmpty();
443 | }
444 | }
445 |
446 | public void refreshComplete() {
447 | if (refreshHeader != null) {
448 | isLoading = false;
449 | refreshHeader.refreshComplete();
450 | loadingFooter.reset();
451 | loadingFooter.setTag(false);//isScroll
452 | }
453 | }
454 |
455 | public void startRefresh(boolean isAnim) {
456 | if (refreshHeader != null && isRefreshEnable && getAdapter() != null) {
457 | if (isAnim) scrollToPosition(0);
458 | refreshHeader.startRefresh(isAnim);
459 | }
460 | }
461 |
462 | public void setRefreshEnable(boolean enable) {
463 | isRefreshEnable = enable;
464 | if (!enable && refreshHeader != null && getAdapter() != null && loadListener != null) {
465 | isLoading = false;
466 | refreshHeader.refreshComplete();
467 | }
468 | }
469 |
470 | public void refreshError() {
471 | refreshComplete();
472 | checkError();
473 | }
474 | /*------------------------------------------刷新头部操作----------------------------end--------*/
475 |
476 |
477 | /*------------------------------------------尾部操作--------------------------------begin-----*/
478 | private void initLoading() {
479 | //用户没有设置刷新头部时,设置默认的刷新头部,否则使用用户的刷新头
480 | if (loadingFooter == null) {
481 | loadingFooter = new SRVLoadFooter(getContext());
482 | }
483 | loadingFooter.initFooter();
484 | wrapperAdapter.loadingEnable(isLoadingEnable);
485 | //刷新和加载只支持垂直方向的LinearLayoutManager和GridLayoutManager布局
486 | loadingFooter.setTag(false);//isScroll
487 | loadingFooter.setErrorRetryListener(new AbsLoadFooter.ErrorRetryListener() {
488 | @Override
489 | public void errorRetry() {
490 | judgeLastItem();
491 | }
492 | });
493 | addOnScrollListener(new OnScrollListener() {
494 | @Override
495 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
496 | boolean isScroll = (boolean) loadingFooter.getTag();
497 | if (!isScroll && newState == SCROLL_STATE_IDLE && isLoadingEnable && isPullUp) {
498 | judgeLastItem();
499 | }
500 | }
501 |
502 | @Override
503 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
504 | if (dy > 0 && isLoadingEnable) {
505 | loadingFooter.setTag(true);//current scroll
506 | judgeLastItem();
507 | }
508 | }
509 | });
510 | }
511 |
512 | /**
513 | * 判断是否开始加载更多,只有滑动到最后一个Item,并且当前有数据时,才会加载更多
514 | */
515 | private synchronized void judgeLastItem() {
516 | LinearLayoutManager manager = (LinearLayoutManager) getLayoutManager();
517 | int last = manager.findLastVisibleItemPosition();
518 | if (!isLoading && last > 1 && last == (wrapperAdapter.getItemCount() - 1)) {
519 | isLoading = true;
520 | loadingFooter.loadBegin();
521 | loadListener.loading();
522 | }
523 | }
524 |
525 | /**
526 | * 设置自己的加载尾部
527 | */
528 | public void setLoadingFooter(AbsLoadFooter view) {
529 | if (view == null || getAdapter() != null) return;
530 | loadingFooter = view;
531 | }
532 |
533 | public void addFooter(View view) {
534 | if (view == null) return;
535 | checkAddView(view);
536 | footers.put(FOOTER_TYPE++, view);
537 | if (wrapperAdapter != null) {
538 | wrapperAdapter.notifyAddFooter();
539 | checkEmpty();
540 | }
541 | }
542 |
543 | public void removeFooter(View view) {
544 | if (wrapperAdapter != null) {
545 | wrapperAdapter.removeFooter(view);
546 | checkEmpty();
547 | }
548 | }
549 |
550 | public void loadingComplete() {
551 | if (loadingFooter != null) {
552 | isLoading = false;
553 | loadingFooter.loadSuccess();
554 | }
555 | }
556 |
557 | public void setLoadingEnable(boolean enable) {
558 | isLoadingEnable = enable;
559 | if (wrapperAdapter != null) {
560 | wrapperAdapter.loadingEnable(enable);
561 | }
562 | if (!enable && getAdapter() != null && loadListener != null) {
563 | loadingComplete();
564 | }
565 | }
566 |
567 | public void loadNoMoreData() {
568 | if (loadingFooter != null) {
569 | isLoading = true;
570 | loadingFooter.loadingNoMoreData();
571 | }
572 | }
573 |
574 | public void loadingError() {
575 | if (loadingFooter != null) {
576 | isLoading = false;
577 | loadingFooter.loadingError();
578 | }
579 | }
580 | /*------------------------------------------尾部操作-------------------------------end------*/
581 |
582 |
583 | private void checkAddView(View view) {
584 | if (view.getParent() != null) {
585 | throw new IllegalStateException("The specified child already has a parent. "
586 | + "You must call removeView() on the child's parent first.");
587 | }
588 | }
589 |
590 | private class WrapperAdapter extends RecyclerView.Adapter {
591 | private final int REFRESH_HEADER = 1314520;
592 | private final int LOAD_FOOTER = HEADER_TYPE + FOOTER_TYPE;
593 | private static final int STATE_LOADING = 1;
594 | private static final int STATE_EMPTY = 2;
595 | private static final int STATE_ERROR = 3;
596 | private static final int STATE_NORMAL = 0;
597 | private final ClickListener listener;
598 | private final Adapter adapter;
599 |
600 | private WrapperAdapter(Adapter adapter) {
601 | this.adapter = adapter;
602 | listener = new ClickListener();
603 | }
604 |
605 | private Adapter getAdapter() {
606 | return adapter;
607 | }
608 |
609 | private boolean isHeader(int position) {
610 | return position < getHeaderCount();
611 | }
612 |
613 | private void notifyAddHeader() {
614 | notifyItemInserted(getOnlyHeaderCount());
615 | }
616 |
617 | private void setRefreshHeader(View view) {
618 | headers.put(REFRESH_HEADER, view);
619 | notifyItemInserted(0);
620 | }
621 |
622 | private void removeHeader(View view) {
623 | if (view == null) return;
624 | for (int i = 0; i < getHeaderCount(); i++) {
625 | if (view == headers.valueAt(i)) {
626 | headers.removeAt(i);
627 | notifyItemRemoved(i);
628 | break;
629 | }
630 | }
631 | }
632 |
633 | private boolean isFooter(int position) {
634 | return position >= (getDataCount() + getHeaderCount());
635 | }
636 |
637 | private void notifyAddFooter() {
638 | int insertPosition = getHeaderCount() + getDataCount() + getFooterCount();
639 | //如果有加载尾部,则在尾部之前插入Item,保证加载尾部是最后一个Item
640 | if (hasLoadingFooter()) insertPosition -= 1;
641 | notifyItemInserted(insertPosition);
642 | }
643 |
644 | private void setLoadFooter(View view) {
645 | footers.put(LOAD_FOOTER, view);
646 | int insertPosition = getHeaderCount() + getDataCount() + getFooterCount();
647 | notifyItemInserted(insertPosition);
648 | }
649 |
650 | private void removeFooter(View view) {
651 | if (view == null) return;
652 | for (int i = 0; i < getFooterCount(); i++) {
653 | if (view == footers.valueAt(i)) {
654 | footers.removeAt(i);
655 | notifyItemRemoved(getHeaderCount() + getDataCount() + i);
656 | break;
657 | }
658 | }
659 | }
660 |
661 | private void loadingEnable(boolean enable) {
662 | if (enable) {
663 | if (!hasLoadingFooter()) {
664 | setLoadFooter(loadingFooter);
665 | }
666 | } else {
667 | if (hasLoadingFooter()) {
668 | removeFooter(loadingFooter);
669 | }
670 | }
671 | }
672 |
673 | private void updateStateView(int state) {
674 | switch (state) {
675 | case STATE_LOADING:
676 | if (currentState == STATE_LOADING) return;
677 | initStateView(STATE_LOADING);
678 | initLoadingFooter(false);
679 | addStateView(LoadView);
680 | break;
681 | case STATE_EMPTY:
682 | if (currentState == STATE_EMPTY) return;
683 | initStateView(STATE_EMPTY);
684 | initLoadingFooter(false);
685 | addStateView(emptyView);
686 | break;
687 | case STATE_ERROR:
688 | if (currentState == STATE_ERROR) return;
689 | initStateView(STATE_ERROR);
690 | initLoadingFooter(false);
691 | addStateView(errorView);
692 | break;
693 | default:
694 | if (currentState == STATE_NORMAL) return;
695 | initStateView(STATE_NORMAL);
696 | initLoadingFooter(true);
697 | }
698 | }
699 |
700 | private void initStateView(int state) {
701 | if (currentState == STATE_LOADING) {
702 | removeStateView(LoadView);
703 | } else if (currentState == STATE_EMPTY) {
704 | removeStateView(emptyView);
705 | } else if (currentState == STATE_ERROR) {
706 | removeStateView(errorView);
707 | }
708 | currentState = state;
709 | }
710 |
711 | /**
712 | * 状态布局显示隐藏时,加载尾部的添加和删除操作,仅用于updateStateView()方法
713 | */
714 | private void initLoadingFooter(boolean isAdd) {
715 | if (!isLoadingEnable) return;
716 | loadingEnable(isAdd);
717 | }
718 |
719 | /**
720 | * 状态布局以footer的方式添加显示
721 | *
722 | * @param view stateView
723 | */
724 | private void addStateView(View view) {
725 | footers.put(FOOTER_TYPE++, view);
726 | notifyAddFooter();
727 | }
728 |
729 | private void removeStateView(View view) {
730 | removeFooter(view);
731 | }
732 |
733 | @Override
734 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
735 | if (headers.get(viewType) != null) {
736 | return new Holder(headers.get(viewType));
737 | } else if (footers.get(viewType) != null) {
738 | return new Holder(footers.get(viewType));
739 | }
740 | return adapter.onCreateViewHolder(parent, viewType);
741 | }
742 |
743 | @Override
744 | public void onBindViewHolder(ViewHolder holder, int position) {
745 | srvBindViewHolder(holder, position, null);
746 | }
747 |
748 | @SuppressWarnings("unchecked")
749 | private void srvBindViewHolder(ViewHolder holder, int position, List payloads) {
750 | if (isHeader(position) || isFooter(position)) return;
751 | position -= getHeaderCount();
752 | holder.itemView.setTag(R.id.srv_item_click, position);
753 | holder.itemView.setOnClickListener(listener);
754 | if (payloads == null) {
755 | adapter.onBindViewHolder(holder, position);
756 | } else {
757 | adapter.onBindViewHolder(holder, position, payloads);
758 | }
759 | }
760 |
761 | @Override
762 | public void onBindViewHolder(ViewHolder holder, int position, List payloads) {
763 | srvBindViewHolder(holder, position, payloads);
764 | }
765 |
766 | @Override
767 | public int getItemViewType(int position) {
768 | if (isHeader(position)) {
769 | return headers.keyAt(position);
770 | } else if (isFooter(position)) {
771 | return footers.keyAt(position - getHeaderCount() - getDataCount());
772 | }
773 | return adapter.getItemViewType(position - getHeaderCount());
774 | }
775 |
776 | @Override
777 | public int getItemCount() {
778 | return getDataCount() + getHeaderCount() + getFooterCount();
779 | }
780 |
781 | private int getHeaderCount() {
782 | return headers.size();
783 | }
784 |
785 | private int getFooterCount() {
786 | return footers.size();
787 | }
788 |
789 | private int getDataCount() {
790 | return adapter.getItemCount();
791 | }
792 |
793 | private int getOnlyHeaderCount() {
794 | return getHeaderCount() - (headers.get(REFRESH_HEADER) == null ? 0 : 1);
795 | }
796 |
797 | private int getOnlyFooterCount() {
798 | return getFooterCount() - (hasLoadingFooter() ? 1 : 0) - (currentState == STATE_NORMAL ? 0 : 1);
799 | }
800 |
801 | private boolean hasLoadingFooter() {
802 | return footers.get(LOAD_FOOTER) != null;
803 | }
804 |
805 | private boolean isEmpty() {
806 | return (getDataCount() + getOnlyHeaderCount() + getOnlyFooterCount()) == 0;
807 | }
808 |
809 | private class Holder extends ViewHolder {
810 | Holder(View itemView) {
811 | super(itemView);
812 | }
813 | }
814 |
815 | /**
816 | * GridLayout(GridView)的头部特殊处理
817 | */
818 | @Override
819 | public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
820 | super.onAttachedToRecyclerView(recyclerView);
821 | RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
822 | if (manager instanceof GridLayoutManager) {
823 | final GridLayoutManager gridManager = (GridLayoutManager) manager;
824 | gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
825 | @Override
826 | public int getSpanSize(int position) {
827 | boolean b = isHeader(position) || isFooter(position);
828 | return b ? gridManager.getSpanCount() : 1;
829 | }
830 | });
831 | }
832 | }
833 |
834 | /**
835 | * StaggeredGridLayout(瀑布流)的头部特殊处理
836 | */
837 | @Override
838 | public void onViewAttachedToWindow(ViewHolder holder) {
839 | super.onViewAttachedToWindow(holder);
840 | ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
841 | if (params instanceof StaggeredGridLayoutManager.LayoutParams) {
842 | if (holder.getLayoutPosition() < getHeaderCount() || holder.getLayoutPosition() > (getHeaderCount()
843 | + getDataCount() - 1)) {
844 | StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) params;
845 | p.setFullSpan(true);
846 | }
847 | }
848 | }
849 | }
850 |
851 |
852 | /*-----------------------------------Item的点击事件------------------------------start-------*/
853 | private class ClickListener implements View.OnClickListener {
854 | @Override
855 | public void onClick(View v) {
856 | if (clickListener != null) {
857 | int position = (int) v.getTag(R.id.srv_item_click);
858 | clickListener.click(v, position);
859 | }
860 | }
861 | }
862 |
863 | public interface ItemClickListener {
864 | void click(View v, int position);
865 | }
866 |
867 | private ItemClickListener clickListener;
868 |
869 | public void setItemClickListener(ItemClickListener listener) {
870 | clickListener = listener;
871 | }
872 | /*-----------------------------------Item的点击事件------------------------------end-------*/
873 |
874 |
875 | /**
876 | * 设置LinearLayoutManager的分割线
877 | */
878 | private class SRVDivider extends RecyclerView.ItemDecoration {
879 |
880 | private float dividerHeight, leftMargin, rightMargin;
881 | private final int mOrientation;
882 | private final Paint mPaint;
883 |
884 | private SRVDivider(int orientation) {
885 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
886 | mPaint.setStyle(Paint.Style.FILL);
887 | this.mOrientation = orientation;
888 | }
889 |
890 | /**
891 | * 横向的分割线
892 | *
893 | * @param height 分割线高
894 | * @param color 分割线颜色
895 | * @param leftMargin 分割线距离左边的距离
896 | * @param rightMargin 分割线距离右边的距离
897 | */
898 | private void initVerticalDivider(float height, int color, float leftMargin, float rightMargin) {
899 | this.dividerHeight = height;
900 | this.leftMargin = leftMargin;
901 | this.rightMargin = rightMargin;
902 | mPaint.setColor(color);
903 | }
904 |
905 | private void initHorizontalDivider(float height, int color) {
906 | this.dividerHeight = height;
907 | mPaint.setColor(color);
908 | }
909 |
910 | /**
911 | * 刷新头部和加载尾部以及stateView不需要分割线
912 | */
913 | private boolean isLoadView(View view) {
914 | return refreshHeader == view || loadingFooter == view || currentState != WrapperAdapter.STATE_NORMAL;
915 | }
916 |
917 | @Override
918 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
919 | if (mOrientation == LinearLayoutManager.VERTICAL) {
920 | drawHorizontal(c, parent);
921 | } else {
922 | drawVertical(c, parent);
923 | }
924 | }
925 |
926 | /**
927 | * 绘制横向的 item 分割线
928 | */
929 | private void drawHorizontal(Canvas canvas, RecyclerView parent) {
930 | final float left = parent.getPaddingLeft() + leftMargin;
931 | final float right = parent.getMeasuredWidth() - parent.getPaddingRight() - rightMargin;
932 | final int childSize = parent.getChildCount();
933 | for (int i = 0; i < childSize; i++) {
934 | final View child = parent.getChildAt(i);
935 | if (!isLoadView(child)) {
936 | RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();
937 | final int top = child.getBottom() + p.bottomMargin;
938 | final float bottom = top + dividerHeight;
939 | canvas.drawRect(left, top, right, bottom, mPaint);
940 | }
941 | }
942 | }
943 |
944 | /**
945 | * 绘制纵向的 item 分割线
946 | */
947 | private void drawVertical(Canvas canvas, RecyclerView parent) {
948 | final int top = parent.getPaddingTop();
949 | final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
950 | final int childSize = parent.getChildCount();
951 | for (int i = 0; i < childSize; i++) {
952 | final View child = parent.getChildAt(i);
953 | if (!isLoadView(child)) {
954 | RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();
955 | final int left = child.getRight() + p.rightMargin;
956 | final float right = left + dividerHeight;
957 | canvas.drawRect(left, top, right, bottom, mPaint);
958 | }
959 | }
960 | }
961 |
962 | @Override
963 | public void getItemOffsets(Rect outRect, View v, RecyclerView p, RecyclerView.State s) {
964 | int size = isLoadView(v) ? 0 : (int) dividerHeight;
965 | if (mOrientation == LinearLayoutManager.VERTICAL) {
966 | outRect.set(0, 0, 0, size);
967 | } else {
968 | outRect.set(0, 0, size, 0);
969 | }
970 | }
971 | }
972 |
973 | }
974 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/java/com/hzw/srecyclerview/SRecyclerViewModule.java:
--------------------------------------------------------------------------------
1 | package com.hzw.srecyclerview;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * 功能:全局的SRecyclerView配置
7 | * Created by 何志伟 on 2017/7/17.
8 | */
9 | public interface SRecyclerViewModule {
10 |
11 | AbsRefreshHeader getRefreshHeader(Context context);
12 |
13 | AbsLoadFooter getLoadingFooter(Context context);
14 |
15 | AbsStateView getEmptyView(Context context);
16 |
17 | AbsStateView getErrorView(Context context);
18 |
19 | AbsStateView getLoadingView(Context context);
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/res/drawable/srv_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HzwSunshine/SRecyclerView/5729c610807438ec1fa7d8f293a3c859289b7f08/srecyclerview/src/main/res/drawable/srv_arrow.png
--------------------------------------------------------------------------------
/srecyclerview/src/main/res/layout/srv_load_footer.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
20 |
21 |
31 |
32 |
33 |
44 |
45 |
56 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/res/layout/srv_refresh_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
23 |
24 |
36 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/res/values/attr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/srecyclerview/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 正在刷新…
3 | 下拉刷新
4 | 释放立即刷新
5 | 正在加载…
6 | 暂无更多数据啦!
7 | 加载失败,请稍后重试
8 |
9 |
--------------------------------------------------------------------------------