@phucprime/react-native-image-editor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +247 -0
  3. package/android/build.gradle +62 -0
  4. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  5. package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  6. package/android/gradlew +240 -0
  7. package/android/gradlew.bat +91 -0
  8. package/android/libs/photo-editor-android.jar +0 -0
  9. package/android/src/main/AndroidManifest.xml +5 -0
  10. package/android/src/main/java/com/ahmedadeltito/photoeditor/BaseActivity.java +300 -0
  11. package/android/src/main/java/com/ahmedadeltito/photoeditor/ColorPickerAdapter.java +129 -0
  12. package/android/src/main/java/com/ahmedadeltito/photoeditor/EmojiAdapter.java +91 -0
  13. package/android/src/main/java/com/ahmedadeltito/photoeditor/EmojiFragment.java +56 -0
  14. package/android/src/main/java/com/ahmedadeltito/photoeditor/GalleryUtils.java +73 -0
  15. package/android/src/main/java/com/ahmedadeltito/photoeditor/ImageAdapter.java +69 -0
  16. package/android/src/main/java/com/ahmedadeltito/photoeditor/ImageFragment.java +93 -0
  17. package/android/src/main/java/com/ahmedadeltito/photoeditor/MainActivity.java +29 -0
  18. package/android/src/main/java/com/ahmedadeltito/photoeditor/MediaActivity.java +95 -0
  19. package/android/src/main/java/com/ahmedadeltito/photoeditor/PhotoEditorActivity.java +1055 -0
  20. package/android/src/main/java/com/ahmedadeltito/photoeditor/TranslationService.java +31 -0
  21. package/android/src/main/java/com/ahmedadeltito/photoeditor/UtilFunctions.java +176 -0
  22. package/android/src/main/java/com/ahmedadeltito/photoeditor/widget/RealtimeBlurView.java +359 -0
  23. package/android/src/main/java/com/ahmedadeltito/photoeditor/widget/SlidingUpPanelLayout.java +1527 -0
  24. package/android/src/main/java/com/ahmedadeltito/photoeditor/widget/ViewDragHelper.java +1466 -0
  25. package/android/src/main/java/ui/photoeditor/RNPhotoEditorModule.java +108 -0
  26. package/android/src/main/java/ui/photoeditor/RNPhotoEditorPackage.java +29 -0
  27. package/android/src/main/res/drawable/above_shadow.xml +7 -0
  28. package/android/src/main/res/drawable/below_shadow.xml +7 -0
  29. package/android/src/main/res/drawable/fading_shadow.xml +7 -0
  30. package/android/src/main/res/drawable/rounded_border_text_view.xml +13 -0
  31. package/android/src/main/res/drawable-xhdpi/aa.png +0 -0
  32. package/android/src/main/res/drawable-xhdpi/bb.png +0 -0
  33. package/android/src/main/res/drawable-xhdpi/c.png +0 -0
  34. package/android/src/main/res/drawable-xhdpi/cc.png +0 -0
  35. package/android/src/main/res/drawable-xhdpi/d.png +0 -0
  36. package/android/src/main/res/drawable-xhdpi/dd.png +0 -0
  37. package/android/src/main/res/drawable-xhdpi/e.png +0 -0
  38. package/android/src/main/res/drawable-xhdpi/ee.png +0 -0
  39. package/android/src/main/res/drawable-xhdpi/i.png +0 -0
  40. package/android/src/main/res/drawable-xhdpi/j.png +0 -0
  41. package/android/src/main/res/drawable-xhdpi/k.png +0 -0
  42. package/android/src/main/res/drawable-xhdpi/l.png +0 -0
  43. package/android/src/main/res/drawable-xhdpi/m.png +0 -0
  44. package/android/src/main/res/drawable-xhdpi/n.png +0 -0
  45. package/android/src/main/res/drawable-xhdpi/o.png +0 -0
  46. package/android/src/main/res/drawable-xhdpi/p.png +0 -0
  47. package/android/src/main/res/drawable-xhdpi/q.png +0 -0
  48. package/android/src/main/res/drawable-xhdpi/r.png +0 -0
  49. package/android/src/main/res/drawable-xhdpi/s.png +0 -0
  50. package/android/src/main/res/drawable-xhdpi/t.png +0 -0
  51. package/android/src/main/res/drawable-xhdpi/u.png +0 -0
  52. package/android/src/main/res/drawable-xhdpi/v.png +0 -0
  53. package/android/src/main/res/drawable-xhdpi/w.png +0 -0
  54. package/android/src/main/res/drawable-xhdpi/x.png +0 -0
  55. package/android/src/main/res/drawable-xhdpi/y.png +0 -0
  56. package/android/src/main/res/drawable-xhdpi/z.png +0 -0
  57. package/android/src/main/res/layout/activity_main.xml +28 -0
  58. package/android/src/main/res/layout/activity_photo_editor.xml +316 -0
  59. package/android/src/main/res/layout/add_text_popup_window.xml +47 -0
  60. package/android/src/main/res/layout/color_picker_item_list.xml +15 -0
  61. package/android/src/main/res/layout/fragment_main_photo_edit_emoji.xml +11 -0
  62. package/android/src/main/res/layout/fragment_main_photo_edit_image.xml +10 -0
  63. package/android/src/main/res/layout/fragment_photo_edit_emoji_item_list.xml +17 -0
  64. package/android/src/main/res/layout/fragment_photo_edit_image_item_list.xml +16 -0
  65. package/android/src/main/res/raw/emojioneandroid +0 -0
  66. package/android/src/main/res/raw/eventtusicons +0 -0
  67. package/android/src/main/res/raw/font_awesome_solid.otf +0 -0
  68. package/android/src/main/res/raw/icomoon.ttf +0 -0
  69. package/android/src/main/res/values/arrays.xml +447 -0
  70. package/android/src/main/res/values/attrs.xml +30 -0
  71. package/android/src/main/res/values/colors.xml +21 -0
  72. package/android/src/main/res/values/dimens.xml +4 -0
  73. package/android/src/main/res/values/strings.xml +33 -0
  74. package/android/src/main/res/values/styles.xml +27 -0
  75. package/ios/RNImageEditor.podspec +26 -0
  76. package/ios/RNPhotoEditor.h +13 -0
  77. package/ios/RNPhotoEditor.m +163 -0
  78. package/ios/RNPhotoEditor.xcodeproj/project.pbxproj +362 -0
  79. package/lib/index.d.ts +125 -0
  80. package/lib/index.d.ts.map +1 -0
  81. package/lib/index.js +114 -0
  82. package/lib/index.js.map +1 -0
  83. package/package.json +73 -0
  84. package/react-native-image-editor.podspec +39 -0
  85. package/src/index.ts +232 -0
@@ -0,0 +1,1527 @@
1
+ package com.ahmedadeltito.photoeditor.widget;
2
+
3
+ import android.annotation.SuppressLint;
4
+ import android.content.Context;
5
+ import android.content.res.TypedArray;
6
+ import android.graphics.Canvas;
7
+ import android.graphics.Paint;
8
+ import android.graphics.PixelFormat;
9
+ import android.graphics.Rect;
10
+ import android.graphics.drawable.Drawable;
11
+ import android.os.Build;
12
+ import android.os.Parcel;
13
+ import android.os.Parcelable;
14
+ import androidx.annotation.NonNull;
15
+ import androidx.core.view.MotionEventCompat;
16
+ import androidx.core.view.ViewCompat;
17
+ import androidx.recyclerview.widget.RecyclerView;
18
+ import android.util.AttributeSet;
19
+ import android.view.Gravity;
20
+ import android.view.MotionEvent;
21
+ import android.view.View;
22
+ import android.view.ViewGroup;
23
+ import android.view.accessibility.AccessibilityEvent;
24
+ import android.widget.ListView;
25
+ import android.widget.ScrollView;
26
+
27
+ import ui.photoeditor.R;
28
+ import com.nineoldandroids.view.animation.AnimatorProxy;
29
+
30
+ public class SlidingUpPanelLayout extends ViewGroup {
31
+
32
+ private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
33
+
34
+ /**
35
+ * Default peeking out panel height
36
+ */
37
+ private static final int DEFAULT_PANEL_HEIGHT = 0; // dp;
38
+
39
+ /**
40
+ * Default anchor point height
41
+ */
42
+ private static final float DEFAULT_ANCHOR_POINT = 1.0f; // In relative %
43
+
44
+ /**
45
+ * Default initial state for the component
46
+ */
47
+ private static PanelState DEFAULT_SLIDE_STATE = PanelState.COLLAPSED;
48
+
49
+ /**
50
+ * Default height of the shadow above the peeking out panel
51
+ */
52
+ private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
53
+
54
+ /**
55
+ * If no fade color is given by default it will fade to 80% gray.
56
+ */
57
+ private static final int DEFAULT_FADE_COLOR = 0x99000000;
58
+
59
+ /**
60
+ * Default Minimum velocity that will be detected as a fling
61
+ */
62
+ private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second
63
+ /**
64
+ * Default is set to false because that is how it was written
65
+ */
66
+ private static final boolean DEFAULT_OVERLAY_FLAG = false;
67
+ /**
68
+ * Default is set to true for clip panel for performance reasons
69
+ */
70
+ private static final boolean DEFAULT_CLIP_PANEL_FLAG = true;
71
+ /**
72
+ * Default attributes for layout
73
+ */
74
+ private static final int[] DEFAULT_ATTRS = new int[]{
75
+ android.R.attr.gravity
76
+ };
77
+
78
+ /**
79
+ * Minimum velocity that will be detected as a fling
80
+ */
81
+ private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
82
+
83
+ /**
84
+ * The fade color used for the panel covered by the slider. 0 = no fading.
85
+ */
86
+ private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
87
+
88
+ /**
89
+ * Default parallax length of the main view
90
+ */
91
+ private static final int DEFAULT_PARALLAX_OFFSET = 0;
92
+
93
+ /**
94
+ * The paint used to dim the main layout when sliding
95
+ */
96
+ private final Paint mCoveredFadePaint = new Paint();
97
+
98
+ /**
99
+ * Drawable used to draw the shadow between panes.
100
+ */
101
+ private final Drawable mShadowDrawable;
102
+
103
+ /**
104
+ * The size of the overhang in pixels.
105
+ */
106
+ private int mPanelHeight = -1;
107
+
108
+ /**
109
+ * The size of the shadow in pixels.
110
+ */
111
+ private int mShadowHeight = -1;
112
+
113
+ /**
114
+ * Parallax offset
115
+ */
116
+ private int mParallaxOffset = -1;
117
+
118
+ /**
119
+ * True if the collapsed panel should be dragged up.
120
+ */
121
+ private boolean mIsSlidingUp;
122
+
123
+ /**
124
+ * Panel overlays the windows instead of putting it underneath it.
125
+ */
126
+ private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;
127
+
128
+ /**
129
+ * The main view is clipped to the main top border
130
+ */
131
+ private boolean mClipPanel = DEFAULT_CLIP_PANEL_FLAG;
132
+
133
+ /**
134
+ * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
135
+ * used for dragging.
136
+ */
137
+ private View mDragView;
138
+
139
+ /**
140
+ * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
141
+ * used for dragging.
142
+ */
143
+ private int mDragViewResId = -1;
144
+
145
+ /**
146
+ * If provided, the panel will transfer the scroll from this view to itself when needed.
147
+ */
148
+ private View mScrollableView;
149
+ private int mScrollableViewResId;
150
+
151
+ /**
152
+ * The child view that can slide, if any.
153
+ */
154
+ private View mSlideableView;
155
+
156
+ /**
157
+ * The main view
158
+ */
159
+ private View mMainView;
160
+
161
+ /**
162
+ * Current state of the slideable view.
163
+ */
164
+ public enum PanelState {
165
+ EXPANDED,
166
+ COLLAPSED,
167
+ ANCHORED,
168
+ HIDDEN,
169
+ DRAGGING
170
+ }
171
+
172
+ private PanelState mSlideState = DEFAULT_SLIDE_STATE;
173
+
174
+ /**
175
+ * If the current slide state is DRAGGING, this will store the last non dragging state
176
+ */
177
+ private PanelState mLastNotDraggingSlideState = null;
178
+
179
+ /**
180
+ * How far the panel is offset from its expanded position.
181
+ * range [0, 1] where 0 = collapsed, 1 = expanded.
182
+ */
183
+ private float mSlideOffset;
184
+
185
+ /**
186
+ * How far in pixels the slideable panel may move.
187
+ */
188
+ private int mSlideRange;
189
+
190
+ /**
191
+ * An anchor point where the panel can stop during sliding
192
+ */
193
+ private float mAnchorPoint = 1.f;
194
+
195
+ /**
196
+ * A panel view is locked into internal scrolling or another condition that
197
+ * is preventing a drag.
198
+ */
199
+ private boolean mIsUnableToDrag;
200
+
201
+ /**
202
+ * Flag indicating that sliding feature is enabled\disabled
203
+ */
204
+ private boolean mIsTouchEnabled;
205
+
206
+ private float mPrevMotionY;
207
+ private float mInitialMotionX;
208
+ private float mInitialMotionY;
209
+ private boolean mIsScrollableViewHandlingTouch = false;
210
+
211
+ private PanelSlideListener mPanelSlideListener;
212
+
213
+ private final ViewDragHelper mDragHelper;
214
+
215
+ /**
216
+ * Stores whether or not the pane was expanded the last time it was slideable.
217
+ * If expand/collapse operations are invoked this state is modified. Used by
218
+ * instance state save/restore.
219
+ */
220
+ private boolean mFirstLayout = true;
221
+
222
+ private final Rect mTmpRect = new Rect();
223
+
224
+ /**
225
+ * Listener for monitoring events about sliding panes.
226
+ */
227
+ public interface PanelSlideListener {
228
+ /**
229
+ * Called when a sliding pane's position changes.
230
+ *
231
+ * @param panel The child view that was moved
232
+ * @param slideOffset The new offset of this sliding pane within its range, from 0-1
233
+ */
234
+ public void onPanelSlide(View panel, float slideOffset);
235
+
236
+ /**
237
+ * Called when a sliding panel becomes slid completely collapsed.
238
+ *
239
+ * @param panel The child view that was slid to an collapsed position
240
+ */
241
+ public void onPanelCollapsed(View panel);
242
+
243
+ /**
244
+ * Called when a sliding panel becomes slid completely expanded.
245
+ *
246
+ * @param panel The child view that was slid to a expanded position
247
+ */
248
+ public void onPanelExpanded(View panel);
249
+
250
+ /**
251
+ * Called when a sliding panel becomes anchored.
252
+ *
253
+ * @param panel The child view that was slid to a anchored position
254
+ */
255
+ public void onPanelAnchored(View panel);
256
+
257
+ /**
258
+ * Called when a sliding panel becomes completely hidden.
259
+ *
260
+ * @param panel The child view that was slid to a hidden position
261
+ */
262
+ public void onPanelHidden(View panel);
263
+ }
264
+
265
+ /**
266
+ * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
267
+ * of the listener methods you can extend this instead of implement the full interface.
268
+ */
269
+ public static class SimplePanelSlideListener implements PanelSlideListener {
270
+ @Override
271
+ public void onPanelSlide(View panel, float slideOffset) {
272
+ }
273
+
274
+ @Override
275
+ public void onPanelCollapsed(View panel) {
276
+ }
277
+
278
+ @Override
279
+ public void onPanelExpanded(View panel) {
280
+ }
281
+
282
+ @Override
283
+ public void onPanelAnchored(View panel) {
284
+ }
285
+
286
+ @Override
287
+ public void onPanelHidden(View panel) {
288
+ }
289
+ }
290
+
291
+ public SlidingUpPanelLayout(Context context) {
292
+ this(context, null);
293
+ }
294
+
295
+ public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
296
+ this(context, attrs, 0);
297
+ }
298
+
299
+ public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
300
+ super(context, attrs, defStyle);
301
+
302
+ if (isInEditMode()) {
303
+ mShadowDrawable = null;
304
+ mDragHelper = null;
305
+ return;
306
+ }
307
+
308
+ if (attrs != null) {
309
+ TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);
310
+
311
+ if (defAttrs != null) {
312
+ int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY);
313
+ setGravity(gravity);
314
+ }
315
+
316
+ defAttrs.recycle();
317
+
318
+ TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);
319
+
320
+ if (ta != null) {
321
+ mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoPanelHeight, -1);
322
+ mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoShadowHeight, -1);
323
+ mParallaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoParallaxOffset, -1);
324
+
325
+ mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_umanoFlingVelocity, DEFAULT_MIN_FLING_VELOCITY);
326
+ mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_umanoFadeColor, DEFAULT_FADE_COLOR);
327
+
328
+ mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoDragView, -1);
329
+ mScrollableViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoScrollableView, -1);
330
+
331
+ mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoOverlay, DEFAULT_OVERLAY_FLAG);
332
+ mClipPanel = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoClipPanel, DEFAULT_CLIP_PANEL_FLAG);
333
+
334
+ mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_umanoAnchorPoint, DEFAULT_ANCHOR_POINT);
335
+
336
+ mSlideState = PanelState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_umanoInitialState, DEFAULT_SLIDE_STATE.ordinal())];
337
+ }
338
+
339
+ ta.recycle();
340
+ }
341
+
342
+ final float density = context.getResources().getDisplayMetrics().density;
343
+ if (mPanelHeight == -1) {
344
+ mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
345
+ }
346
+ if (mShadowHeight == -1) {
347
+ mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
348
+ }
349
+ if (mParallaxOffset == -1) {
350
+ mParallaxOffset = (int) (DEFAULT_PARALLAX_OFFSET * density);
351
+ }
352
+ // If the shadow height is zero, don't show the shadow
353
+ if (mShadowHeight > 0) {
354
+ if (mIsSlidingUp) {
355
+ mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow);
356
+ } else {
357
+ mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow);
358
+ }
359
+ } else {
360
+ mShadowDrawable = null;
361
+ }
362
+
363
+ setWillNotDraw(false);
364
+
365
+ mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
366
+ mDragHelper.setMinVelocity(mMinFlingVelocity * density);
367
+
368
+ mIsTouchEnabled = true;
369
+ }
370
+
371
+ /**
372
+ * Set the Drag View after the view is inflated
373
+ */
374
+ @Override
375
+ protected void onFinishInflate() {
376
+ super.onFinishInflate();
377
+ if (mDragViewResId != -1) {
378
+ setDragView(findViewById(mDragViewResId));
379
+ }
380
+ if (mScrollableViewResId != -1) {
381
+ setScrollableView(findViewById(mScrollableViewResId));
382
+ }
383
+ }
384
+
385
+ public void setGravity(int gravity) {
386
+ if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) {
387
+ throw new IllegalArgumentException("gravity must be set to either top or bottom");
388
+ }
389
+ mIsSlidingUp = gravity == Gravity.BOTTOM;
390
+ if (!mFirstLayout) {
391
+ requestLayout();
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Set the color used to fade the pane covered by the sliding pane out when the pane
397
+ * will become fully covered in the expanded state.
398
+ *
399
+ * @param color An ARGB-packed color value
400
+ */
401
+ public void setCoveredFadeColor(int color) {
402
+ mCoveredFadeColor = color;
403
+ invalidate();
404
+ }
405
+
406
+ /**
407
+ * @return The ARGB-packed color value used to fade the fixed pane
408
+ */
409
+ public int getCoveredFadeColor() {
410
+ return mCoveredFadeColor;
411
+ }
412
+
413
+ /**
414
+ * Set sliding enabled flag
415
+ *
416
+ * @param enabled flag value
417
+ */
418
+ public void setTouchEnabled(boolean enabled) {
419
+ mIsTouchEnabled = enabled;
420
+ }
421
+
422
+ public boolean isTouchEnabled() {
423
+ return mIsTouchEnabled && mSlideableView != null && mSlideState != PanelState.HIDDEN;
424
+ }
425
+
426
+ /**
427
+ * Set the collapsed panel height in pixels
428
+ *
429
+ * @param val A height in pixels
430
+ */
431
+ public void setPanelHeight(int val) {
432
+ if (getPanelHeight() == val) {
433
+ return;
434
+ }
435
+
436
+ mPanelHeight = val;
437
+ if (!mFirstLayout) {
438
+ requestLayout();
439
+ }
440
+
441
+ if (getPanelState() == PanelState.COLLAPSED) {
442
+ smoothToBottom();
443
+ invalidate();
444
+ return;
445
+ }
446
+ }
447
+
448
+ protected void smoothToBottom() {
449
+ smoothSlideTo(0, 0);
450
+ }
451
+
452
+ /**
453
+ * @return The current shadow height
454
+ */
455
+ public int getShadowHeight() {
456
+ return mShadowHeight;
457
+ }
458
+
459
+ /**
460
+ * Set the shadow height
461
+ *
462
+ * @param val A height in pixels
463
+ */
464
+ public void setShadowHeight(int val) {
465
+ mShadowHeight = val;
466
+ if (!mFirstLayout) {
467
+ invalidate();
468
+ }
469
+ }
470
+
471
+ /**
472
+ * @return The current collapsed panel height
473
+ */
474
+ public int getPanelHeight() {
475
+ return mPanelHeight;
476
+ }
477
+
478
+ /**
479
+ * @return The current parallax offset
480
+ */
481
+ public int getCurrentParallaxOffset() {
482
+ // Clamp slide offset at zero for parallax computation;
483
+ int offset = (int) (mParallaxOffset * Math.max(mSlideOffset, 0));
484
+ return mIsSlidingUp ? -offset : offset;
485
+ }
486
+
487
+ /**
488
+ * Set parallax offset for the panel
489
+ *
490
+ * @param val A height in pixels
491
+ */
492
+ public void setParallaxOffset(int val) {
493
+ mParallaxOffset = val;
494
+ if (!mFirstLayout) {
495
+ requestLayout();
496
+ }
497
+ }
498
+
499
+ /**
500
+ * @return The current minimin fling velocity
501
+ */
502
+ public int getMinFlingVelocity() {
503
+ return mMinFlingVelocity;
504
+ }
505
+
506
+ /**
507
+ * Sets the minimum fling velocity for the panel
508
+ *
509
+ * @param val the new value
510
+ */
511
+ public void setMinFlingVelocity(int val) {
512
+ mMinFlingVelocity = val;
513
+ }
514
+
515
+ /**
516
+ * Sets the panel slide listener
517
+ *
518
+ * @param listener
519
+ */
520
+ public void setPanelSlideListener(PanelSlideListener listener) {
521
+ mPanelSlideListener = listener;
522
+ }
523
+
524
+ /**
525
+ * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
526
+ *
527
+ * @param dragView A view that will be used to drag the panel.
528
+ */
529
+ public void setDragView(View dragView) {
530
+ if (mDragView != null) {
531
+ mDragView.setOnClickListener(null);
532
+ }
533
+ mDragView = dragView;
534
+ if (mDragView != null) {
535
+ mDragView.setClickable(true);
536
+ mDragView.setFocusable(false);
537
+ mDragView.setFocusableInTouchMode(false);
538
+ mDragView.setOnClickListener(new OnClickListener() {
539
+ @Override
540
+ public void onClick(View v) {
541
+ if (!isEnabled() || !isTouchEnabled()) return;
542
+ if (mSlideState != PanelState.EXPANDED && mSlideState != PanelState.ANCHORED) {
543
+ if (mAnchorPoint < 1.0f) {
544
+ setPanelState(PanelState.ANCHORED);
545
+ } else {
546
+ setPanelState(PanelState.EXPANDED);
547
+ }
548
+ } else {
549
+ setPanelState(PanelState.COLLAPSED);
550
+ }
551
+ }
552
+ });
553
+ ;
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
559
+ *
560
+ * @param dragViewResId The resource ID of the new drag view
561
+ */
562
+ public void setDragView(int dragViewResId) {
563
+ mDragViewResId = dragViewResId;
564
+ setDragView(findViewById(dragViewResId));
565
+ }
566
+
567
+ /**
568
+ * Set the scrollable child of the sliding layout. If set, scrolling will be transfered between
569
+ * the panel and the view when necessary
570
+ *
571
+ * @param scrollableView The scrollable view
572
+ */
573
+ public void setScrollableView(View scrollableView) {
574
+ mScrollableView = scrollableView;
575
+ }
576
+
577
+ /**
578
+ * Set an anchor point where the panel can stop during sliding
579
+ *
580
+ * @param anchorPoint A value between 0 and 1, determining the position of the anchor point
581
+ * starting from the top of the layout.
582
+ */
583
+ public void setAnchorPoint(float anchorPoint) {
584
+ if (anchorPoint > 0 && anchorPoint <= 1) {
585
+ mAnchorPoint = anchorPoint;
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Gets the currently set anchor point
591
+ *
592
+ * @return the currently set anchor point
593
+ */
594
+ public float getAnchorPoint() {
595
+ return mAnchorPoint;
596
+ }
597
+
598
+ /**
599
+ * Sets whether or not the panel overlays the content
600
+ *
601
+ * @param overlayed
602
+ */
603
+ public void setOverlayed(boolean overlayed) {
604
+ mOverlayContent = overlayed;
605
+ }
606
+
607
+ /**
608
+ * Check if the panel is set as an overlay.
609
+ */
610
+ public boolean isOverlayed() {
611
+ return mOverlayContent;
612
+ }
613
+
614
+ /**
615
+ * Sets whether or not the main content is clipped to the top of the panel
616
+ *
617
+ * @param clip
618
+ */
619
+ public void setClipPanel(boolean clip) {
620
+ mClipPanel = clip;
621
+ }
622
+
623
+ /**
624
+ * Check whether or not the main content is clipped to the top of the panel
625
+ */
626
+ public boolean isClipPanel() {
627
+ return mClipPanel;
628
+ }
629
+
630
+ void dispatchOnPanelSlide(View panel) {
631
+ if (mPanelSlideListener != null) {
632
+ mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
633
+ }
634
+ }
635
+
636
+ void dispatchOnPanelExpanded(View panel) {
637
+ if (mPanelSlideListener != null) {
638
+ mPanelSlideListener.onPanelExpanded(panel);
639
+ }
640
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
641
+ }
642
+
643
+ void dispatchOnPanelCollapsed(View panel) {
644
+ if (mPanelSlideListener != null) {
645
+ mPanelSlideListener.onPanelCollapsed(panel);
646
+ }
647
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
648
+ }
649
+
650
+ void dispatchOnPanelAnchored(View panel) {
651
+ if (mPanelSlideListener != null) {
652
+ mPanelSlideListener.onPanelAnchored(panel);
653
+ }
654
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
655
+ }
656
+
657
+ void dispatchOnPanelHidden(View panel) {
658
+ if (mPanelSlideListener != null) {
659
+ mPanelSlideListener.onPanelHidden(panel);
660
+ }
661
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
662
+ }
663
+
664
+ void updateObscuredViewVisibility() {
665
+ if (getChildCount() == 0) {
666
+ return;
667
+ }
668
+ final int leftBound = getPaddingLeft();
669
+ final int rightBound = getWidth() - getPaddingRight();
670
+ final int topBound = getPaddingTop();
671
+ final int bottomBound = getHeight() - getPaddingBottom();
672
+ final int left;
673
+ final int right;
674
+ final int top;
675
+ final int bottom;
676
+ if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
677
+ left = mSlideableView.getLeft();
678
+ right = mSlideableView.getRight();
679
+ top = mSlideableView.getTop();
680
+ bottom = mSlideableView.getBottom();
681
+ } else {
682
+ left = right = top = bottom = 0;
683
+ }
684
+ View child = getChildAt(0);
685
+ final int clampedChildLeft = Math.max(leftBound, child.getLeft());
686
+ final int clampedChildTop = Math.max(topBound, child.getTop());
687
+ final int clampedChildRight = Math.min(rightBound, child.getRight());
688
+ final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
689
+ final int vis;
690
+ if (clampedChildLeft >= left && clampedChildTop >= top &&
691
+ clampedChildRight <= right && clampedChildBottom <= bottom) {
692
+ vis = INVISIBLE;
693
+ } else {
694
+ vis = VISIBLE;
695
+ }
696
+ child.setVisibility(vis);
697
+ }
698
+
699
+ void setAllChildrenVisible() {
700
+ for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
701
+ final View child = getChildAt(i);
702
+ if (child.getVisibility() == INVISIBLE) {
703
+ child.setVisibility(VISIBLE);
704
+ }
705
+ }
706
+ }
707
+
708
+ private static boolean hasOpaqueBackground(View v) {
709
+ final Drawable bg = v.getBackground();
710
+ return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
711
+ }
712
+
713
+ @Override
714
+ protected void onAttachedToWindow() {
715
+ super.onAttachedToWindow();
716
+ mFirstLayout = true;
717
+ }
718
+
719
+ @Override
720
+ protected void onDetachedFromWindow() {
721
+ super.onDetachedFromWindow();
722
+ mFirstLayout = true;
723
+ }
724
+
725
+ @Override
726
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
727
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
728
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
729
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
730
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
731
+
732
+ if (widthMode != MeasureSpec.EXACTLY) {
733
+ throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
734
+ } else if (heightMode != MeasureSpec.EXACTLY) {
735
+ throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
736
+ }
737
+
738
+ final int childCount = getChildCount();
739
+
740
+ if (childCount != 2) {
741
+ throw new IllegalStateException("Sliding up panel layout must have exactly 2 children!");
742
+ }
743
+
744
+ mMainView = getChildAt(0);
745
+ mSlideableView = getChildAt(1);
746
+ if (mDragView == null) {
747
+ setDragView(mSlideableView);
748
+ }
749
+
750
+ // If the sliding panel is not visible, then put the whole view in the hidden state
751
+ if (mSlideableView.getVisibility() != VISIBLE) {
752
+ mSlideState = PanelState.HIDDEN;
753
+ }
754
+
755
+ int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
756
+ int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
757
+
758
+ // First pass. Measure based on child LayoutParams width/height.
759
+ for (int i = 0; i < childCount; i++) {
760
+ final View child = getChildAt(i);
761
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
762
+
763
+ // We always measure the sliding panel in order to know it's height (needed for show panel)
764
+ if (child.getVisibility() == GONE && i == 0) {
765
+ continue;
766
+ }
767
+
768
+ int height = layoutHeight;
769
+ int width = layoutWidth;
770
+ if (child == mMainView) {
771
+ if (!mOverlayContent && mSlideState != PanelState.HIDDEN) {
772
+ height -= mPanelHeight;
773
+ }
774
+
775
+ width -= lp.leftMargin + lp.rightMargin;
776
+ } else if (child == mSlideableView) {
777
+ // The slideable view should be aware of its top margin.
778
+ // See https://github.com/umano/AndroidSlidingUpPanel/issues/412.
779
+ height -= lp.topMargin;
780
+ }
781
+
782
+ int childWidthSpec;
783
+ if (lp.width == LayoutParams.WRAP_CONTENT) {
784
+ childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
785
+ } else if (lp.width == LayoutParams.MATCH_PARENT) {
786
+ childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
787
+ } else {
788
+ childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
789
+ }
790
+
791
+ int childHeightSpec;
792
+ if (lp.height == LayoutParams.WRAP_CONTENT) {
793
+ childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
794
+ } else if (lp.height == LayoutParams.MATCH_PARENT) {
795
+ childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
796
+ } else {
797
+ childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
798
+ }
799
+
800
+ child.measure(childWidthSpec, childHeightSpec);
801
+
802
+ if (child == mSlideableView) {
803
+ mSlideRange = mSlideableView.getMeasuredHeight() - mPanelHeight;
804
+ }
805
+ }
806
+
807
+ setMeasuredDimension(widthSize, heightSize);
808
+ }
809
+
810
+ @Override
811
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
812
+ final int paddingLeft = getPaddingLeft();
813
+ final int paddingTop = getPaddingTop();
814
+
815
+ final int childCount = getChildCount();
816
+
817
+ if (mFirstLayout) {
818
+ switch (mSlideState) {
819
+ case EXPANDED:
820
+ mSlideOffset = 1.0f;
821
+ break;
822
+ case ANCHORED:
823
+ mSlideOffset = mAnchorPoint;
824
+ break;
825
+ case HIDDEN:
826
+ int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
827
+ mSlideOffset = computeSlideOffset(newTop);
828
+ break;
829
+ default:
830
+ mSlideOffset = 0.f;
831
+ break;
832
+ }
833
+ }
834
+
835
+ for (int i = 0; i < childCount; i++) {
836
+ final View child = getChildAt(i);
837
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
838
+
839
+ // Always layout the sliding view on the first layout
840
+ if (child.getVisibility() == GONE && (i == 0 || mFirstLayout)) {
841
+ continue;
842
+ }
843
+
844
+ final int childHeight = child.getMeasuredHeight();
845
+ int childTop = paddingTop;
846
+
847
+ if (child == mSlideableView) {
848
+ childTop = computePanelTopPosition(mSlideOffset);
849
+ }
850
+
851
+ if (!mIsSlidingUp) {
852
+ if (child == mMainView && !mOverlayContent) {
853
+ childTop = computePanelTopPosition(mSlideOffset) + mSlideableView.getMeasuredHeight();
854
+ }
855
+ }
856
+ final int childBottom = childTop + childHeight;
857
+ final int childLeft = paddingLeft + lp.leftMargin;
858
+ final int childRight = childLeft + child.getMeasuredWidth();
859
+
860
+ child.layout(childLeft, childTop, childRight, childBottom);
861
+ }
862
+
863
+ if (mFirstLayout) {
864
+ updateObscuredViewVisibility();
865
+ }
866
+ applyParallaxForCurrentSlideOffset();
867
+
868
+ mFirstLayout = false;
869
+ }
870
+
871
+ @Override
872
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
873
+ super.onSizeChanged(w, h, oldw, oldh);
874
+ // Recalculate sliding panes and their details
875
+ if (h != oldh) {
876
+ mFirstLayout = true;
877
+ }
878
+ }
879
+
880
+ @Override
881
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
882
+ // If the scrollable view is handling touch, never intercept
883
+ if (mIsScrollableViewHandlingTouch) {
884
+ mDragHelper.cancel();
885
+ return false;
886
+ }
887
+
888
+ final int action = MotionEventCompat.getActionMasked(ev);
889
+ final float x = ev.getX();
890
+ final float y = ev.getY();
891
+
892
+ switch (action) {
893
+ case MotionEvent.ACTION_DOWN: {
894
+ mIsUnableToDrag = false;
895
+ mInitialMotionX = x;
896
+ mInitialMotionY = y;
897
+ break;
898
+ }
899
+
900
+ case MotionEvent.ACTION_MOVE: {
901
+ final float adx = Math.abs(x - mInitialMotionX);
902
+ final float ady = Math.abs(y - mInitialMotionY);
903
+ final int dragSlop = mDragHelper.getTouchSlop();
904
+
905
+ if ((ady > dragSlop && adx > ady) || !isViewUnder(mDragView, (int) mInitialMotionX, (int) mInitialMotionY)) {
906
+ mDragHelper.cancel();
907
+ mIsUnableToDrag = true;
908
+ return false;
909
+ }
910
+ break;
911
+ }
912
+
913
+ case MotionEvent.ACTION_CANCEL:
914
+ case MotionEvent.ACTION_UP:
915
+ // If the dragView is still dragging when we get here, we need to call processTouchEvent
916
+ // so that the view is settled
917
+ // Added to make scrollable views work (tokudu)
918
+ if (mDragHelper.isDragging()) {
919
+ mDragHelper.processTouchEvent(ev);
920
+ return true;
921
+ }
922
+ break;
923
+ }
924
+ return mDragHelper.shouldInterceptTouchEvent(ev);
925
+ }
926
+
927
+ @Override
928
+ public boolean onTouchEvent(@NonNull MotionEvent ev) {
929
+ if (!isEnabled() || !isTouchEnabled()) {
930
+ return super.onTouchEvent(ev);
931
+ }
932
+ try {
933
+ mDragHelper.processTouchEvent(ev);
934
+ return true;
935
+ } catch (Exception ex) {
936
+ // Ignore the pointer out of range exception
937
+ return false;
938
+ }
939
+ }
940
+
941
+ @Override
942
+ public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
943
+ final int action = MotionEventCompat.getActionMasked(ev);
944
+
945
+ if (!isEnabled() || !isTouchEnabled() || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
946
+ mDragHelper.cancel();
947
+ return super.dispatchTouchEvent(ev);
948
+ }
949
+
950
+ final float y = ev.getY();
951
+
952
+ if (action == MotionEvent.ACTION_DOWN) {
953
+ mIsScrollableViewHandlingTouch = false;
954
+ mPrevMotionY = y;
955
+ } else if (action == MotionEvent.ACTION_MOVE) {
956
+ float dy = y - mPrevMotionY;
957
+ mPrevMotionY = y;
958
+
959
+ // If the scroll view isn't under the touch, pass the
960
+ // event along to the dragView.
961
+ if (!isViewUnder(mScrollableView, (int) mInitialMotionX, (int) mInitialMotionY)) {
962
+ return super.dispatchTouchEvent(ev);
963
+ }
964
+
965
+ // Which direction (up or down) is the drag moving?
966
+ if (dy * (mIsSlidingUp ? 1 : -1) > 0) { // Collapsing
967
+ // Is the child less than fully scrolled?
968
+ // Then let the child handle it.
969
+ if (getScrollableViewScrollPosition() > 0) {
970
+ mIsScrollableViewHandlingTouch = true;
971
+ return super.dispatchTouchEvent(ev);
972
+ }
973
+
974
+ // Was the child handling the touch previously?
975
+ // Then we need to rejigger things so that the
976
+ // drag panel gets a proper down event.
977
+ if (mIsScrollableViewHandlingTouch) {
978
+ // Send an 'UP' event to the child.
979
+ MotionEvent up = MotionEvent.obtain(ev);
980
+ up.setAction(MotionEvent.ACTION_CANCEL);
981
+ super.dispatchTouchEvent(up);
982
+ up.recycle();
983
+
984
+ // Send a 'DOWN' event to the panel. (We'll cheat
985
+ // and hijack this one)
986
+ ev.setAction(MotionEvent.ACTION_DOWN);
987
+ }
988
+
989
+ mIsScrollableViewHandlingTouch = false;
990
+ return this.onTouchEvent(ev);
991
+ } else if (dy * (mIsSlidingUp ? 1 : -1) < 0) { // Expanding
992
+ // Is the panel less than fully expanded?
993
+ // Then we'll handle the drag here.
994
+ if (mSlideOffset < 1.0f) {
995
+ mIsScrollableViewHandlingTouch = false;
996
+ return this.onTouchEvent(ev);
997
+ }
998
+
999
+ // Was the panel handling the touch previously?
1000
+ // Then we need to rejigger things so that the
1001
+ // child gets a proper down event.
1002
+ if (!mIsScrollableViewHandlingTouch && mDragHelper.isDragging()) {
1003
+ mDragHelper.cancel();
1004
+ ev.setAction(MotionEvent.ACTION_DOWN);
1005
+ }
1006
+
1007
+ mIsScrollableViewHandlingTouch = true;
1008
+ return super.dispatchTouchEvent(ev);
1009
+ }
1010
+ } else if (action == MotionEvent.ACTION_UP && mIsScrollableViewHandlingTouch) {
1011
+ // If the scrollable view was handling the touch and we receive an up
1012
+ // we want to clear any previous dragging state so we don't intercept a touch stream accidentally
1013
+ mDragHelper.setDragState(ViewDragHelper.STATE_IDLE);
1014
+ }
1015
+
1016
+ // In all other cases, just let the default behavior take over.
1017
+ return super.dispatchTouchEvent(ev);
1018
+ }
1019
+
1020
+ private boolean isViewUnder(View view, int x, int y) {
1021
+ if (view == null) return false;
1022
+ int[] viewLocation = new int[2];
1023
+ view.getLocationOnScreen(viewLocation);
1024
+ int[] parentLocation = new int[2];
1025
+ this.getLocationOnScreen(parentLocation);
1026
+ int screenX = parentLocation[0] + x;
1027
+ int screenY = parentLocation[1] + y;
1028
+ return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
1029
+ screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
1030
+ }
1031
+
1032
+ protected int getScrollableViewScrollPosition() {
1033
+ if (mScrollableView == null) return 0;
1034
+ if (mScrollableView instanceof ScrollView) {
1035
+ if (mIsSlidingUp) {
1036
+ return mScrollableView.getScrollY();
1037
+ } else {
1038
+ ScrollView sv = ((ScrollView) mScrollableView);
1039
+ View child = sv.getChildAt(0);
1040
+ return (child.getBottom() - (sv.getHeight() + sv.getScrollY()));
1041
+ }
1042
+ } else if (mScrollableView instanceof ListView && ((ListView) mScrollableView).getChildCount() > 0) {
1043
+ ListView lv = ((ListView) mScrollableView);
1044
+ if (lv.getAdapter() == null) return 0;
1045
+ if (mIsSlidingUp) {
1046
+ View firstChild = lv.getChildAt(0);
1047
+ // Approximate the scroll position based on the top child and the first visible item
1048
+ return lv.getFirstVisiblePosition() * firstChild.getHeight() - firstChild.getTop();
1049
+ } else {
1050
+ View lastChild = lv.getChildAt(lv.getChildCount() - 1);
1051
+ // Approximate the scroll position based on the bottom child and the last visible item
1052
+ return (lv.getAdapter().getCount() - lv.getLastVisiblePosition() - 1) * lastChild.getHeight() + lastChild.getBottom() - lv.getBottom();
1053
+ }
1054
+ } else if (mScrollableView instanceof RecyclerView && ((RecyclerView) mScrollableView).getChildCount() > 0) {
1055
+ RecyclerView rv = ((RecyclerView) mScrollableView);
1056
+ RecyclerView.LayoutManager lm = rv.getLayoutManager();
1057
+ if (rv.getAdapter() == null) return 0;
1058
+ if (mIsSlidingUp) {
1059
+ View firstChild = rv.getChildAt(0);
1060
+ // Approximate the scroll position based on the top child and the first visible item
1061
+ return rv.getChildLayoutPosition(firstChild) * lm.getDecoratedMeasuredHeight(firstChild) - lm.getDecoratedTop(firstChild);
1062
+ } else {
1063
+ View lastChild = rv.getChildAt(rv.getChildCount() - 1);
1064
+ // Approximate the scroll position based on the bottom child and the last visible item
1065
+ return (rv.getAdapter().getItemCount() - 1) * lm.getDecoratedMeasuredHeight(lastChild) + lm.getDecoratedBottom(lastChild) - rv.getBottom();
1066
+ }
1067
+ } else {
1068
+ return 0;
1069
+ }
1070
+ }
1071
+
1072
+ /*
1073
+ * Computes the top position of the panel based on the slide offset.
1074
+ */
1075
+ private int computePanelTopPosition(float slideOffset) {
1076
+ int slidingViewHeight = mSlideableView != null ? mSlideableView.getMeasuredHeight() : 0;
1077
+ int slidePixelOffset = (int) (slideOffset * mSlideRange);
1078
+ // Compute the top of the panel if its collapsed
1079
+ return mIsSlidingUp
1080
+ ? getMeasuredHeight() - getPaddingBottom() - mPanelHeight - slidePixelOffset
1081
+ : getPaddingTop() - slidingViewHeight + mPanelHeight + slidePixelOffset;
1082
+ }
1083
+
1084
+ /*
1085
+ * Computes the slide offset based on the top position of the panel
1086
+ */
1087
+ private float computeSlideOffset(int topPosition) {
1088
+ // Compute the panel top position if the panel is collapsed (offset 0)
1089
+ final int topBoundCollapsed = computePanelTopPosition(0);
1090
+
1091
+ // Determine the new slide offset based on the collapsed top position and the new required
1092
+ // top position
1093
+ return (mIsSlidingUp
1094
+ ? (float) (topBoundCollapsed - topPosition) / mSlideRange
1095
+ : (float) (topPosition - topBoundCollapsed) / mSlideRange);
1096
+ }
1097
+
1098
+ /**
1099
+ * Returns the current state of the panel as an enum.
1100
+ *
1101
+ * @return the current panel state
1102
+ */
1103
+ public PanelState getPanelState() {
1104
+ return mSlideState;
1105
+ }
1106
+
1107
+ /**
1108
+ * Change panel state to the given state with
1109
+ *
1110
+ * @param state - new panel state
1111
+ */
1112
+ public void setPanelState(PanelState state) {
1113
+ if (state == null || state == PanelState.DRAGGING) {
1114
+ throw new IllegalArgumentException("Panel state cannot be null or DRAGGING.");
1115
+ }
1116
+ if (!isEnabled()
1117
+ || (!mFirstLayout && mSlideableView == null)
1118
+ || state == mSlideState
1119
+ || mSlideState == PanelState.DRAGGING) return;
1120
+
1121
+ if (mFirstLayout) {
1122
+ mSlideState = state;
1123
+ } else {
1124
+ if (mSlideState == PanelState.HIDDEN) {
1125
+ mSlideableView.setVisibility(View.VISIBLE);
1126
+ requestLayout();
1127
+ }
1128
+ switch (state) {
1129
+ case ANCHORED:
1130
+ smoothSlideTo(mAnchorPoint, 0);
1131
+ break;
1132
+ case COLLAPSED:
1133
+ smoothSlideTo(0, 0);
1134
+ break;
1135
+ case EXPANDED:
1136
+ smoothSlideTo(1.0f, 0);
1137
+ break;
1138
+ case HIDDEN:
1139
+ int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
1140
+ smoothSlideTo(computeSlideOffset(newTop), 0);
1141
+ break;
1142
+ }
1143
+ }
1144
+ }
1145
+
1146
+ /**
1147
+ * Update the parallax based on the current slide offset.
1148
+ */
1149
+ @SuppressLint("NewApi")
1150
+ private void applyParallaxForCurrentSlideOffset() {
1151
+ if (mParallaxOffset > 0) {
1152
+ int mainViewOffset = getCurrentParallaxOffset();
1153
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
1154
+ mMainView.setTranslationY(mainViewOffset);
1155
+ } else {
1156
+ AnimatorProxy.wrap(mMainView).setTranslationY(mainViewOffset);
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ private void onPanelDragged(int newTop) {
1162
+ mLastNotDraggingSlideState = mSlideState;
1163
+ mSlideState = PanelState.DRAGGING;
1164
+ // Recompute the slide offset based on the new top position
1165
+ mSlideOffset = computeSlideOffset(newTop);
1166
+ applyParallaxForCurrentSlideOffset();
1167
+ // Dispatch the slide event
1168
+ dispatchOnPanelSlide(mSlideableView);
1169
+ // If the slide offset is negative, and overlay is not on, we need to increase the
1170
+ // height of the main content
1171
+ LayoutParams lp = (LayoutParams) mMainView.getLayoutParams();
1172
+ int defaultHeight = getHeight() - getPaddingBottom() - getPaddingTop() - mPanelHeight;
1173
+
1174
+ if (mSlideOffset <= 0 && !mOverlayContent) {
1175
+ // expand the main view
1176
+ lp.height = mIsSlidingUp ? (newTop - getPaddingBottom()) : (getHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight() - newTop);
1177
+ mMainView.requestLayout();
1178
+ } else if (lp.height != defaultHeight && !mOverlayContent) {
1179
+ lp.height = defaultHeight;
1180
+ mMainView.requestLayout();
1181
+ }
1182
+ }
1183
+
1184
+ @Override
1185
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1186
+ boolean result;
1187
+
1188
+ int save = 0;
1189
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1190
+ save = canvas.saveLayer(null, null);
1191
+ } else {
1192
+ save = canvas.save();
1193
+ }
1194
+
1195
+ if (mSlideableView != child) { // if main view
1196
+ // Clip against the slider; no sense drawing what will immediately be covered,
1197
+ // Unless the panel is set to overlay content
1198
+ canvas.getClipBounds(mTmpRect);
1199
+ if (!mOverlayContent) {
1200
+ if (mIsSlidingUp) {
1201
+ mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
1202
+ } else {
1203
+ mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());
1204
+ }
1205
+ }
1206
+ if (mClipPanel) {
1207
+ canvas.clipRect(mTmpRect);
1208
+ }
1209
+
1210
+ result = super.drawChild(canvas, child, drawingTime);
1211
+
1212
+ if (mCoveredFadeColor != 0 && mSlideOffset > 0) {
1213
+ final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
1214
+ final int imag = (int) (baseAlpha * mSlideOffset);
1215
+ final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
1216
+ mCoveredFadePaint.setColor(color);
1217
+ canvas.drawRect(mTmpRect, mCoveredFadePaint);
1218
+ }
1219
+ } else {
1220
+ result = super.drawChild(canvas, child, drawingTime);
1221
+ }
1222
+
1223
+ canvas.restoreToCount(save);
1224
+
1225
+ return result;
1226
+ }
1227
+
1228
+ /**
1229
+ * Smoothly animate mDraggingPane to the target X position within its range.
1230
+ *
1231
+ * @param slideOffset position to animate to
1232
+ * @param velocity initial velocity in case of fling, or 0.
1233
+ */
1234
+ boolean smoothSlideTo(float slideOffset, int velocity) {
1235
+ if (!isEnabled() || mSlideableView == null) {
1236
+ // Nothing to do.
1237
+ return false;
1238
+ }
1239
+
1240
+ int panelTop = computePanelTopPosition(slideOffset);
1241
+ if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) {
1242
+ setAllChildrenVisible();
1243
+ ViewCompat.postInvalidateOnAnimation(this);
1244
+ return true;
1245
+ }
1246
+ return false;
1247
+ }
1248
+
1249
+ @Override
1250
+ public void computeScroll() {
1251
+ if (mDragHelper != null && mDragHelper.continueSettling(true)) {
1252
+ if (!isEnabled()) {
1253
+ mDragHelper.abort();
1254
+ return;
1255
+ }
1256
+
1257
+ ViewCompat.postInvalidateOnAnimation(this);
1258
+ }
1259
+ }
1260
+
1261
+ @Override
1262
+ public void draw(Canvas c) {
1263
+ super.draw(c);
1264
+
1265
+ // draw the shadow
1266
+ if (mShadowDrawable != null) {
1267
+ final int right = mSlideableView.getRight();
1268
+ final int top;
1269
+ final int bottom;
1270
+ if (mIsSlidingUp) {
1271
+ top = mSlideableView.getTop() - mShadowHeight;
1272
+ bottom = mSlideableView.getTop();
1273
+ } else {
1274
+ top = mSlideableView.getBottom();
1275
+ bottom = mSlideableView.getBottom() + mShadowHeight;
1276
+ }
1277
+ final int left = mSlideableView.getLeft();
1278
+ mShadowDrawable.setBounds(left, top, right, bottom);
1279
+ mShadowDrawable.draw(c);
1280
+ }
1281
+ }
1282
+
1283
+ /**
1284
+ * Tests scrollability within child views of v given a delta of dx.
1285
+ *
1286
+ * @param v View to test for horizontal scrollability
1287
+ * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1288
+ * or just its children (false).
1289
+ * @param dx Delta scrolled in pixels
1290
+ * @param x X coordinate of the active touch point
1291
+ * @param y Y coordinate of the active touch point
1292
+ * @return true if child views of v can be scrolled by delta of dx.
1293
+ */
1294
+ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1295
+ if (v instanceof ViewGroup) {
1296
+ final ViewGroup group = (ViewGroup) v;
1297
+ final int scrollX = v.getScrollX();
1298
+ final int scrollY = v.getScrollY();
1299
+ final int count = group.getChildCount();
1300
+ // Count backwards - let topmost views consume scroll distance first.
1301
+ for (int i = count - 1; i >= 0; i--) {
1302
+ final View child = group.getChildAt(i);
1303
+ if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1304
+ y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1305
+ canScroll(child, true, dx, x + scrollX - child.getLeft(),
1306
+ y + scrollY - child.getTop())) {
1307
+ return true;
1308
+ }
1309
+ }
1310
+ }
1311
+ return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1312
+ }
1313
+
1314
+
1315
+ @Override
1316
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1317
+ return new LayoutParams();
1318
+ }
1319
+
1320
+ @Override
1321
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1322
+ return p instanceof MarginLayoutParams
1323
+ ? new LayoutParams((MarginLayoutParams) p)
1324
+ : new LayoutParams(p);
1325
+ }
1326
+
1327
+ @Override
1328
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1329
+ return p instanceof LayoutParams && super.checkLayoutParams(p);
1330
+ }
1331
+
1332
+ @Override
1333
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1334
+ return new LayoutParams(getContext(), attrs);
1335
+ }
1336
+
1337
+ @Override
1338
+ public Parcelable onSaveInstanceState() {
1339
+ Parcelable superState = super.onSaveInstanceState();
1340
+
1341
+ SavedState ss = new SavedState(superState);
1342
+ if (mSlideState != PanelState.DRAGGING) {
1343
+ ss.mSlideState = mSlideState;
1344
+ } else {
1345
+ ss.mSlideState = mLastNotDraggingSlideState;
1346
+ }
1347
+ return ss;
1348
+ }
1349
+
1350
+ @Override
1351
+ public void onRestoreInstanceState(Parcelable state) {
1352
+ SavedState ss = (SavedState) state;
1353
+ super.onRestoreInstanceState(ss.getSuperState());
1354
+ mSlideState = ss.mSlideState != null ? ss.mSlideState : DEFAULT_SLIDE_STATE;
1355
+ }
1356
+
1357
+ private class DragHelperCallback extends ViewDragHelper.Callback {
1358
+
1359
+ @Override
1360
+ public boolean tryCaptureView(View child, int pointerId) {
1361
+ if (mIsUnableToDrag) {
1362
+ return false;
1363
+ }
1364
+
1365
+ return child == mSlideableView;
1366
+ }
1367
+
1368
+ @Override
1369
+ public void onViewDragStateChanged(int state) {
1370
+ if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
1371
+ mSlideOffset = computeSlideOffset(mSlideableView.getTop());
1372
+ applyParallaxForCurrentSlideOffset();
1373
+
1374
+ if (mSlideOffset == 1) {
1375
+ if (mSlideState != PanelState.EXPANDED) {
1376
+ updateObscuredViewVisibility();
1377
+ mSlideState = PanelState.EXPANDED;
1378
+ dispatchOnPanelExpanded(mSlideableView);
1379
+ }
1380
+ } else if (mSlideOffset == 0) {
1381
+ if (mSlideState != PanelState.COLLAPSED) {
1382
+ mSlideState = PanelState.COLLAPSED;
1383
+ dispatchOnPanelCollapsed(mSlideableView);
1384
+ }
1385
+ } else if (mSlideOffset < 0) {
1386
+ mSlideState = PanelState.HIDDEN;
1387
+ mSlideableView.setVisibility(View.INVISIBLE);
1388
+ dispatchOnPanelHidden(mSlideableView);
1389
+ } else if (mSlideState != PanelState.ANCHORED) {
1390
+ updateObscuredViewVisibility();
1391
+ mSlideState = PanelState.ANCHORED;
1392
+ dispatchOnPanelAnchored(mSlideableView);
1393
+ }
1394
+ }
1395
+ }
1396
+
1397
+ @Override
1398
+ public void onViewCaptured(View capturedChild, int activePointerId) {
1399
+ setAllChildrenVisible();
1400
+ }
1401
+
1402
+ @Override
1403
+ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1404
+ onPanelDragged(top);
1405
+ invalidate();
1406
+ }
1407
+
1408
+ @Override
1409
+ public void onViewReleased(View releasedChild, float xvel, float yvel) {
1410
+ int target = 0;
1411
+
1412
+ // direction is always positive if we are sliding in the expanded direction
1413
+ float direction = mIsSlidingUp ? -yvel : yvel;
1414
+
1415
+ if (direction > 0 && mSlideOffset <= mAnchorPoint) {
1416
+ // swipe up -> expand and stop at anchor point
1417
+ target = computePanelTopPosition(mAnchorPoint);
1418
+ } else if (direction > 0 && mSlideOffset > mAnchorPoint) {
1419
+ // swipe up past anchor -> expand
1420
+ target = computePanelTopPosition(1.0f);
1421
+ } else if (direction < 0 && mSlideOffset >= mAnchorPoint) {
1422
+ // swipe down -> collapse and stop at anchor point
1423
+ target = computePanelTopPosition(mAnchorPoint);
1424
+ } else if (direction < 0 && mSlideOffset < mAnchorPoint) {
1425
+ // swipe down past anchor -> collapse
1426
+ target = computePanelTopPosition(0.0f);
1427
+ } else if (mSlideOffset >= (1.f + mAnchorPoint) / 2) {
1428
+ // zero velocity, and far enough from anchor point => expand to the top
1429
+ target = computePanelTopPosition(1.0f);
1430
+ } else if (mSlideOffset >= mAnchorPoint / 2) {
1431
+ // zero velocity, and close enough to anchor point => go to anchor
1432
+ target = computePanelTopPosition(mAnchorPoint);
1433
+ } else {
1434
+ // settle at the bottom
1435
+ target = computePanelTopPosition(0.0f);
1436
+ }
1437
+
1438
+ mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), target);
1439
+ invalidate();
1440
+ }
1441
+
1442
+ @Override
1443
+ public int getViewVerticalDragRange(View child) {
1444
+ return mSlideRange;
1445
+ }
1446
+
1447
+ @Override
1448
+ public int clampViewPositionVertical(View child, int top, int dy) {
1449
+ final int collapsedTop = computePanelTopPosition(0.f);
1450
+ final int expandedTop = computePanelTopPosition(1.0f);
1451
+ if (mIsSlidingUp) {
1452
+ return Math.min(Math.max(top, expandedTop), collapsedTop);
1453
+ } else {
1454
+ return Math.min(Math.max(top, collapsedTop), expandedTop);
1455
+ }
1456
+ }
1457
+ }
1458
+
1459
+ public static class LayoutParams extends MarginLayoutParams {
1460
+ private static final int[] ATTRS = new int[]{
1461
+ android.R.attr.layout_weight
1462
+ };
1463
+
1464
+ public LayoutParams() {
1465
+ super(MATCH_PARENT, MATCH_PARENT);
1466
+ }
1467
+
1468
+ public LayoutParams(int width, int height) {
1469
+ super(width, height);
1470
+ }
1471
+
1472
+ public LayoutParams(ViewGroup.LayoutParams source) {
1473
+ super(source);
1474
+ }
1475
+
1476
+ public LayoutParams(MarginLayoutParams source) {
1477
+ super(source);
1478
+ }
1479
+
1480
+ public LayoutParams(LayoutParams source) {
1481
+ super(source);
1482
+ }
1483
+
1484
+ public LayoutParams(Context c, AttributeSet attrs) {
1485
+ super(c, attrs);
1486
+
1487
+ final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1488
+ a.recycle();
1489
+ }
1490
+ }
1491
+
1492
+ static class SavedState extends BaseSavedState {
1493
+ PanelState mSlideState;
1494
+
1495
+ SavedState(Parcelable superState) {
1496
+ super(superState);
1497
+ }
1498
+
1499
+ private SavedState(Parcel in) {
1500
+ super(in);
1501
+ try {
1502
+ mSlideState = Enum.valueOf(PanelState.class, in.readString());
1503
+ } catch (IllegalArgumentException e) {
1504
+ mSlideState = PanelState.COLLAPSED;
1505
+ }
1506
+ }
1507
+
1508
+ @Override
1509
+ public void writeToParcel(Parcel out, int flags) {
1510
+ super.writeToParcel(out, flags);
1511
+ out.writeString(mSlideState.toString());
1512
+ }
1513
+
1514
+ public static final Creator<SavedState> CREATOR =
1515
+ new Creator<SavedState>() {
1516
+ @Override
1517
+ public SavedState createFromParcel(Parcel in) {
1518
+ return new SavedState(in);
1519
+ }
1520
+
1521
+ @Override
1522
+ public SavedState[] newArray(int size) {
1523
+ return new SavedState[size];
1524
+ }
1525
+ };
1526
+ }
1527
+ }