OSDN Git Service

d12956feb8259647afb5806129a97a9c9652625b
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / eleven / widgets / ShowHideMasterLayout.java
1 /*
2  * Copyright 2012 Google Inc. Licensed under the Apache License, Version 2.0
3  * (the "License"); you may not use this file except in compliance with the
4  * License. You may obtain a copy of the License at
5  * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6  * or agreed to in writing, software distributed under the License is
7  * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8  * KIND, either express or implied. See the License for the specific language
9  * governing permissions and limitations under the License.
10  */
11
12 package com.cyanogenmod.eleven.widgets;
13
14 import android.animation.Animator;
15 import android.animation.AnimatorSet;
16 import android.animation.ObjectAnimator;
17 import android.annotation.TargetApi;
18 import android.content.Context;
19 import android.os.Build;
20 import android.util.AttributeSet;
21 import android.view.GestureDetector;
22 import android.view.MotionEvent;
23 import android.view.View;
24 import android.view.ViewConfiguration;
25 import android.view.ViewGroup;
26
27 /**
28  * A layout that supports the Show/Hide pattern for portrait tablet layouts. See
29  * <a href=
30  * "http://developer.android.com/design/patterns/multi-pane-layouts.html#orientation"
31  * >Android Design &gt; Patterns &gt; Multi-pane Layouts & gt; Compound Views
32  * and Orientation Changes</a> for more details on this pattern. This layout
33  * should normally be used in association with the Up button. Specifically, show
34  * the master pane using {@link #showMaster(boolean, int)} when the Up button is
35  * pressed. If the master pane is visible, defer to normal Up behavior.
36  * <p>
37  * TODO: swiping should be more tactile and actually follow the user's finger.
38  * <p>
39  * Requires API level 11
40  */
41 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
42 public class ShowHideMasterLayout extends ViewGroup implements Animator.AnimatorListener {
43
44     /**
45      * A flag for {@link #showMaster(boolean, int)} indicating that the change
46      * in visiblity should not be animated.
47      */
48     public final static int FLAG_IMMEDIATE = 0x1;
49
50     private View sMasterView;
51
52     private View mDetailView;
53
54     private OnMasterVisibilityChangedListener mOnMasterVisibilityChangedListener;
55
56     private GestureDetector mGestureDetector;
57
58     private Runnable mShowMasterCompleteRunnable;
59
60     private boolean mFirstShow = true;
61
62     private boolean mMasterVisible = true;
63
64     private boolean mFlingToExposeMaster;
65
66     private boolean mIsAnimating;
67
68     /* The last measured master width, including its margins */
69     private int mTranslateAmount;
70
71     public interface OnMasterVisibilityChangedListener {
72         public void onMasterVisibilityChanged(boolean visible);
73     }
74
75     public ShowHideMasterLayout(final Context context) {
76         super(context);
77         init();
78     }
79
80     public ShowHideMasterLayout(final Context context, final AttributeSet attrs) {
81         super(context, attrs);
82         init();
83     }
84
85     public ShowHideMasterLayout(final Context context, final AttributeSet attrs, final int defStyle) {
86         super(context, attrs, defStyle);
87         init();
88     }
89
90     private void init() {
91         mGestureDetector = new GestureDetector(getContext(), mGestureListener);
92     }
93
94     @Override
95     public LayoutParams generateLayoutParams(final AttributeSet attrs) {
96         return new MarginLayoutParams(getContext(), attrs);
97     }
98
99     @Override
100     protected LayoutParams generateLayoutParams(final LayoutParams p) {
101         return new MarginLayoutParams(p);
102     }
103
104     @Override
105     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
106         final int mCount = getChildCount();
107
108         /* Measure once to find the maximum child size */
109         int sMaxHeight = 0;
110         int sMaxWidth = 0;
111         int mChildState = 0;
112
113         for (int i = 0; i < mCount; i++) {
114             final View mChild = getChildAt(i);
115             if (mChild.getVisibility() == GONE) {
116                 continue;
117             }
118
119             measureChildWithMargins(mChild, widthMeasureSpec, 0, heightMeasureSpec, 0);
120             final MarginLayoutParams mLayoutParams = (MarginLayoutParams)mChild.getLayoutParams();
121             sMaxWidth = Math.max(sMaxWidth, mChild.getMeasuredWidth() + mLayoutParams.leftMargin
122                     + mLayoutParams.rightMargin);
123             sMaxHeight = Math.max(sMaxHeight, mChild.getMeasuredHeight() + mLayoutParams.topMargin
124                     + mLayoutParams.bottomMargin);
125             mChildState = combineMeasuredStates(mChildState, mChild.getMeasuredState());
126         }
127
128         /* Account for padding too */
129         sMaxWidth += getPaddingLeft() + getPaddingRight();
130         sMaxHeight += getPaddingLeft() + getPaddingRight();
131
132         /* Check against our minimum height and width */
133         sMaxHeight = Math.max(sMaxHeight, getSuggestedMinimumHeight());
134         sMaxWidth = Math.max(sMaxWidth, getSuggestedMinimumWidth());
135
136         /* Set our own measured size */
137         setMeasuredDimension(
138                 resolveSizeAndState(sMaxWidth, widthMeasureSpec, mChildState),
139                 resolveSizeAndState(sMaxHeight, heightMeasureSpec,
140                         mChildState << MEASURED_HEIGHT_STATE_SHIFT));
141
142         /* Measure children for them to set their measured dimensions */
143         for (int i = 0; i < mCount; i++) {
144             final View child = getChildAt(i);
145             if (child.getVisibility() == GONE) {
146                 continue;
147             }
148
149             final MarginLayoutParams mLayoutParams = (MarginLayoutParams)child.getLayoutParams();
150
151             int mChildWidthMeasureSpec;
152             int mChildHeightMeasureSpec;
153
154             if (mLayoutParams.width == LayoutParams.MATCH_PARENT) {
155                 mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth()
156                         - getPaddingLeft() - getPaddingRight() - mLayoutParams.leftMargin
157                         - mLayoutParams.rightMargin, MeasureSpec.EXACTLY);
158             } else {
159                 mChildWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft()
160                         + getPaddingRight() + mLayoutParams.leftMargin + mLayoutParams.rightMargin,
161                         mLayoutParams.width);
162             }
163
164             if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
165                 mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight()
166                         - getPaddingTop() - getPaddingBottom() - mLayoutParams.topMargin
167                         - mLayoutParams.bottomMargin, MeasureSpec.EXACTLY);
168             } else {
169                 mChildHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
170                         getPaddingTop() + getPaddingBottom() + mLayoutParams.topMargin
171                                 + mLayoutParams.bottomMargin, mLayoutParams.height);
172             }
173
174             child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
175         }
176     }
177
178     @Override
179     protected void onLayout(final boolean changed, final int l, final int t, final int r,
180             final int b) {
181         updateChildReferences();
182
183         if (sMasterView == null || mDetailView == null) {
184             return;
185         }
186
187         final int sMasterWidth = sMasterView.getMeasuredWidth();
188         final MarginLayoutParams sMasterLp = (MarginLayoutParams)sMasterView.getLayoutParams();
189         final MarginLayoutParams mDetailLp = (MarginLayoutParams)mDetailView.getLayoutParams();
190
191         mTranslateAmount = sMasterWidth + sMasterLp.leftMargin + sMasterLp.rightMargin;
192
193         sMasterView.layout(l + sMasterLp.leftMargin, t + sMasterLp.topMargin, l
194                 + sMasterLp.leftMargin + sMasterWidth, b - sMasterLp.bottomMargin);
195
196         mDetailView.layout(l + mDetailLp.leftMargin + mTranslateAmount, t + mDetailLp.topMargin, r
197                 - mDetailLp.rightMargin + mTranslateAmount, b - mDetailLp.bottomMargin);
198
199         /* Update translationX values */
200         if (!mIsAnimating) {
201             final float mTranslationX = mMasterVisible ? 0 : -mTranslateAmount;
202             sMasterView.setTranslationX(mTranslationX);
203             mDetailView.setTranslationX(mTranslationX);
204         }
205     }
206
207     private void updateChildReferences() {
208         final int mChildCount = getChildCount();
209         sMasterView = mChildCount > 0 ? getChildAt(0) : null;
210         mDetailView = mChildCount > 1 ? getChildAt(1) : null;
211     }
212
213     /**
214      * Allow or disallow the user to flick right on the detail pane to expose
215      * the master pane.
216      *
217      * @param enabled Whether or not to enable this interaction.
218      */
219     public void setFlingToExposeMasterEnabled(final boolean enabled) {
220         mFlingToExposeMaster = enabled;
221     }
222
223     /**
224      * Request the given listener be notified when the master pane is shown or
225      * hidden.
226      *
227      * @param listener The listener to notify when the master pane is shown or
228      *            hidden.
229      */
230     public void setOnMasterVisibilityChangedListener(
231             final OnMasterVisibilityChangedListener listener) {
232         mOnMasterVisibilityChangedListener = listener;
233     }
234
235     /**
236      * Returns whether or not the master pane is visible.
237      *
238      * @return True if the master pane is visible.
239      */
240     public boolean isMasterVisible() {
241         return mMasterVisible;
242     }
243
244     /**
245      * Calls {@link #showMaster(boolean, int, Runnable)} with a null runnable.
246      */
247     public void showMaster(final boolean show, final int flags) {
248         showMaster(show, flags, null);
249     }
250
251     /**
252      * Shows or hides the master pane.
253      *
254      * @param show Whether or not to show the master pane.
255      * @param flags {@link #FLAG_IMMEDIATE} to show/hide immediately, or 0 to
256      *            animate.
257      * @param completeRunnable An optional runnable to run when any animations
258      *            related to this are complete.
259      */
260     public void showMaster(final boolean show, final int flags, final Runnable completeRunnable) {
261         if (!mFirstShow && mMasterVisible == show) {
262             return;
263         }
264
265         mShowMasterCompleteRunnable = completeRunnable;
266         mFirstShow = false;
267
268         mMasterVisible = show;
269         if (mOnMasterVisibilityChangedListener != null) {
270             mOnMasterVisibilityChangedListener.onMasterVisibilityChanged(show);
271         }
272
273         updateChildReferences();
274
275         if (sMasterView == null || mDetailView == null) {
276             return;
277         }
278
279         final float mTranslationX = show ? 0 : -mTranslateAmount;
280
281         if ((flags & FLAG_IMMEDIATE) != 0) {
282             sMasterView.setTranslationX(mTranslationX);
283             mDetailView.setTranslationX(mTranslationX);
284             if (mShowMasterCompleteRunnable != null) {
285                 mShowMasterCompleteRunnable.run();
286                 mShowMasterCompleteRunnable = null;
287             }
288         } else {
289             final long mDuration = getResources()
290                     .getInteger(android.R.integer.config_shortAnimTime);
291
292             /* Animate if we have Honeycomb APIs, don't animate otherwise */
293             mIsAnimating = true;
294             final AnimatorSet mAnimatorSet = new AnimatorSet();
295             sMasterView.setLayerType(LAYER_TYPE_HARDWARE, null);
296             mDetailView.setLayerType(LAYER_TYPE_HARDWARE, null);
297             mAnimatorSet.play(
298                     ObjectAnimator.ofFloat(sMasterView, "translationX", mTranslationX).setDuration(
299                             mDuration)).with(
300                     ObjectAnimator.ofFloat(mDetailView, "translationX", mTranslationX).setDuration(
301                             mDuration));
302             mAnimatorSet.addListener(this);
303             mAnimatorSet.start();
304         }
305     }
306
307     @Override
308     public void requestDisallowInterceptTouchEvent(final boolean disallowIntercept) {
309         // Really bad hack... we really shouldn't do this.
310         // super.requestDisallowInterceptTouchEvent(disallowIntercept);
311     }
312
313     @Override
314     public boolean onInterceptTouchEvent(final MotionEvent event) {
315         if (mFlingToExposeMaster && !mMasterVisible) {
316             mGestureDetector.onTouchEvent(event);
317         }
318
319         if (event.getAction() == MotionEvent.ACTION_DOWN && sMasterView != null && mMasterVisible) {
320             if (event.getX() > mTranslateAmount) {
321                 return true;
322             }
323         }
324         return super.onInterceptTouchEvent(event);
325     }
326
327     @Override
328     public boolean onTouchEvent(final MotionEvent event) {
329         if (mFlingToExposeMaster && !mMasterVisible && mGestureDetector.onTouchEvent(event)) {
330             return true;
331         }
332
333         if (event.getAction() == MotionEvent.ACTION_DOWN && sMasterView != null && mMasterVisible) {
334             if (event.getX() > mTranslateAmount) {
335                 showMaster(false, 0);
336                 return true;
337             }
338         }
339         return super.onTouchEvent(event);
340     }
341
342     @Override
343     public void onAnimationEnd(final Animator animator) {
344         mIsAnimating = false;
345         sMasterView.setLayerType(LAYER_TYPE_NONE, null);
346         mDetailView.setLayerType(LAYER_TYPE_NONE, null);
347         requestLayout();
348         if (mShowMasterCompleteRunnable != null) {
349             mShowMasterCompleteRunnable.run();
350             mShowMasterCompleteRunnable = null;
351         }
352     }
353
354     @Override
355     public void onAnimationCancel(final Animator animator) {
356         mIsAnimating = false;
357         sMasterView.setLayerType(LAYER_TYPE_NONE, null);
358         mDetailView.setLayerType(LAYER_TYPE_NONE, null);
359         requestLayout();
360         if (mShowMasterCompleteRunnable != null) {
361             mShowMasterCompleteRunnable.run();
362             mShowMasterCompleteRunnable = null;
363         }
364     }
365
366     private final GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
367         @Override
368         public boolean onDown(final MotionEvent e) {
369             return true;
370         }
371
372         @Override
373         public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
374                 final float velocityY) {
375             final ViewConfiguration mViewConfig = ViewConfiguration.get(getContext());
376             final float mAbsVelocityX = Math.abs(velocityX);
377             final float mAbsVelocityY = Math.abs(velocityY);
378             if (mFlingToExposeMaster && !mMasterVisible && velocityX > 0
379                     && mAbsVelocityX >= mAbsVelocityY
380                     && mAbsVelocityX > mViewConfig.getScaledMinimumFlingVelocity()
381                     && mAbsVelocityX < mViewConfig.getScaledMaximumFlingVelocity()) {
382                 showMaster(true, 0);
383                 return true;
384             }
385             return super.onFling(e1, e2, velocityX, velocityY);
386         }
387     };
388
389     @Override
390     public void onAnimationStart(final Animator animator) {
391         /* Nothing to do */
392     }
393
394     @Override
395     public void onAnimationRepeat(final Animator animator) {
396         /* Nothing to do */
397     }
398 }