├── .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 | slide_up 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 | --------------------------------------------------------------------------------