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