├── GroupRefreshLoadListView
├── .classpath
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── assets
│ └── .gitignore
├── libs
│ └── android-support-v4.jar
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ ├── ic_launcher.png
│ │ └── listview_plus_arrow.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_test.xml
│ │ ├── line_content_layout.xml
│ │ └── line_group_layout.xml
│ ├── menu
│ │ └── main.xml
│ ├── values-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ ├── values-w820dp
│ │ └── dimens.xml
│ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── jph
│ └── examples
│ ├── activity
│ ├── MainActivity.java
│ ├── PinnedSectionListActivity.java
│ └── TestActivity.java
│ ├── adapter
│ ├── BillAdapter.java
│ └── TestAdapter.java
│ └── bean
│ ├── Bill.java
│ ├── BillList.java
│ ├── Message.java
│ └── MessageGroup.java
├── README.md
├── library-ListViewPlus
├── .classpath
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── ic_launcher-web.png
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ ├── ic_launcher.png
│ │ └── listview_plus_arrow.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ ├── ic.png
│ │ └── ic_launcher.png
│ ├── layout
│ │ ├── listview_plus_footer.xml
│ │ └── listview_plus_header.xml
│ ├── values-w820dp
│ │ └── dimens.xml
│ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── jph
│ └── view
│ └── ListViewPlus.java
├── library-PinnedSectionListActivity
├── .classpath
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── assets
│ └── .gitignore
├── build.gradle
├── proguard-project.txt
├── project.properties
├── res
│ └── .gitignore
└── src
│ └── com
│ └── hb
│ └── views
│ └── PinnedSectionListView.java
└── raw
├── 上拉加载.png
├── 下拉刷新.png
└── 运行效果图.gif
/GroupRefreshLoadListView/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/.gitignore:
--------------------------------------------------------------------------------
1 | /gen
2 | /bin
3 | /libs
4 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | GroupRefreshLoadListView
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
16 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/assets/.gitignore:
--------------------------------------------------------------------------------
1 | # placeholder
2 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/GroupRefreshLoadListView/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-17
15 | android.library.reference.1=..\\library-PinnedSectionListActivity
16 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/GroupRefreshLoadListView/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/drawable-hdpi/listview_plus_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/GroupRefreshLoadListView/res/drawable-hdpi/listview_plus_arrow.png
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/GroupRefreshLoadListView/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/GroupRefreshLoadListView/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/GroupRefreshLoadListView/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/layout/activity_test.xml:
--------------------------------------------------------------------------------
1 |
5 |
12 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/layout/line_content_layout.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
15 |
16 |
23 |
24 |
32 |
33 |
41 |
42 |
49 |
50 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/layout/line_group_layout.xml:
--------------------------------------------------------------------------------
1 |
5 |
15 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ffff4444
4 | #ff99cc00
5 | #ffffbb33
6 | #ff33b5e5
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pinned Section Demo
5 | Fast scroll
6 | Add padding
7 | Show shadow
8 | Show Header & Footer
9 | Update dataset
10 | TestActivity
11 | Hello world!
12 | Settings
13 |
14 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/src/com/jph/examples/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.jph.examples.activity;
2 |
3 | import java.util.ArrayList;
4 |
5 | import android.app.Activity;
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.view.View;
9 | import android.widget.AdapterView;
10 | import android.widget.AdapterView.OnItemClickListener;
11 | import android.widget.Toast;
12 |
13 | import com.hb.views.PinnedSectionListView;
14 | import com.jph.examples.R;
15 | import com.jph.examples.adapter.BillAdapter;
16 | import com.jph.examples.adapter.BillAdapter.Item;
17 | import com.jph.examples.bean.Bill;
18 | import com.jph.view.ListViewPlus;
19 | import com.jph.view.ListViewPlus.ListViewPlusListener;
20 |
21 | /**
22 | * 方支付宝账单页分组+上拉加载下拉刷新效果
23 | * @author JPH
24 | * @Date 2015-3-7下午11:27:07
25 | */
26 | public class MainActivity extends Activity implements ListViewPlusListener{
27 | private PinnedSectionListView listView;
28 | private BillAdapter adapter;
29 | private ArrayListbills;
30 | private int page=1;
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_test);
35 | listView=(PinnedSectionListView) findViewById(R.id.list);
36 | bills=new ArrayList();
37 | listView.setLoadEnable(true);
38 | listView.setListViewPlusListener(this);
39 | adapter=new BillAdapter(this, bills);
40 | listView.setAdapter(adapter);
41 | listView.setOnItemClickListener(new OnItemClickListener() {
42 | @Override
43 | public void onItemClick(AdapterView> parent, View view,
44 | int position, long id) {
45 | Item item=(Item) adapter.getItem(position-1);
46 | if(item.getType()==Item.SECTION)return;
47 | Toast.makeText(MainActivity.this,item.getTitle() ,Toast.LENGTH_LONG).show();
48 | }
49 | });
50 | loadData(ListViewPlus.REFRESH);
51 | }
52 | private void loadData(final int what) {
53 | new Handler().postDelayed(new Runnable() {
54 | @Override
55 | public void run() {
56 | if (ListViewPlus.REFRESH==what) {
57 | bills.clear();
58 | listView.stopRefresh();
59 | page=1;
60 | }else {
61 | listView.stopLoadMore();
62 | page++;
63 | }
64 | for (int i = (page-1)*20; i implements
46 | PinnedSectionListAdapter {
47 |
48 | private static final int[] COLORS = new int[] { R.color.green_light,
49 | R.color.orange_light, R.color.blue_light, R.color.red_light };
50 |
51 | public SimpleAdapter(Context context, int resource,
52 | int textViewResourceId) {
53 | super(context, resource, textViewResourceId);
54 | generateDataset('A', 'Z', false);
55 | }
56 |
57 | public void generateDataset(char from, char to, boolean clear) {
58 |
59 | if (clear)
60 | clear();
61 |
62 | final int sectionsNumber = to - from + 1;
63 | int sectionPosition = 0, listPosition = 0;
64 | for (char i = 0; i < sectionsNumber; i++) {
65 | Item section = new Item(Item.SECTION,
66 | String.valueOf((char) ('A' + i)));
67 | section.sectionPosition = sectionPosition;
68 | section.listPosition = listPosition++;
69 | add(section);
70 |
71 | final int itemsNumber = (int) Math.abs((Math.cos(2f * Math.PI
72 | / 3f * sectionsNumber / (i + 1f)) * 25f));
73 | for (int j = 0; j < itemsNumber; j++) {
74 | Item item = new Item(Item.ITEM,
75 | section.text.toUpperCase(Locale.ENGLISH) + " - "
76 | + j);
77 | item.sectionPosition = sectionPosition;
78 | item.listPosition = listPosition++;
79 | add(item);
80 | }
81 |
82 | sectionPosition++;
83 | }
84 | }
85 |
86 | @Override
87 | public View getView(int position, View convertView, ViewGroup parent) {
88 | TextView view = (TextView) super.getView(position, convertView,
89 | parent);
90 | view.setTextColor(Color.DKGRAY);
91 | view.setTag("" + position);
92 | Item item = getItem(position);
93 | if (item.type == Item.SECTION) {
94 | view.setBackgroundColor(parent.getResources().getColor(
95 | COLORS[item.sectionPosition % COLORS.length]));
96 | }
97 | return view;
98 | }
99 |
100 | @Override
101 | public int getViewTypeCount() {
102 | return 2;
103 | }
104 |
105 | @Override
106 | public int getItemViewType(int position) {
107 | return getItem(position).type;
108 | }
109 |
110 | @Override
111 | public boolean isItemViewTypePinned(int viewType) {
112 | return viewType == Item.SECTION;
113 | }
114 |
115 | }
116 |
117 | static class Item {
118 |
119 | public static final int ITEM = 0;
120 | public static final int SECTION = 1;
121 |
122 | public final int type;
123 | public final String text;
124 |
125 | public int sectionPosition;
126 | public int listPosition;
127 |
128 | public Item(int type, String text) {
129 | this.type = type;
130 | this.text = text;
131 | }
132 |
133 | @Override
134 | public String toString() {
135 | return text;
136 | }
137 |
138 | }
139 |
140 | private boolean hasHeaderAndFooter;
141 | private boolean isFastScroll;
142 | private boolean addPadding;
143 | private boolean isShadowVisible = true;
144 | private int mDatasetUpdateCount;
145 |
146 | @Override
147 | protected void onCreate(Bundle savedInstanceState) {
148 | super.onCreate(savedInstanceState);
149 | setContentView(R.layout.activity_main);
150 | if (savedInstanceState != null) {
151 | isFastScroll = savedInstanceState.getBoolean("isFastScroll");
152 | addPadding = savedInstanceState.getBoolean("addPadding");
153 | isShadowVisible = savedInstanceState.getBoolean("isShadowVisible");
154 | hasHeaderAndFooter = savedInstanceState
155 | .getBoolean("hasHeaderAndFooter");
156 | }
157 | ((ListViewPlus) getListView()).setLoadEnable(true);
158 | initializeHeaderAndFooter();
159 | initializeAdapter();
160 | initializePadding();
161 | }
162 |
163 | @Override
164 | protected void onSaveInstanceState(Bundle outState) {
165 | super.onSaveInstanceState(outState);
166 | outState.putBoolean("isFastScroll", isFastScroll);
167 | outState.putBoolean("addPadding", addPadding);
168 | outState.putBoolean("isShadowVisible", isShadowVisible);
169 | outState.putBoolean("hasHeaderAndFooter", hasHeaderAndFooter);
170 | }
171 |
172 | @Override
173 | protected void onListItemClick(ListView l, View v, int position, long id) {
174 | Item item = (Item) getListView().getAdapter().getItem(position);
175 | if (item != null) {
176 | Toast.makeText(this, "Item " + position + ": " + item.text,
177 | Toast.LENGTH_SHORT).show();
178 | } else {
179 | Toast.makeText(this, "Item " + position, Toast.LENGTH_SHORT).show();
180 | }
181 | }
182 |
183 | @Override
184 | public boolean onCreateOptionsMenu(Menu menu) {
185 | getMenuInflater().inflate(R.menu.main, menu);
186 | menu.getItem(0).setChecked(isFastScroll);
187 | menu.getItem(1).setChecked(addPadding);
188 | menu.getItem(2).setChecked(isShadowVisible);
189 | return true;
190 | }
191 |
192 | @Override
193 | public boolean onOptionsItemSelected(MenuItem item) {
194 | switch (item.getItemId()) {
195 | case R.id.action_fastscroll:
196 | isFastScroll = !isFastScroll;
197 | item.setChecked(isFastScroll);
198 | initializeAdapter();
199 | break;
200 | case R.id.action_addpadding:
201 | addPadding = !addPadding;
202 | item.setChecked(addPadding);
203 | initializePadding();
204 | break;
205 | case R.id.action_showShadow:
206 | isShadowVisible = !isShadowVisible;
207 | item.setChecked(isShadowVisible);
208 | ((PinnedSectionListView) getListView())
209 | .setShadowVisible(isShadowVisible);
210 | break;
211 | case R.id.action_showHeaderAndFooter:
212 | hasHeaderAndFooter = !hasHeaderAndFooter;
213 | item.setChecked(hasHeaderAndFooter);
214 | initializeHeaderAndFooter();
215 | break;
216 | case R.id.action_updateDataset:
217 | updateDataset();
218 | break;
219 | }
220 | return true;
221 | }
222 |
223 | private void updateDataset() {
224 | mDatasetUpdateCount++;
225 | SimpleAdapter adapter = (SimpleAdapter) getListAdapter();
226 | switch (mDatasetUpdateCount % 4) {
227 | case 0:
228 | adapter.generateDataset('A', 'B', true);
229 | break;
230 | case 1:
231 | adapter.generateDataset('C', 'M', true);
232 | break;
233 | case 2:
234 | adapter.generateDataset('P', 'Z', true);
235 | break;
236 | case 3:
237 | adapter.generateDataset('A', 'Z', true);
238 | break;
239 | }
240 | adapter.notifyDataSetChanged();
241 | }
242 |
243 | private void initializePadding() {
244 | float density = getResources().getDisplayMetrics().density;
245 | int padding = addPadding ? (int) (16 * density) : 0;
246 | getListView().setPadding(padding, padding, padding, padding);
247 | }
248 |
249 | private void initializeHeaderAndFooter() {
250 | setListAdapter(null);
251 | if (hasHeaderAndFooter) {
252 | ListView list = getListView();
253 |
254 | LayoutInflater inflater = LayoutInflater.from(this);
255 | TextView header1 = (TextView) inflater.inflate(
256 | android.R.layout.simple_list_item_1, list, false);
257 | header1.setText("First header");
258 | list.addHeaderView(header1);
259 |
260 | TextView header2 = (TextView) inflater.inflate(
261 | android.R.layout.simple_list_item_1, list, false);
262 | header2.setText("Second header");
263 | list.addHeaderView(header2);
264 |
265 | TextView footer = (TextView) inflater.inflate(
266 | android.R.layout.simple_list_item_1, list, false);
267 | footer.setText("Single footer");
268 | list.addFooterView(footer);
269 | }
270 | initializeAdapter();
271 | }
272 |
273 | @SuppressLint("NewApi")
274 | private void initializeAdapter() {
275 | getListView().setFastScrollEnabled(isFastScroll);
276 | setListAdapter(new SimpleAdapter(this,
277 | android.R.layout.simple_list_item_1, android.R.id.text1));
278 | }
279 |
280 | @Override
281 | public void onClick(View v) {
282 | Toast.makeText(this, "Item: " + v.getTag(), Toast.LENGTH_SHORT).show();
283 | }
284 |
285 | }
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/src/com/jph/examples/activity/TestActivity.java:
--------------------------------------------------------------------------------
1 | package com.jph.examples.activity;
2 |
3 | import java.util.ArrayList;
4 | import android.app.Activity;
5 | import android.os.Bundle;
6 | import android.os.Handler;
7 | import android.view.View;
8 | import android.widget.AdapterView;
9 | import android.widget.Toast;
10 | import android.widget.AdapterView.OnItemClickListener;
11 |
12 | import com.hb.views.PinnedSectionListView;
13 | import com.jph.examples.R;
14 | import com.jph.examples.adapter.TestAdapter;
15 | import com.jph.examples.adapter.TestAdapter.Item;
16 | import com.jph.examples.bean.BillList;
17 | import com.jph.examples.bean.Message;
18 | import com.jph.examples.bean.MessageGroup;
19 | import com.jph.view.ListViewPlus;
20 | import com.jph.view.ListViewPlus.ListViewPlusListener;
21 |
22 | public class TestActivity extends Activity implements ListViewPlusListener{
23 | private PinnedSectionListView listView;
24 | private TestAdapter adapter;
25 | private BillList billList;
26 | @Override
27 | protected void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(R.layout.activity_test);
30 | listView=(PinnedSectionListView) findViewById(R.id.list);
31 | listView.setLoadEnable(true);
32 | listView.setListViewPlusListener(this);
33 | adapter=new TestAdapter(this, billList);
34 | listView.setAdapter(adapter);
35 | listView.setOnItemClickListener(new OnItemClickListener() {
36 | @Override
37 | public void onItemClick(AdapterView> parent, View view,
38 | int position, long id) {
39 | Item item=(Item) adapter.getItem(position-1);
40 | if(item.getType()==Item.SECTION)return;
41 | Toast.makeText(TestActivity.this,item.getTitle() ,Toast.LENGTH_LONG).show();
42 | }
43 | });
44 | loadData(ListViewPlus.REFRESH);
45 | }
46 | private void loadData(final int what) {
47 | new Handler().postDelayed(new Runnable() {
48 | @Override
49 | public void run() {
50 | ArrayList messageGroups = new ArrayList();
51 | if (ListViewPlus.REFRESH==what) {
52 | listView.stopRefresh();
53 | }else {
54 | listView.stopLoadMore();
55 | }
56 | for (int i = 0; i <10; i++) {
57 | ArrayListmessages=new ArrayList();
58 | for (int j = 1; j < 10; j++) {
59 | messages.add(new Message(i+"-"+j, "", ""));
60 | }
61 | messageGroups.add(new MessageGroup(""+i, messages));
62 | }
63 | billList=new BillList(0, messageGroups);
64 | adapter.inflaterItems(billList, ListViewPlus.REFRESH==what);
65 | adapter.notifyDataSetChanged();
66 | }
67 | }, 1000);
68 | }
69 | @Override
70 | public void onRefresh() {
71 | loadData(ListViewPlus.REFRESH);
72 | }
73 | @Override
74 | public void onLoadMore() {
75 | loadData(ListViewPlus.LOAD);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/src/com/jph/examples/adapter/BillAdapter.java:
--------------------------------------------------------------------------------
1 | package com.jph.examples.adapter;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.ArrayList;
5 | import java.util.Date;
6 | import java.util.Iterator;
7 | import java.util.Locale;
8 | import java.util.Map.Entry;
9 | import java.util.TreeMap;
10 |
11 | import android.app.Activity;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.BaseAdapter;
16 | import android.widget.TextView;
17 |
18 | import com.hb.views.PinnedSectionListView.PinnedSectionListAdapter;
19 | import com.jph.examples.R;
20 | import com.jph.examples.bean.Bill;
21 |
22 | /**
23 | * 账单历史记录的适配器
24 | * @author JPH
25 | * @Date 2015-3-7下午10:43:10
26 | */
27 | public class BillAdapter extends BaseAdapter implements PinnedSectionListAdapter{
28 | private static final int[] COLORS = new int[] { R.color.green_light,
29 | R.color.orange_light, R.color.blue_light, R.color.red_light };
30 | private Activity activity;
31 | /**服务器返回的历史账单列表**/
32 | private ArrayListbills;
33 | /**本地分组后的账单**/
34 | private TreeMap>groupBills;
35 | /**Adapter的数据源**/
36 | private ArrayList- items;
37 | private SimpleDateFormat sdf;
38 | public BillAdapter(Activity activity,ArrayList bills) {
39 | this.activity = activity;
40 | this.bills=bills;
41 | items=new ArrayList
- ();
42 | groupBills=new TreeMap>();
43 | sdf=new SimpleDateFormat("yyyy-MM", Locale.getDefault());
44 | }
45 | /**
46 | * 初始化列表项
47 | * @author JPH
48 | */
49 | public void inflaterItems() {
50 | items.clear();
51 | groupBills.clear();
52 | for(Bill bill:bills){//遍历bills将集合中的所有数据以月份进行分类
53 | String groupName=sdf.format(new Date(bill.getDate()));
54 | if (groupBills.containsKey(groupName)) {//如果Map已经存在以该记录的日期为分组名的分组,则将该条记录插入到该分组中
55 | groupBills.get(groupName).add(bill);
56 | }else {//如果不存在,以该记录的日期作为分组名称创建分组,并将该记录插入到创建的分组中
57 | ArrayListtempBills=new ArrayList();
58 | tempBills.add(bill);
59 | groupBills.put(groupName, tempBills);
60 | }
61 | }
62 |
63 | Iterator>>iterator=groupBills.descendingMap().entrySet().iterator();
64 | while(iterator.hasNext()){//将分组后的数据添加到数据源的集合中
65 | Entry>entry=iterator.next();
66 | items.add(new Item(Item.SECTION, entry.getKey()));//将分组添加到集合中
67 | for(Bill bill:entry.getValue()){//将组中的数据添加到集合中
68 | items.add(new Item(Item.ITEM, bill));
69 | }
70 | }
71 | }
72 | @Override
73 | public int getCount() {
74 | return items.size();
75 | }
76 | @Override
77 | public Object getItem(int position) {
78 | return items.get(position);
79 | }
80 | @Override
81 | public long getItemId(int position) {
82 | return position;
83 | }
84 | @Override
85 | public View getView(int position, View convertView, ViewGroup parent) {
86 | Item item = (Item) getItem(position);
87 | if (item.type == Item.SECTION) {
88 | convertView=LayoutInflater.from(activity).inflate(R.layout.line_group_layout, null);
89 | convertView.setBackgroundColor(activity.getResources().getColor(COLORS[position%4]));
90 | TextView tvGroupName=(TextView) convertView.findViewById(R.id.tvGroupName);
91 | tvGroupName.setText(item.getGroupName());
92 | }else {
93 | convertView=LayoutInflater.from(activity).inflate(R.layout.line_content_layout, null);
94 | TextView tvTitle=(TextView) convertView.findViewById(R.id.tvTitle);
95 | TextView tvSum=(TextView) convertView.findViewById(R.id.tvSum);
96 | TextView tvDate=(TextView) convertView.findViewById(R.id.tvDateTime);
97 | TextView tvStatus=(TextView) convertView.findViewById(R.id.tvStatus);
98 | tvTitle.setText(item.getTitle());
99 | tvSum.setText(item.getSum());
100 | tvStatus.setText(item.getStatus());
101 | SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
102 | tvDate.setText(sdf.format(new Date(item.getDate())));
103 | }
104 | return convertView;
105 | }
106 | @Override
107 | public int getItemViewType(int position) {
108 | return items.get(position).getType();
109 | }
110 | @Override
111 | public int getViewTypeCount() {
112 | return 2;
113 | }
114 | @Override
115 | public boolean isItemViewTypePinned(int viewType) {
116 | return viewType==Item.SECTION;
117 | }
118 | @Override
119 | public void notifyDataSetChanged() {
120 | inflaterItems();//重新初始化数据
121 | super.notifyDataSetChanged();
122 | }
123 | public class Item extends Bill{
124 | public static final int ITEM = 0;
125 | public static final int SECTION = 1;
126 | private String groupName;
127 | public int type;
128 | public Item(int type,Bill bill) {
129 | super(bill);
130 | this.type=type;
131 | }
132 | public Item(int type,String groupName) {
133 | super(null);
134 | this.type=type;
135 | this.groupName=groupName;
136 | }
137 | public String getGroupName() {
138 | return groupName;
139 | }
140 | public void setGroupName(String groupName) {
141 | this.groupName = groupName;
142 | }
143 | public int getType() {
144 | return type;
145 | }
146 | public void setType(int type) {
147 | this.type = type;
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/src/com/jph/examples/adapter/TestAdapter.java:
--------------------------------------------------------------------------------
1 | package com.jph.examples.adapter;
2 |
3 | import java.util.ArrayList;
4 | import android.app.Activity;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.BaseAdapter;
9 | import android.widget.TextView;
10 | import com.hb.views.PinnedSectionListView.PinnedSectionListAdapter;
11 | import com.jph.examples.R;
12 | import com.jph.examples.bean.BillList;
13 | import com.jph.examples.bean.Message;
14 | import com.jph.examples.bean.MessageGroup;
15 |
16 | public class TestAdapter extends BaseAdapter implements PinnedSectionListAdapter{
17 | private static final int[] COLORS = new int[] { R.color.green_light,
18 | R.color.orange_light, R.color.blue_light, R.color.red_light };
19 | private Activity activity;
20 | private ArrayList
- items;
21 | public TestAdapter(Activity activity, BillList billList) {
22 | this.activity = activity;
23 | items=new ArrayList
- ();
24 | inflaterItems(billList, true);
25 | }
26 | /**
27 | * 初始化列表项
28 | * @param billList
29 | * @param isClear 是否清除原有原有数据
30 | * @return
31 | */
32 | public void inflaterItems(BillList billList,boolean isClear) {
33 | if (isClear) items.clear();
34 | if(billList==null)return;
35 | ArrayListmessageGroups=billList.getMessageGroups();
36 | for(MessageGroup messageGroup:messageGroups){
37 | ArrayListmessages=messageGroup.getMessages();
38 | items.add(new Item(Item.SECTION, messageGroup.getGroupName()));
39 | for(Message message:messages){
40 | items.add(new Item(Item.ITEM, message));
41 | }
42 | }
43 | }
44 | @Override
45 | public int getCount() {
46 | return items.size();
47 | }
48 | @Override
49 | public Object getItem(int position) {
50 | return items.get(position);
51 | }
52 | @Override
53 | public long getItemId(int position) {
54 | return position;
55 | }
56 | @Override
57 | public View getView(int position, View convertView, ViewGroup parent) {
58 | Item item = (Item) getItem(position);
59 | if (item.type == Item.SECTION) {
60 | convertView=LayoutInflater.from(activity).inflate(R.layout.line_group_layout, null);
61 | convertView.setBackgroundColor(activity.getResources().getColor(COLORS[position%4]));
62 | TextView tvGroupName=(TextView) convertView.findViewById(R.id.tvGroupName);
63 | tvGroupName.setText(item.getGroupName());
64 | }else {
65 | convertView=LayoutInflater.from(activity).inflate(R.layout.line_content_layout, null);
66 | TextView tvTitle=(TextView) convertView.findViewById(R.id.tvTitle);
67 | TextView tvContent=(TextView) convertView.findViewById(R.id.tvDateTime);
68 | TextView tvDate=(TextView) convertView.findViewById(R.id.tvSum);
69 | tvTitle.setText(item.getTitle());
70 | tvContent.setText(item.getContent());
71 | tvDate.setText(item.getDate());
72 | }
73 | return convertView;
74 | }
75 | @Override
76 | public int getItemViewType(int position) {
77 | return items.get(position).getType();
78 | }
79 | @Override
80 | public int getViewTypeCount() {
81 | return 2;
82 | }
83 | @Override
84 | public boolean isItemViewTypePinned(int viewType) {
85 | return viewType==Item.SECTION;
86 | }
87 | public class Item extends Message{
88 | public static final int ITEM = 0;
89 | public static final int SECTION = 1;
90 | private String groupName;
91 | public int type;
92 | public Item(int type,Message message) {
93 | super(message);
94 | this.type=type;
95 | }
96 | public Item(int type,String groupName) {
97 | super(null);
98 | this.type=type;
99 | this.groupName=groupName;
100 | }
101 | public String getGroupName() {
102 | return groupName;
103 | }
104 | public void setGroupName(String groupName) {
105 | this.groupName = groupName;
106 | }
107 | public int getType() {
108 | return type;
109 | }
110 | public void setType(int type) {
111 | this.type = type;
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/src/com/jph/examples/bean/Bill.java:
--------------------------------------------------------------------------------
1 | package com.jph.examples.bean;
2 |
3 |
4 | /**
5 | * 账单概括实体类
6 | * @author JPH
7 | * @Date 2015-3-7下午9:49:39
8 | */
9 | public class Bill {
10 | private int id;
11 | private String title;
12 | private String sum;
13 | private long date;
14 | private String status;
15 | public Bill(Bill bill){
16 | if(bill==null)return;
17 | this.id=bill.id;
18 | this.title=bill.title;
19 | this.sum=bill.sum;
20 | this.date=bill.date;
21 | this.status=bill.status;
22 | }
23 | public Bill(int id, String title, String money, long date, String status) {
24 | super();
25 | this.id = id;
26 | this.title = title;
27 | this.sum = money;
28 | this.date = date;
29 | this.status = status;
30 | }
31 | public int getId() {
32 | return id;
33 | }
34 | public void setId(int id) {
35 | this.id = id;
36 | }
37 | public String getTitle() {
38 | return title;
39 | }
40 | public void setTitle(String title) {
41 | this.title = title;
42 | }
43 | public String getSum() {
44 | return sum;
45 | }
46 | public void setSum(String sum) {
47 | this.sum = sum;
48 | }
49 | public long getDate() {
50 | return date;
51 | }
52 | public void setDate(long date) {
53 | this.date = date;
54 | }
55 | public String getStatus() {
56 | return status;
57 | }
58 | public void setStatus(String status) {
59 | this.status = status;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/src/com/jph/examples/bean/BillList.java:
--------------------------------------------------------------------------------
1 | package com.jph.examples.bean;
2 |
3 | import java.util.ArrayList;
4 |
5 | public class BillList {
6 | private int status;
7 | private ArrayListmessageGroups;
8 | public BillList(int status, ArrayList messageGroups) {
9 | this.status = status;
10 | this.messageGroups = messageGroups;
11 | }
12 | public int getStatus() {
13 | return status;
14 | }
15 | public void setStatus(int status) {
16 | this.status = status;
17 | }
18 | public ArrayList getMessageGroups() {
19 | return messageGroups;
20 | }
21 | public void setMessageGroups(ArrayList messageGroups) {
22 | this.messageGroups = messageGroups;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/src/com/jph/examples/bean/Message.java:
--------------------------------------------------------------------------------
1 | package com.jph.examples.bean;
2 |
3 | public class Message{
4 | private String title;
5 | private String content;
6 | private String date;
7 | public Message(Message message){
8 | if(message!=null){
9 | this.title=message.getTitle();
10 | this.content=message.getContent();
11 | this.date=message.getDate();
12 | }
13 | }
14 | public Message(String title, String content, String date) {
15 | this.title = title;
16 | this.content = content;
17 | this.date = date;
18 | }
19 | public String getTitle() {
20 | return title;
21 | }
22 | public void setTitle(String title) {
23 | this.title = title;
24 | }
25 | public String getContent() {
26 | return content;
27 | }
28 | public void setContent(String content) {
29 | this.content = content;
30 | }
31 | public String getDate() {
32 | return date;
33 | }
34 | public void setDate(String date) {
35 | this.date = date;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/GroupRefreshLoadListView/src/com/jph/examples/bean/MessageGroup.java:
--------------------------------------------------------------------------------
1 | package com.jph.examples.bean;
2 |
3 | import java.util.ArrayList;
4 |
5 | public class MessageGroup {
6 | /**分组名称**/
7 | private String groupName;
8 | private ArrayListmessages;
9 | public MessageGroup() {
10 | }
11 | public MessageGroup(String groupName, ArrayList messages) {
12 | this.groupName = groupName;
13 | this.messages = messages;
14 | }
15 | public String getGroupName() {
16 | return groupName;
17 | }
18 | public void setGroupName(String groupName) {
19 | this.groupName = groupName;
20 | }
21 | public ArrayList getMessages() {
22 | return messages;
23 | }
24 | public void setMessages(ArrayList messages) {
25 | this.messages = messages;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GroupListView简介 #
2 | - GroupListView支持上拉加载下拉刷新,并且可以根据需要选择是否启用下拉刷新、上拉加载
3 | - GroupListView支持列表项悬停效果
4 |
5 | ## 运行效果图 ##
6 | 
7 |
8 |
9 | ## PS. ##
10 |
11 | >为了实现支付宝账单列表的悬停、上拉加载、下拉刷新的效果,我从Git上Fetch了pulltorefresh和PinnedSectionList两个开源项目,并对pulltorefresh进行修改实现上拉加载的效果,将修改后的pulltorefresh更名成了ListViewPlus,最后将PinnedSectionList的library依赖于ListViewPlus,就实现了带有悬停效果并且支持上拉加载、下拉刷新的GroupListView,现在将其开源出来供大家使用。
12 |
13 | ## 使用方法 ##
14 | 1. GroupListView是基于ListView开发的一个控件,所以大家可以使用ListView的方式来使用它。
15 | 2. ListViewPlus部分:关于ListViewPlus方面的功能大家可以参考[https://github.com/crazycodeboy/ListViewPlus](https://github.com/crazycodeboy/ListViewPlus "ListViewPlus")
16 | 3. PinnedSectionList部分:
17 |
18 | PinnedSectionList的使用需要一个实现如下方法的适配器:
19 |
20 | ```java
21 | @Override
22 | public int getItemViewType(int position) {
23 | return items.get(position).getType();
24 | }
25 | @Override
26 | public int getViewTypeCount() {
27 | return 2;
28 | }
29 | @Override
30 | public boolean isItemViewTypePinned(int viewType) {
31 | return viewType==Item.SECTION;
32 | }
33 | ```
34 |
35 | 4 .其它使用细节可以参照实例。
36 |
37 |
38 |
--------------------------------------------------------------------------------
/library-ListViewPlus/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/library-ListViewPlus/.gitignore:
--------------------------------------------------------------------------------
1 | /gen
2 | /bin
3 |
--------------------------------------------------------------------------------
/library-ListViewPlus/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | library-ListViewPlus
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/library-ListViewPlus/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/library-ListViewPlus/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/library-ListViewPlus/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/library-ListViewPlus/ic_launcher-web.png
--------------------------------------------------------------------------------
/library-ListViewPlus/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/library-ListViewPlus/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 | android.library=true
16 |
--------------------------------------------------------------------------------
/library-ListViewPlus/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/library-ListViewPlus/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library-ListViewPlus/res/drawable-hdpi/listview_plus_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/library-ListViewPlus/res/drawable-hdpi/listview_plus_arrow.png
--------------------------------------------------------------------------------
/library-ListViewPlus/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/library-ListViewPlus/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library-ListViewPlus/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/library-ListViewPlus/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library-ListViewPlus/res/drawable-xxhdpi/ic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/library-ListViewPlus/res/drawable-xxhdpi/ic.png
--------------------------------------------------------------------------------
/library-ListViewPlus/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/library-ListViewPlus/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library-ListViewPlus/res/layout/listview_plus_footer.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
18 |
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/library-ListViewPlus/res/layout/listview_plus_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
18 |
19 |
24 |
25 |
29 |
30 |
35 |
36 |
41 |
42 |
43 |
44 |
52 |
53 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/library-ListViewPlus/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/library-ListViewPlus/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/library-ListViewPlus/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 上拉加载下拉刷新ListView
5 | Hello world!
6 | Settings
7 | 下拉刷新
8 | 松开刷新数据
9 | 正在加载...
10 | 上次更新时间:
11 | 查看更多
12 | 松开载入更多
13 |
14 |
--------------------------------------------------------------------------------
/library-ListViewPlus/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/library-ListViewPlus/src/com/jph/view/ListViewPlus.java:
--------------------------------------------------------------------------------
1 | package com.jph.view;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.Date;
5 | import java.util.Locale;
6 |
7 | import android.content.Context;
8 | import android.util.AttributeSet;
9 | import android.view.Gravity;
10 | import android.view.LayoutInflater;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 | import android.view.ViewTreeObserver.OnGlobalLayoutListener;
14 | import android.view.animation.Animation;
15 | import android.view.animation.DecelerateInterpolator;
16 | import android.view.animation.RotateAnimation;
17 | import android.widget.AbsListView;
18 | import android.widget.AbsListView.OnScrollListener;
19 | import android.widget.ImageView;
20 | import android.widget.LinearLayout;
21 | import android.widget.ListAdapter;
22 | import android.widget.ListView;
23 | import android.widget.ProgressBar;
24 | import android.widget.RelativeLayout;
25 | import android.widget.Scroller;
26 | import android.widget.TextView;
27 |
28 | import com.jph.lp.R;
29 |
30 | /**
31 | * 基于ListView的自定义控件实现上拉加载下拉刷新
32 | * @author JPH
33 | * @date 2015-3-10 下午1:01:56
34 | */
35 | public class ListViewPlus extends ListView implements OnScrollListener {
36 | /**区分当前操作是刷新还是加载**/
37 | public static final int REFRESH = 0;
38 | public static final int LOAD = 1;
39 | private float mLastY = -1; // save event y
40 | private Scroller mScroller; // used for scroll back
41 | private OnScrollListener mScrollListener; // user's scroll listener
42 |
43 | // the interface to trigger refresh and load more.
44 | private ListViewPlusListener mListViewListener;
45 |
46 | // -- header view
47 | private ListViewPlusHeader mHeaderView;
48 | // header view content, use it to calculate the Header's height. And hide it
49 | // when disable pull refresh.
50 | private RelativeLayout mHeaderViewContent;
51 | private TextView mHeaderTimeView;
52 | private int mHeaderViewHeight; // header view's height
53 | private boolean mEnablePullRefresh = true;
54 | private boolean mPullRefreshing = false; // is refreashing.
55 |
56 | // -- footer view
57 | private ListViewPlusFooter mFooterView;
58 | private boolean mEnablePullLoad;
59 | private boolean mPullLoading;
60 | private boolean mIsFooterReady = false;
61 |
62 | // total list items, used to detect is at the bottom of listview.
63 | private int mTotalItemCount;
64 |
65 | // for mScroller, scroll back from header or footer.
66 | private int mScrollBack;
67 | private int minItemCount=3;
68 | private final static int SCROLLBACK_HEADER = 0;
69 | private final static int SCROLLBACK_FOOTER = 1;
70 |
71 | private final static int SCROLL_DURATION = 400; // scroll back duration
72 | private final static int PULL_LOAD_MORE_DELTA = 50; // when pull up >= 50px
73 | // at bottom, trigger
74 | // load more.
75 | private final static float OFFSET_RADIO = 1.8f; // support iOS like pull
76 | // feature.
77 |
78 | /**
79 | * @param context
80 | */
81 | public ListViewPlus(Context context) {
82 | super(context);
83 | initWithContext(context);
84 | }
85 |
86 | public ListViewPlus(Context context, AttributeSet attrs) {
87 | super(context, attrs);
88 | initWithContext(context);
89 | }
90 |
91 | public ListViewPlus(Context context, AttributeSet attrs, int defStyle) {
92 | super(context, attrs, defStyle);
93 | initWithContext(context);
94 | }
95 |
96 | private void initWithContext(Context context) {
97 | mScroller = new Scroller(context, new DecelerateInterpolator());
98 | // listview_plus need the scroll event, and it will dispatch the event to
99 | // user's listener (as a proxy).
100 | super.setOnScrollListener(this);
101 |
102 | // init header view
103 | mHeaderView = new ListViewPlusHeader(context);
104 | mHeaderViewContent = (RelativeLayout) mHeaderView
105 | .findViewById(R.id.listview_plus_header_content);
106 | mHeaderTimeView = (TextView) mHeaderView
107 | .findViewById(R.id.listview_plus_header_time);
108 | addHeaderView(mHeaderView);
109 |
110 | // init footer view
111 | mFooterView = new ListViewPlusFooter(context);
112 |
113 | // init header height
114 | mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(
115 | new OnGlobalLayoutListener() {
116 | @Override
117 | public void onGlobalLayout() {
118 | mHeaderViewHeight = mHeaderViewContent.getHeight();
119 | getViewTreeObserver()
120 | .removeGlobalOnLayoutListener(this);
121 | }
122 | });
123 | }
124 |
125 | @Override
126 | public void setAdapter(ListAdapter adapter) {
127 | // make sure ListViewPlusFooter is the last footer view, and only add once.
128 | if (mIsFooterReady == false) {
129 | mIsFooterReady = true;
130 | addFooterView(mFooterView);
131 | }
132 | super.setAdapter(adapter);
133 | }
134 |
135 | /**
136 | * enable or disable pull down refresh feature.
137 | *
138 | * @param enable
139 | */
140 | public void setRefreshEnable(boolean enable) {
141 | mEnablePullRefresh = enable;
142 | if (!mEnablePullRefresh) { // disable, hide the content
143 | mHeaderViewContent.setVisibility(View.INVISIBLE);
144 | } else {
145 | mHeaderViewContent.setVisibility(View.VISIBLE);
146 | }
147 | }
148 |
149 | /**
150 | * enable or disable pull up load more feature.
151 | *
152 | * @param enable
153 | */
154 | public void setLoadEnable(boolean enable) {
155 | mEnablePullLoad = enable;
156 | if (!mEnablePullLoad) {
157 | mFooterView.hide();
158 | mFooterView.setOnClickListener(null);
159 | //make sure "pull up" don't show a line in bottom when listview with one page
160 | setFooterDividersEnabled(false);
161 | } else {
162 | mPullLoading = false;
163 | mFooterView.show();
164 | mFooterView.setState(ListViewPlusFooter.STATE_NORMAL);
165 | //make sure "pull up" don't show a line in bottom when listview with one page
166 | setFooterDividersEnabled(true);
167 | // both "pull up" and "click" will invoke load more.
168 | mFooterView.setOnClickListener(new OnClickListener() {
169 | @Override
170 | public void onClick(View v) {
171 | startLoadMore();
172 | }
173 | });
174 | }
175 | }
176 |
177 | /**
178 | * stop refresh, reset header view.
179 | */
180 | public void stopRefresh() {
181 | if (mPullRefreshing == true) {
182 | mPullRefreshing = false;
183 | resetHeaderHeight();
184 | setRefreshTime(getCurrentTime());
185 | }
186 | }
187 |
188 | /**
189 | * stop load more, reset footer view.
190 | */
191 | public void stopLoadMore() {
192 | if (mPullLoading == true) {
193 | mPullLoading = false;
194 | mFooterView.setState(ListViewPlusFooter.STATE_NORMAL);
195 | }
196 | }
197 |
198 | /**
199 | * set last refresh time
200 | *
201 | * @param time
202 | */
203 | public void setRefreshTime(String time) {
204 | mHeaderTimeView.setText(time);
205 | }
206 |
207 | private void invokeOnScrolling() {
208 | if (mScrollListener instanceof OnXScrollListener) {
209 | OnXScrollListener l = (OnXScrollListener) mScrollListener;
210 | l.onXScrolling(this);
211 | }
212 | }
213 |
214 | private void updateHeaderHeight(float delta) {
215 | mHeaderView.setVisiableHeight((int) delta
216 | + mHeaderView.getVisiableHeight());
217 | if (mEnablePullRefresh && !mPullRefreshing) { // 未处于刷新状态,更新箭头
218 | if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
219 | mHeaderView.setState(ListViewPlusHeader.STATE_READY);
220 | } else {
221 | mHeaderView.setState(ListViewPlusHeader.STATE_NORMAL);
222 | }
223 | }
224 | setSelection(0); // scroll to top each time
225 | }
226 |
227 | /**
228 | * reset header view's height.
229 | */
230 | private void resetHeaderHeight() {
231 | int height = mHeaderView.getVisiableHeight();
232 | if (height == 0) // not visible.
233 | return;
234 | // refreshing and header isn't shown fully. do nothing.
235 | if (mPullRefreshing && height <= mHeaderViewHeight) {
236 | return;
237 | }
238 | int finalHeight = 0; // default: scroll back to dismiss header.
239 | // is refreshing, just scroll back to show all the header.
240 | if (mPullRefreshing && height > mHeaderViewHeight) {
241 | finalHeight = mHeaderViewHeight;
242 | }
243 | mScrollBack = SCROLLBACK_HEADER;
244 | mScroller.startScroll(0, height, 0, finalHeight - height,
245 | SCROLL_DURATION);
246 | // trigger computeScroll
247 | invalidate();
248 | }
249 |
250 | private void updateFooterHeight(float delta) {
251 | int height = mFooterView.getBottomMargin() + (int) delta;
252 | if (mEnablePullLoad && !mPullLoading) {
253 | if (height > PULL_LOAD_MORE_DELTA) { // height enough to invoke load
254 | // more.
255 | mFooterView.setState(ListViewPlusFooter.STATE_READY);
256 | } else {
257 | mFooterView.setState(ListViewPlusFooter.STATE_NORMAL);
258 | }
259 | }
260 | mFooterView.setBottomMargin(height);
261 |
262 | // setSelection(mTotalItemCount - 1); // scroll to bottom
263 | }
264 |
265 | private void resetFooterHeight() {
266 | int bottomMargin = mFooterView.getBottomMargin();
267 | if (bottomMargin > 0) {
268 | mScrollBack = SCROLLBACK_FOOTER;
269 | mScroller.startScroll(0, bottomMargin, 0, -bottomMargin,
270 | SCROLL_DURATION);
271 | invalidate();
272 | }
273 | }
274 |
275 | private void startLoadMore() {
276 | mPullLoading = true;
277 | mFooterView.setState(ListViewPlusFooter.STATE_LOADING);
278 | if (mListViewListener != null) {
279 | mListViewListener.onLoadMore();
280 | }
281 | }
282 |
283 | @Override
284 | public boolean onTouchEvent(MotionEvent ev) {
285 | if (mLastY == -1) {
286 | mLastY = ev.getRawY();
287 | }
288 |
289 | switch (ev.getAction()) {
290 | case MotionEvent.ACTION_DOWN:
291 | mLastY = ev.getRawY();
292 | break;
293 | case MotionEvent.ACTION_MOVE:
294 | final float deltaY = ev.getRawY() - mLastY;
295 | mLastY = ev.getRawY();
296 | if (getFirstVisiblePosition() == 0
297 | && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {
298 | // the first item is showing, header has shown or pull down.
299 | updateHeaderHeight(deltaY / OFFSET_RADIO);
300 | invokeOnScrolling();
301 | } else if (getLastVisiblePosition() == mTotalItemCount - 1
302 | && (mFooterView.getBottomMargin() > 0 || deltaY < 0)) {
303 | // last item, already pulled up or want to pull up.
304 | updateFooterHeight(-deltaY / OFFSET_RADIO);
305 | }
306 | break;
307 | default:
308 | mLastY = -1; // reset
309 | if (getFirstVisiblePosition() == 0) {
310 | // invoke refresh
311 | if (mEnablePullRefresh
312 | && mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
313 | mPullRefreshing = true;
314 | mHeaderView.setState(ListViewPlusHeader.STATE_REFRESHING);
315 | if (mListViewListener != null) {
316 | mListViewListener.onRefresh();
317 | }
318 | }
319 | resetHeaderHeight();
320 | } else if (getLastVisiblePosition() == mTotalItemCount - 1) {
321 | // invoke load more.
322 | if (mEnablePullLoad
323 | && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA
324 | && !mPullLoading) {
325 | startLoadMore();
326 | }
327 | resetFooterHeight();
328 | }
329 | break;
330 | }
331 | return super.onTouchEvent(ev);
332 | }
333 |
334 | @Override
335 | public void computeScroll() {
336 | if (mScroller.computeScrollOffset()) {
337 | if (mScrollBack == SCROLLBACK_HEADER) {
338 | mHeaderView.setVisiableHeight(mScroller.getCurrY());
339 | } else {
340 | mFooterView.setBottomMargin(mScroller.getCurrY());
341 | }
342 | postInvalidate();
343 | invokeOnScrolling();
344 | }
345 | super.computeScroll();
346 | }
347 |
348 | @Override
349 | public void setOnScrollListener(OnScrollListener l) {
350 | mScrollListener = l;
351 | }
352 | @Override
353 | public void onScrollStateChanged(AbsListView view, int scrollState) {
354 | if (mScrollListener != null) {
355 | mScrollListener.onScrollStateChanged(view, scrollState);
356 | }
357 | }
358 | @Override
359 | public void onScroll(AbsListView view, int firstVisibleItem,
360 | int visibleItemCount, int totalItemCount) {
361 | // send to user's listener
362 | if(mFooterView!=null){
363 | if (totalItemCount
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | /gen
3 | /bin
4 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | library-PinnedSectionListActivity
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/assets/.gitignore:
--------------------------------------------------------------------------------
1 | # placeholder
2 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:0.11.1'
7 | }
8 | }
9 |
10 | apply plugin: 'android-library'
11 |
12 | android {
13 | sourceSets {
14 | main {
15 | manifest.srcFile 'AndroidManifest.xml'
16 | java.srcDirs = ['src']
17 | }
18 | }
19 | compileSdkVersion 17
20 | buildToolsVersion '19.1'
21 | }
22 |
23 | android.libraryVariants
24 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-17
15 | android.library=true
16 | android.library.reference.1=../library-ListViewPlus
17 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/res/.gitignore:
--------------------------------------------------------------------------------
1 | # placeholder
2 |
--------------------------------------------------------------------------------
/library-PinnedSectionListActivity/src/com/hb/views/PinnedSectionListView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Sergej Shafarenka, halfbit.de
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file kt in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hb.views;
18 |
19 | import android.content.Context;
20 | import android.database.DataSetObserver;
21 | import android.graphics.Canvas;
22 | import android.graphics.Color;
23 | import android.graphics.PointF;
24 | import android.graphics.Rect;
25 | import android.graphics.drawable.GradientDrawable;
26 | import android.graphics.drawable.GradientDrawable.Orientation;
27 | import android.os.Parcelable;
28 | import android.util.AttributeSet;
29 | import android.view.MotionEvent;
30 | import android.view.SoundEffectConstants;
31 | import android.view.View;
32 | import android.view.ViewConfiguration;
33 | import android.view.accessibility.AccessibilityEvent;
34 | import android.widget.AbsListView;
35 | import android.widget.HeaderViewListAdapter;
36 | import android.widget.ListAdapter;
37 | import android.widget.ListView;
38 | import android.widget.SectionIndexer;
39 |
40 | import com.hb.views.pinnedsection.BuildConfig;
41 | import com.jph.view.ListViewPlus;
42 |
43 | /**
44 | * ListView, which is capable to pin section views at its top while the rest is still scrolled.
45 | */
46 | public class PinnedSectionListView extends ListViewPlus {
47 |
48 | //-- inner classes
49 |
50 | /** List adapter to be implemented for being used with PinnedSectionListView adapter. */
51 | public static interface PinnedSectionListAdapter extends ListAdapter {
52 | /** This method shall return 'true' if views of given type has to be pinned. */
53 | boolean isItemViewTypePinned(int viewType);
54 | }
55 |
56 | /** Wrapper class for pinned section view and its position in the list. */
57 | static class PinnedSection {
58 | public View view;
59 | public int position;
60 | public long id;
61 | }
62 |
63 | //-- class fields
64 |
65 | // fields used for handling touch events
66 | private final Rect mTouchRect = new Rect();
67 | private final PointF mTouchPoint = new PointF();
68 | private int mTouchSlop;
69 | private View mTouchTarget;
70 | private MotionEvent mDownEvent;
71 |
72 | // fields used for drawing shadow under a pinned section
73 | private GradientDrawable mShadowDrawable;
74 | private int mSectionsDistanceY;
75 | private int mShadowHeight;
76 |
77 | /** Delegating listener, can be null. */
78 | OnScrollListener mDelegateOnScrollListener;
79 |
80 | /** Shadow for being recycled, can be null. */
81 | PinnedSection mRecycleSection;
82 |
83 | /** shadow instance with a pinned view, can be null. */
84 | PinnedSection mPinnedSection;
85 |
86 | /** Pinned view Y-translation. We use it to stick pinned view to the next section. */
87 | int mTranslateY;
88 |
89 | /** Scroll listener which does the magic */
90 | private final OnScrollListener mOnScrollListener = new OnScrollListener() {
91 |
92 | @Override public void onScrollStateChanged(AbsListView view, int scrollState) {
93 | if (mDelegateOnScrollListener != null) { // delegate
94 | mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
95 | }
96 | }
97 |
98 | @Override
99 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
100 |
101 | if (mDelegateOnScrollListener != null) { // delegate
102 | mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
103 | }
104 |
105 | // get expected adapter or fail fast
106 | ListAdapter adapter = getAdapter();
107 | if (adapter == null || visibleItemCount == 0) return; // nothing to do
108 |
109 | final boolean isFirstVisibleItemSection =
110 | isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));
111 |
112 | if (isFirstVisibleItemSection) {
113 | View sectionView = getChildAt(0);
114 | if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
115 | destroyPinnedShadow();
116 | } else { // section doesn't stick to the top, make sure we have a pinned shadow
117 | ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount);
118 | }
119 |
120 | } else { // section is not at the first visible position
121 | int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
122 | if (sectionPosition > -1) { // we have section position
123 | ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
124 | } else { // there is no section for the first visible item, destroy shadow
125 | destroyPinnedShadow();
126 | }
127 | }
128 | };
129 |
130 | };
131 |
132 | /** Default change observer. */
133 | private final DataSetObserver mDataSetObserver = new DataSetObserver() {
134 | @Override public void onChanged() {
135 | recreatePinnedShadow();
136 | };
137 | @Override public void onInvalidated() {
138 | recreatePinnedShadow();
139 | }
140 | };
141 |
142 | //-- constructors
143 |
144 | public PinnedSectionListView(Context context, AttributeSet attrs) {
145 | super(context, attrs);
146 | initView();
147 | }
148 |
149 | public PinnedSectionListView(Context context, AttributeSet attrs, int defStyle) {
150 | super(context, attrs, defStyle);
151 | initView();
152 | }
153 |
154 | private void initView() {
155 | setOnScrollListener(mOnScrollListener);
156 | mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
157 | initShadow(true);
158 | }
159 |
160 | //-- public API methods
161 |
162 | public void setShadowVisible(boolean visible) {
163 | initShadow(visible);
164 | if (mPinnedSection != null) {
165 | View v = mPinnedSection.view;
166 | invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom() + mShadowHeight);
167 | }
168 | }
169 |
170 | //-- pinned section drawing methods
171 |
172 | public void initShadow(boolean visible) {
173 | if (visible) {
174 | if (mShadowDrawable == null) {
175 | mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM,
176 | new int[] { Color.parseColor("#ffa0a0a0"), Color.parseColor("#50a0a0a0"), Color.parseColor("#00a0a0a0")});
177 | mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density);
178 | }
179 | } else {
180 | if (mShadowDrawable != null) {
181 | mShadowDrawable = null;
182 | mShadowHeight = 0;
183 | }
184 | }
185 | }
186 |
187 | /** Create shadow wrapper with a pinned view for a view at given position */
188 | void createPinnedShadow(int position) {
189 |
190 | // try to recycle shadow
191 | PinnedSection pinnedShadow = mRecycleSection;
192 | mRecycleSection = null;
193 |
194 | // create new shadow, if needed
195 | if (pinnedShadow == null) pinnedShadow = new PinnedSection();
196 | // request new view using recycled view, if such
197 | View pinnedView = getAdapter().getView(position, pinnedShadow.view, PinnedSectionListView.this);
198 |
199 | // read layout parameters
200 | LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
201 | if (layoutParams == null) {
202 | layoutParams = (LayoutParams) generateDefaultLayoutParams();
203 | pinnedView.setLayoutParams(layoutParams);
204 | }
205 |
206 | int heightMode = MeasureSpec.getMode(layoutParams.height);
207 | int heightSize = MeasureSpec.getSize(layoutParams.height);
208 |
209 | if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;
210 |
211 | int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
212 | if (heightSize > maxHeight) heightSize = maxHeight;
213 |
214 | // measure & layout
215 | int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
216 | int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
217 | pinnedView.measure(ws, hs);
218 | pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
219 | mTranslateY = 0;
220 |
221 | // initialize pinned shadow
222 | pinnedShadow.view = pinnedView;
223 | pinnedShadow.position = position;
224 | pinnedShadow.id = getAdapter().getItemId(position);
225 |
226 | // store pinned shadow
227 | mPinnedSection = pinnedShadow;
228 | }
229 |
230 | /** Destroy shadow wrapper for currently pinned view */
231 | void destroyPinnedShadow() {
232 | if (mPinnedSection != null) {
233 | // keep shadow for being recycled later
234 | mRecycleSection = mPinnedSection;
235 | mPinnedSection = null;
236 | }
237 | }
238 |
239 | /** Makes sure we have an actual pinned shadow for given position. */
240 | void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
241 | if (visibleItemCount < 2) { // no need for creating shadow at all, we have a single visible item
242 | destroyPinnedShadow();
243 | return;
244 | }
245 |
246 | if (mPinnedSection != null
247 | && mPinnedSection.position != sectionPosition) { // invalidate shadow, if required
248 | destroyPinnedShadow();
249 | }
250 |
251 | if (mPinnedSection == null) { // create shadow, if empty
252 | createPinnedShadow(sectionPosition);
253 | }
254 |
255 | // align shadow according to next section position, if needed
256 | int nextPosition = sectionPosition + 1;
257 | if (nextPosition < getCount()) {
258 | int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition,
259 | visibleItemCount - (nextPosition - firstVisibleItem));
260 | if (nextSectionPosition > -1) {
261 | View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
262 | final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
263 | mSectionsDistanceY = nextSectionView.getTop() - bottom;
264 | if (mSectionsDistanceY < 0) {
265 | // next section overlaps pinned shadow, move it up
266 | mTranslateY = mSectionsDistanceY;
267 | } else {
268 | // next section does not overlap with pinned, stick to top
269 | mTranslateY = 0;
270 | }
271 | } else {
272 | // no other sections are visible, stick to top
273 | mTranslateY = 0;
274 | mSectionsDistanceY = Integer.MAX_VALUE;
275 | }
276 | }
277 |
278 | }
279 |
280 | int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
281 | ListAdapter adapter = getAdapter();
282 |
283 | int adapterDataCount = adapter.getCount();
284 | if (getLastVisiblePosition() >= adapterDataCount) return -1; // dataset has changed, no candidate
285 |
286 | if (firstVisibleItem+visibleItemCount >= adapterDataCount){//added to prevent index Outofbound (in case)
287 | visibleItemCount = adapterDataCount-firstVisibleItem;
288 | }
289 |
290 | for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) {
291 | int position = firstVisibleItem + childIndex;
292 | int viewType = adapter.getItemViewType(position);
293 | if (isItemViewTypePinned(adapter, viewType)) return position;
294 | }
295 | return -1;
296 | }
297 |
298 | int findCurrentSectionPosition(int fromPosition) {
299 | ListAdapter adapter = getAdapter();
300 |
301 | if (fromPosition >= adapter.getCount()) return -1; // dataset has changed, no candidate
302 |
303 | if (adapter instanceof SectionIndexer) {
304 | // try fast way by asking section indexer
305 | SectionIndexer indexer = (SectionIndexer) adapter;
306 | int sectionPosition = indexer.getSectionForPosition(fromPosition);
307 | int itemPosition = indexer.getPositionForSection(sectionPosition);
308 | int typeView = adapter.getItemViewType(itemPosition);
309 | if (isItemViewTypePinned(adapter, typeView)) {
310 | return itemPosition;
311 | } // else, no luck
312 | }
313 |
314 | // try slow way by looking through to the next section item above
315 | for (int position=fromPosition; position>=0; position--) {
316 | int viewType = adapter.getItemViewType(position);
317 | if (isItemViewTypePinned(adapter, viewType)) return position;
318 | }
319 | return -1; // no candidate found
320 | }
321 |
322 | void recreatePinnedShadow() {
323 | destroyPinnedShadow();
324 | ListAdapter adapter = getAdapter();
325 | if (adapter != null && adapter.getCount() > 0) {
326 | int firstVisiblePosition = getFirstVisiblePosition();
327 | int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);
328 | if (sectionPosition == -1) return; // no views to pin, exit
329 | ensureShadowForPosition(sectionPosition,
330 | firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition);
331 | }
332 | }
333 |
334 | @Override
335 | public void setOnScrollListener(OnScrollListener listener) {
336 | if (listener == mOnScrollListener) {
337 | super.setOnScrollListener(listener);
338 | } else {
339 | mDelegateOnScrollListener = listener;
340 | }
341 | }
342 |
343 | @Override
344 | public void onRestoreInstanceState(Parcelable state) {
345 | super.onRestoreInstanceState(state);
346 | post(new Runnable() {
347 | @Override public void run() { // restore pinned view after configuration change
348 | recreatePinnedShadow();
349 | }
350 | });
351 | }
352 |
353 | @Override
354 | public void setAdapter(ListAdapter adapter) {
355 |
356 | // assert adapter in debug mode
357 | if (BuildConfig.DEBUG && adapter != null) {
358 | if (!(adapter instanceof PinnedSectionListAdapter))
359 | throw new IllegalArgumentException("Does your adapter implement PinnedSectionListAdapter?");
360 | if (adapter.getViewTypeCount() < 2)
361 | throw new IllegalArgumentException("Does your adapter handle at least two types" +
362 | " of views in getViewTypeCount() method: items and sections?");
363 | }
364 |
365 | // unregister observer at old adapter and register on new one
366 | ListAdapter oldAdapter = getAdapter();
367 | if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver);
368 | if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver);
369 |
370 | // destroy pinned shadow, if new adapter is not same as old one
371 | if (oldAdapter != adapter) destroyPinnedShadow();
372 |
373 | super.setAdapter(adapter);
374 | }
375 |
376 | @Override
377 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
378 | super.onLayout(changed, l, t, r, b);
379 | if (mPinnedSection != null) {
380 | int parentWidth = r - l - getPaddingLeft() - getPaddingRight();
381 | int shadowWidth = mPinnedSection.view.getWidth();
382 | if (parentWidth != shadowWidth) {
383 | recreatePinnedShadow();
384 | }
385 | }
386 | }
387 |
388 | @Override
389 | protected void dispatchDraw(Canvas canvas) {
390 | super.dispatchDraw(canvas);
391 |
392 | if (mPinnedSection != null) {
393 |
394 | // prepare variables
395 | int pLeft = getListPaddingLeft();
396 | int pTop = getListPaddingTop();
397 | View view = mPinnedSection.view;
398 |
399 | // draw child
400 | canvas.save();
401 |
402 | int clipHeight = view.getHeight() +
403 | (mShadowDrawable == null ? 0 : Math.min(mShadowHeight, mSectionsDistanceY));
404 | canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight);
405 |
406 | canvas.translate(pLeft, pTop + mTranslateY);
407 | drawChild(canvas, mPinnedSection.view, getDrawingTime());
408 |
409 | if (mShadowDrawable != null && mSectionsDistanceY > 0) {
410 | mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),
411 | mPinnedSection.view.getBottom(),
412 | mPinnedSection.view.getRight(),
413 | mPinnedSection.view.getBottom() + mShadowHeight);
414 | mShadowDrawable.draw(canvas);
415 | }
416 |
417 | canvas.restore();
418 | }
419 | }
420 |
421 | //-- touch handling methods
422 |
423 | @Override
424 | public boolean dispatchTouchEvent(MotionEvent ev) {
425 |
426 | final float x = ev.getX();
427 | final float y = ev.getY();
428 | final int action = ev.getAction();
429 |
430 | if (action == MotionEvent.ACTION_DOWN
431 | && mTouchTarget == null
432 | && mPinnedSection != null
433 | && isPinnedViewTouched(mPinnedSection.view, x, y)) { // create touch target
434 |
435 | // user touched pinned view
436 | mTouchTarget = mPinnedSection.view;
437 | mTouchPoint.x = x;
438 | mTouchPoint.y = y;
439 |
440 | // copy down event for eventually be used later
441 | mDownEvent = MotionEvent.obtain(ev);
442 | }
443 |
444 | if (mTouchTarget != null) {
445 | if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to pinned view
446 | mTouchTarget.dispatchTouchEvent(ev);
447 | }
448 |
449 | if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned view
450 | super.dispatchTouchEvent(ev);
451 | performPinnedItemClick();
452 | clearTouchTarget();
453 |
454 | } else if (action == MotionEvent.ACTION_CANCEL) { // cancel
455 | clearTouchTarget();
456 |
457 | } else if (action == MotionEvent.ACTION_MOVE) {
458 | if (Math.abs(y - mTouchPoint.y) > mTouchSlop) {
459 |
460 | // cancel sequence on touch target
461 | MotionEvent event = MotionEvent.obtain(ev);
462 | event.setAction(MotionEvent.ACTION_CANCEL);
463 | mTouchTarget.dispatchTouchEvent(event);
464 | event.recycle();
465 |
466 | // provide correct sequence to super class for further handling
467 | super.dispatchTouchEvent(mDownEvent);
468 | super.dispatchTouchEvent(ev);
469 | clearTouchTarget();
470 |
471 | }
472 | }
473 |
474 | return true;
475 | }
476 |
477 | // call super if this was not our pinned view
478 | return super.dispatchTouchEvent(ev);
479 | }
480 |
481 | private boolean isPinnedViewTouched(View view, float x, float y) {
482 | view.getHitRect(mTouchRect);
483 |
484 | // by taping top or bottom padding, the list performs on click on a border item.
485 | // we don't add top padding here to keep behavior consistent.
486 | mTouchRect.top += mTranslateY;
487 |
488 | mTouchRect.bottom += mTranslateY + getPaddingTop();
489 | mTouchRect.left += getPaddingLeft();
490 | mTouchRect.right -= getPaddingRight();
491 | return mTouchRect.contains((int)x, (int)y);
492 | }
493 |
494 | private void clearTouchTarget() {
495 | mTouchTarget = null;
496 | if (mDownEvent != null) {
497 | mDownEvent.recycle();
498 | mDownEvent = null;
499 | }
500 | }
501 |
502 | private boolean performPinnedItemClick() {
503 | if (mPinnedSection == null) return false;
504 |
505 | OnItemClickListener listener = getOnItemClickListener();
506 | if (listener != null && getAdapter().isEnabled(mPinnedSection.position)) {
507 | View view = mPinnedSection.view;
508 | playSoundEffect(SoundEffectConstants.CLICK);
509 | if (view != null) {
510 | view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
511 | }
512 | listener.onItemClick(this, view, mPinnedSection.position, mPinnedSection.id);
513 | return true;
514 | }
515 | return false;
516 | }
517 |
518 | public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {
519 | if (adapter instanceof HeaderViewListAdapter) {
520 | adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
521 | }
522 | return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType);
523 | }
524 |
525 | }
526 |
--------------------------------------------------------------------------------
/raw/上拉加载.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/raw/上拉加载.png
--------------------------------------------------------------------------------
/raw/下拉刷新.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/raw/下拉刷新.png
--------------------------------------------------------------------------------
/raw/运行效果图.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazycodeboy/GroupListView/8c4f696a7000b02fded831544ffb66442ef4e501/raw/运行效果图.gif
--------------------------------------------------------------------------------