Android横向ListView

深碍√TFBOYSˉ_ 2022-07-13 11:13 557阅读 0赞

在日常开发中经常会发现横向的ListView。下面讨论实现方案。
1.动态的添加布局。

  1. RelativeLayout view = (RelativeLayout) LayoutInflater.from(this)
  2. .inflate(R.layout.demo, null);
  3. ListView.addView(view);

2.通过继承AdapterView(ListAdapter)自定义类实现
部分关键代码如下:
类名:HorizontalListView(这个类不是我实现的,我只是拿来用)

这里写图片描述
这里写图片描述

布局代码

  1. <HorizontalListView
  2. android:id="@+id/listview"
  3. android:layout_width="wrap_content"
  4. android:layout_height="240dp"
  5. android:clipToPadding="true"
  6. android:paddingLeft="12dp"
  7. app:dividerWidth="35dp"
  8. />

继承自AdapterView(ListAdapter),用法和普通的ListView相似。

代码粘贴如下:

  1. package com.homelink.newlink.view;
  2. import android.annotation.SuppressLint;
  3. import android.annotation.TargetApi;
  4. import android.content.Context;
  5. import android.content.res.TypedArray;
  6. import android.database.DataSetObserver;
  7. import android.graphics.Canvas;
  8. import android.graphics.Rect;
  9. import android.graphics.drawable.Drawable;
  10. import android.os.Build;
  11. import android.os.Bundle;
  12. import android.os.Parcelable;
  13. import android.support.v4.view.ViewCompat;
  14. import android.support.v4.widget.EdgeEffectCompat;
  15. import android.util.AttributeSet;
  16. import android.view.GestureDetector;
  17. import android.view.HapticFeedbackConstants;
  18. import android.view.MotionEvent;
  19. import android.view.View;
  20. import android.view.ViewGroup;
  21. import android.widget.AdapterView;
  22. import android.widget.ListAdapter;
  23. import android.widget.ListView;
  24. import android.widget.ScrollView;
  25. import android.widget.Scroller;
  26. import com.homelink.newlink.R;
  27. import com.lianjia.common.utils.device.DensityUtil;
  28. import java.util.ArrayList;
  29. import java.util.LinkedList;
  30. import java.util.List;
  31. import java.util.Queue;
  32. /**
  33. * Created by jou on 2017/1/4.
  34. */
  35. public class HorizontalListView extends AdapterView<ListAdapter> {
  36. /**
  37. * Defines where to insert items into the ViewGroup, as defined in {@code ViewGroup
  38. * #addViewInLayout(View, int, LayoutParams, boolean)}
  39. */
  40. private static final int INSERT_AT_END_OF_LIST = -1;
  41. private static final int INSERT_AT_START_OF_LIST = 0;
  42. /** The velocity to use for overscroll absorption */
  43. private static final float FLING_DEFAULT_ABSORB_VELOCITY = 30f;
  44. /** The friction amount to use for the fling tracker */
  45. private static final float FLING_FRICTION = 0.009f;
  46. /**
  47. * Used for tracking the state data necessary to restore the HorizontalListView to its previous
  48. * state after a rotation occurs
  49. */
  50. private static final String BUNDLE_ID_CURRENT_X = "BUNDLE_ID_CURRENT_X";
  51. /**
  52. * The bundle id of the parents state. Used to restore the parent's state after a rotation
  53. * occurs
  54. */
  55. private static final String BUNDLE_ID_PARENT_STATE = "BUNDLE_ID_PARENT_STATE";
  56. /** Tracks ongoing flings */
  57. protected Scroller mFlingTracker = new Scroller(getContext());
  58. /** Gesture listener to receive callbacks when gestures are detected */
  59. private final GestureListener mGestureListener = new GestureListener();
  60. /** Used for detecting gestures within this view so they can be handled */
  61. private GestureDetector mGestureDetector;
  62. /** This tracks the starting layout position of the leftmost view */
  63. private int mDisplayOffset;
  64. /** Holds a reference to the adapter bound to this view */
  65. protected ListAdapter mAdapter;
  66. /** Holds a cache of recycled views to be reused as needed */
  67. private List<Queue<View>> mRemovedViewsCache = new ArrayList<Queue<View>>();
  68. /** Flag used to mark when the adapters data has changed, so the view can be relaid out */
  69. private boolean mDataChanged = false;
  70. /** Temporary rectangle to be used for measurements */
  71. private Rect mRect = new Rect();
  72. /** Tracks the currently touched view, used to delegate touches to the view being touched */
  73. private View mViewBeingTouched = null;
  74. /** The width of the divider that will be used between list items */
  75. private int mDividerWidth = 0;
  76. /** The drawable that will be used as the list divider */
  77. private Drawable mDivider = null;
  78. /** The x position of the currently rendered view */
  79. protected int mCurrentX;
  80. /** The x position of the next to be rendered view */
  81. protected int mNextX;
  82. /** Used to hold the scroll position to restore to post rotate */
  83. private Integer mRestoreX = null;
  84. /**
  85. * Tracks the maximum possible X position, stays at max value until last item is laid out and it
  86. * can be determined
  87. */
  88. private int mMaxX = Integer.MAX_VALUE;
  89. /** The adapter index of the leftmost view currently visible */
  90. private int mLeftViewAdapterIndex;
  91. /** The adapter index of the rightmost view currently visible */
  92. private int mRightViewAdapterIndex;
  93. /** This tracks the currently selected accessibility item */
  94. private int mCurrentlySelectedAdapterIndex;
  95. /**
  96. * Callback interface to notify listener that the user has scrolled this view to the point that
  97. * it is low on data.
  98. */
  99. private RunningOutOfDataListener mRunningOutOfDataListener = null;
  100. /**
  101. * This tracks the user value set of how many items from the end will be considered running out
  102. * of data.
  103. */
  104. private int mRunningOutOfDataThreshold = 0;
  105. /**
  106. * Tracks if we have told the listener that we are running low on data. We only want to tell
  107. * them once.
  108. */
  109. private boolean mHasNotifiedRunningLowOnData = false;
  110. /**
  111. * Callback interface to be invoked when the scroll state has changed.
  112. */
  113. private OnScrollStateChangedListener mOnScrollStateChangedListener = null;
  114. /**
  115. * Represents the current scroll state of this view. Needed so we can detect when the state
  116. * changes so scroll listener can be notified.
  117. */
  118. private OnScrollStateChangedListener.ScrollState mCurrentScrollState =
  119. OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE;
  120. /**
  121. * Tracks the state of the left edge glow.
  122. */
  123. private EdgeEffectCompat mEdgeGlowLeft;
  124. /**
  125. * Tracks the state of the right edge glow.
  126. */
  127. private EdgeEffectCompat mEdgeGlowRight;
  128. /** The height measure spec for this view, used to help size children views */
  129. private int mHeightMeasureSpec;
  130. /** Used to track if a view touch should be blocked because it stopped a fling */
  131. private boolean mBlockTouchAction = false;
  132. /**
  133. * Used to track if the parent vertically scrollable view has been told to
  134. * DisallowInterceptTouchEvent
  135. */
  136. private boolean mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent = false;
  137. /**
  138. * The listener that receives notifications when this view is clicked.
  139. */
  140. private OnClickListener mOnClickListener;
  141. /**
  142. * Recode the position of press and loose
  143. */
  144. private MotionEvent mPressEvent;
  145. private MotionEvent mLooseEvent;
  146. /**
  147. * MaoDian mode
  148. */
  149. private boolean mIsAnchorEnable;
  150. /**
  151. * Filing mode
  152. */
  153. private boolean mIsFilingEnable = true;
  154. public HorizontalListView(Context context, AttributeSet attrs) {
  155. super(context, attrs);
  156. mEdgeGlowLeft = new EdgeEffectCompat(context);
  157. mEdgeGlowRight = new EdgeEffectCompat(context);
  158. mGestureDetector = new GestureDetector(context, mGestureListener);
  159. bindGestureDetector();
  160. initView();
  161. retrieveXmlConfiguration(context, attrs);
  162. setWillNotDraw(false);
  163. // If the OS version is high enough then set the friction on the fling tracker */
  164. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  165. HoneycombPlus.setFriction(mFlingTracker, FLING_FRICTION);
  166. }
  167. }
  168. /** Registers the gesture detector to receive gesture notifications for this view */
  169. private void bindGestureDetector() {
  170. // Generic touch listener that can be applied to any view that needs to process gestures
  171. final OnTouchListener gestureListenerHandler = new OnTouchListener() {
  172. @Override public boolean onTouch(final View v, final MotionEvent event) {
  173. // Delegate the touch event to our gesture detector
  174. return mGestureDetector.onTouchEvent(event);
  175. }
  176. };
  177. setOnTouchListener(gestureListenerHandler);
  178. }
  179. /**
  180. * When this HorizontalListView is embedded within a vertical scrolling view it is important to
  181. * disable the parent view from interacting with
  182. * any touch events while the user is scrolling within this HorizontalListView. This will start
  183. * at this view and go up the view tree looking
  184. * for a vertical scrolling view. If one is found it will enable or disable parent touch
  185. * interception.
  186. *
  187. * @param disallowIntercept If true the parent will be prevented from intercepting child touch
  188. * events
  189. */
  190. private void requestParentListViewToNotInterceptTouchEvents(Boolean disallowIntercept) {
  191. // Prevent calling this more than once needlessly
  192. if (mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent != disallowIntercept) {
  193. View view = this;
  194. while (view.getParent() instanceof View) {
  195. // If the parent is a ListView or ScrollView then disallow intercepting of touch events
  196. if (view.getParent() instanceof ListView || view.getParent() instanceof ScrollView) {
  197. view.getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
  198. mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent = disallowIntercept;
  199. return;
  200. }
  201. view = (View) view.getParent();
  202. }
  203. }
  204. }
  205. /**
  206. * Parse the XML configuration for this widget
  207. *
  208. * @param context Context used for extracting attributes
  209. * @param attrs The Attribute Set containing the ColumnView attributes
  210. */
  211. private void retrieveXmlConfiguration(Context context, AttributeSet attrs) {
  212. if (attrs != null) {
  213. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorizontalListView);
  214. // Get the provided drawable from the XML
  215. final Drawable d = a.getDrawable(R.styleable.HorizontalListView_android_divider);
  216. if (d != null) {
  217. // If a drawable is provided to use as the divider then use its intrinsic width for the divider width
  218. setDivider(d);
  219. }
  220. // If a width is explicitly specified then use that width
  221. final int dividerWidth =
  222. a.getDimensionPixelSize(R.styleable.HorizontalListView_dividerWidth, 0);
  223. if (dividerWidth != 0) {
  224. setDividerWidth(dividerWidth);
  225. }
  226. a.recycle();
  227. }
  228. }
  229. @Override public Parcelable onSaveInstanceState() {
  230. Bundle bundle = new Bundle();
  231. // Add the parent state to the bundle
  232. bundle.putParcelable(BUNDLE_ID_PARENT_STATE, super.onSaveInstanceState());
  233. // Add our state to the bundle
  234. bundle.putInt(BUNDLE_ID_CURRENT_X, mCurrentX);
  235. return bundle;
  236. }
  237. @Override public void onRestoreInstanceState(Parcelable state) {
  238. if (state instanceof Bundle) {
  239. Bundle bundle = (Bundle) state;
  240. // Restore our state from the bundle
  241. mRestoreX = Integer.valueOf((bundle.getInt(BUNDLE_ID_CURRENT_X)));
  242. // Restore out parent's state from the bundle
  243. super.onRestoreInstanceState(bundle.getParcelable(BUNDLE_ID_PARENT_STATE));
  244. }
  245. }
  246. /**
  247. * Sets the drawable that will be drawn between each item in the list. If the drawable does
  248. * not have an intrinsic width, you should also call {@link #setDividerWidth(int)}
  249. *
  250. * @param divider The drawable to use.
  251. */
  252. public void setDivider(Drawable divider) {
  253. mDivider = divider;
  254. if (divider != null) {
  255. setDividerWidth(divider.getIntrinsicWidth());
  256. } else {
  257. setDividerWidth(0);
  258. }
  259. }
  260. /**
  261. * Sets the width of the divider that will be drawn between each item in the list. Calling
  262. * this will override the intrinsic width as set by {@link #setDivider(android.graphics.drawable.Drawable)}
  263. *
  264. * @param width The width of the divider in pixels.
  265. */
  266. public void setDividerWidth(int width) {
  267. mDividerWidth = width;
  268. // Force the view to rerender itself
  269. requestLayout();
  270. invalidate();
  271. }
  272. private void initView() {
  273. mLeftViewAdapterIndex = -1;
  274. mRightViewAdapterIndex = -1;
  275. mDisplayOffset = 0;
  276. mCurrentX = 0;
  277. mNextX = 0;
  278. mMaxX = Integer.MAX_VALUE;
  279. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
  280. }
  281. /**
  282. * Will re-initialize the HorizontalListView to remove all child views rendered and reset to
  283. * initial configuration.
  284. */
  285. private void reset() {
  286. initView();
  287. removeAllViewsInLayout();
  288. requestLayout();
  289. }
  290. /** DataSetObserver used to capture adapter data change events */
  291. private DataSetObserver mAdapterDataObserver = new DataSetObserver() {
  292. @Override public void onChanged() {
  293. mDataChanged = true;
  294. // Clear so we can notify again as we run out of data
  295. mHasNotifiedRunningLowOnData = false;
  296. unpressTouchedChild();
  297. // Invalidate and request layout to force this view to completely redraw itself
  298. invalidate();
  299. requestLayout();
  300. }
  301. @Override public void onInvalidated() {
  302. // Clear so we can notify again as we run out of data
  303. mHasNotifiedRunningLowOnData = false;
  304. unpressTouchedChild();
  305. reset();
  306. // Invalidate and request layout to force this view to completely redraw itself
  307. invalidate();
  308. requestLayout();
  309. }
  310. };
  311. @Override public void setSelection(int position) {
  312. mCurrentlySelectedAdapterIndex = position;
  313. }
  314. @Override public View getSelectedView() {
  315. return getChild(mCurrentlySelectedAdapterIndex);
  316. }
  317. @Override public void setAdapter(ListAdapter adapter) {
  318. if (mAdapter != null) {
  319. mAdapter.unregisterDataSetObserver(mAdapterDataObserver);
  320. }
  321. if (adapter != null) {
  322. // Clear so we can notify again as we run out of data
  323. mHasNotifiedRunningLowOnData = false;
  324. mAdapter = adapter;
  325. mAdapter.registerDataSetObserver(mAdapterDataObserver);
  326. }
  327. initializeRecycledViewCache(mAdapter.getViewTypeCount());
  328. reset();
  329. }
  330. @Override public ListAdapter getAdapter() {
  331. return mAdapter;
  332. }
  333. /**
  334. * Will create and initialize a cache for the given number of different types of views.
  335. *
  336. * @param viewTypeCount - The total number of different views supported
  337. */
  338. private void initializeRecycledViewCache(int viewTypeCount) {
  339. // The cache is created such that the response from mAdapter.getItemViewType is the array index to the correct cache for that item.
  340. mRemovedViewsCache.clear();
  341. for (int i = 0; i < viewTypeCount; i++) {
  342. mRemovedViewsCache.add(new LinkedList<View>());
  343. }
  344. }
  345. /**
  346. * Returns a recycled view from the cache that can be reused, or null if one is not available.
  347. */
  348. private View getRecycledView(int adapterIndex) {
  349. int itemViewType = mAdapter.getItemViewType(adapterIndex);
  350. if (isItemViewTypeValid(itemViewType)) {
  351. return mRemovedViewsCache.get(itemViewType).poll();
  352. }
  353. return null;
  354. }
  355. /**
  356. * Adds the provided view to a recycled views cache.
  357. */
  358. private void recycleView(int adapterIndex, View view) {
  359. // There is one Queue of views for each different type of view.
  360. // Just add the view to the pile of other views of the same type.
  361. // The order they are added and removed does not matter.
  362. int itemViewType = mAdapter.getItemViewType(adapterIndex);
  363. if (isItemViewTypeValid(itemViewType)) {
  364. mRemovedViewsCache.get(itemViewType).offer(view);
  365. }
  366. }
  367. private boolean isItemViewTypeValid(int itemViewType) {
  368. return itemViewType < mRemovedViewsCache.size();
  369. }
  370. /** Adds a child to this viewgroup and measures it so it renders the correct size */
  371. private void addAndMeasureChild(final View child, int viewPos) {
  372. LayoutParams params = getLayoutParams(child);
  373. addViewInLayout(child, viewPos, params, true);
  374. measureChild(child);
  375. }
  376. /**
  377. * Measure the provided child.
  378. *
  379. * @param child The child.
  380. */
  381. private void measureChild(View child) {
  382. LayoutParams childLayoutParams = getLayoutParams(child);
  383. int childHeightSpec =
  384. ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, getPaddingTop() + getPaddingBottom(),
  385. childLayoutParams.height);
  386. int childWidthSpec;
  387. if (childLayoutParams.width > 0) {
  388. childWidthSpec = MeasureSpec.makeMeasureSpec(childLayoutParams.width, MeasureSpec.EXACTLY);
  389. } else {
  390. childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
  391. }
  392. child.measure(childWidthSpec, childHeightSpec);
  393. }
  394. /** Gets a child's layout parameters, defaults if not available. */
  395. private LayoutParams getLayoutParams(View child) {
  396. LayoutParams layoutParams = child.getLayoutParams();
  397. if (layoutParams == null) {
  398. // Since this is a horizontal list view default to matching the parents height, and wrapping the width
  399. layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
  400. }
  401. return layoutParams;
  402. }
  403. @SuppressLint("WrongCall") @Override
  404. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  405. super.onLayout(changed, left, top, right, bottom);
  406. if (mAdapter == null) {
  407. return;
  408. }
  409. // Force the OS to redraw this view
  410. invalidate();
  411. // If the data changed then reset everything and render from scratch at the same offset as last time
  412. if (mDataChanged) {
  413. int oldCurrentX = mCurrentX;
  414. initView();
  415. removeAllViewsInLayout();
  416. mNextX = oldCurrentX;
  417. mDataChanged = false;
  418. }
  419. // If restoring from a rotation
  420. if (mRestoreX != null) {
  421. mNextX = mRestoreX;
  422. mRestoreX = null;
  423. }
  424. // If in a fling
  425. if (mFlingTracker.computeScrollOffset()) {
  426. // Compute the next position
  427. mNextX = mFlingTracker.getCurrX();
  428. }
  429. // Prevent scrolling past 0 so you can't scroll past the end of the list to the left
  430. if (mNextX < 0) {
  431. mNextX = 0;
  432. // Show an edge effect absorbing the current velocity
  433. if (mEdgeGlowLeft.isFinished()) {
  434. mEdgeGlowLeft.onAbsorb((int) determineFlingAbsorbVelocity());
  435. }
  436. mFlingTracker.forceFinished(true);
  437. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
  438. } else if (mNextX > mMaxX) {
  439. // Clip the maximum scroll position at mMaxX so you can't scroll past the end of the list to the right
  440. mNextX = mMaxX;
  441. // Show an edge effect absorbing the current velocity
  442. if (mEdgeGlowRight.isFinished()) {
  443. mEdgeGlowRight.onAbsorb((int) determineFlingAbsorbVelocity());
  444. }
  445. mFlingTracker.forceFinished(true);
  446. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
  447. }
  448. // Calculate our delta from the last time the view was drawn
  449. int dx = mCurrentX - mNextX;
  450. removeNonVisibleChildren(dx);
  451. fillList(dx);
  452. positionChildren(dx);
  453. // Since the view has now been drawn, update our current position
  454. mCurrentX = mNextX;
  455. // If we have scrolled enough to lay out all views, then determine the maximum scroll position now
  456. if (determineMaxX()) {
  457. // Redo the layout pass since we now know the maximum scroll position
  458. onLayout(changed, left, top, right, bottom);
  459. return;
  460. }
  461. // If the fling has finished
  462. if (mFlingTracker.isFinished()) {
  463. // If the fling just ended
  464. if (mCurrentScrollState == OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING) {
  465. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
  466. }
  467. } else {
  468. // Still in a fling so schedule the next frame
  469. ViewCompat.postOnAnimation(this, mDelayedLayout);
  470. }
  471. }
  472. @Override protected float getLeftFadingEdgeStrength() {
  473. int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
  474. // If completely at the edge then disable the fading edge
  475. if (mCurrentX == 0) {
  476. return 0;
  477. } else if (mCurrentX < horizontalFadingEdgeLength) {
  478. // We are very close to the edge, so enable the fading edge proportional to the distance from the edge, and the width of the edge effect
  479. return (float) mCurrentX / horizontalFadingEdgeLength;
  480. } else {
  481. // The current x position is more then the width of the fading edge so enable it fully.
  482. return 1;
  483. }
  484. }
  485. @Override protected float getRightFadingEdgeStrength() {
  486. int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
  487. // If completely at the edge then disable the fading edge
  488. if (mCurrentX == mMaxX) {
  489. return 0;
  490. } else if ((mMaxX - mCurrentX) < horizontalFadingEdgeLength) {
  491. // We are very close to the edge, so enable the fading edge proportional to the distance from the ednge, and the width of the edge effect
  492. return (float) (mMaxX - mCurrentX) / horizontalFadingEdgeLength;
  493. } else {
  494. // The distance from the maximum x position is more then the width of the fading edge so enable it fully.
  495. return 1;
  496. }
  497. }
  498. /** Determines the current fling absorb velocity */
  499. private float determineFlingAbsorbVelocity() {
  500. // If the OS version is high enough get the real velocity */
  501. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  502. return IceCreamSandwichPlus.getCurrVelocity(mFlingTracker);
  503. } else {
  504. // Unable to get the velocity so just return a default.
  505. // In actuality this is never used since EdgeEffectCompat does not draw anything unless the device is ICS+.
  506. // Less then ICS EdgeEffectCompat essentially performs a NOP.
  507. return FLING_DEFAULT_ABSORB_VELOCITY;
  508. }
  509. }
  510. /** Use to schedule a request layout via a runnable */
  511. private Runnable mDelayedLayout = new Runnable() {
  512. @Override public void run() {
  513. requestLayout();
  514. }
  515. };
  516. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  517. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  518. // Cache off the measure spec
  519. mHeightMeasureSpec = heightMeasureSpec;
  520. }
  521. /**
  522. * Determine the Max X position. This is the farthest that the user can scroll the screen. Until
  523. * the last adapter item has been
  524. * laid out it is impossible to calculate; once that has occurred this will perform the
  525. * calculation, and if necessary force a
  526. * redraw and relayout of this view.
  527. *
  528. * @return true if the maxx position was just determined
  529. */
  530. private boolean determineMaxX() {
  531. // If the last view has been laid out, then we can determine the maximum x position
  532. if (isLastItemInAdapter(mRightViewAdapterIndex)) {
  533. View rightView = getRightmostChild();
  534. if (rightView != null) {
  535. int oldMaxX = mMaxX;
  536. // Determine the maximum x position
  537. mMaxX = mCurrentX + (rightView.getRight() - getPaddingLeft()) - getRenderWidth();
  538. // Handle the case where the views do not fill at least 1 screen
  539. if (mMaxX < 0) {
  540. mMaxX = 0;
  541. }
  542. if (mMaxX != oldMaxX) {
  543. return true;
  544. }
  545. }
  546. }
  547. return false;
  548. }
  549. /** Adds children views to the left and right of the current views until the screen is full */
  550. private void fillList(final int dx) {
  551. // Get the rightmost child and determine its right edge
  552. int edge = 0;
  553. View child = getRightmostChild();
  554. if (child != null) {
  555. edge = child.getRight();
  556. }
  557. // Add new children views to the right, until past the edge of the screen
  558. fillListRight(edge, dx);
  559. // Get the leftmost child and determine its left edge
  560. edge = 0;
  561. child = getLeftmostChild();
  562. if (child != null) {
  563. edge = child.getLeft();
  564. }
  565. // Add new children views to the left, until past the edge of the screen
  566. fillListLeft(edge, dx);
  567. }
  568. private void removeNonVisibleChildren(final int dx) {
  569. View child = getLeftmostChild();
  570. // Loop removing the leftmost child, until that child is on the screen
  571. while (child != null && child.getRight() + dx <= 0) {
  572. // The child is being completely removed so remove its width from the display offset and its divider if it has one.
  573. // To remove add the size of the child and its divider (if it has one) to the offset.
  574. // You need to add since its being removed from the left side, i.e. shifting the offset to the right.
  575. mDisplayOffset += isLastItemInAdapter(mLeftViewAdapterIndex) ? child.getMeasuredWidth()
  576. : mDividerWidth + child.getMeasuredWidth();
  577. // Add the removed view to the cache
  578. recycleView(mLeftViewAdapterIndex, child);
  579. // Actually remove the view
  580. removeViewInLayout(child);
  581. // Keep track of the adapter index of the left most child
  582. mLeftViewAdapterIndex++;
  583. // Get the new leftmost child
  584. child = getLeftmostChild();
  585. }
  586. child = getRightmostChild();
  587. // Loop removing the rightmost child, until that child is on the screen
  588. while (child != null && child.getLeft() + dx >= getWidth()) {
  589. recycleView(mRightViewAdapterIndex, child);
  590. removeViewInLayout(child);
  591. mRightViewAdapterIndex--;
  592. child = getRightmostChild();
  593. }
  594. }
  595. private void fillListRight(int rightEdge, final int dx) {
  596. // Loop adding views to the right until the screen is filled
  597. while (rightEdge + dx + mDividerWidth < getWidth()
  598. && mRightViewAdapterIndex + 1 < mAdapter.getCount()) {
  599. mRightViewAdapterIndex++;
  600. // If mLeftViewAdapterIndex < 0 then this is the first time a view is being added, and left == right
  601. if (mLeftViewAdapterIndex < 0) {
  602. mLeftViewAdapterIndex = mRightViewAdapterIndex;
  603. }
  604. // Get the view from the adapter, utilizing a cached view if one is available
  605. View child =
  606. mAdapter.getView(mRightViewAdapterIndex, getRecycledView(mRightViewAdapterIndex), this);
  607. addAndMeasureChild(child, INSERT_AT_END_OF_LIST);
  608. // If first view, then no divider to the left of it, otherwise add the space for the divider width
  609. rightEdge += (mRightViewAdapterIndex == 0 ? 0 : mDividerWidth) + child.getMeasuredWidth();
  610. // Check if we are running low on data so we can tell listeners to go get more
  611. determineIfLowOnData();
  612. }
  613. }
  614. private void fillListLeft(int leftEdge, final int dx) {
  615. // Loop adding views to the left until the screen is filled
  616. while (leftEdge + dx - mDividerWidth > 0 && mLeftViewAdapterIndex >= 1) {
  617. mLeftViewAdapterIndex--;
  618. View child =
  619. mAdapter.getView(mLeftViewAdapterIndex, getRecycledView(mLeftViewAdapterIndex), this);
  620. addAndMeasureChild(child, INSERT_AT_START_OF_LIST);
  621. // If first view, then no divider to the left of it
  622. leftEdge -= mLeftViewAdapterIndex == 0 ? child.getMeasuredWidth()
  623. : mDividerWidth + child.getMeasuredWidth();
  624. // If on a clean edge then just remove the child, otherwise remove the divider as well
  625. mDisplayOffset -=
  626. leftEdge + dx == 0 ? child.getMeasuredWidth() : mDividerWidth + child.getMeasuredWidth();
  627. }
  628. }
  629. /** Loops through each child and positions them onto the screen */
  630. private void positionChildren(final int dx) {
  631. int childCount = getChildCount();
  632. if (childCount > 0) {
  633. mDisplayOffset += dx;
  634. int leftOffset = mDisplayOffset;
  635. // Loop each child view
  636. for (int i = 0; i < childCount; i++) {
  637. View child = getChildAt(i);
  638. int left = leftOffset + getPaddingLeft();
  639. int top = getPaddingTop();
  640. int right = left + child.getMeasuredWidth();
  641. int bottom = top + child.getMeasuredHeight();
  642. // Layout the child
  643. child.layout(left, top, right, bottom);
  644. // Increment our offset by added child's size and divider width
  645. leftOffset += child.getMeasuredWidth() + mDividerWidth;
  646. }
  647. }
  648. }
  649. /** Gets the current child that is leftmost on the screen. */
  650. private View getLeftmostChild() {
  651. return getChildAt(0);
  652. }
  653. /** Gets the current child that is rightmost on the screen. */
  654. private View getRightmostChild() {
  655. return getChildAt(getChildCount() - 1);
  656. }
  657. /**
  658. * Finds a child view that is contained within this view, given the adapter index.
  659. *
  660. * @return View The child view, or or null if not found.
  661. */
  662. private View getChild(int adapterIndex) {
  663. if (adapterIndex >= mLeftViewAdapterIndex && adapterIndex <= mRightViewAdapterIndex) {
  664. return getChildAt(adapterIndex - mLeftViewAdapterIndex);
  665. }
  666. return null;
  667. }
  668. /**
  669. * Returns the index of the child that contains the coordinates given.
  670. * This is useful to determine which child has been touched.
  671. * This can be used for a call to {@link #getChildAt(int)}
  672. *
  673. * @param x X-coordinate
  674. * @param y Y-coordinate
  675. * @return The index of the child that contains the coordinates. If no child is found then
  676. * returns -1
  677. */
  678. private int getChildIndex(final int x, final int y) {
  679. int childCount = getChildCount();
  680. for (int index = 0; index < childCount; index++) {
  681. getChildAt(index).getHitRect(mRect);
  682. if (mRect.contains(x, y)) {
  683. return index;
  684. }
  685. }
  686. return -1;
  687. }
  688. /** Simple convenience method for determining if this index is the last index in the adapter */
  689. private boolean isLastItemInAdapter(int index) {
  690. return index == mAdapter.getCount() - 1;
  691. }
  692. /** Gets the height in px this view will be rendered. (padding removed) */
  693. private int getRenderHeight() {
  694. return getHeight() - getPaddingTop() - getPaddingBottom();
  695. }
  696. /** Gets the width in px this view will be rendered. (padding removed) */
  697. private int getRenderWidth() {
  698. return getWidth() - getPaddingLeft() - getPaddingRight();
  699. }
  700. /** Scroll to the provided offset */
  701. public void scrollTo(int x) {
  702. mFlingTracker.startScroll(mNextX, 0, x - mNextX, 0);
  703. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING);
  704. requestLayout();
  705. }
  706. @Override public int getFirstVisiblePosition() {
  707. return mLeftViewAdapterIndex;
  708. }
  709. @Override public int getLastVisiblePosition() {
  710. return mRightViewAdapterIndex;
  711. }
  712. /** Draws the overscroll edge glow effect on the left and right sides of the horizontal list */
  713. private void drawEdgeGlow(Canvas canvas) {
  714. if (mEdgeGlowLeft != null && !mEdgeGlowLeft.isFinished() && isEdgeGlowEnabled()) {
  715. // The Edge glow is meant to come from the top of the screen, so rotate it to draw on the left side.
  716. final int restoreCount = canvas.save();
  717. final int height = getHeight();
  718. canvas.rotate(-90, 0, 0);
  719. canvas.translate(-height + getPaddingBottom(), 0);
  720. mEdgeGlowLeft.setSize(getRenderHeight(), getRenderWidth());
  721. if (mEdgeGlowLeft.draw(canvas)) {
  722. invalidate();
  723. }
  724. canvas.restoreToCount(restoreCount);
  725. } else if (mEdgeGlowRight != null && !mEdgeGlowRight.isFinished() && isEdgeGlowEnabled()) {
  726. // The Edge glow is meant to come from the top of the screen, so rotate it to draw on the right side.
  727. final int restoreCount = canvas.save();
  728. final int width = getWidth();
  729. canvas.rotate(90, 0, 0);
  730. canvas.translate(getPaddingTop(), -width);
  731. mEdgeGlowRight.setSize(getRenderHeight(), getRenderWidth());
  732. if (mEdgeGlowRight.draw(canvas)) {
  733. invalidate();
  734. }
  735. canvas.restoreToCount(restoreCount);
  736. }
  737. }
  738. /** Draws the dividers that go in between the horizontal list view items */
  739. private void drawDividers(Canvas canvas) {
  740. final int count = getChildCount();
  741. // Only modify the left and right in the loop, we set the top and bottom here since they are always the same
  742. final Rect bounds = mRect;
  743. mRect.top = getPaddingTop();
  744. mRect.bottom = mRect.top + getRenderHeight();
  745. // Draw the list dividers
  746. for (int i = 0; i < count; i++) {
  747. // Don't draw a divider to the right of the last item in the adapter
  748. if (!(i == count - 1 && isLastItemInAdapter(mRightViewAdapterIndex))) {
  749. View child = getChildAt(i);
  750. bounds.left = child.getRight();
  751. bounds.right = child.getRight() + mDividerWidth;
  752. // Clip at the left edge of the screen
  753. if (bounds.left < getPaddingLeft()) {
  754. bounds.left = getPaddingLeft();
  755. }
  756. // Clip at the right edge of the screen
  757. if (bounds.right > getWidth() - getPaddingRight()) {
  758. bounds.right = getWidth() - getPaddingRight();
  759. }
  760. // Draw a divider to the right of the child
  761. drawDivider(canvas, bounds);
  762. // If the first view, determine if a divider should be shown to the left of it.
  763. // A divider should be shown if the left side of this view does not fill to the left edge of the screen.
  764. if (i == 0 && child.getLeft() > getPaddingLeft()) {
  765. bounds.left = getPaddingLeft();
  766. bounds.right = child.getLeft();
  767. drawDivider(canvas, bounds);
  768. }
  769. }
  770. }
  771. }
  772. /**
  773. * Draws a divider in the given bounds.
  774. *
  775. * @param canvas The canvas to draw to.
  776. * @param bounds The bounds of the divider.
  777. */
  778. private void drawDivider(Canvas canvas, Rect bounds) {
  779. if (mDivider != null) {
  780. mDivider.setBounds(bounds);
  781. mDivider.draw(canvas);
  782. }
  783. }
  784. @Override protected void onDraw(Canvas canvas) {
  785. super.onDraw(canvas);
  786. drawDividers(canvas);
  787. }
  788. @Override protected void dispatchDraw(Canvas canvas) {
  789. super.dispatchDraw(canvas);
  790. drawEdgeGlow(canvas);
  791. }
  792. @Override protected void dispatchSetPressed(boolean pressed) {
  793. // Don't dispatch setPressed to our children. We call setPressed on ourselves to
  794. // get the selector in the right state, but we don't want to press each child.
  795. }
  796. protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
  797. if (mIsAnchorEnable && velocityX != 0) {
  798. int scrollDistance = getWidth() - DensityUtil.dip2px(getContext(),30);
  799. float distanceX = e1.getX() - e2.getX();
  800. if (distanceX > 0) {
  801. scrollTo((mLeftViewAdapterIndex + 1) * scrollDistance);
  802. } else if (distanceX < 0) {
  803. scrollTo((mRightViewAdapterIndex - 1) * scrollDistance);
  804. }
  805. } else {
  806. mFlingTracker.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0);
  807. }
  808. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING);
  809. requestLayout();
  810. return true;
  811. }
  812. protected boolean onDown(MotionEvent e) {
  813. // If the user just caught a fling, then disable all touch actions until they release their finger
  814. mBlockTouchAction = !mFlingTracker.isFinished();
  815. // Allow a finger down event to catch a fling
  816. mFlingTracker.forceFinished(true);
  817. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
  818. unpressTouchedChild();
  819. if (!mBlockTouchAction) {
  820. // Find the child that was pressed
  821. final int index = getChildIndex((int) e.getX(), (int) e.getY());
  822. if (index >= 0) {
  823. // Save off view being touched so it can later be released
  824. mViewBeingTouched = getChildAt(index);
  825. if (mViewBeingTouched != null) {
  826. // Set the view as pressed
  827. mViewBeingTouched.setPressed(true);
  828. refreshDrawableState();
  829. }
  830. }
  831. }
  832. return true;
  833. }
  834. /** If a view is currently pressed then unpress it */
  835. private void unpressTouchedChild() {
  836. if (mViewBeingTouched != null) {
  837. // Set the view as not pressed
  838. mViewBeingTouched.setPressed(false);
  839. refreshDrawableState();
  840. // Null out the view so we don't leak it
  841. mViewBeingTouched = null;
  842. }
  843. }
  844. private class GestureListener extends GestureDetector.SimpleOnGestureListener {
  845. @Override public boolean onDown(MotionEvent e) {
  846. return HorizontalListView.this.onDown(e);
  847. }
  848. @Override
  849. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
  850. if (mIsFilingEnable) {
  851. HorizontalListView.this.onFling(e1, e2, velocityX, velocityY);
  852. }
  853. return true;
  854. }
  855. @Override
  856. public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
  857. // Lock the user into interacting just with this view
  858. requestParentListViewToNotInterceptTouchEvents(true);
  859. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_TOUCH_SCROLL);
  860. unpressTouchedChild();
  861. mNextX += (int) distanceX;
  862. updateOverscrollAnimation(Math.round(distanceX));
  863. requestLayout();
  864. mPressEvent = e1;
  865. mLooseEvent = e2;
  866. return true;
  867. }
  868. @Override public boolean onSingleTapConfirmed(MotionEvent e) {
  869. unpressTouchedChild();
  870. OnItemClickListener onItemClickListener = getOnItemClickListener();
  871. final int index = getChildIndex((int) e.getX(), (int) e.getY());
  872. // If the tap is inside one of the child views, and we are not blocking touches
  873. if (index >= 0 && !mBlockTouchAction) {
  874. View child = getChildAt(index);
  875. int adapterIndex = mLeftViewAdapterIndex + index;
  876. if (onItemClickListener != null) {
  877. onItemClickListener.onItemClick(HorizontalListView.this, child, adapterIndex,
  878. mAdapter.getItemId(adapterIndex));
  879. return true;
  880. }
  881. }
  882. if (mOnClickListener != null && !mBlockTouchAction) {
  883. mOnClickListener.onClick(HorizontalListView.this);
  884. }
  885. return false;
  886. }
  887. @Override public void onLongPress(MotionEvent e) {
  888. unpressTouchedChild();
  889. final int index = getChildIndex((int) e.getX(), (int) e.getY());
  890. if (index >= 0 && !mBlockTouchAction) {
  891. View child = getChildAt(index);
  892. OnItemLongClickListener onItemLongClickListener = getOnItemLongClickListener();
  893. if (onItemLongClickListener != null) {
  894. int adapterIndex = mLeftViewAdapterIndex + index;
  895. boolean handled =
  896. onItemLongClickListener.onItemLongClick(HorizontalListView.this, child, adapterIndex,
  897. mAdapter.getItemId(adapterIndex));
  898. if (handled) {
  899. // BZZZTT!!1!
  900. performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
  901. }
  902. }
  903. }
  904. }
  905. }
  906. @Override public boolean onTouchEvent(MotionEvent event) {
  907. // Detect when the user lifts their finger off the screen after a touch
  908. int scrollThreshold = (getWidth() - DensityUtil.dip2px(getContext(),90)) / 2;
  909. int scrollDistance = getWidth() - DensityUtil.dip2px(getContext(),30);
  910. if (event.getAction() == MotionEvent.ACTION_UP) {
  911. // If not flinging then we are idle now. The user just finished a finger scroll.
  912. if (mFlingTracker == null || mFlingTracker.isFinished()) {
  913. setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
  914. }
  915. if (mIsAnchorEnable && null != mPressEvent && null != mLooseEvent) {
  916. float distanceX = mPressEvent.getX() - mLooseEvent.getX();
  917. if (distanceX >= 0) {
  918. if (distanceX <= scrollThreshold) {
  919. scrollTo(mLeftViewAdapterIndex * scrollDistance);
  920. } else {
  921. scrollTo((mLeftViewAdapterIndex + 1) * scrollDistance);
  922. }
  923. } else {
  924. if (distanceX >= -scrollThreshold) {
  925. scrollTo(mRightViewAdapterIndex * scrollDistance);
  926. } else {
  927. scrollTo(mLeftViewAdapterIndex * scrollDistance);
  928. }
  929. }
  930. }
  931. // Allow the user to interact with parent views
  932. requestParentListViewToNotInterceptTouchEvents(false);
  933. releaseEdgeGlow();
  934. } else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
  935. unpressTouchedChild();
  936. releaseEdgeGlow();
  937. // Allow the user to interact with parent views
  938. requestParentListViewToNotInterceptTouchEvents(false);
  939. }
  940. return super.onTouchEvent(event);
  941. }
  942. /** Release the EdgeGlow so it animates */
  943. private void releaseEdgeGlow() {
  944. if (mEdgeGlowLeft != null) {
  945. mEdgeGlowLeft.onRelease();
  946. }
  947. if (mEdgeGlowRight != null) {
  948. mEdgeGlowRight.onRelease();
  949. }
  950. }
  951. /**
  952. * Set MaoDian mode
  953. */
  954. public void enableAnchor(boolean isAnchorEnable) {
  955. mIsAnchorEnable = isAnchorEnable;
  956. }
  957. /**
  958. * Set Filing mode
  959. */
  960. public void enableFiling(boolean isFilingEnable) {
  961. mIsFilingEnable = isFilingEnable;
  962. }
  963. /**
  964. * Sets a listener to be called when the HorizontalListView has been scrolled to a point where
  965. * it is
  966. * running low on data. An example use case is wanting to auto download more data when the user
  967. * has scrolled to the point where only 10 items are left to be rendered off the right of the
  968. * screen. To get called back at that point just register with this function with a
  969. * numberOfItemsLeftConsideredLow value of 10. <br>
  970. * <br>
  971. * This will only be called once to notify that the HorizontalListView is running low on data.
  972. * Calling notifyDataSetChanged on the adapter will allow this to be called again once low on
  973. * data.
  974. *
  975. * @param listener The listener to be notified when the number of array adapters items left to
  976. * be shown is running low.
  977. * @param numberOfItemsLeftConsideredLow The number of array adapter items that have not yet
  978. * been displayed that is considered too low.
  979. */
  980. public void setRunningOutOfDataListener(RunningOutOfDataListener listener,
  981. int numberOfItemsLeftConsideredLow) {
  982. mRunningOutOfDataListener = listener;
  983. mRunningOutOfDataThreshold = numberOfItemsLeftConsideredLow;
  984. }
  985. /**
  986. * This listener is used to allow notification when the HorizontalListView is running low on
  987. * data to display.
  988. */
  989. public static interface RunningOutOfDataListener {
  990. /**
  991. * Called when the HorizontalListView is running out of data and has reached at least the
  992. * provided threshold.
  993. */
  994. void onRunningOutOfData();
  995. }
  996. /**
  997. * Determines if we are low on data and if so will call to notify the listener, if there is
  998. * one,
  999. * that we are running low on data.
  1000. */
  1001. private void determineIfLowOnData() {
  1002. // Check if the threshold has been reached and a listener is registered
  1003. if (mRunningOutOfDataListener != null && mAdapter != null &&
  1004. mAdapter.getCount() - (mRightViewAdapterIndex + 1) < mRunningOutOfDataThreshold) {
  1005. // Prevent notification more than once
  1006. if (!mHasNotifiedRunningLowOnData) {
  1007. mHasNotifiedRunningLowOnData = true;
  1008. mRunningOutOfDataListener.onRunningOutOfData();
  1009. }
  1010. }
  1011. }
  1012. /**
  1013. * Register a callback to be invoked when the HorizontalListView has been clicked.
  1014. *
  1015. * @param listener The callback that will be invoked.
  1016. */
  1017. @Override public void setOnClickListener(OnClickListener listener) {
  1018. mOnClickListener = listener;
  1019. }
  1020. /**
  1021. * Interface definition for a callback to be invoked when the view scroll state has changed.
  1022. */
  1023. public interface OnScrollStateChangedListener {
  1024. public enum ScrollState {
  1025. /**
  1026. * The view is not scrolling. Note navigating the list using the trackball counts as
  1027. * being
  1028. * in the idle state since these transitions are not animated.
  1029. */
  1030. SCROLL_STATE_IDLE,
  1031. /**
  1032. * The user is scrolling using touch, and their finger is still on the screen
  1033. */
  1034. SCROLL_STATE_TOUCH_SCROLL,
  1035. /**
  1036. * The user had previously been scrolling using touch and had performed a fling. The
  1037. * animation is now coasting to a stop
  1038. */
  1039. SCROLL_STATE_FLING
  1040. }
  1041. /**
  1042. * Callback method to be invoked when the scroll state changes.
  1043. *
  1044. * @param scrollState The current scroll state.
  1045. */
  1046. public void onScrollStateChanged(ScrollState scrollState);
  1047. }
  1048. /**
  1049. * Sets a listener to be invoked when the scroll state has changed.
  1050. *
  1051. * @param listener The listener to be invoked.
  1052. */
  1053. public void setOnScrollStateChangedListener(OnScrollStateChangedListener listener) {
  1054. mOnScrollStateChangedListener = listener;
  1055. }
  1056. /**
  1057. * Call to set the new scroll state.
  1058. * If it has changed and a listener is registered then it will be notified.
  1059. */
  1060. private void setCurrentScrollState(OnScrollStateChangedListener.ScrollState newScrollState) {
  1061. // If the state actually changed then notify listener if there is one
  1062. if (mCurrentScrollState != newScrollState && mOnScrollStateChangedListener != null) {
  1063. mOnScrollStateChangedListener.onScrollStateChanged(newScrollState);
  1064. }
  1065. mCurrentScrollState = newScrollState;
  1066. }
  1067. /**
  1068. * Updates the over scroll animation based on the scrolled offset.
  1069. *
  1070. * @param scrolledOffset The scroll offset
  1071. */
  1072. private void updateOverscrollAnimation(final int scrolledOffset) {
  1073. if (mEdgeGlowLeft == null || mEdgeGlowRight == null) {
  1074. return;
  1075. }
  1076. // Calculate where the next scroll position would be
  1077. int nextScrollPosition = mCurrentX + scrolledOffset;
  1078. // If not currently in a fling (Don't want to allow fling offset updates to cause over scroll animation)
  1079. if (mFlingTracker == null || mFlingTracker.isFinished()) {
  1080. // If currently scrolled off the left side of the list and the adapter is not empty
  1081. if (nextScrollPosition < 0) {
  1082. // Calculate the amount we have scrolled since last frame
  1083. int overscroll = Math.abs(scrolledOffset);
  1084. // Tell the edge glow to redraw itself at the new offset
  1085. mEdgeGlowLeft.onPull((float) overscroll / getRenderWidth());
  1086. // Cancel animating right glow
  1087. if (!mEdgeGlowRight.isFinished()) {
  1088. mEdgeGlowRight.onRelease();
  1089. }
  1090. } else if (nextScrollPosition > mMaxX) {
  1091. // Scrolled off the right of the list
  1092. // Calculate the amount we have scrolled since last frame
  1093. int overscroll = Math.abs(scrolledOffset);
  1094. // Tell the edge glow to redraw itself at the new offset
  1095. mEdgeGlowRight.onPull((float) overscroll / getRenderWidth());
  1096. // Cancel animating left glow
  1097. if (!mEdgeGlowLeft.isFinished()) {
  1098. mEdgeGlowLeft.onRelease();
  1099. }
  1100. }
  1101. }
  1102. }
  1103. /**
  1104. * Checks if the edge glow should be used enabled.
  1105. * The glow is not enabled unless there are more views than can fit on the screen at one time.
  1106. */
  1107. private boolean isEdgeGlowEnabled() {
  1108. if (mAdapter == null || mAdapter.isEmpty()) {
  1109. return false;
  1110. }
  1111. // If the maxx is more then zero then the user can scroll, so the edge effects should be shown
  1112. return mMaxX > 0;
  1113. }
  1114. @TargetApi(11)
  1115. /** Wrapper class to protect access to API version 11 and above features */ private static final class HoneycombPlus {
  1116. static {
  1117. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
  1118. throw new RuntimeException("Should not get to HoneycombPlus class unless sdk is >= 11!");
  1119. }
  1120. }
  1121. /** Sets the friction for the provided scroller */
  1122. public static void setFriction(Scroller scroller, float friction) {
  1123. if (scroller != null) {
  1124. scroller.setFriction(friction);
  1125. }
  1126. }
  1127. }
  1128. @TargetApi(14)
  1129. /** Wrapper class to protect access to API version 14 and above features */ private static final class IceCreamSandwichPlus {
  1130. static {
  1131. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  1132. throw new RuntimeException(
  1133. "Should not get to IceCreamSandwichPlus class unless sdk is >= 14!");
  1134. }
  1135. }
  1136. /** Gets the velocity for the provided scroller */
  1137. public static float getCurrVelocity(Scroller scroller) {
  1138. return scroller.getCurrVelocity();
  1139. }
  1140. }
  1141. }

发表评论

表情:
评论列表 (有 0 条评论,557人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android ListView

    ListView作为android的经常使用的控件,在使用时,经常会忘记一些使用要点,这里记录一下一些使用过程中的需求,以便后面使用查询: 设置 监听 使用