├── .gitignore
├── AndroidManifest.xml
├── LICENSE
├── README.md
├── demo.gif
├── libs
└── android-support-v4.jar
├── res
├── drawable
│ └── shadow_top.xml
├── layout
│ └── main.xml
└── values
│ └── strings.xml
└── src
└── com
└── chenjishi
└── slideupdemo
├── MainActivity.java
└── SlidingUpPaneLayout.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Log Files
26 | *.log
27 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SlidingUpPaneLayout
2 | ===================
3 |
4 | SlidingUpPaneLayout provides a vertical, multi-pane layout for use at the top level of a UI.You can slide up and down to slide the top view, exactly like android.support.v4.widget.SlidingPaneLayout, but the direction is vertical.
5 |
6 | top view is slidable, and bottom view is fixed.
7 |
8 | ##ScreenShot
9 |
10 |
12 |
13 | ##LICENSE
14 |
15 | Feel Free to Use this:)
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenjishi/SlidingUpPaneLayout/9f331ae0813a7e5145f73a99e807e135802e749b/demo.gif
--------------------------------------------------------------------------------
/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenjishi/SlidingUpPaneLayout/9f331ae0813a7e5145f73a99e807e135802e749b/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/res/drawable/shadow_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
14 |
20 |
21 |
28 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SlidingUpPaneLayout
4 |
5 |
--------------------------------------------------------------------------------
/src/com/chenjishi/slideupdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenjishi.slideupdemo;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | public class MainActivity extends Activity {
7 |
8 | @Override
9 | public void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | setContentView(R.layout.main);
12 |
13 | final float density = getResources().getDisplayMetrics().density;
14 |
15 | SlidingUpPaneLayout slidingUpPaneLayout = (SlidingUpPaneLayout) findViewById(R.id.sliding_up_layout);
16 | slidingUpPaneLayout.setParallaxDistance((int) (200 * density));
17 | slidingUpPaneLayout.setShadowResourceTop(R.drawable.shadow_top);
18 |
19 | /**
20 | * limit scroll zone to 32dp, if you want whole view can scroll
21 | * just ignore this method, don't call it
22 | */
23 | // slidingUpPaneLayout.setEdgeSize((int) (density * 32));
24 |
25 | slidingUpPaneLayout.openPane();
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/chenjishi/slideupdemo/SlidingUpPaneLayout.java:
--------------------------------------------------------------------------------
1 | package com.chenjishi.slideupdemo;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.*;
6 | import android.graphics.drawable.Drawable;
7 | import android.os.Build;
8 | import android.os.Parcel;
9 | import android.os.Parcelable;
10 | import android.support.v4.view.AccessibilityDelegateCompat;
11 | import android.support.v4.view.MotionEventCompat;
12 | import android.support.v4.view.ViewCompat;
13 | import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
14 | import android.support.v4.widget.ViewDragHelper;
15 | import android.util.AttributeSet;
16 | import android.util.Log;
17 | import android.view.MotionEvent;
18 | import android.view.View;
19 | import android.view.ViewGroup;
20 | import android.view.ViewParent;
21 | import android.view.accessibility.AccessibilityEvent;
22 |
23 | import java.lang.reflect.Field;
24 | import java.lang.reflect.Method;
25 | import java.util.ArrayList;
26 |
27 | /**
28 | * Created by chenjishi on 14/11/5.
29 | */
30 | public class SlidingUpPaneLayout extends ViewGroup {
31 |
32 | private static final String TAG = "SlidingUpPaneLayout";
33 |
34 | /**
35 | * Default size of the overhang for a pane in the open state.
36 | * At least this much of a sliding pane will remain visible.
37 | * This indicates that there is more content available and provides
38 | * a "physical" edge to grab to pull it closed.
39 | */
40 | private static final int DEFAULT_OVERHANG_SIZE = 32; // dp;
41 |
42 | /**
43 | * If no fade color is given by default it will fade to 80% gray.
44 | */
45 | private static final int DEFAULT_FADE_COLOR = 0xcccccccc;
46 |
47 | /**
48 | * The fade color used for the sliding panel. 0 = no fading.
49 | */
50 | private int mSliderFadeColor = DEFAULT_FADE_COLOR;
51 |
52 | /**
53 | * Minimum velocity that will be detected as a fling
54 | */
55 | private static final int MIN_FLING_VELOCITY = 400; // dips per second
56 |
57 | /**
58 | * The fade color used for the panel covered by the slider. 0 = no fading.
59 | */
60 | private int mCoveredFadeColor;
61 |
62 | /**
63 | * Drawable used to draw the shadow between panes by default.
64 | */
65 | private Drawable mShadowDrawableTop;
66 |
67 | private float mEdgeSize;
68 |
69 | /**
70 | * The size of the overhang in pixels.
71 | * This is the minimum section of the sliding panel that will
72 | * be visible in the open state to allow for a closing drag.
73 | */
74 | private final int mOverhangSize;
75 |
76 | /**
77 | * True if a panel can slide with the current measurements
78 | */
79 | private boolean mCanSlide;
80 |
81 | /**
82 | * The child view that can slide, if any.
83 | */
84 | private View mSlideableView;
85 |
86 | /**
87 | * How far the panel is offset from its closed position.
88 | * range [0, 1] where 0 = closed, 1 = open.
89 | */
90 | private float mSlideOffset;
91 |
92 | /**
93 | * How far the non-sliding panel is parallaxed from its usual position when open.
94 | * range [0, 1]
95 | */
96 | private float mParallaxOffset;
97 |
98 | /**
99 | * How far in pixels the slideable panel may move.
100 | */
101 | private int mSlideRange;
102 |
103 | /**
104 | * A panel view is locked into internal scrolling or another condition that
105 | * is preventing a drag.
106 | */
107 | private boolean mIsUnableToDrag;
108 |
109 | /**
110 | * Distance in pixels to parallax the fixed pane by when fully closed
111 | */
112 | private int mParallaxBy;
113 |
114 | private float mInitialMotionX;
115 | private float mInitialMotionY;
116 |
117 | private PanelSlideListener mPanelSlideListener;
118 |
119 | private final ViewDragHelper mDragHelper;
120 |
121 | /**
122 | * Stores whether or not the pane was open the last time it was slideable.
123 | * If open/close operations are invoked this state is modified. Used by
124 | * instance state save/restore.
125 | */
126 | private boolean mPreservedOpenState;
127 | private boolean mFirstLayout = true;
128 |
129 | private final ArrayList mPostedRunnables =
130 | new ArrayList();
131 |
132 | private final Rect mTmpRect = new Rect();
133 |
134 | static final SlidingPanelLayoutImpl IMPL;
135 |
136 | static {
137 | final int deviceVersion = Build.VERSION.SDK_INT;
138 | if (deviceVersion >= 17) {
139 | IMPL = new SlidingPanelLayoutImplJBMR1();
140 | } else if (deviceVersion >= 16) {
141 | IMPL = new SlidingPanelLayoutImplJB();
142 | } else {
143 | IMPL = new SlidingPanelLayoutImplBase();
144 | }
145 | }
146 |
147 | public interface PanelSlideListener {
148 |
149 | public void onPanelSlide(View panel, float slideOffset);
150 |
151 | public void onPanelOpened(View panel);
152 |
153 | public void onPanelClosed(View panel);
154 | }
155 |
156 | public SlidingUpPaneLayout(Context context) {
157 | this(context, null);
158 | }
159 |
160 | public SlidingUpPaneLayout(Context context, AttributeSet attrs) {
161 | this(context, attrs, 0);
162 | }
163 |
164 | public SlidingUpPaneLayout(Context context, AttributeSet attrs, int defStyle) {
165 | super(context, attrs, defStyle);
166 |
167 | final float density = context.getResources().getDisplayMetrics().density;
168 | mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f);
169 |
170 | setWillNotDraw(false);
171 |
172 | ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
173 | ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
174 |
175 | mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
176 | mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
177 | }
178 |
179 | public void setParallaxDistance(int parallaxBy) {
180 | mParallaxBy = parallaxBy;
181 | requestLayout();
182 | }
183 |
184 | public int getParallaxDistance() {
185 | return mParallaxBy;
186 | }
187 |
188 | public void setSliderFadeColor(int color) {
189 | mSliderFadeColor = color;
190 | }
191 |
192 | public int getSliderFadeColor() {
193 | return mSliderFadeColor;
194 | }
195 |
196 | public void setCoveredFadeColor(int color) {
197 | mCoveredFadeColor = color;
198 | }
199 |
200 | public int getCoveredFadeColor() {
201 | return mCoveredFadeColor;
202 | }
203 |
204 | public void setPanelSlideListener(PanelSlideListener listener) {
205 | mPanelSlideListener = listener;
206 | }
207 |
208 | void dispatchOnPanelOpened(View panel) {
209 | if (mPanelSlideListener != null) {
210 | mPanelSlideListener.onPanelOpened(panel);
211 | }
212 | sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
213 | }
214 |
215 | void dispatchOnPanelSlide(View panel) {
216 | if (mPanelSlideListener != null) {
217 | mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
218 | }
219 | }
220 |
221 | void dispatchOnPanelClosed(View panel) {
222 | if (mPanelSlideListener != null) {
223 | mPanelSlideListener.onPanelClosed(panel);
224 | }
225 | sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
226 | }
227 |
228 | void updateObscuredViewsVisibility(View panel) {
229 | final int startBound = getPaddingTop();
230 | final int endBound = getHeight() - getPaddingBottom();
231 | final int leftBound = getPaddingLeft();
232 | final int rightBound = getWidth() - getPaddingRight();
233 | final int left;
234 | final int right;
235 | final int top;
236 | final int bottom;
237 | if (panel != null && viewIsOpaque(panel)) {
238 | left = panel.getLeft();
239 | right = panel.getRight();
240 | top = panel.getTop();
241 | bottom = panel.getBottom();
242 | } else {
243 | left = right = top = bottom = 0;
244 | }
245 |
246 | for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
247 | final View child = getChildAt(i);
248 |
249 | if (child == panel) {
250 | break;
251 | }
252 |
253 | final int clampedChildTop = Math.max(startBound, child.getTop());
254 | final int clampedChildLeft = Math.max(leftBound, child.getLeft());
255 | final int clampedChildBottom = Math.min(endBound, child.getBottom());
256 | final int clampedChildRight = Math.min(rightBound, child.getRight());
257 | final int vis;
258 | if (clampedChildLeft >= left && clampedChildTop >= top &&
259 | clampedChildRight <= right && clampedChildBottom <= bottom) {
260 | vis = INVISIBLE;
261 | } else {
262 | vis = VISIBLE;
263 | }
264 | child.setVisibility(vis);
265 | }
266 | }
267 |
268 | public void setEdgeSize(int offset) {
269 | mEdgeSize = offset;
270 | }
271 |
272 | void setAllChildrenVisible() {
273 | for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
274 | final View child = getChildAt(i);
275 | if (child.getVisibility() == INVISIBLE) {
276 | child.setVisibility(VISIBLE);
277 | }
278 | }
279 | }
280 |
281 | private void onPanelDragged(int newTop) {
282 | if (mSlideableView == null) {
283 | mSlideOffset = 0;
284 | return;
285 | }
286 |
287 | final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
288 |
289 | final int newStart = newTop;
290 |
291 | final int paddingStart = getPaddingTop();
292 | final int lpMargin = lp.topMargin;
293 | final int startBound = paddingStart + lpMargin;
294 |
295 | mSlideOffset = (float) (newStart - startBound) / mSlideRange;
296 |
297 | if (mParallaxBy != 0) {
298 | parallaxOtherViews(mSlideOffset);
299 | }
300 |
301 | if (lp.dimWhenOffset) {
302 | dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
303 | }
304 |
305 | dispatchOnPanelSlide(mSlideableView);
306 | }
307 |
308 | private void parallaxOtherViews(float slideOffset) {
309 | final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams();
310 | final boolean dimViews = slideLp.dimWhenOffset &&
311 | slideLp.topMargin <= 0;
312 | final int childCount = getChildCount();
313 | for (int i = 0; i < childCount; i++) {
314 | final View v = getChildAt(i);
315 | if (v == mSlideableView) continue;
316 |
317 | final int oldOffset = (int) ((1 - mParallaxOffset) * mParallaxBy);
318 | mParallaxOffset = slideOffset;
319 | final int newOffset = (int) ((1 - slideOffset) * mParallaxBy);
320 | final int dy = oldOffset - newOffset;
321 |
322 | v.offsetTopAndBottom(dy);
323 |
324 | if (dimViews) {
325 | dimChildView(v, 1 - mParallaxOffset, mCoveredFadeColor);
326 | }
327 | }
328 | }
329 |
330 | private void dimChildView(View v, float mag, int fadeColor) {
331 | final LayoutParams lp = (LayoutParams) v.getLayoutParams();
332 |
333 | if (mag > 0 && fadeColor != 0) {
334 | final int baseAlpha = (fadeColor & 0xff000000) >>> 24;
335 | int imag = (int) (baseAlpha * mag);
336 | int color = imag << 24 | (fadeColor & 0xffffff);
337 | if (lp.dimPaint == null) {
338 | lp.dimPaint = new Paint();
339 | }
340 | lp.dimPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_OVER));
341 | if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_HARDWARE) {
342 | ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, lp.dimPaint);
343 | }
344 | invalidateChildRegion(v);
345 | } else if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_NONE) {
346 | if (lp.dimPaint != null) {
347 | lp.dimPaint.setColorFilter(null);
348 | }
349 | final DisableLayerRunnable dlr = new DisableLayerRunnable(v);
350 | mPostedRunnables.add(dlr);
351 | ViewCompat.postOnAnimation(this, dlr);
352 | }
353 | }
354 |
355 | private static boolean viewIsOpaque(View v) {
356 | if (ViewCompat.isOpaque(v)) return true;
357 |
358 | // View#isOpaque didn't take all valid opaque scrollbar modes into account
359 | // before API 18 (JB-MR2). On newer devices rely solely on isOpaque above and return false
360 | // here. On older devices, check the view's background drawable directly as a fallback.
361 | if (Build.VERSION.SDK_INT >= 18) return false;
362 |
363 | final Drawable bg = v.getBackground();
364 | if (bg != null) {
365 | return bg.getOpacity() == PixelFormat.OPAQUE;
366 | }
367 | return false;
368 | }
369 |
370 | @Override
371 | protected void onAttachedToWindow() {
372 | super.onAttachedToWindow();
373 | mFirstLayout = true;
374 | }
375 |
376 | @Override
377 | protected void onDetachedFromWindow() {
378 | super.onDetachedFromWindow();
379 | mFirstLayout = true;
380 |
381 | for (int i = 0, count = mPostedRunnables.size(); i < count; i++) {
382 | final DisableLayerRunnable dlr = mPostedRunnables.get(i);
383 | dlr.run();
384 | }
385 | mPostedRunnables.clear();
386 | }
387 |
388 | @Override
389 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
390 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
391 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
392 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
393 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
394 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
395 |
396 | if (heightMode != MeasureSpec.EXACTLY) {
397 | if (isInEditMode()) {
398 | if (heightMode == MeasureSpec.AT_MOST) {
399 | heightMode = MeasureSpec.EXACTLY;
400 | } else if (heightMode == MeasureSpec.UNSPECIFIED) {
401 | heightMode = MeasureSpec.EXACTLY;
402 | heightSize = 300;
403 | }
404 | } else {
405 | throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
406 | }
407 | } else if (widthMode == MeasureSpec.UNSPECIFIED) {
408 | if (isInEditMode()) {
409 | widthMode = MeasureSpec.AT_MOST;
410 | widthSize = 300;
411 | } else {
412 | throw new IllegalStateException("Width must not be UNSPECIFIED");
413 | }
414 | }
415 |
416 | int layoutWidth = 0;
417 | int maxLayoutWidth = -1;
418 | switch (widthMode) {
419 | case MeasureSpec.EXACTLY:
420 | layoutWidth = maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
421 | break;
422 | case MeasureSpec.AT_MOST:
423 | maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
424 | break;
425 | }
426 |
427 | float weightSum = 0;
428 | boolean canSlide = false;
429 | final int heightAvailable = heightSize - getPaddingTop() - getPaddingBottom();
430 | int heightRemaining = heightAvailable;
431 | final int childCount = getChildCount();
432 |
433 | if (childCount > 2) {
434 | Log.e(TAG, "onMeasure: More than two child views are not supported.");
435 | }
436 |
437 | // We'll find the current one below.
438 | mSlideableView = null;
439 |
440 | // First pass. Measure based on child LayoutParams width/height.
441 | // Weight will incur a second pass.
442 | for (int i = 0; i < childCount; i++) {
443 | final View child = getChildAt(i);
444 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
445 |
446 | if (child.getVisibility() == GONE) {
447 | lp.dimWhenOffset = false;
448 | continue;
449 | }
450 |
451 | if (lp.weight > 0) {
452 | weightSum += lp.weight;
453 |
454 | // If we have no height, weight is the only contributor to the final size.
455 | // Measure this view on the weight pass only.
456 | if (lp.height == 0) continue;
457 | }
458 |
459 | int childWidthSpec;
460 | if (lp.width == LayoutParams.WRAP_CONTENT) {
461 | childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST);
462 | } else if (lp.width == LayoutParams.MATCH_PARENT) {
463 | childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.EXACTLY);
464 | } else {
465 | childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
466 | }
467 |
468 | int childHeightSpec;
469 | final int verticalMargin = lp.topMargin + lp.bottomMargin;
470 | if (lp.height == LayoutParams.WRAP_CONTENT) {
471 | childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin, MeasureSpec.AT_MOST);
472 | } else if (lp.height == LayoutParams.MATCH_PARENT) {
473 | childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin, MeasureSpec.EXACTLY);
474 | } else {
475 | childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
476 | }
477 |
478 | child.measure(childWidthSpec, childHeightSpec);
479 | final int childWidth = child.getMeasuredWidth();
480 | final int childHeight = child.getMeasuredHeight();
481 |
482 | if (widthMode == MeasureSpec.AT_MOST && childWidth > layoutWidth) {
483 | layoutWidth = Math.min(childWidth, maxLayoutWidth);
484 | }
485 |
486 | heightRemaining -= childHeight;
487 | canSlide |= lp.slideable = heightRemaining < 0;
488 | if (lp.slideable) {
489 | mSlideableView = child;
490 | }
491 | }
492 |
493 | // Resolve weight and make sure non-sliding panels are smaller than the full screen.
494 | if (canSlide || weightSum > 0) {
495 | final int fixedPanelHeightLimit = heightAvailable - mOverhangSize;
496 |
497 | for (int i = 0; i < childCount; i++) {
498 | final View child = getChildAt(i);
499 |
500 | if (child.getVisibility() == GONE) {
501 | continue;
502 | }
503 |
504 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
505 |
506 | if (child.getVisibility() == GONE) {
507 | continue;
508 | }
509 |
510 | final boolean skippedFirstPass = lp.height == 0 && lp.weight > 0;
511 | final int measuredHeight = skippedFirstPass ? 0 : child.getMeasuredHeight();
512 | if (canSlide && child != mSlideableView) {
513 | if (lp.height < 0 && (measuredHeight > fixedPanelHeightLimit || lp.weight > 0)) {
514 | // Fixed panels in a sliding configuration should
515 | // be clamped to the fixed panel limit.
516 | final int childWidthSpec;
517 | if (skippedFirstPass) {
518 | // Do initial width measurement if we skipped measuring this view
519 | // the first time around.
520 | if (lp.width == LayoutParams.WRAP_CONTENT) {
521 | childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST);
522 | } else if (lp.width == LayoutParams.MATCH_PARENT) {
523 | childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.EXACTLY);
524 | } else {
525 | childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
526 | }
527 | } else {
528 | childWidthSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
529 | MeasureSpec.EXACTLY);
530 | }
531 |
532 | final int childHeightSpec = MeasureSpec.makeMeasureSpec(fixedPanelHeightLimit, MeasureSpec.EXACTLY);
533 | child.measure(childWidthSpec, childHeightSpec);
534 | }
535 | } else if (lp.weight > 0) {
536 | int childWidthSpec;
537 | if (lp.height == 0) {
538 | // This was skipped the first time; figure out a real width spec.
539 | if (lp.width == LayoutParams.WRAP_CONTENT) {
540 | childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST);
541 | } else if (lp.width == LayoutParams.MATCH_PARENT) {
542 | childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.EXACTLY);
543 | } else {
544 | childWidthSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY);
545 | }
546 | } else {
547 | childWidthSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY);
548 | }
549 |
550 | if (canSlide) {
551 | final int verticalMargin = lp.topMargin + lp.bottomMargin;
552 | final int newHeight = heightAvailable - verticalMargin;
553 | final int childHeightSpec = MeasureSpec.makeMeasureSpec(newHeight,
554 | MeasureSpec.EXACTLY);
555 | if (measuredHeight != newHeight) {
556 | child.measure(childWidthSpec, childHeightSpec);
557 | }
558 | } else {
559 | // Distribute the extra height proportionally similar to LinearLayout
560 | final int heightToDistribute = Math.max(0, heightRemaining);
561 | final int addedHeight = (int) (lp.weight * heightToDistribute / weightSum);
562 | final int childHeightSpec = MeasureSpec.makeMeasureSpec(
563 | measuredHeight + addedHeight, MeasureSpec.EXACTLY);
564 | child.measure(childWidthSpec, childHeightSpec);
565 | }
566 | }
567 | }
568 | }
569 |
570 | final int measuredHeight = heightSize;
571 | final int measuredWidth = layoutWidth + getPaddingLeft() + getPaddingRight();
572 |
573 | setMeasuredDimension(measuredWidth, measuredHeight);
574 | mCanSlide = canSlide;
575 |
576 | if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) {
577 | // Cancel scrolling in progress, it's no longer relevant.
578 | mDragHelper.abort();
579 | }
580 | }
581 |
582 | @Override
583 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
584 | mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP);
585 |
586 | final int height = b - t;
587 | final int paddingStart = getPaddingTop();
588 | final int paddingEnd = getPaddingBottom();
589 | final int paddingLeft = getPaddingLeft();
590 |
591 | final int childCount = getChildCount();
592 | int yStart = paddingStart;
593 | int nextYStart = yStart;
594 |
595 | if (mFirstLayout) {
596 | mSlideOffset = mCanSlide && mPreservedOpenState ? 1.f : 0.f;
597 | }
598 |
599 | for (int i = 0; i < childCount; i++) {
600 | final View child = getChildAt(i);
601 |
602 | if (child.getVisibility() == GONE) {
603 | continue;
604 | }
605 |
606 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
607 |
608 | final int childHeight = child.getMeasuredHeight();
609 | int offset = 0;
610 |
611 | if (lp.slideable) {
612 | final int margin = lp.topMargin + lp.bottomMargin;
613 | final int range = Math.min(nextYStart,
614 | height - paddingEnd - mOverhangSize) - yStart - margin;
615 | mSlideRange = range;
616 | final int lpMargin = lp.topMargin;
617 | lp.dimWhenOffset = yStart + lpMargin + range + childHeight / 2 >
618 | height - paddingEnd;
619 | final int pos = (int) (range * mSlideOffset);
620 | yStart += pos + lpMargin;
621 | mSlideOffset = (float) pos / mSlideRange;
622 | } else if (mCanSlide && mParallaxBy != 0) {
623 | offset = (int) ((1 - mSlideOffset) * mParallaxBy);
624 | yStart = nextYStart;
625 | } else {
626 | yStart = nextYStart;
627 | }
628 |
629 | final int childTop = yStart - offset;
630 | final int childBottom = childTop + childHeight;
631 |
632 | final int childLeft = paddingLeft;
633 | final int childRight = childLeft + child.getMeasuredWidth();
634 | child.layout(paddingLeft, childTop, childRight, childBottom);
635 |
636 | nextYStart += child.getHeight();
637 | }
638 |
639 | if (mFirstLayout) {
640 | if (mCanSlide) {
641 | if (mParallaxBy != 0) {
642 | parallaxOtherViews(mSlideOffset);
643 | }
644 | if (((LayoutParams) mSlideableView.getLayoutParams()).dimWhenOffset) {
645 | dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
646 | }
647 | } else {
648 | // Reset the dim level of all children; it's irrelevant when nothing moves.
649 | for (int i = 0; i < childCount; i++) {
650 | dimChildView(getChildAt(i), 0, mSliderFadeColor);
651 | }
652 | }
653 | updateObscuredViewsVisibility(mSlideableView);
654 | }
655 |
656 | mFirstLayout = false;
657 | }
658 |
659 | @Override
660 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
661 | super.onSizeChanged(w, h, oldw, oldh);
662 | // Recalculate sliding panes and their details
663 | if (w != oldw) {
664 | mFirstLayout = true;
665 | }
666 | }
667 |
668 | @Override
669 | public void requestChildFocus(View child, View focused) {
670 | super.requestChildFocus(child, focused);
671 | if (!isInTouchMode() && !mCanSlide) {
672 | mPreservedOpenState = child == mSlideableView;
673 | }
674 | }
675 |
676 | @Override
677 | public boolean onInterceptTouchEvent(MotionEvent ev) {
678 | final int action = MotionEventCompat.getActionMasked(ev);
679 |
680 | // Preserve the open state based on the last view that was touched.
681 | if (!mCanSlide && action == MotionEvent.ACTION_DOWN && getChildCount() > 1) {
682 | // After the first things will be slideable.
683 | final View secondChild = getChildAt(1);
684 | if (secondChild != null) {
685 | mPreservedOpenState = !mDragHelper.isViewUnder(secondChild,
686 | (int) ev.getX(), (int) ev.getY());
687 | }
688 | }
689 |
690 | if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
691 | mDragHelper.cancel();
692 | return super.onInterceptTouchEvent(ev);
693 | }
694 |
695 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
696 | mDragHelper.cancel();
697 | }
698 |
699 | boolean interceptTap = false;
700 |
701 | switch (action) {
702 | case MotionEvent.ACTION_DOWN: {
703 | mIsUnableToDrag = false;
704 | final float x = ev.getX();
705 | final float y = ev.getY();
706 | mInitialMotionX = x;
707 | mInitialMotionY = y;
708 |
709 | if (mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y) &&
710 | isDimmed(mSlideableView)) {
711 | interceptTap = true;
712 | }
713 | break;
714 | }
715 |
716 | case MotionEvent.ACTION_MOVE: {
717 | final float x = ev.getX();
718 | final float y = ev.getY();
719 | final float adx = Math.abs(x - mInitialMotionX);
720 | final float ady = Math.abs(y - mInitialMotionY);
721 | final int slop = mDragHelper.getTouchSlop();
722 | if (ady > slop && adx > ady) {
723 | mDragHelper.cancel();
724 | mIsUnableToDrag = true;
725 | return false;
726 | }
727 | }
728 | }
729 |
730 | final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
731 |
732 | return interceptForDrag || interceptTap;
733 | }
734 |
735 | @Override
736 | public boolean onTouchEvent(MotionEvent event) {
737 | if (mEdgeSize > 0) {
738 | if (!isOpen() && event.getAction() == MotionEvent.ACTION_DOWN && event.getY() > mOverhangSize) {
739 | return false;
740 | }
741 | }
742 |
743 | if (!mCanSlide) return super.onTouchEvent(event);
744 |
745 | mDragHelper.processTouchEvent(event);
746 |
747 | final int action = event.getAction();
748 | boolean wantTouchEvents = true;
749 |
750 | switch (action & MotionEventCompat.ACTION_MASK) {
751 | case MotionEvent.ACTION_DOWN: {
752 | final float x = event.getX();
753 | final float y = event.getY();
754 | mInitialMotionX = x;
755 | mInitialMotionY = y;
756 | break;
757 | }
758 |
759 | case MotionEvent.ACTION_UP: {
760 | if (isDimmed(mSlideableView)) {
761 | final float x = event.getX();
762 | final float y = event.getY();
763 | final float dx = x - mInitialMotionX;
764 | final float dy = y - mInitialMotionY;
765 | final int slop = mDragHelper.getTouchSlop();
766 | if (dx * dx + dy * dy < slop * slop &&
767 | mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y)) {
768 | // Taps close a dimmed open pane.
769 | closePane(mSlideableView, 0);
770 | break;
771 | }
772 |
773 | }
774 | break;
775 | }
776 | }
777 |
778 | return wantTouchEvents;
779 | }
780 |
781 | private boolean closePane(View pane, int initialVelocity) {
782 | if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) {
783 | mPreservedOpenState = false;
784 | return true;
785 | }
786 |
787 | return false;
788 | }
789 |
790 | private boolean openPane(View pane, int initialVelocity) {
791 | if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
792 | mPreservedOpenState = true;
793 | return true;
794 | }
795 |
796 | return false;
797 | }
798 |
799 | public boolean openPane() {
800 | return openPane(mSlideableView, 0);
801 | }
802 |
803 | public boolean closePane() {
804 | return closePane(mSlideableView, 0);
805 | }
806 |
807 | public boolean isOpen() {
808 | return !mCanSlide || mSlideOffset == 1;
809 | }
810 |
811 | public boolean isSlideable() {
812 | return mCanSlide;
813 | }
814 |
815 | @Override
816 | protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
817 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
818 | boolean result;
819 | final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
820 |
821 | if (mCanSlide && !lp.slideable && mSlideableView != null) {
822 | // Clip against the slider; no sense drawing what will immediately be covered.
823 | canvas.getClipBounds(mTmpRect);
824 | mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
825 | canvas.clipRect(mTmpRect);
826 | }
827 |
828 | if (Build.VERSION.SDK_INT >= 11) {
829 | result = super.drawChild(canvas, child, drawingTime);
830 | } else {
831 | if (lp.dimWhenOffset && mSlideOffset > 0) {
832 | if (!child.isDrawingCacheEnabled()) {
833 | child.setDrawingCacheEnabled(true);
834 | }
835 | final Bitmap cache = child.getDrawingCache();
836 | if (cache != null) {
837 | canvas.drawBitmap(cache, child.getLeft(), child.getTop(), lp.dimPaint);
838 | result = false;
839 | } else {
840 | result = super.drawChild(canvas, child, drawingTime);
841 | }
842 | } else {
843 | if (child.isDrawingCacheEnabled()) {
844 | child.setDrawingCacheEnabled(false);
845 | }
846 | result = super.drawChild(canvas, child, drawingTime);
847 | }
848 | }
849 |
850 | canvas.restoreToCount(save);
851 |
852 | return result;
853 | }
854 |
855 | private void invalidateChildRegion(View v) {
856 | IMPL.invalidateChildRegion(this, v);
857 | }
858 |
859 | boolean smoothSlideTo(float slideOffset, int velocity) {
860 | if (!mCanSlide) return false;
861 |
862 | final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
863 |
864 | int startBound = getPaddingTop() + lp.topMargin;
865 | int y = (int) (startBound + slideOffset * mSlideRange);
866 |
867 | if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
868 | setAllChildrenVisible();
869 | ViewCompat.postInvalidateOnAnimation(this);
870 | return true;
871 | }
872 |
873 | return false;
874 | }
875 |
876 | @Override
877 | public void computeScroll() {
878 | if (mDragHelper.continueSettling(true)) {
879 | if (!mCanSlide) {
880 | mDragHelper.abort();
881 | return;
882 | }
883 |
884 | ViewCompat.postInvalidateOnAnimation(this);
885 | }
886 | }
887 |
888 | public void setShadowDrawableTop(Drawable d) {
889 | mShadowDrawableTop = d;
890 | }
891 |
892 | public void setShadowResourceTop(int resId) {
893 | setShadowDrawableTop(getResources().getDrawable(resId));
894 | }
895 |
896 | @Override
897 | public void draw(Canvas canvas) {
898 | super.draw(canvas);
899 | Drawable shadowDrawable = mShadowDrawableTop;
900 |
901 | final View shadowView = getChildCount() > 1 ? getChildAt(1) : null;
902 | if (shadowView == null || shadowDrawable == null) return;
903 |
904 | final int left = shadowView.getLeft();
905 | final int right = shadowView.getRight();
906 |
907 | final int shadowHeight = shadowDrawable.getIntrinsicHeight();
908 | final int top;
909 | final int bottom;
910 |
911 | bottom = shadowView.getTop();
912 | top = bottom - shadowHeight;
913 |
914 | shadowDrawable.setBounds(left, top, right, bottom);
915 | shadowDrawable.draw(canvas);
916 | }
917 |
918 | protected boolean canScroll(View v, boolean checkV, int dy, int x, int y) {
919 | if (v instanceof ViewGroup) {
920 | final ViewGroup group = (ViewGroup) v;
921 | final int scrollX = v.getScrollX();
922 | final int scrollY = v.getScrollY();
923 | final int count = group.getChildCount();
924 |
925 | // Count backwards - let topmost views consume scroll distance first.
926 | for (int i = count - 1; i >= 0; i--) {
927 | // TODO: Add versioned support here for transformed views.
928 | // This will not work for transformed views in Honeycomb+
929 | final View child = group.getChildAt(i);
930 | if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
931 | y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
932 | canScroll(child, true, dy, x + scrollX - child.getLeft(),
933 | y + scrollY - child.getTop())) {
934 | return true;
935 | }
936 | }
937 | }
938 |
939 | return checkV && ViewCompat.canScrollVertically(v, -dy);
940 | }
941 |
942 | boolean isDimmed(View child) {
943 | if (child == null) return false;
944 |
945 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
946 | return mCanSlide && lp.dimWhenOffset && mSlideOffset > 0;
947 | }
948 |
949 | @Override
950 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
951 | return new LayoutParams();
952 | }
953 |
954 | @Override
955 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
956 | return p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p)
957 | : new LayoutParams(p);
958 | }
959 |
960 | @Override
961 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
962 | return p instanceof LayoutParams && super.checkLayoutParams(p);
963 | }
964 |
965 | @Override
966 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
967 | return new LayoutParams(getContext(), attrs);
968 | }
969 |
970 | @Override
971 | protected Parcelable onSaveInstanceState() {
972 | Parcelable superState = super.onSaveInstanceState();
973 |
974 | SavedState ss = new SavedState(superState);
975 | ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState;
976 |
977 | return ss;
978 | }
979 |
980 | @Override
981 | protected void onRestoreInstanceState(Parcelable state) {
982 | SavedState ss = (SavedState) state;
983 | super.onRestoreInstanceState(state);
984 |
985 | if (ss.isOpen) {
986 | openPane();
987 | } else {
988 | closePane();
989 | }
990 | mPreservedOpenState = ss.isOpen;
991 | }
992 |
993 | static class SavedState extends BaseSavedState {
994 | boolean isOpen;
995 |
996 | SavedState(Parcelable superState) {
997 | super(superState);
998 | }
999 |
1000 | private SavedState(Parcel source) {
1001 | super(source);
1002 | isOpen = source.readInt() != 0;
1003 | }
1004 |
1005 | @Override
1006 | public void writeToParcel(Parcel dest, int flags) {
1007 | super.writeToParcel(dest, flags);
1008 | dest.writeInt(isOpen ? 1 : 0);
1009 | }
1010 |
1011 | public static final Creator CREATOR =
1012 | new Creator() {
1013 | public SavedState createFromParcel(Parcel in) {
1014 | return new SavedState(in);
1015 | }
1016 |
1017 | public SavedState[] newArray(int size) {
1018 | return new SavedState[size];
1019 | }
1020 | };
1021 | }
1022 |
1023 | private class DisableLayerRunnable implements Runnable {
1024 | final View mChildView;
1025 |
1026 | DisableLayerRunnable(View childView) {
1027 | mChildView = childView;
1028 | }
1029 |
1030 | @Override
1031 | public void run() {
1032 | if (mChildView.getParent() == SlidingUpPaneLayout.this) {
1033 | ViewCompat.setLayerType(mChildView, ViewCompat.LAYER_TYPE_NONE, null);
1034 | invalidateChildRegion(mChildView);
1035 | }
1036 | mPostedRunnables.remove(this);
1037 | }
1038 | }
1039 |
1040 | public static class LayoutParams extends MarginLayoutParams {
1041 | private static final int[] ATTRS = new int[]{
1042 | android.R.attr.layout_weight
1043 | };
1044 |
1045 | /**
1046 | * The weighted proportion of how much of the leftover space
1047 | * this child should consume after measurement.
1048 | */
1049 | public float weight = 0;
1050 |
1051 | /**
1052 | * True if this pane is the slideable pane in the layout.
1053 | */
1054 | boolean slideable;
1055 |
1056 | /**
1057 | * True if this view should be drawn dimmed
1058 | * when it's been offset from its default position.
1059 | */
1060 | boolean dimWhenOffset;
1061 |
1062 | Paint dimPaint;
1063 |
1064 | public LayoutParams() {
1065 | super(MATCH_PARENT, MATCH_PARENT);
1066 | }
1067 |
1068 | public LayoutParams(int width, int height) {
1069 | super(width, height);
1070 | }
1071 |
1072 | public LayoutParams(ViewGroup.LayoutParams source) {
1073 | super(source);
1074 | }
1075 |
1076 | public LayoutParams(MarginLayoutParams source) {
1077 | super(source);
1078 | }
1079 |
1080 | public LayoutParams(LayoutParams source) {
1081 | super(source);
1082 | this.weight = source.weight;
1083 | }
1084 |
1085 | public LayoutParams(Context c, AttributeSet attrs) {
1086 | super(c, attrs);
1087 |
1088 | final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1089 | this.weight = a.getFloat(0, 0);
1090 | a.recycle();
1091 | }
1092 | }
1093 |
1094 | interface SlidingPanelLayoutImpl {
1095 | void invalidateChildRegion(SlidingUpPaneLayout parent, View child);
1096 | }
1097 |
1098 | static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl {
1099 | @Override
1100 | public void invalidateChildRegion(SlidingUpPaneLayout parent, View child) {
1101 | ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(),
1102 | child.getRight(), child.getBottom());
1103 | }
1104 | }
1105 |
1106 | static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase {
1107 | /*
1108 | * Private API hacks! Nasty! Bad!
1109 | *
1110 | * In Jellybean, some optimizations in the hardware UI renderer
1111 | * prevent a changed Paint on a View using a hardware layer from having
1112 | * the intended effect. This twiddles some internal bits on the view to force
1113 | * it to recreate the display list.
1114 | */
1115 | private Method mGetDisplayList;
1116 | private Field mRecreateDisplayList;
1117 |
1118 | SlidingPanelLayoutImplJB() {
1119 | try {
1120 | mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null);
1121 | } catch (NoSuchMethodException e) {
1122 | Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e);
1123 | }
1124 | try {
1125 | mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList");
1126 | mRecreateDisplayList.setAccessible(true);
1127 | } catch (NoSuchFieldException e) {
1128 | Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e);
1129 | }
1130 | }
1131 |
1132 | @Override
1133 | public void invalidateChildRegion(SlidingUpPaneLayout parent, View child) {
1134 | if (mGetDisplayList != null && mRecreateDisplayList != null) {
1135 | try {
1136 | mRecreateDisplayList.setBoolean(child, true);
1137 | mGetDisplayList.invoke(child, (Object[]) null);
1138 | } catch (Exception e) {
1139 | Log.e(TAG, "Error refreshing display list state", e);
1140 | }
1141 | } else {
1142 | // Slow path. REALLY slow path. Let's hope we don't get here.
1143 | child.invalidate();
1144 | return;
1145 | }
1146 | super.invalidateChildRegion(parent, child);
1147 | }
1148 | }
1149 |
1150 | static class SlidingPanelLayoutImplJBMR1 extends SlidingPanelLayoutImplBase {
1151 | @Override
1152 | public void invalidateChildRegion(SlidingUpPaneLayout parent, View child) {
1153 | ViewCompat.setLayerPaint(child, ((LayoutParams) child.getLayoutParams()).dimPaint);
1154 | }
1155 | }
1156 |
1157 | class AccessibilityDelegate extends AccessibilityDelegateCompat {
1158 | private final Rect mTmpRect = new Rect();
1159 |
1160 | @Override
1161 | public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
1162 | final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
1163 | super.onInitializeAccessibilityNodeInfo(host, superNode);
1164 | copyNodeInfoNoChildren(info, superNode);
1165 | superNode.recycle();
1166 |
1167 | info.setClassName(SlidingUpPaneLayout.class.getName());
1168 | info.setSource(host);
1169 |
1170 | final ViewParent parent = ViewCompat.getParentForAccessibility(host);
1171 | if (parent instanceof View) {
1172 | info.setParent((View) parent);
1173 | }
1174 |
1175 | // This is a best-approximation of addChildrenForAccessibility()
1176 | // that accounts for filtering.
1177 | final int childCount = getChildCount();
1178 | for (int i = 0; i < childCount; i++) {
1179 | final View child = getChildAt(i);
1180 | if (!filter(child) && (child.getVisibility() == View.VISIBLE)) {
1181 | // Force importance to "yes" since we can't read the value.
1182 | ViewCompat.setImportantForAccessibility(
1183 | child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
1184 | info.addChild(child);
1185 | }
1186 | }
1187 | }
1188 |
1189 | @Override
1190 | public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
1191 | super.onInitializeAccessibilityEvent(host, event);
1192 |
1193 | event.setClassName(SlidingUpPaneLayout.class.getName());
1194 | }
1195 |
1196 | @Override
1197 | public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1198 | AccessibilityEvent event) {
1199 | if (!filter(child)) {
1200 | return super.onRequestSendAccessibilityEvent(host, child, event);
1201 | }
1202 | return false;
1203 | }
1204 |
1205 | public boolean filter(View child) {
1206 | return isDimmed(child);
1207 | }
1208 |
1209 | /**
1210 | * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
1211 | * seem to be a few elements that are not easily cloneable using the underlying API.
1212 | * Leave it private here as it's not general-purpose useful.
1213 | */
1214 | private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
1215 | AccessibilityNodeInfoCompat src) {
1216 | final Rect rect = mTmpRect;
1217 |
1218 | src.getBoundsInParent(rect);
1219 | dest.setBoundsInParent(rect);
1220 |
1221 | src.getBoundsInScreen(rect);
1222 | dest.setBoundsInScreen(rect);
1223 |
1224 | dest.setVisibleToUser(src.isVisibleToUser());
1225 | dest.setPackageName(src.getPackageName());
1226 | dest.setClassName(src.getClassName());
1227 | dest.setContentDescription(src.getContentDescription());
1228 |
1229 | dest.setEnabled(src.isEnabled());
1230 | dest.setClickable(src.isClickable());
1231 | dest.setFocusable(src.isFocusable());
1232 | dest.setFocused(src.isFocused());
1233 | dest.setAccessibilityFocused(src.isAccessibilityFocused());
1234 | dest.setSelected(src.isSelected());
1235 | dest.setLongClickable(src.isLongClickable());
1236 |
1237 | dest.addAction(src.getActions());
1238 |
1239 | dest.setMovementGranularities(src.getMovementGranularities());
1240 | }
1241 | }
1242 |
1243 | private class DragHelperCallback extends ViewDragHelper.Callback {
1244 |
1245 | @Override
1246 | public boolean tryCaptureView(View child, int pointerId) {
1247 | if (mIsUnableToDrag) return false;
1248 |
1249 | return ((LayoutParams) child.getLayoutParams()).slideable;
1250 | }
1251 |
1252 | @Override
1253 | public void onViewDragStateChanged(int state) {
1254 | if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
1255 | if (mSlideOffset == 0) {
1256 | updateObscuredViewsVisibility(mSlideableView);
1257 | dispatchOnPanelClosed(mSlideableView);
1258 | mPreservedOpenState = false;
1259 | } else {
1260 | dispatchOnPanelOpened(mSlideableView);
1261 | mPreservedOpenState = true;
1262 | }
1263 | }
1264 | }
1265 |
1266 | @Override
1267 | public void onViewCaptured(View capturedChild, int activePointerId) {
1268 | setAllChildrenVisible();
1269 | }
1270 |
1271 | @Override
1272 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1273 | onPanelDragged(top);
1274 | invalidate();
1275 | }
1276 |
1277 | @Override
1278 | public void onViewReleased(View releasedChild, float xvel, float yvel) {
1279 | final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
1280 |
1281 | int top = getPaddingTop() + lp.topMargin;
1282 | if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
1283 | top += mSlideRange;
1284 | }
1285 |
1286 | mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
1287 | invalidate();
1288 | }
1289 |
1290 | @Override
1291 | public int getViewVerticalDragRange(View child) {
1292 | return mSlideRange;
1293 | }
1294 |
1295 | @Override
1296 | public int clampViewPositionHorizontal(View child, int left, int dx) {
1297 | return child.getLeft();
1298 | }
1299 |
1300 | @Override
1301 | public int clampViewPositionVertical(View child, int top, int dy) {
1302 | final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
1303 |
1304 | int startBound = getPaddingTop() + lp.topMargin;
1305 | int endBound = startBound + mSlideRange;
1306 | final int newTop = Math.min(Math.max(top, startBound), endBound);
1307 |
1308 | return newTop;
1309 | }
1310 |
1311 | @Override
1312 | public void onEdgeDragStarted(int edgeFlags, int pointerId) {
1313 | mDragHelper.captureChildView(mSlideableView, pointerId);
1314 | }
1315 | }
1316 | }
1317 |
--------------------------------------------------------------------------------