PinnedSectionListView在github上的链接地址是:https://github.com/beworker/pinned-section-listview 。

下载下来后直接将PinnedSectionListView.java(在一些SDK版本拉动的时候会异常崩溃,异常信息和修改后的文档在后面)复制粘贴在要用的包中:

activity_main.xml:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. tools:context="com.zzw.testpinnedsectionlistview.MainActivity" >
  6.  
  7. <com.zzw.testpinnedsectionlistview.PinnedSectionListView
  8. android:id="@+id/listView"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent" />
  11.  
  12. </RelativeLayout>

item.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical" >
  6.  
  7. <ImageView
  8. android:id="@+id/imageView"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_alignParentLeft="true"
  12. android:layout_centerVertical="true"
  13. android:src="@drawable/ic_launcher" />
  14.  
  15. <TextView
  16. android:id="@+id/textView"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:layout_alignBottom="@+id/imageView1"
  20. android:layout_alignParentRight="true"
  21. android:gravity="center"
  22. android:textSize="20sp"
  23. android:layout_alignTop="@+id/imageView1"
  24. android:layout_toRightOf="@+id/imageView1"
  25. android:text="TextView" />
  26.  
  27. </RelativeLayout>

item.xml

MainActivity.java:

  1. package com.zzw.testpinnedsectionlistview;
  2.  
  3. import java.util.ArrayList;
  4.  
  5. import com.zzw.testpinnedsectionlistview.PinnedSectionListView.PinnedSectionListAdapter;
  6.  
  7. import android.app.Activity;
  8. import android.content.Context;
  9. import android.graphics.Color;
  10. import android.os.Bundle;
  11. import android.view.LayoutInflater;
  12. import android.view.View;
  13. import android.view.ViewGroup;
  14. import android.widget.ArrayAdapter;
  15. import android.widget.TextView;
  16.  
  17. public class MainActivity extends Activity {
  18.  
  19. private ArrayList<Item> items = null;
  20.  
  21. private final int VIEW_TYPE_COUNT = 2;
  22.  
  23. @Override
  24. protected void onCreate(Bundle savedInstanceState) {
  25. super.onCreate(savedInstanceState);
  26. setContentView(R.layout.activity_main);
  27.  
  28. items =new ArrayList<MainActivity.Item>();
  29. // 假设我们演示以A,B,C,,,F这样的字符串作为分组的标签。
  30. // 每一组装载5个子数据。
  31. String[] groups = { "A", "B", "C", "D", "E" };
  32. for (int i = 0; i < groups.length; i++) {
  33. String s = groups[i];
  34.  
  35. Item group = new Item();
  36. group.type = Item.GROUP;
  37. group.text = s;
  38. items.add(group);
  39.  
  40. for (int j = 0; j < 10; j++) {
  41. Item child = new Item();
  42. child.type = Item.CHILD;
  43. child.text = s + "组数据:" + j;
  44. items.add(child);
  45. }
  46. }
  47.  
  48. PinnedSectionListView listView = (PinnedSectionListView) findViewById(R.id.listView);
  49. listView.setAdapter(new MyAdapter(this, -1));
  50.  
  51. }
  52.  
  53. private class Item {
  54. public static final int GROUP = 0;
  55. public static final int CHILD = 1;
  56.  
  57. public int type;
  58. public String text;
  59. }
  60.  
  61. private class MyAdapter extends ArrayAdapter<Item> implements PinnedSectionListAdapter {
  62.  
  63. private LayoutInflater inflater;
  64.  
  65. public MyAdapter(Context context, int resource) {
  66. super(context, resource);
  67.  
  68. inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  69. }
  70.  
  71. @Override
  72. public int getItemViewType(int position) {
  73.  
  74. return items.get(position).type;
  75. }
  76.  
  77. @Override
  78. public int getCount() {
  79.  
  80. return items.size();
  81. }
  82.  
  83. @Override
  84. public int getViewTypeCount() {
  85.  
  86. return VIEW_TYPE_COUNT;
  87. }
  88.  
  89. @Override
  90. public Item getItem(int position) {
  91.  
  92. return items.get(position);
  93. }
  94.  
  95. @Override
  96. public View getView(int position, View convertView, ViewGroup parent) {
  97. switch (getItemViewType(position)) {
  98. case Item.GROUP:
  99.  
  100. if (convertView == null) {
  101. convertView = inflater.inflate(android.R.layout.simple_list_item_1, null);
  102. }
  103.  
  104. TextView textView_group = (TextView) convertView.findViewById(android.R.id.text1);
  105. textView_group.setText(getItem(position).text);
  106. textView_group.setTextColor(Color.BLUE);
  107. textView_group.setTextSize(30);
  108. textView_group.setBackgroundColor(Color.GRAY);
  109.  
  110. break;
  111.  
  112. case Item.CHILD:
  113. if (convertView == null) {
  114. convertView = inflater.inflate(R.layout.item, null);
  115. }
  116.  
  117. TextView textView_child = (TextView) convertView.findViewById(R.id.textView);
  118. textView_child.setText(getItem(position).text);
  119. textView_child.setBackgroundColor(Color.YELLOW);
  120.  
  121. break;
  122. }
  123. return convertView;
  124. }
  125.  
  126. /*
  127. * 假设此方法返回皆为false。那么PinnedSectionListView将退化成为一个基础的ListView.
  128. * 只不过退化后的ListView只是一个拥有两个View Type的ListView。
  129. *
  130. * 从某种角度上讲,此方法对于PinnedSectionListView至关重要
  131. * 返回值true或false,将直接导致PinnedSectionListView是一个PinnedSectionListView,
  132. * 还是一个普通的ListView
  133. *
  134. */
  135. @Override
  136. public boolean isItemViewTypePinned(int viewType) {
  137. boolean type = false;
  138. switch (viewType) {
  139. case Item.GROUP:
  140. type = true;
  141. break;
  142.  
  143. case Item.CHILD:
  144. type = false;
  145. break;
  146.  
  147. default:
  148. type = false;
  149. break;
  150. }
  151. return type;
  152. }
  153. }
  154. }

程序运行拉动的时候有的sdk版本会出现程序崩溃,LogCat是这样情况:

报错的是原代码PinnedSectionListView.java中的(200行左右):

  1. // read layout parameters
  2. LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
  3. if (layoutParams == null) {
  4. layoutParams = (LayoutParams) generateDefaultLayoutParams();
  5. pinnedView.setLayoutParams(layoutParams);
  6. }

主要是这句话:

  1. layoutParams = (LayoutParams) generateDefaultLayoutParams();

经由研究发现,此原因是在调用Android系统的generateDefaultLayoutParams()方法时候,发生异常,致使代码运行获得的结果layoutParams不正常,进而导致PinnedSectionListView崩溃。

解决方案:
自己动手重写Android系统的generateDefaultLayoutParams()方法,返回自己定制的LayoutParams值。

具体实现:
在PinnedSectionListView.java中增加自己重写的generateDefaultLayoutParams()方法:

  1. @Override
  2. protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
  3.  
  4. LayoutParams mLayoutParams=new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
  5.  
  6. return mLayoutParams;
  7. }

最终修复bug,改进后的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.zzw.testpinnedsectionlistview;
  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.ViewGroup;
  34. import android.view.accessibility.AccessibilityEvent;
  35. import android.widget.AbsListView;
  36. import android.widget.HeaderViewListAdapter;
  37. import android.widget.ListAdapter;
  38. import android.widget.ListView;
  39. import android.widget.SectionIndexer;
  40.  
  41. /**
  42. * ListView, which is capable to pin section views at its top while the rest is still scrolled.
  43. */
  44. public class PinnedSectionListView extends ListView {
  45.  
  46. //-- inner classes
  47.  
  48. /** List adapter to be implemented for being used with PinnedSectionListView adapter. */
  49. public static interface PinnedSectionListAdapter extends ListAdapter {
  50. /** This method shall return 'true' if views of given type has to be pinned. */
  51. boolean isItemViewTypePinned(int viewType);
  52. }
  53.  
  54. /** Wrapper class for pinned section view and its position in the list. */
  55. static class PinnedSection {
  56. public View view;
  57. public int position;
  58. public long id;
  59. }
  60.  
  61. //-- class fields
  62.  
  63. // fields used for handling touch events
  64. private final Rect mTouchRect = new Rect();
  65. private final PointF mTouchPoint = new PointF();
  66. private int mTouchSlop;
  67. private View mTouchTarget;
  68. private MotionEvent mDownEvent;
  69.  
  70. // fields used for drawing shadow under a pinned section
  71. private GradientDrawable mShadowDrawable;
  72. private int mSectionsDistanceY;
  73. private int mShadowHeight;
  74.  
  75. /** Delegating listener, can be null. */
  76. OnScrollListener mDelegateOnScrollListener;
  77.  
  78. /** Shadow for being recycled, can be null. */
  79. PinnedSection mRecycleSection;
  80.  
  81. /** shadow instance with a pinned view, can be null. */
  82. PinnedSection mPinnedSection;
  83.  
  84. /** Pinned view Y-translation. We use it to stick pinned view to the next section. */
  85. int mTranslateY;
  86.  
  87. /** Scroll listener which does the magic */
  88. private final OnScrollListener mOnScrollListener = new OnScrollListener() {
  89.  
  90. @Override public void onScrollStateChanged(AbsListView view, int scrollState) {
  91. if (mDelegateOnScrollListener != null) { // delegate
  92. mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
  93. }
  94. }
  95.  
  96. @Override
  97. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  98.  
  99. if (mDelegateOnScrollListener != null) { // delegate
  100. mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
  101. }
  102.  
  103. // get expected adapter or fail fast
  104. ListAdapter adapter = getAdapter();
  105. if (adapter == null || visibleItemCount == 0) return; // nothing to do
  106.  
  107. final boolean isFirstVisibleItemSection =
  108. isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));
  109.  
  110. if (isFirstVisibleItemSection) {
  111. View sectionView = getChildAt(0);
  112. if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
  113. destroyPinnedShadow();
  114. } else { // section doesn't stick to the top, make sure we have a pinned shadow
  115. ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount);
  116. }
  117.  
  118. } else { // section is not at the first visible position
  119. int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
  120. if (sectionPosition > -1) { // we have section position
  121. ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
  122. } else { // there is no section for the first visible item, destroy shadow
  123. destroyPinnedShadow();
  124. }
  125. }
  126. };
  127.  
  128. };
  129.  
  130. /** Default change observer. */
  131. private final DataSetObserver mDataSetObserver = new DataSetObserver() {
  132. @Override public void onChanged() {
  133. recreatePinnedShadow();
  134. };
  135. @Override public void onInvalidated() {
  136. recreatePinnedShadow();
  137. }
  138. };
  139.  
  140. //-- constructors
  141.  
  142. public PinnedSectionListView(Context context, AttributeSet attrs) {
  143. super(context, attrs);
  144. initView();
  145. }
  146.  
  147. public PinnedSectionListView(Context context, AttributeSet attrs, int defStyle) {
  148. super(context, attrs, defStyle);
  149. initView();
  150. }
  151.  
  152. private void initView() {
  153. setOnScrollListener(mOnScrollListener);
  154. mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
  155. initShadow(true);
  156. }
  157.  
  158. //-- public API methods
  159.  
  160. public void setShadowVisible(boolean visible) {
  161. initShadow(visible);
  162. if (mPinnedSection != null) {
  163. View v = mPinnedSection.view;
  164. invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom() + mShadowHeight);
  165. }
  166. }
  167.  
  168. //-- pinned section drawing methods
  169.  
  170. public void initShadow(boolean visible) {
  171. if (visible) {
  172. if (mShadowDrawable == null) {
  173. mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM,
  174. new int[] { Color.parseColor("#ffa0a0a0"), Color.parseColor("#50a0a0a0"), Color.parseColor("#00a0a0a0")});
  175. mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density);
  176. }
  177. } else {
  178. if (mShadowDrawable != null) {
  179. mShadowDrawable = null;
  180. mShadowHeight = 0;
  181. }
  182. }
  183. }
  184.  
  185. //*****添加*****//
  186. @Override
  187. protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
  188. LayoutParams mLayoutParams=new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);
  189. return mLayoutParams;
  190. }
  191. //*****添加*****//
  192.  
  193. /** Create shadow wrapper with a pinned view for a view at given position */
  194. void createPinnedShadow(int position) {
  195.  
  196. // try to recycle shadow
  197. PinnedSection pinnedShadow = mRecycleSection;
  198. mRecycleSection = null;
  199.  
  200. // create new shadow, if needed
  201. if (pinnedShadow == null) pinnedShadow = new PinnedSection();
  202. // request new view using recycled view, if such
  203. View pinnedView = getAdapter().getView(position, pinnedShadow.view, PinnedSectionListView.this);
  204.  
  205. // read layout parameters
  206. LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
  207. if (layoutParams == null) {
  208. layoutParams = (LayoutParams) generateDefaultLayoutParams();
  209. pinnedView.setLayoutParams(layoutParams);
  210. }
  211.  
  212. int heightMode = MeasureSpec.getMode(layoutParams.height);
  213. int heightSize = MeasureSpec.getSize(layoutParams.height);
  214.  
  215. if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;
  216.  
  217. int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
  218. if (heightSize > maxHeight) heightSize = maxHeight;
  219.  
  220. // measure & layout
  221. int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
  222. int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
  223. pinnedView.measure(ws, hs);
  224. pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
  225. mTranslateY = 0;
  226.  
  227. // initialize pinned shadow
  228. pinnedShadow.view = pinnedView;
  229. pinnedShadow.position = position;
  230. pinnedShadow.id = getAdapter().getItemId(position);
  231.  
  232. // store pinned shadow
  233. mPinnedSection = pinnedShadow;
  234. }
  235.  
  236. /** Destroy shadow wrapper for currently pinned view */
  237. void destroyPinnedShadow() {
  238. if (mPinnedSection != null) {
  239. // keep shadow for being recycled later
  240. mRecycleSection = mPinnedSection;
  241. mPinnedSection = null;
  242. }
  243. }
  244.  
  245. /** Makes sure we have an actual pinned shadow for given position. */
  246. void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
  247. if (visibleItemCount < 2) { // no need for creating shadow at all, we have a single visible item
  248. destroyPinnedShadow();
  249. return;
  250. }
  251.  
  252. if (mPinnedSection != null
  253. && mPinnedSection.position != sectionPosition) { // invalidate shadow, if required
  254. destroyPinnedShadow();
  255. }
  256.  
  257. if (mPinnedSection == null) { // create shadow, if empty
  258. createPinnedShadow(sectionPosition);
  259. }
  260.  
  261. // align shadow according to next section position, if needed
  262. int nextPosition = sectionPosition + 1;
  263. if (nextPosition < getCount()) {
  264. int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition,
  265. visibleItemCount - (nextPosition - firstVisibleItem));
  266. if (nextSectionPosition > -1) {
  267. View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
  268. final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
  269. mSectionsDistanceY = nextSectionView.getTop() - bottom;
  270. if (mSectionsDistanceY < 0) {
  271. // next section overlaps pinned shadow, move it up
  272. mTranslateY = mSectionsDistanceY;
  273. } else {
  274. // next section does not overlap with pinned, stick to top
  275. mTranslateY = 0;
  276. }
  277. } else {
  278. // no other sections are visible, stick to top
  279. mTranslateY = 0;
  280. mSectionsDistanceY = Integer.MAX_VALUE;
  281. }
  282. }
  283.  
  284. }
  285.  
  286. int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
  287. ListAdapter adapter = getAdapter();
  288.  
  289. int adapterDataCount = adapter.getCount();
  290. if (getLastVisiblePosition() >= adapterDataCount) return -1; // dataset has changed, no candidate
  291.  
  292. if (firstVisibleItem+visibleItemCount >= adapterDataCount){//added to prevent index Outofbound (in case)
  293. visibleItemCount = adapterDataCount-firstVisibleItem;
  294. }
  295.  
  296. for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) {
  297. int position = firstVisibleItem + childIndex;
  298. int viewType = adapter.getItemViewType(position);
  299. if (isItemViewTypePinned(adapter, viewType)) return position;
  300. }
  301. return -1;
  302. }
  303.  
  304. int findCurrentSectionPosition(int fromPosition) {
  305. ListAdapter adapter = getAdapter();
  306.  
  307. if (fromPosition >= adapter.getCount()) return -1; // dataset has changed, no candidate
  308.  
  309. if (adapter instanceof SectionIndexer) {
  310. // try fast way by asking section indexer
  311. SectionIndexer indexer = (SectionIndexer) adapter;
  312. int sectionPosition = indexer.getSectionForPosition(fromPosition);
  313. int itemPosition = indexer.getPositionForSection(sectionPosition);
  314. int typeView = adapter.getItemViewType(itemPosition);
  315. if (isItemViewTypePinned(adapter, typeView)) {
  316. return itemPosition;
  317. } // else, no luck
  318. }
  319.  
  320. // try slow way by looking through to the next section item above
  321. for (int position=fromPosition; position>=0; position--) {
  322. int viewType = adapter.getItemViewType(position);
  323. if (isItemViewTypePinned(adapter, viewType)) return position;
  324. }
  325. return -1; // no candidate found
  326. }
  327.  
  328. void recreatePinnedShadow() {
  329. destroyPinnedShadow();
  330. ListAdapter adapter = getAdapter();
  331. if (adapter != null && adapter.getCount() > 0) {
  332. int firstVisiblePosition = getFirstVisiblePosition();
  333. int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);
  334. if (sectionPosition == -1) return; // no views to pin, exit
  335. ensureShadowForPosition(sectionPosition,
  336. firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition);
  337. }
  338. }
  339.  
  340. @Override
  341. public void setOnScrollListener(OnScrollListener listener) {
  342. if (listener == mOnScrollListener) {
  343. super.setOnScrollListener(listener);
  344. } else {
  345. mDelegateOnScrollListener = listener;
  346. }
  347. }
  348.  
  349. @Override
  350. public void onRestoreInstanceState(Parcelable state) {
  351. super.onRestoreInstanceState(state);
  352. post(new Runnable() {
  353. @Override public void run() { // restore pinned view after configuration change
  354. recreatePinnedShadow();
  355. }
  356. });
  357. }
  358.  
  359. @Override
  360. public void setAdapter(ListAdapter adapter) {
  361.  
  362. // assert adapter in debug mode
  363. if (BuildConfig.DEBUG && adapter != null) {
  364. if (!(adapter instanceof PinnedSectionListAdapter))
  365. throw new IllegalArgumentException("Does your adapter implement PinnedSectionListAdapter?");
  366. if (adapter.getViewTypeCount() < 2)
  367. throw new IllegalArgumentException("Does your adapter handle at least two types" +
  368. " of views in getViewTypeCount() method: items and sections?");
  369. }
  370.  
  371. // unregister observer at old adapter and register on new one
  372. ListAdapter oldAdapter = getAdapter();
  373. if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver);
  374. if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver);
  375.  
  376. // destroy pinned shadow, if new adapter is not same as old one
  377. if (oldAdapter != adapter) destroyPinnedShadow();
  378.  
  379. super.setAdapter(adapter);
  380. }
  381.  
  382. @Override
  383. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  384. super.onLayout(changed, l, t, r, b);
  385. if (mPinnedSection != null) {
  386. int parentWidth = r - l - getPaddingLeft() - getPaddingRight();
  387. int shadowWidth = mPinnedSection.view.getWidth();
  388. if (parentWidth != shadowWidth) {
  389. recreatePinnedShadow();
  390. }
  391. }
  392. }
  393.  
  394. @Override
  395. protected void dispatchDraw(Canvas canvas) {
  396. super.dispatchDraw(canvas);
  397.  
  398. if (mPinnedSection != null) {
  399.  
  400. // prepare variables
  401. int pLeft = getListPaddingLeft();
  402. int pTop = getListPaddingTop();
  403. View view = mPinnedSection.view;
  404.  
  405. // draw child
  406. canvas.save();
  407.  
  408. int clipHeight = view.getHeight() +
  409. (mShadowDrawable == null ? 0 : Math.min(mShadowHeight, mSectionsDistanceY));
  410. canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight);
  411.  
  412. canvas.translate(pLeft, pTop + mTranslateY);
  413. drawChild(canvas, mPinnedSection.view, getDrawingTime());
  414.  
  415. if (mShadowDrawable != null && mSectionsDistanceY > 0) {
  416. mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),
  417. mPinnedSection.view.getBottom(),
  418. mPinnedSection.view.getRight(),
  419. mPinnedSection.view.getBottom() + mShadowHeight);
  420. mShadowDrawable.draw(canvas);
  421. }
  422.  
  423. canvas.restore();
  424. }
  425. }
  426.  
  427. //-- touch handling methods
  428.  
  429. @Override
  430. public boolean dispatchTouchEvent(MotionEvent ev) {
  431.  
  432. final float x = ev.getX();
  433. final float y = ev.getY();
  434. final int action = ev.getAction();
  435.  
  436. if (action == MotionEvent.ACTION_DOWN
  437. && mTouchTarget == null
  438. && mPinnedSection != null
  439. && isPinnedViewTouched(mPinnedSection.view, x, y)) { // create touch target
  440.  
  441. // user touched pinned view
  442. mTouchTarget = mPinnedSection.view;
  443. mTouchPoint.x = x;
  444. mTouchPoint.y = y;
  445.  
  446. // copy down event for eventually be used later
  447. mDownEvent = MotionEvent.obtain(ev);
  448. }
  449.  
  450. if (mTouchTarget != null) {
  451. if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to pinned view
  452. mTouchTarget.dispatchTouchEvent(ev);
  453. }
  454.  
  455. if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned view
  456. super.dispatchTouchEvent(ev);
  457. performPinnedItemClick();
  458. clearTouchTarget();
  459.  
  460. } else if (action == MotionEvent.ACTION_CANCEL) { // cancel
  461. clearTouchTarget();
  462.  
  463. } else if (action == MotionEvent.ACTION_MOVE) {
  464. if (Math.abs(y - mTouchPoint.y) > mTouchSlop) {
  465.  
  466. // cancel sequence on touch target
  467. MotionEvent event = MotionEvent.obtain(ev);
  468. event.setAction(MotionEvent.ACTION_CANCEL);
  469. mTouchTarget.dispatchTouchEvent(event);
  470. event.recycle();
  471.  
  472. // provide correct sequence to super class for further handling
  473. super.dispatchTouchEvent(mDownEvent);
  474. super.dispatchTouchEvent(ev);
  475. clearTouchTarget();
  476.  
  477. }
  478. }
  479.  
  480. return true;
  481. }
  482.  
  483. // call super if this was not our pinned view
  484. return super.dispatchTouchEvent(ev);
  485. }
  486.  
  487. private boolean isPinnedViewTouched(View view, float x, float y) {
  488. view.getHitRect(mTouchRect);
  489.  
  490. // by taping top or bottom padding, the list performs on click on a border item.
  491. // we don't add top padding here to keep behavior consistent.
  492. mTouchRect.top += mTranslateY;
  493.  
  494. mTouchRect.bottom += mTranslateY + getPaddingTop();
  495. mTouchRect.left += getPaddingLeft();
  496. mTouchRect.right -= getPaddingRight();
  497. return mTouchRect.contains((int)x, (int)y);
  498. }
  499.  
  500. private void clearTouchTarget() {
  501. mTouchTarget = null;
  502. if (mDownEvent != null) {
  503. mDownEvent.recycle();
  504. mDownEvent = null;
  505. }
  506. }
  507.  
  508. private boolean performPinnedItemClick() {
  509. if (mPinnedSection == null) return false;
  510.  
  511. OnItemClickListener listener = getOnItemClickListener();
  512. if (listener != null && getAdapter().isEnabled(mPinnedSection.position)) {
  513. View view = mPinnedSection.view;
  514. playSoundEffect(SoundEffectConstants.CLICK);
  515. if (view != null) {
  516. view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  517. }
  518. listener.onItemClick(this, view, mPinnedSection.position, mPinnedSection.id);
  519. return true;
  520. }
  521. return false;
  522. }
  523.  
  524. public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {
  525. if (adapter instanceof HeaderViewListAdapter) {
  526. adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
  527. }
  528. return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType);
  529. }
  530.  
  531. }

PinnedSectionListView.java

通信录分组并且分组标签悬停划入划出(包含错误信息及修改)--第三方开源--PinnedSectionListView的更多相关文章

  1. jQuery鼠标划入划出

    今天来简单的谈谈jQuery的一个划入划出的方法,.首先划入划出能想到的东西有哪些呢,. 1:hover 2:mouseenter/mouseleave 3:mouseover/mouseout. 一 ...

  2. JS实现穿墙效果(判断鼠标划入划出的方向)

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  3. CSS动画划入划出酷炫

    HTML插入 <!DOCTYPE html> <html class="no-js iarouse"> <head> <meta char ...

  4. 非智能手机通信录备份并还原至Android智能手机方法

    随着智能手机早已深入普通用户的生活,2-3线城市的用户也逐渐从使用非智能机换成使用智能机.最近便遇见了这样一个转移通讯录的需求.之前使用的手机型号是BBK K201,通信录中绝大部分保存在了手机中,最 ...

  5. (五)backbone - DEMO - 通信录改造之使用requirejs

    DEMO介绍是 DEMO通信录的扩展,使用requirejs模块化整合 大体实现 • model文件 model/contact.js define(function (){ // user cont ...

  6. 管理Android通信录

    Android提供了Contacts应用程序来管理联系人,并且Android系统还为联系人管理提供了ContentProvider,这就同意其他应用程序以ContentResolver来管理联系人数据 ...

  7. 通信录列表+复杂Adapter分析

    概述 最近写论文之余玩起了github,发现有个citypicker挺不错的,高仿了美团城市选择和定位的一些功能 地址链接 效果图如下: 自己手动写了一遍优化了一些内容,学到了一些姿势,下面对其中一些 ...

  8. pandas学习(数据分组与分组运算、离散化处理、数据合并)

    pandas学习(数据分组与分组运算.离散化处理.数据合并) 目录 数据分组与分组运算 离散化处理 数据合并 数据分组与分组运算 GroupBy技术:实现数据的分组,和分组运算,作用类似于数据透视表 ...

  9. 今天研究了一下手机通信录管理系统(C语言)

    题目:手机通信录管理系统 一.题目要求 二.需求分析 三.设计步骤/编写代码 四.上机/运行结果 五.总结 一.题目要求 模拟手机通信录管理系统,实现对手机中的通信录进行管理操作.功能要求: (1)查 ...

随机推荐

  1. 1.4.2 solr字段类型--(1.4.2.1)字段类型定义和字段类型属性

    1.4.2 solr字段类型 (1.4.2.1) 字段类型定义和字段类型属性. (1.4.2.2) solr附带的字段类型 (1.4.2.3) 使用货币和汇率 (1.4.2.4) 使用Dates(日期 ...

  2. 自定义 404 与 500 错误页面,URL 地址不会重定向(二)

    上一篇是使用了全局过虑器来实现,还可以使用 HttpApplication 来处理. 参考文章: http://www.cnblogs.com/dudu/p/aspnet_custom_error.h ...

  3. Uva 10305 - Ordering Tasks 拓扑排序基础水题 队列和dfs实现

    今天刚学的拓扑排序,大概搞懂后发现这题是赤裸裸的水题. 于是按自己想法敲了一遍,用queue做的,也就是Kahn算法,复杂度o(V+E),调完交上去,WA了... 于是检查了一遍又交了一发,还是WA. ...

  4. XML基础概念

    XML基础概念 一.什么是XML. 可扩展标记语言(EXtensible Markup Language),标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言. 二.XML特点 1 ...

  5. find 忽略文件夹选项-prune的说明

    注意:因为习惯在当前路径查找时候,常忽略./ 的指定,但读者不要因此而完全忘记find的格式. 查找时忽略指定目录,是要使用-prune选项,但实际上最重要的还是要和path配合.-prune的意义是 ...

  6. 原生js显示分页效果

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  7. 剑指Offer28 最小的K个数(Partition函数应用+大顶堆)

    包含了Partition函数的多种用法 以及大顶堆操作 /*********************************************************************** ...

  8. Matlab的GUI参数传递方式总结

    MATLAB GUI传递方式 1.全局变量: 2.作为函数的参数传递: 3.利用控件的userdata数据: 4.为handles结构体添加新字段: 5.setappdata函数为句柄添加数据: 6. ...

  9. BZOJ 3725

    Description 有一堵长度为n的墙需要刷漆,你有一把长度为k的刷子.墙和刷子都被均匀划分成单位长度的小格,刷子的每一格中都沾有某种颜色(纯色)的漆.你需要用这把刷子在墙上每一个可能的位置(只要 ...

  10. Python(2.7.6) 异常类的继承关系

    BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration ...