├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── checkstyle.xml
├── library
├── AndroidManifest.xml
├── pom.xml
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-ldpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── main.xml
│ └── values
│ │ ├── attrs.xml
│ │ └── ids.xml
└── src
│ └── net
│ └── simonvt
│ └── threepanelayout
│ ├── BuildLayerFrameLayout.java
│ ├── FloatScroller.java
│ ├── NoClickThroughFrameLayout.java
│ ├── Scroller.java
│ ├── SmoothInterpolator.java
│ └── ThreePaneLayout.java
├── pom.xml
└── samples
├── AndroidManifest.xml
├── pom.xml
├── project.properties
├── res
├── drawable-hdpi
│ ├── ic_launcher.png
│ └── menu_arrow.png
├── drawable-ldpi
│ └── ic_launcher.png
├── drawable-mdpi
│ ├── ic_launcher.png
│ └── menu_arrow.png
├── drawable-xhdpi
│ ├── ic_launcher.png
│ └── menu_arrow.png
├── layout
│ ├── fragment_rightpane.xml
│ ├── left_pane.xml
│ ├── main.xml
│ ├── middle_pane.xml
│ └── right_pane.xml
├── values-land
│ └── bools.xml
├── values-v14
│ └── themes.xml
└── values
│ ├── bools.xml
│ ├── strings.xml
│ └── themes.xml
└── src
└── net
└── simonvt
└── threepanelayout
└── samples
├── LeftPaneFragment.java
├── MiddlePaneFragment.java
├── RightPaneFragment.java
└── SamplesActivity.java
/.gitignore:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | !.gitignore
4 | !.travis.yml
5 | !CHANGELOG.md
6 | !README.md
7 | !LICENSE
8 | !checkstyle.xml
9 | !pom.xml
10 |
11 | !art/
12 |
13 | !library/
14 | library/*
15 | !library/src/
16 | !library/res/
17 | !library/AndroidManifest.xml
18 | !library/build.xml
19 | !library/pom.xml
20 | !library/project.properties
21 |
22 | !samples/
23 | samples/*
24 | !samples/src/
25 | !samples/res/
26 | !samples/libs/
27 | !samples/AndroidManifest.xml
28 | !samples/build.xml
29 | !samples/pom.xml
30 | !samples/project.properties
31 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | Version 1.0.0 *(2013-03-31)*
5 | ----------------------------
6 |
7 | Initial release.
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ThreePaneLayout
2 | ===============
3 |
4 | A three-pane layout where up to two panes are visible at a time.
5 |
6 | When the left pane is showing, the middle pane will always be shown. When the
7 | right pane is shown, it's up to the user to decide whether the middle pane is
8 | shown.
9 |
10 |
11 |
12 | Usage
13 | =====
14 |
15 | ThreePaneLayout can be used both in code and xml. It is not possible to
16 | directly define child views in xml or add them by calling `ViewGroup#addView`,
17 | but rather helper methods/attributes should be used.
18 |
19 |
20 |
21 | XML definition
22 | --------------
23 |
24 | ```xml
25 |
33 | ```
34 |
35 |
36 |
37 | Creating in code
38 | ----------------
39 |
40 | ```java
41 | mThreePaneLayout = new ThreePaneLayout(context);
42 | mThreePaneLayout.setLeftPaneLayout(R.layout.left_pane);
43 | mThreePaneLayout.setMiddlePaneLayout(R.layout.middle_pane);
44 | mThreePaneLayout.setRightPaneLayout(R.layout.right_pane);
45 | ```
46 |
47 |
48 |
49 | License
50 | =======
51 |
52 | Copyright 2013 Simon Vig Therkildsen
53 |
54 | Licensed under the Apache License, Version 2.0 (the "License");
55 | you may not use this file except in compliance with the License.
56 | You may obtain a copy of the License at
57 |
58 | http://www.apache.org/licenses/LICENSE-2.0
59 |
60 | Unless required by applicable law or agreed to in writing, software
61 | distributed under the License is distributed on an "AS IS" BASIS,
62 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
63 | See the License for the specific language governing permissions and
64 | limitations under the License.
65 |
--------------------------------------------------------------------------------
/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/library/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/library/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.0.0
5 |
6 |
7 | net.simonvt
8 | threepanelayout-parent
9 | 1.0.2-SNAPSHOT
10 | ../pom.xml
11 |
12 |
13 | threepanelayout
14 | ThreePaneLayout
15 | apklib
16 |
17 |
18 |
19 | com.google.android
20 | android
21 | provided
22 |
23 |
24 |
25 |
26 | src
27 |
28 |
29 |
30 | com.jayway.maven.plugins.android.generation2
31 | android-maven-plugin
32 | true
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/library/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | android.library=true
14 | # Project target.
15 | target=android-16
16 |
--------------------------------------------------------------------------------
/library/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/library/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/library/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/library/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/library/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/library/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/library/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/library/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/library/src/net/simonvt/threepanelayout/BuildLayerFrameLayout.java:
--------------------------------------------------------------------------------
1 | package net.simonvt.threepanelayout;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.util.AttributeSet;
6 | import android.widget.FrameLayout;
7 |
8 | /**
9 | * FrameLayout which caches the hardware layer if available.
10 | *
11 | * If it's not posted twice the layer either wont be built on start, or it'll be built twice.
12 | */
13 | public class BuildLayerFrameLayout extends FrameLayout {
14 |
15 | private boolean mChanged;
16 |
17 | private boolean mHardwareLayersEnabled = true;
18 |
19 | private boolean mAttached;
20 |
21 | private boolean mFirst = true;
22 |
23 | public BuildLayerFrameLayout(Context context) {
24 | super(context);
25 | setLayerType(LAYER_TYPE_HARDWARE, null);
26 | }
27 |
28 | public BuildLayerFrameLayout(Context context, AttributeSet attrs) {
29 | super(context, attrs);
30 | setLayerType(LAYER_TYPE_HARDWARE, null);
31 | }
32 |
33 | public BuildLayerFrameLayout(Context context, AttributeSet attrs, int defStyle) {
34 | super(context, attrs, defStyle);
35 | setLayerType(LAYER_TYPE_HARDWARE, null);
36 | }
37 |
38 | void setHardwareLayersEnabled(boolean enabled) {
39 | mHardwareLayersEnabled = enabled;
40 | }
41 |
42 | @Override
43 | protected void onAttachedToWindow() {
44 | super.onAttachedToWindow();
45 | mAttached = true;
46 | }
47 |
48 | @Override
49 | protected void onDetachedFromWindow() {
50 | super.onDetachedFromWindow();
51 | mAttached = false;
52 | }
53 |
54 | @Override
55 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
56 | super.onSizeChanged(w, h, oldw, oldh);
57 |
58 | if (ThreePaneLayout.USE_TRANSLATIONS && mHardwareLayersEnabled) {
59 | post(new Runnable() {
60 | @Override
61 | public void run() {
62 | mChanged = true;
63 | invalidate();
64 | }
65 | });
66 | }
67 | }
68 |
69 | @Override
70 | protected void dispatchDraw(Canvas canvas) {
71 | super.dispatchDraw(canvas);
72 |
73 | if (mChanged && ThreePaneLayout.USE_TRANSLATIONS) {
74 | post(new Runnable() {
75 | @Override
76 | public void run() {
77 | if (mAttached) {
78 | final int layerType = getLayerType();
79 | // If it's already a hardware layer, it'll be built anyway.
80 | if (layerType != LAYER_TYPE_HARDWARE || mFirst) {
81 | mFirst = false;
82 | setLayerType(LAYER_TYPE_HARDWARE, null);
83 | buildLayer();
84 | setLayerType(LAYER_TYPE_NONE, null);
85 | }
86 | }
87 | }
88 | });
89 |
90 | mChanged = false;
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/library/src/net/simonvt/threepanelayout/FloatScroller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2006 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.simonvt.threepanelayout;
18 |
19 | import android.view.animation.AnimationUtils;
20 | import android.view.animation.Interpolator;
21 |
22 | /**
23 | * This class encapsulates scrolling. The duration of the scroll
24 | * can be passed in the constructor and specifies the maximum time that
25 | * the scrolling animation should take. Past this time, the scrolling is
26 | * automatically moved to its final stage and computeScrollOffset()
27 | * will always return false to indicate that scrolling is over.
28 | */
29 | public class FloatScroller {
30 |
31 | private float mStart;
32 | private float mFinal;
33 |
34 | private float mCurr;
35 | private long mStartTime;
36 | private int mDuration;
37 | private float mDurationReciprocal;
38 | private float mDeltaX;
39 | private boolean mFinished;
40 | private Interpolator mInterpolator;
41 |
42 | /**
43 | * Create a Scroller with the specified interpolator. If the interpolator is
44 | * null, the default (viscous) interpolator will be used. Specify whether or
45 | * not to support progressive "flywheel" behavior in flinging.
46 | */
47 | public FloatScroller(Interpolator interpolator) {
48 | mFinished = true;
49 | mInterpolator = interpolator;
50 | }
51 |
52 | /**
53 | * Returns whether the scroller has finished scrolling.
54 | *
55 | * @return True if the scroller has finished scrolling, false otherwise.
56 | */
57 | public final boolean isFinished() {
58 | return mFinished;
59 | }
60 |
61 | /**
62 | * Force the finished field to a particular value.
63 | *
64 | * @param finished The new finished value.
65 | */
66 | public final void forceFinished(boolean finished) {
67 | mFinished = finished;
68 | }
69 |
70 | /**
71 | * Returns how long the scroll event will take, in milliseconds.
72 | *
73 | * @return The duration of the scroll in milliseconds.
74 | */
75 | public final int getDuration() {
76 | return mDuration;
77 | }
78 |
79 | /**
80 | * Returns the current offset in the scroll.
81 | *
82 | * @return The new offset as an absolute distance from the origin.
83 | */
84 | public final float getCurr() {
85 | return mCurr;
86 | }
87 |
88 | /**
89 | * Returns the start offset in the scroll.
90 | *
91 | * @return The start offset as an absolute distance from the origin.
92 | */
93 | public final float getStart() {
94 | return mStart;
95 | }
96 |
97 | /**
98 | * Returns where the scroll will end. Valid only for "fling" scrolls.
99 | *
100 | * @return The final offset as an absolute distance from the origin.
101 | */
102 | public final float getFinal() {
103 | return mFinal;
104 | }
105 |
106 | public boolean computeScrollOffset() {
107 | if (mFinished) {
108 | return false;
109 | }
110 |
111 | int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
112 |
113 | if (timePassed < mDuration) {
114 | float x = timePassed * mDurationReciprocal;
115 | x = mInterpolator.getInterpolation(x);
116 | mCurr = mStart + x * mDeltaX;
117 |
118 | } else {
119 | mCurr = mFinal;
120 | mFinished = true;
121 | }
122 | return true;
123 | }
124 |
125 | public void startScroll(float start, float delta, int duration) {
126 | mFinished = false;
127 | mDuration = duration;
128 | mStartTime = AnimationUtils.currentAnimationTimeMillis();
129 | mStart = start;
130 | mFinal = start + delta;
131 | mDeltaX = delta;
132 | mDurationReciprocal = 1.0f / (float) mDuration;
133 | }
134 |
135 | /**
136 | * Stops the animation. Contrary to {@link #forceFinished(boolean)},
137 | * aborting the animating cause the scroller to move to the final x and y
138 | * position
139 | *
140 | * @see #forceFinished(boolean)
141 | */
142 | public void abortAnimation() {
143 | mCurr = mFinal;
144 | mFinished = true;
145 | }
146 |
147 | /**
148 | * Extend the scroll animation. This allows a running animation to scroll
149 | * further and longer, when used with {@link #setFinal(float)}.
150 | *
151 | * @param extend Additional time to scroll in milliseconds.
152 | * @see #setFinal(float)
153 | */
154 | public void extendDuration(int extend) {
155 | int passed = timePassed();
156 | mDuration = passed + extend;
157 | mDurationReciprocal = 1.0f / mDuration;
158 | mFinished = false;
159 | }
160 |
161 | /**
162 | * Returns the time elapsed since the beginning of the scrolling.
163 | *
164 | * @return The elapsed time in milliseconds.
165 | */
166 | public int timePassed() {
167 | return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
168 | }
169 |
170 | public void setFinal(float newVal) {
171 | mFinal = newVal;
172 | mDeltaX = mFinal - mStart;
173 | mFinished = false;
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/library/src/net/simonvt/threepanelayout/NoClickThroughFrameLayout.java:
--------------------------------------------------------------------------------
1 | package net.simonvt.threepanelayout;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.MotionEvent;
6 |
7 | /**
8 | * FrameLayout which doesn't let touch events propagate to views positioned behind it in the view hierarchy.
9 | */
10 | public class NoClickThroughFrameLayout extends BuildLayerFrameLayout {
11 |
12 | public NoClickThroughFrameLayout(Context context) {
13 | super(context);
14 | }
15 |
16 | public NoClickThroughFrameLayout(Context context, AttributeSet attrs) {
17 | super(context, attrs);
18 | }
19 |
20 | public NoClickThroughFrameLayout(Context context, AttributeSet attrs, int defStyle) {
21 | super(context, attrs, defStyle);
22 | }
23 |
24 | @Override
25 | public boolean onTouchEvent(MotionEvent event) {
26 | return true;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/library/src/net/simonvt/threepanelayout/Scroller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2006 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.simonvt.threepanelayout;
18 |
19 | import android.content.Context;
20 | import android.hardware.SensorManager;
21 | import android.os.Build;
22 | import android.util.FloatMath;
23 | import android.view.ViewConfiguration;
24 | import android.view.animation.AnimationUtils;
25 | import android.view.animation.Interpolator;
26 |
27 |
28 | /**
29 | * This class encapsulates scrolling. The duration of the scroll
30 | * can be passed in the constructor and specifies the maximum time that
31 | * the scrolling animation should take. Past this time, the scrolling is
32 | * automatically moved to its final stage and computeScrollOffset()
33 | * will always return false to indicate that scrolling is over.
34 | */
35 | public class Scroller {
36 | private int mMode;
37 |
38 | private int mStartX;
39 | private int mStartY;
40 | private int mFinalX;
41 | private int mFinalY;
42 |
43 | private int mMinX;
44 | private int mMaxX;
45 | private int mMinY;
46 | private int mMaxY;
47 |
48 | private int mCurrX;
49 | private int mCurrY;
50 | private long mStartTime;
51 | private int mDuration;
52 | private float mDurationReciprocal;
53 | private float mDeltaX;
54 | private float mDeltaY;
55 | private boolean mFinished;
56 | private Interpolator mInterpolator;
57 | private boolean mFlywheel;
58 |
59 | private float mVelocity;
60 |
61 | private static final int DEFAULT_DURATION = 250;
62 | private static final int SCROLL_MODE = 0;
63 | private static final int FLING_MODE = 1;
64 |
65 | private static final float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
66 | private static final float ALPHA = 800; // pixels / seconds
67 | private static final float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
68 | private static final float END_TENSION = 1.0f - START_TENSION;
69 | private static final int NB_SAMPLES = 100;
70 | private static final float[] SPLINE = new float[NB_SAMPLES + 1];
71 |
72 | private float mDeceleration;
73 | private final float mPpi;
74 |
75 | static {
76 | float xMin = 0.0f;
77 | for (int i = 0; i <= NB_SAMPLES; i++) {
78 | final float t = (float) i / NB_SAMPLES;
79 | float xMax = 1.0f;
80 | float x, tx, coef;
81 | while (true) {
82 | x = xMin + (xMax - xMin) / 2.0f;
83 | coef = 3.0f * x * (1.0f - x);
84 | tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
85 | if (Math.abs(tx - t) < 1E-5) break;
86 | if (tx > t) xMax = x;
87 | else xMin = x;
88 | }
89 | final float d = coef + x * x * x;
90 | SPLINE[i] = d;
91 | }
92 | SPLINE[NB_SAMPLES] = 1.0f;
93 |
94 | // This controls the viscous fluid effect (how much of it)
95 | sViscousFluidScale = 8.0f;
96 | // must be set to 1.0 (used in viscousFluid())
97 | sViscousFluidNormalize = 1.0f;
98 | sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
99 | }
100 |
101 | private static float sViscousFluidScale;
102 | private static float sViscousFluidNormalize;
103 |
104 | /**
105 | * Create a Scroller with the default duration and interpolator.
106 | */
107 | public Scroller(Context context) {
108 | this(context, null);
109 | }
110 |
111 | /**
112 | * Create a Scroller with the specified interpolator. If the interpolator is
113 | * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
114 | * be in effect for apps targeting Honeycomb or newer.
115 | */
116 | public Scroller(Context context, Interpolator interpolator) {
117 | this(context, interpolator,
118 | context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
119 | }
120 |
121 | /**
122 | * Create a Scroller with the specified interpolator. If the interpolator is
123 | * null, the default (viscous) interpolator will be used. Specify whether or
124 | * not to support progressive "flywheel" behavior in flinging.
125 | */
126 | public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
127 | mFinished = true;
128 | mInterpolator = interpolator;
129 | mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
130 | mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
131 | mFlywheel = flywheel;
132 | }
133 |
134 | /**
135 | * The amount of friction applied to flings. The default value
136 | * is {@link android.view.ViewConfiguration#getScrollFriction}.
137 | *
138 | * @param friction A scalar dimension-less value representing the coefficient of
139 | * friction.
140 | */
141 | public final void setFriction(float friction) {
142 | mDeceleration = computeDeceleration(friction);
143 | }
144 |
145 | private float computeDeceleration(float friction) {
146 | return SensorManager.GRAVITY_EARTH // g (m/s^2)
147 | * 39.37f // inch/meter
148 | * mPpi // pixels per inch
149 | * friction;
150 | }
151 |
152 | /**
153 | *
154 | * Returns whether the scroller has finished scrolling.
155 | *
156 | * @return True if the scroller has finished scrolling, false otherwise.
157 | */
158 | public final boolean isFinished() {
159 | return mFinished;
160 | }
161 |
162 | /**
163 | * Force the finished field to a particular value.
164 | *
165 | * @param finished The new finished value.
166 | */
167 | public final void forceFinished(boolean finished) {
168 | mFinished = finished;
169 | }
170 |
171 | /**
172 | * Returns how long the scroll event will take, in milliseconds.
173 | *
174 | * @return The duration of the scroll in milliseconds.
175 | */
176 | public final int getDuration() {
177 | return mDuration;
178 | }
179 |
180 | /**
181 | * Returns the current X offset in the scroll.
182 | *
183 | * @return The new X offset as an absolute distance from the origin.
184 | */
185 | public final int getCurrX() {
186 | return mCurrX;
187 | }
188 |
189 | /**
190 | * Returns the current Y offset in the scroll.
191 | *
192 | * @return The new Y offset as an absolute distance from the origin.
193 | */
194 | public final int getCurrY() {
195 | return mCurrY;
196 | }
197 |
198 | /**
199 | * Returns the current velocity.
200 | *
201 | * @return The original velocity less the deceleration. Result may be
202 | * negative.
203 | */
204 | public float getCurrVelocity() {
205 | return mVelocity - mDeceleration * timePassed() / 2000.0f;
206 | }
207 |
208 | /**
209 | * Returns the start X offset in the scroll.
210 | *
211 | * @return The start X offset as an absolute distance from the origin.
212 | */
213 | public final int getStartX() {
214 | return mStartX;
215 | }
216 |
217 | /**
218 | * Returns the start Y offset in the scroll.
219 | *
220 | * @return The start Y offset as an absolute distance from the origin.
221 | */
222 | public final int getStartY() {
223 | return mStartY;
224 | }
225 |
226 | /**
227 | * Returns where the scroll will end. Valid only for "fling" scrolls.
228 | *
229 | * @return The final X offset as an absolute distance from the origin.
230 | */
231 | public final int getFinalX() {
232 | return mFinalX;
233 | }
234 |
235 | /**
236 | * Returns where the scroll will end. Valid only for "fling" scrolls.
237 | *
238 | * @return The final Y offset as an absolute distance from the origin.
239 | */
240 | public final int getFinalY() {
241 | return mFinalY;
242 | }
243 |
244 | /**
245 | * Call this when you want to know the new location. If it returns true,
246 | * the animation is not yet finished. loc will be altered to provide the
247 | * new location.
248 | */
249 | public boolean computeScrollOffset() {
250 | if (mFinished) {
251 | return false;
252 | }
253 |
254 | int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
255 |
256 | if (timePassed < mDuration) {
257 | switch (mMode) {
258 | case SCROLL_MODE:
259 | float x = timePassed * mDurationReciprocal;
260 |
261 | if (mInterpolator == null)
262 | x = viscousFluid(x);
263 | else
264 | x = mInterpolator.getInterpolation(x);
265 |
266 | mCurrX = mStartX + Math.round(x * mDeltaX);
267 | mCurrY = mStartY + Math.round(x * mDeltaY);
268 | break;
269 | case FLING_MODE:
270 | final float t = (float) timePassed / mDuration;
271 | final int index = (int) (NB_SAMPLES * t);
272 | final float tInf = (float) index / NB_SAMPLES;
273 | final float tSup = (float) (index + 1) / NB_SAMPLES;
274 | final float dInf = SPLINE[index];
275 | final float dSup = SPLINE[index + 1];
276 | final float distanceCoef = dInf + (t - tInf) / (tSup - tInf) * (dSup - dInf);
277 |
278 | mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
279 | // Pin to mMinX <= mCurrX <= mMaxX
280 | mCurrX = Math.min(mCurrX, mMaxX);
281 | mCurrX = Math.max(mCurrX, mMinX);
282 |
283 | mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
284 | // Pin to mMinY <= mCurrY <= mMaxY
285 | mCurrY = Math.min(mCurrY, mMaxY);
286 | mCurrY = Math.max(mCurrY, mMinY);
287 |
288 | if (mCurrX == mFinalX && mCurrY == mFinalY) {
289 | mFinished = true;
290 | }
291 |
292 | break;
293 | }
294 | } else {
295 | mCurrX = mFinalX;
296 | mCurrY = mFinalY;
297 | mFinished = true;
298 | }
299 | return true;
300 | }
301 |
302 | /**
303 | * Start scrolling by providing a starting point and the distance to travel.
304 | * The scroll will use the default value of 250 milliseconds for the
305 | * duration.
306 | *
307 | * @param startX Starting horizontal scroll offset in pixels. Positive
308 | * numbers will scroll the content to the left.
309 | * @param startY Starting vertical scroll offset in pixels. Positive numbers
310 | * will scroll the content up.
311 | * @param dx Horizontal distance to travel. Positive numbers will scroll the
312 | * content to the left.
313 | * @param dy Vertical distance to travel. Positive numbers will scroll the
314 | * content up.
315 | */
316 | public void startScroll(int startX, int startY, int dx, int dy) {
317 | startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
318 | }
319 |
320 | /**
321 | * Start scrolling by providing a starting point and the distance to travel.
322 | *
323 | * @param startX Starting horizontal scroll offset in pixels. Positive
324 | * numbers will scroll the content to the left.
325 | * @param startY Starting vertical scroll offset in pixels. Positive numbers
326 | * will scroll the content up.
327 | * @param dx Horizontal distance to travel. Positive numbers will scroll the
328 | * content to the left.
329 | * @param dy Vertical distance to travel. Positive numbers will scroll the
330 | * content up.
331 | * @param duration Duration of the scroll in milliseconds.
332 | */
333 | public void startScroll(int startX, int startY, int dx, int dy, int duration) {
334 | mMode = SCROLL_MODE;
335 | mFinished = false;
336 | mDuration = duration;
337 | mStartTime = AnimationUtils.currentAnimationTimeMillis();
338 | mStartX = startX;
339 | mStartY = startY;
340 | mFinalX = startX + dx;
341 | mFinalY = startY + dy;
342 | mDeltaX = dx;
343 | mDeltaY = dy;
344 | mDurationReciprocal = 1.0f / (float) mDuration;
345 | }
346 |
347 | /**
348 | * Start scrolling based on a fling gesture. The distance travelled will
349 | * depend on the initial velocity of the fling.
350 | *
351 | * @param startX Starting point of the scroll (X)
352 | * @param startY Starting point of the scroll (Y)
353 | * @param velocityX Initial velocity of the fling (X) measured in pixels per
354 | * second.
355 | * @param velocityY Initial velocity of the fling (Y) measured in pixels per
356 | * second
357 | * @param minX Minimum X value. The scroller will not scroll past this
358 | * point.
359 | * @param maxX Maximum X value. The scroller will not scroll past this
360 | * point.
361 | * @param minY Minimum Y value. The scroller will not scroll past this
362 | * point.
363 | * @param maxY Maximum Y value. The scroller will not scroll past this
364 | * point.
365 | */
366 | public void fling(int startX, int startY, int velocityX, int velocityY,
367 | int minX, int maxX, int minY, int maxY) {
368 | // Continue a scroll or fling in progress
369 | if (mFlywheel && !mFinished) {
370 | float oldVel = getCurrVelocity();
371 |
372 | float dx = (float) (mFinalX - mStartX);
373 | float dy = (float) (mFinalY - mStartY);
374 | float hyp = FloatMath.sqrt(dx * dx + dy * dy);
375 |
376 | float ndx = dx / hyp;
377 | float ndy = dy / hyp;
378 |
379 | float oldVelocityX = ndx * oldVel;
380 | float oldVelocityY = ndy * oldVel;
381 | if (Math.signum(velocityX) == Math.signum(oldVelocityX)
382 | && Math.signum(velocityY) == Math.signum(oldVelocityY)) {
383 | velocityX += oldVelocityX;
384 | velocityY += oldVelocityY;
385 | }
386 | }
387 |
388 | mMode = FLING_MODE;
389 | mFinished = false;
390 |
391 | float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
392 |
393 | mVelocity = velocity;
394 | final double l = Math.log(START_TENSION * velocity / ALPHA);
395 | mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
396 | mStartTime = AnimationUtils.currentAnimationTimeMillis();
397 | mStartX = startX;
398 | mStartY = startY;
399 |
400 | float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
401 | float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
402 |
403 | int totalDistance =
404 | (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
405 |
406 | mMinX = minX;
407 | mMaxX = maxX;
408 | mMinY = minY;
409 | mMaxY = maxY;
410 |
411 | mFinalX = startX + Math.round(totalDistance * coeffX);
412 | // Pin to mMinX <= mFinalX <= mMaxX
413 | mFinalX = Math.min(mFinalX, mMaxX);
414 | mFinalX = Math.max(mFinalX, mMinX);
415 |
416 | mFinalY = startY + Math.round(totalDistance * coeffY);
417 | // Pin to mMinY <= mFinalY <= mMaxY
418 | mFinalY = Math.min(mFinalY, mMaxY);
419 | mFinalY = Math.max(mFinalY, mMinY);
420 | }
421 |
422 | static float viscousFluid(float x) {
423 | x *= sViscousFluidScale;
424 | if (x < 1.0f) {
425 | x -= (1.0f - (float) Math.exp(-x));
426 | } else {
427 | float start = 0.36787944117f; // 1/e == exp(-1)
428 | x = 1.0f - (float) Math.exp(1.0f - x);
429 | x = start + x * (1.0f - start);
430 | }
431 | x *= sViscousFluidNormalize;
432 | return x;
433 | }
434 |
435 | /**
436 | * Stops the animation. Contrary to {@link #forceFinished(boolean)},
437 | * aborting the animating cause the scroller to move to the final x and y
438 | * position
439 | *
440 | * @see #forceFinished(boolean)
441 | */
442 | public void abortAnimation() {
443 | mCurrX = mFinalX;
444 | mCurrY = mFinalY;
445 | mFinished = true;
446 | }
447 |
448 | /**
449 | * Extend the scroll animation. This allows a running animation to scroll
450 | * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
451 | *
452 | * @param extend Additional time to scroll in milliseconds.
453 | * @see #setFinalX(int)
454 | * @see #setFinalY(int)
455 | */
456 | public void extendDuration(int extend) {
457 | int passed = timePassed();
458 | mDuration = passed + extend;
459 | mDurationReciprocal = 1.0f / mDuration;
460 | mFinished = false;
461 | }
462 |
463 | /**
464 | * Returns the time elapsed since the beginning of the scrolling.
465 | *
466 | * @return The elapsed time in milliseconds.
467 | */
468 | public int timePassed() {
469 | return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
470 | }
471 |
472 | /**
473 | * Sets the final position (X) for this scroller.
474 | *
475 | * @param newX The new X offset as an absolute distance from the origin.
476 | * @see #extendDuration(int)
477 | * @see #setFinalY(int)
478 | */
479 | public void setFinalX(int newX) {
480 | mFinalX = newX;
481 | mDeltaX = mFinalX - mStartX;
482 | mFinished = false;
483 | }
484 |
485 | /**
486 | * Sets the final position (Y) for this scroller.
487 | *
488 | * @param newY The new Y offset as an absolute distance from the origin.
489 | * @see #extendDuration(int)
490 | * @see #setFinalX(int)
491 | */
492 | public void setFinalY(int newY) {
493 | mFinalY = newY;
494 | mDeltaY = mFinalY - mStartY;
495 | mFinished = false;
496 | }
497 |
498 | /**
499 | * @hide
500 | */
501 | public boolean isScrollingInDirection(float xvel, float yvel) {
502 | return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX)
503 | && Math.signum(yvel) == Math.signum(mFinalY - mStartY);
504 | }
505 | }
506 |
--------------------------------------------------------------------------------
/library/src/net/simonvt/threepanelayout/SmoothInterpolator.java:
--------------------------------------------------------------------------------
1 | package net.simonvt.threepanelayout;
2 |
3 | import android.view.animation.Interpolator;
4 |
5 | public class SmoothInterpolator implements Interpolator {
6 |
7 | @Override
8 | public float getInterpolation(float t) {
9 | t -= 1.0f;
10 | return t * t * t * t * t + 1.0f;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/library/src/net/simonvt/threepanelayout/ThreePaneLayout.java:
--------------------------------------------------------------------------------
1 | package net.simonvt.threepanelayout;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Canvas;
8 | import android.graphics.Rect;
9 | import android.graphics.drawable.Drawable;
10 | import android.graphics.drawable.GradientDrawable;
11 | import android.os.Build;
12 | import android.os.Parcel;
13 | import android.os.Parcelable;
14 | import android.util.AttributeSet;
15 | import android.view.LayoutInflater;
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 | import android.view.ViewParent;
19 | import android.view.ViewTreeObserver;
20 | import android.view.animation.AccelerateInterpolator;
21 | import android.view.animation.Interpolator;
22 | import android.widget.FrameLayout;
23 |
24 | public class ThreePaneLayout extends ViewGroup {
25 |
26 | static final boolean USE_TRANSLATIONS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
27 |
28 | private static final boolean DEBUG = false;
29 |
30 | private static final int DURATION_MAX = DEBUG ? 10000 : 800;
31 |
32 | private static final int INDICATOR_ANIM_DURATION = 600;
33 |
34 | public static final int PANE_LEFT = 1;
35 |
36 | public static final int PANE_MIDDLE = 2;
37 |
38 | public static final int PANE_RIGHT = 4;
39 |
40 | private static final int DEFAULT_DROP_SHADOW_WIDTH_DP = 6;
41 |
42 | /**
43 | * The time between each frame.
44 | */
45 | protected static final int ANIMATION_DELAY = 1000 / 60;
46 |
47 | /**
48 | * State when the layout is not animating and the left pane is visible.
49 | */
50 | public static final int STATE_LEFT_VISIBLE = 0;
51 |
52 | /**
53 | * State when the layout is animating to the right pane.
54 | */
55 | public static final int STATE_ANIMATE_RIGHT = 1;
56 |
57 | /**
58 | * State when the layout is animating to the left pane.
59 | */
60 | public static final int STATE_ANIMATE_LEFT = 2;
61 |
62 | /**
63 | * State when the layout is not animating and the right pane is visible.
64 | */
65 | public static final int STATE_RIGHT_VISIBLE = 4;
66 |
67 | private static final Interpolator SMOOTH_INTERPOLATOR = new SmoothInterpolator();
68 |
69 | private static final Interpolator INDICATOR_INTERPOLATOR = new AccelerateInterpolator();
70 |
71 | private FrameLayout mLeftPane;
72 |
73 | private FrameLayout mMiddlePane;
74 |
75 | private FrameLayout mRightPane;
76 |
77 | private float mOffset;
78 |
79 | private boolean mLayerTypeHardware;
80 |
81 | private FloatScroller mScroller;
82 |
83 | private final Runnable mDragRunnable = new Runnable() {
84 | @Override
85 | public void run() {
86 | postAnimationInvalidate();
87 | }
88 | };
89 |
90 | private Drawable mShadow;
91 |
92 | private int mDropShadowWidth;
93 |
94 | private boolean mMiddlePaneCollapsible;
95 |
96 | private int mLeftPaneWidth;
97 |
98 | private int mMiddlePaneCollapsedWidth;
99 |
100 | private int mMiddlePaneExpandedWidth;
101 |
102 | private int mVisiblePanes = PANE_LEFT | PANE_MIDDLE;
103 |
104 | private OnPaneStateChangeListener mPaneStateChangeListener;
105 |
106 | private int mPageState = STATE_LEFT_VISIBLE;
107 |
108 | private View mLeftActiveView;
109 |
110 | private Bitmap mLeftActiveIndicator;
111 |
112 | private final Rect mLeftActiveRect = new Rect();
113 |
114 | private int mLeftActivePosition;
115 |
116 | private int mLeftIndicatorStartPos;
117 |
118 | private int mLeftIndicatorTop;
119 |
120 | private float mLeftIndicatorOffset;
121 |
122 | private final FloatScroller mLeftIndicatorScroller = new FloatScroller(SMOOTH_INTERPOLATOR);
123 |
124 | private final Runnable mLeftIndicatorRunnable = new Runnable() {
125 | @Override
126 | public void run() {
127 | animateLeftIndicatorInvalidate();
128 | }
129 | };
130 |
131 | private boolean mLeftIndicatorAnimating;
132 |
133 | private View mMiddleActiveView;
134 |
135 | private Bitmap mMiddleActiveIndicator;
136 |
137 | private final Rect mMiddleActiveRect = new Rect();
138 |
139 | private int mMiddleActivePosition;
140 |
141 | private int mMiddleIndicatorStartPos;
142 |
143 | private int mMiddleIndicatorTop;
144 |
145 | private float mMiddleIndicatorOffset;
146 |
147 | private final FloatScroller mMiddleIndicatorScroller = new FloatScroller(SMOOTH_INTERPOLATOR);
148 |
149 | private final Runnable mMiddleIndicatorRunnable = new Runnable() {
150 | @Override
151 | public void run() {
152 | animateMiddleIndicatorInvalidate();
153 | }
154 | };
155 |
156 | private boolean mMiddleIndicatorAnimating;
157 |
158 | private ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
159 | = new ViewTreeObserver.OnScrollChangedListener() {
160 | @Override
161 | public void onScrollChanged() {
162 | if (mLeftActiveView != null || mMiddleActiveView != null) {
163 | invalidate();
164 | }
165 | }
166 | };
167 |
168 | public interface OnPaneStateChangeListener {
169 |
170 | void onPaneStateChange(int oldState, int newState);
171 | }
172 |
173 | public ThreePaneLayout(Context context) {
174 | this(context, null);
175 | }
176 |
177 | public ThreePaneLayout(Context context, AttributeSet attrs) {
178 | this(context, attrs, 0);
179 | }
180 |
181 | public ThreePaneLayout(Context context, AttributeSet attrs, int defStyle) {
182 | super(context, attrs, defStyle);
183 |
184 | mScroller = new FloatScroller(SMOOTH_INTERPOLATOR);
185 |
186 | mLeftPane = new BuildLayerFrameLayout(context);
187 | addView(mLeftPane, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
188 |
189 | mMiddlePane = new BuildLayerFrameLayout(context);
190 | addView(mMiddlePane, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
191 |
192 | mRightPane = new BuildLayerFrameLayout(context);
193 | addView(mRightPane, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
194 |
195 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ThreePaneLayout, R.attr.threePaneLayoutStyle,
196 | defStyle);
197 |
198 | final int leftPaneLayout = a.getResourceId(R.styleable.ThreePaneLayout_leftPaneLayout, -1);
199 | if (leftPaneLayout != -1) {
200 | setLeftPaneLayout(leftPaneLayout);
201 | }
202 | final int middlePaneLayout = a.getResourceId(R.styleable.ThreePaneLayout_middlePaneLayout, -1);
203 | if (middlePaneLayout != -1) {
204 | setMiddlePaneLayout(middlePaneLayout);
205 | }
206 | final int rightPaneLayout = a.getResourceId(R.styleable.ThreePaneLayout_rightPaneLayout, -1);
207 | if (rightPaneLayout != -1) {
208 | setRightPaneLayout(rightPaneLayout);
209 | }
210 |
211 | mLeftPaneWidth = a.getDimensionPixelSize(R.styleable.ThreePaneLayout_leftPaneWidth, dpToPx(250));
212 |
213 | mMiddlePaneCollapsible = a.getBoolean(R.styleable.ThreePaneLayout_middlePaneCollapsible, true);
214 |
215 | mMiddlePaneCollapsedWidth = a.getDimensionPixelSize(R.styleable.ThreePaneLayout_middlePaneCollapsedWidth,
216 | dpToPx(450));
217 |
218 | final int leftIndicatorResId = a.getResourceId(R.styleable.ThreePaneLayout_leftActiveIndicator, 0);
219 | if (leftIndicatorResId != 0) {
220 | mLeftActiveIndicator = BitmapFactory.decodeResource(getResources(), leftIndicatorResId);
221 | }
222 |
223 | final int middleIndicatorResId = a.getResourceId(R.styleable.ThreePaneLayout_middleActiveIndicator, 0);
224 | if (middleIndicatorResId != 0) {
225 | mMiddleActiveIndicator = BitmapFactory.decodeResource(getResources(), middleIndicatorResId);
226 | }
227 |
228 | a.recycle();
229 |
230 | mShadow = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, new int[] {
231 | 0xFF000000,
232 | 0x00000000,
233 | });
234 | mDropShadowWidth = (int) (getResources().getDisplayMetrics().density * DEFAULT_DROP_SHADOW_WIDTH_DP + 0.5f);
235 | }
236 |
237 | @Override
238 | protected void onAttachedToWindow() {
239 | super.onAttachedToWindow();
240 | getViewTreeObserver().addOnScrollChangedListener(mScrollChangedListener);
241 | }
242 |
243 | @Override
244 | protected void onDetachedFromWindow() {
245 | getViewTreeObserver().removeOnScrollChangedListener(mScrollChangedListener);
246 | super.onDetachedFromWindow();
247 | }
248 |
249 | private int dpToPx(int dp) {
250 | return (int) (getResources().getDisplayMetrics().density * dp + 0.5f);
251 | }
252 |
253 | public void setLeftPaneLayout(int layoutId) {
254 | mLeftPane.removeAllViews();
255 | LayoutInflater.from(getContext()).inflate(layoutId, mLeftPane);
256 | }
257 |
258 | public void setMiddlePaneLayout(int layoutId) {
259 | mMiddlePane.removeAllViews();
260 | LayoutInflater.from(getContext()).inflate(layoutId, mMiddlePane);
261 | }
262 |
263 | public void setRightPaneLayout(int layoutId) {
264 | mRightPane.removeAllViews();
265 | LayoutInflater.from(getContext()).inflate(layoutId, mRightPane);
266 | }
267 |
268 | public void showLeftPane() {
269 | showLeftPane(true);
270 | }
271 |
272 | public void showLeftPane(boolean animate) {
273 | mVisiblePanes = PANE_LEFT | PANE_MIDDLE;
274 | requestLayout();
275 | animateOffsetTo(0.0f, animate);
276 | if (animate) setPageState(STATE_ANIMATE_LEFT);
277 | }
278 |
279 | public void showRightPane() {
280 | showRightPane(true);
281 | }
282 |
283 | public void showRightPane(boolean animate) {
284 | mVisiblePanes = PANE_RIGHT;
285 | if (mMiddlePaneCollapsible) mVisiblePanes |= PANE_MIDDLE;
286 | requestLayout();
287 | animateOffsetTo(1.0f, animate);
288 | if (animate) setPageState(STATE_ANIMATE_RIGHT);
289 | }
290 |
291 | public boolean isLeftPaneVisible() {
292 | return (mVisiblePanes & PANE_LEFT) != 0;
293 | }
294 |
295 | public boolean isMiddlePaneShowing() {
296 | return (mVisiblePanes & PANE_MIDDLE) != 0;
297 | }
298 |
299 | public boolean isRightPaneVisible() {
300 | return (mVisiblePanes & PANE_RIGHT) != 0;
301 | }
302 |
303 | public boolean isMiddlePaneCollapsible() {
304 | return mMiddlePaneCollapsible;
305 | }
306 |
307 | public void setLeftActiveView(View v) {
308 | setLeftActiveView(v, 0);
309 | }
310 |
311 | public void setLeftActiveView(View v, int position) {
312 | setLeftActiveView(v, position, true);
313 | }
314 |
315 | public void setLeftActiveView(View v, int position, boolean animate) {
316 | final View oldView = mLeftActiveView;
317 | mLeftActiveView = v;
318 | mLeftActivePosition = position;
319 |
320 | if (oldView != null && animate) {
321 | startAnimatingLeftIndicator();
322 | } else {
323 | mLeftIndicatorOffset = 1.0f;
324 | }
325 |
326 | invalidate();
327 | }
328 |
329 | private void startAnimatingLeftIndicator() {
330 | mLeftIndicatorStartPos = mLeftIndicatorTop;
331 | mLeftIndicatorAnimating = true;
332 | mLeftIndicatorScroller.startScroll(0.0f, 1.0f, INDICATOR_ANIM_DURATION);
333 |
334 | animateLeftIndicatorInvalidate();
335 | }
336 |
337 | /**
338 | * Callback when each frame in the indicator animation should be drawn.
339 | */
340 | private void animateLeftIndicatorInvalidate() {
341 | if (mLeftIndicatorScroller.computeScrollOffset()) {
342 | mLeftIndicatorOffset = mLeftIndicatorScroller.getCurr();
343 | invalidate();
344 |
345 | if (!mLeftIndicatorScroller.isFinished()) {
346 | postOnAnimation(mLeftIndicatorRunnable);
347 | return;
348 | }
349 | }
350 |
351 | completeAnimatingLeftIndicator();
352 | }
353 |
354 | /**
355 | * Called when the indicator animation has completed.
356 | */
357 | private void completeAnimatingLeftIndicator() {
358 | mLeftIndicatorOffset = 1.0f;
359 | mLeftIndicatorAnimating = false;
360 | invalidate();
361 | }
362 |
363 | public void setMiddleActiveView(View v) {
364 | setMiddleActiveView(v, 0);
365 | }
366 |
367 | public void setMiddleActiveView(View v, int position) {
368 | setMiddleActiveView(v, position, true);
369 | }
370 |
371 | public void setMiddleActiveView(View v, int position, boolean animate) {
372 | final View oldView = mMiddleActiveView;
373 | mMiddleActiveView = v;
374 | mMiddleActivePosition = position;
375 |
376 | if (oldView != null && animate) {
377 | startAnimatingMiddleIndicator();
378 | } else {
379 | mMiddleIndicatorOffset = 1.0f;
380 | }
381 |
382 | invalidate();
383 | }
384 |
385 | private void startAnimatingMiddleIndicator() {
386 | mMiddleIndicatorStartPos = mMiddleIndicatorTop;
387 | mMiddleIndicatorAnimating = true;
388 | mMiddleIndicatorScroller.startScroll(0.0f, 1.0f, INDICATOR_ANIM_DURATION);
389 |
390 | animateMiddleIndicatorInvalidate();
391 | }
392 |
393 | /**
394 | * Callback when each frame in the indicator animation should be drawn.
395 | */
396 | private void animateMiddleIndicatorInvalidate() {
397 | if (mMiddleIndicatorScroller.computeScrollOffset()) {
398 | mMiddleIndicatorOffset = mMiddleIndicatorScroller.getCurr();
399 | invalidate();
400 |
401 | if (!mMiddleIndicatorScroller.isFinished()) {
402 | postOnAnimation(mMiddleIndicatorRunnable);
403 | return;
404 | }
405 | }
406 |
407 | completeAnimatingMiddleIndicator();
408 | }
409 |
410 | /**
411 | * Called when the indicator animation has completed.
412 | */
413 | private void completeAnimatingMiddleIndicator() {
414 | mMiddleIndicatorOffset = 1.0f;
415 | mMiddleIndicatorAnimating = false;
416 | invalidate();
417 | }
418 |
419 | @Override
420 | protected void dispatchDraw(Canvas canvas) {
421 | super.dispatchDraw(canvas);
422 | final int height = getHeight();
423 | final int dropShadowWidth = mDropShadowWidth;
424 |
425 | final int middlePaneLeft = (int) (mMiddlePane.getLeft() + Math.floor(mMiddlePane.getTranslationX()));
426 | mShadow.setBounds(middlePaneLeft - dropShadowWidth, 0, middlePaneLeft, height);
427 | mShadow.draw(canvas);
428 |
429 | final int rightPaneLeft = (int) (mRightPane.getLeft() + Math.floor(mRightPane.getTranslationX()));
430 | final int scaledDropShadowWidth = (int) (dropShadowWidth * SMOOTH_INTERPOLATOR.getInterpolation(mOffset));
431 | mShadow.setBounds(rightPaneLeft - scaledDropShadowWidth, 0, rightPaneLeft, height);
432 | mShadow.draw(canvas);
433 |
434 | drawLeftIndicator(canvas);
435 | drawMiddleIndicator(canvas);
436 | }
437 |
438 | private void drawLeftIndicator(Canvas canvas) {
439 | if (mLeftActiveView != null && isViewDescendant(mLeftActiveView)) {
440 | Integer position = (Integer) mLeftActiveView.getTag(R.id.tplActiveViewPosition);
441 | final int pos = position == null ? 0 : position;
442 |
443 | if (pos == mLeftActivePosition) {
444 |
445 | mLeftActiveView.getDrawingRect(mLeftActiveRect);
446 | offsetDescendantRectToMyCoords(mLeftActiveView, mLeftActiveRect);
447 |
448 | if (mLeftIndicatorAnimating) {
449 | final int indicatorFinalTop = mLeftActiveRect.top + ((mLeftActiveRect.height()
450 | - mLeftActiveIndicator.getHeight()) / 2);
451 | final int indicatorStartTop = mLeftIndicatorStartPos;
452 | final int diff = indicatorFinalTop - indicatorStartTop;
453 | final int startOffset = (int) (diff * mLeftIndicatorOffset);
454 | mLeftIndicatorTop = indicatorStartTop + startOffset;
455 | } else {
456 | mLeftIndicatorTop = mLeftActiveRect.top + ((mLeftActiveRect.height()
457 | - mLeftActiveIndicator.getHeight()) / 2);
458 | }
459 | final int right = (int) (mMiddlePane.getLeft() + Math.floor(mMiddlePane.getTranslationX()));
460 | final int left = right - mLeftActiveIndicator.getWidth();
461 |
462 | canvas.save();
463 | canvas.clipRect(left, 0, right, getHeight());
464 | canvas.drawBitmap(mLeftActiveIndicator, left, mLeftIndicatorTop, null);
465 | canvas.restore();
466 | }
467 | }
468 | }
469 |
470 | private void drawMiddleIndicator(Canvas canvas) {
471 | if (mMiddleActiveView != null && isViewDescendant(mMiddleActiveView)) {
472 | Integer position = (Integer) mMiddleActiveView.getTag(R.id.tplActiveViewPosition);
473 | final int pos = position == null ? 0 : position;
474 |
475 | if (pos == mMiddleActivePosition) {
476 |
477 | mMiddleActiveView.getDrawingRect(mMiddleActiveRect);
478 | offsetDescendantRectToMyCoords(mMiddleActiveView, mMiddleActiveRect);
479 |
480 | final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation(1.0f - mOffset);
481 | final int interpolatedWidth = (int) (mMiddleActiveIndicator.getWidth() * interpolatedRatio);
482 |
483 | if (mMiddleIndicatorAnimating) {
484 | final int indicatorFinalTop = mMiddleActiveRect.top + ((mMiddleActiveRect.height()
485 | - mMiddleActiveIndicator.getHeight()) / 2);
486 | final int indicatorStartTop = mMiddleIndicatorStartPos;
487 | final int diff = indicatorFinalTop - indicatorStartTop;
488 | final int startOffset = (int) (diff * mMiddleIndicatorOffset);
489 | mMiddleIndicatorTop = indicatorStartTop + startOffset;
490 | } else {
491 | mMiddleIndicatorTop = mMiddleActiveRect.top + ((mMiddleActiveRect.height()
492 | - mMiddleActiveIndicator.getHeight()) / 2);
493 | }
494 | final int right = (int) (mRightPane.getLeft() + mRightPane.getTranslationX());
495 | final int left = right - interpolatedWidth;
496 |
497 | canvas.save();
498 | canvas.clipRect(left, 0, right, getHeight());
499 | canvas.drawBitmap(mMiddleActiveIndicator, left, mMiddleIndicatorTop, null);
500 | canvas.restore();
501 | }
502 | }
503 | }
504 |
505 | protected boolean isViewDescendant(View v) {
506 | ViewParent parent = v.getParent();
507 | while (parent != null) {
508 | if (parent == this) {
509 | return true;
510 | }
511 |
512 | parent = parent.getParent();
513 | }
514 |
515 | return false;
516 | }
517 |
518 | @Override
519 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
520 | final int width = r - l;
521 | final int height = b - t;
522 | final float offset = mOffset;
523 |
524 | // This is easy. Only support 3.2+, so all moving or views are done with translations
525 | final int leftPaneWidth = mLeftPaneWidth;
526 | mLeftPane.layout(0, 0, leftPaneWidth, height);
527 |
528 | final int middlePaneWidth = mMiddlePane.getMeasuredWidth();
529 | mMiddlePane.layout(leftPaneWidth, 0, leftPaneWidth + middlePaneWidth, height);
530 |
531 | final int rightPaneWidth = mRightPane.getMeasuredWidth();
532 | mRightPane.layout(width, 0, width + rightPaneWidth, height);
533 | }
534 |
535 | @Override
536 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
537 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
538 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
539 |
540 | if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
541 | throw new IllegalStateException("Must measure with an exact size");
542 | }
543 |
544 | final int width = MeasureSpec.getSize(widthMeasureSpec);
545 | final int height = MeasureSpec.getSize(heightMeasureSpec);
546 |
547 | setMeasuredDimension(width, height);
548 |
549 | // Measure left pane
550 | final int leftPaneWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mLeftPaneWidth);
551 | final int leftPaneHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
552 | mLeftPane.measure(leftPaneWidthMeasureSpec, leftPaneHeightMeasureSpec);
553 |
554 | // Measure middle pane
555 | final int middlePaneHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
556 | int middlePaneWidthMeasureSpec;
557 |
558 | mMiddlePaneExpandedWidth = width - mLeftPaneWidth;
559 |
560 | if (isRightPaneVisible() && mMiddlePaneCollapsible) {
561 | middlePaneWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMiddlePaneCollapsedWidth);
562 | } else {
563 | middlePaneWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMiddlePaneExpandedWidth);
564 | }
565 |
566 | mMiddlePane.measure(middlePaneWidthMeasureSpec, middlePaneHeightMeasureSpec);
567 |
568 | // Measure right pane
569 | int rightWidthMeasureSpec;
570 | final int rightHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
571 |
572 | if (mMiddlePaneCollapsible) {
573 | rightWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width - mMiddlePaneCollapsedWidth);
574 | } else {
575 | rightWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
576 | }
577 |
578 | final int rightWidth = MeasureSpec.getSize(rightWidthMeasureSpec);
579 |
580 | mRightPane.measure(rightWidthMeasureSpec, rightHeightMeasureSpec);
581 |
582 | // Just making sure it updates the translations
583 | setOffset(mOffset);
584 | }
585 |
586 | private void setOffset(float offset) {
587 | mOffset = offset;
588 |
589 | invalidate();
590 |
591 | final int width = getWidth();
592 | if (mMiddlePaneCollapsible) {
593 | final int middlePaneOffset = (int) (-mLeftPaneWidth * offset);
594 | mLeftPane.setTranslationX(middlePaneOffset);
595 | mMiddlePane.setTranslationX(middlePaneOffset);
596 | final int rightPaneTranslation = (int) (-(width - mMiddlePaneCollapsedWidth) * offset);
597 | mRightPane.setTranslationX(rightPaneTranslation);
598 | } else {
599 | final int viewOffset = (int) (-width * offset);
600 | mLeftPane.setTranslationX(viewOffset);
601 | mMiddlePane.setTranslationX(viewOffset);
602 | mRightPane.setTranslationX(viewOffset);
603 | }
604 | }
605 |
606 | public void setPaneStateChangeListener(OnPaneStateChangeListener paneStateChangeListener) {
607 | mPaneStateChangeListener = paneStateChangeListener;
608 | }
609 |
610 | private void setPageState(int state) {
611 | if (state != mPageState) {
612 | if (mPaneStateChangeListener != null) mPaneStateChangeListener.onPaneStateChange(mPageState, state);
613 | mPageState = state;
614 | }
615 | }
616 |
617 | /**
618 | * If possible, set the layer type to {@link View#LAYER_TYPE_HARDWARE}.
619 | */
620 | protected void startLayerTranslation() {
621 | if (USE_TRANSLATIONS && !mLayerTypeHardware) {
622 | mLayerTypeHardware = true;
623 | mLeftPane.setLayerType(View.LAYER_TYPE_HARDWARE, null);
624 | mMiddlePane.setLayerType(View.LAYER_TYPE_HARDWARE, null);
625 | mRightPane.setLayerType(View.LAYER_TYPE_HARDWARE, null);
626 | }
627 | }
628 |
629 | /**
630 | * If the current layer type is {@link View#LAYER_TYPE_HARDWARE}, this will set it to @link View#LAYER_TYPE_NONE}.
631 | */
632 | private void stopLayerTranslation() {
633 | if (mLayerTypeHardware) {
634 | mLayerTypeHardware = false;
635 | mLeftPane.setLayerType(View.LAYER_TYPE_NONE, null);
636 | mMiddlePane.setLayerType(View.LAYER_TYPE_NONE, null);
637 | mRightPane.setLayerType(View.LAYER_TYPE_NONE, null);
638 | }
639 | }
640 |
641 | protected void stopAnimation() {
642 | removeCallbacks(mDragRunnable);
643 | mScroller.abortAnimation();
644 | stopLayerTranslation();
645 | }
646 |
647 | private void completeAnimation() {
648 | mScroller.abortAnimation();
649 | final float finalVal = mScroller.getFinal();
650 | setOffset(finalVal);
651 | setPageState(isLeftPaneVisible() ? STATE_LEFT_VISIBLE : STATE_RIGHT_VISIBLE);
652 | stopLayerTranslation();
653 | }
654 |
655 | protected void animateOffsetTo(float finalOffset, boolean animate) {
656 | if (!animate) {
657 | setOffset(finalOffset);
658 | return;
659 | }
660 |
661 | final float start = mOffset;
662 | final float dx = finalOffset - start;
663 |
664 | int duration = (int) (DURATION_MAX * Math.abs(dx));
665 | mScroller.startScroll(start, dx, duration);
666 |
667 | startLayerTranslation();
668 | postAnimationInvalidate();
669 | }
670 |
671 | /**
672 | * Callback when each frame in the drawer animation should be drawn.
673 | */
674 | private void postAnimationInvalidate() {
675 | if (mScroller.computeScrollOffset()) {
676 | final float curr = mScroller.getCurr();
677 |
678 | setOffset(curr);
679 | if (!mScroller.isFinished()) {
680 | postOnAnimation(mDragRunnable);
681 | return;
682 | }
683 | }
684 |
685 | completeAnimation();
686 | }
687 |
688 | @Override
689 | public void postOnAnimation(Runnable action) {
690 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
691 | super.postOnAnimation(action);
692 | } else {
693 | postDelayed(action, ANIMATION_DELAY);
694 | }
695 | }
696 |
697 | @Override
698 | protected void onRestoreInstanceState(Parcelable state) {
699 | SavedState savedState = (SavedState) state;
700 | super.onRestoreInstanceState(savedState.getSuperState());
701 |
702 | if (savedState.mRightPaneVisible) showRightPane(false);
703 | }
704 |
705 | @Override
706 | protected Parcelable onSaveInstanceState() {
707 | Parcelable superState = super.onSaveInstanceState();
708 | SavedState state = new SavedState(superState);
709 |
710 | state.mRightPaneVisible = isRightPaneVisible();
711 |
712 | return state;
713 | }
714 |
715 | static class SavedState extends BaseSavedState {
716 |
717 | boolean mRightPaneVisible;
718 |
719 | public SavedState(Parcelable superState) {
720 | super(superState);
721 | }
722 |
723 | public SavedState(Parcel in) {
724 | super(in);
725 | mRightPaneVisible = in.readInt() == 1;
726 | }
727 |
728 | @Override
729 | public void writeToParcel(Parcel dest, int flags) {
730 | super.writeToParcel(dest, flags);
731 | dest.writeInt(mRightPaneVisible ? 1 : 0);
732 | }
733 |
734 | @SuppressWarnings("UnusedDeclaration")
735 | public static final Creator CREATOR = new Creator() {
736 | @Override
737 | public SavedState createFromParcel(Parcel in) {
738 | return new SavedState(in);
739 | }
740 |
741 | @Override
742 | public SavedState[] newArray(int size) {
743 | return new SavedState[size];
744 | }
745 | };
746 | }
747 | }
748 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.0.0
5 |
6 |
7 | org.sonatype.oss
8 | oss-parent
9 | 7
10 |
11 |
12 | net.simonvt
13 | threepanelayout-parent
14 | pom
15 | 1.0.2-SNAPSHOT
16 |
17 | ThreePaneLayout (Parent)
18 | A three-pane layout where only two panes are visible at a time.
19 | https://github.com/SimonVT/ThreePaneLayout
20 | 2013
21 |
22 |
23 | library
24 | samples
25 |
26 |
27 |
28 | http://github.com/SimonVT/ThreePaneLayout/
29 | scm:git:git://github.com/SimonVT/ThreePaneLayout.git
30 | scm:git:git@github.com:SimonVT/ThreePaneLayout.git
31 | HEAD
32 |
33 |
34 |
35 |
36 | Apache License Version 2.0
37 | http://www.apache.org/licenses/LICENSE-2.0.txt
38 | repo
39 |
40 |
41 |
42 |
43 | GitHub Issues
44 | https://github.com/SimonVT/ThreePaneLayout/issues
45 |
46 |
47 |
48 | UTF-8
49 | UTF-8
50 |
51 | 1.6
52 | 4.1.1.4
53 | 16
54 |
55 |
56 |
57 |
58 |
59 | com.google.android
60 | android
61 | ${android.version}
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | org.apache.maven.plugins
71 | maven-compiler-plugin
72 | 3.0
73 |
74 | ${java.version}
75 | ${java.version}
76 |
77 |
78 |
79 |
80 | com.jayway.maven.plugins.android.generation2
81 | android-maven-plugin
82 | 3.5.1
83 |
84 |
85 | ${android.platform}
86 |
87 |
88 | true
89 |
90 |
91 |
92 |
93 |
94 |
95 | org.apache.maven.plugins
96 | maven-release-plugin
97 | 2.4
98 |
99 | true
100 |
101 |
102 |
103 |
104 | org.apache.maven.plugins
105 | maven-checkstyle-plugin
106 | 2.9.1
107 |
108 | true
109 |
110 | ../checkstyle.xml
111 | true
112 |
113 |
114 |
115 | verify
116 |
117 | checkstyle
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/samples/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/samples/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.0.0
5 |
6 |
7 | net.simonvt
8 | threepanelayout-parent
9 | 1.0.2-SNAPSHOT
10 | ../pom.xml
11 |
12 |
13 | threepanelayout-sample
14 | ThreePaneLayout Sample
15 | apk
16 |
17 |
18 |
19 | com.google.android
20 | android
21 | provided
22 |
23 |
24 | net.simonvt
25 | threepanelayout
26 | ${project.version}
27 | apklib
28 |
29 |
30 |
31 |
32 | src
33 |
34 |
35 |
36 | com.jayway.maven.plugins.android.generation2
37 | android-maven-plugin
38 | true
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/samples/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-16
15 | android.library.reference.1=../library
16 |
17 |
--------------------------------------------------------------------------------
/samples/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/samples/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/res/drawable-hdpi/menu_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/samples/res/drawable-hdpi/menu_arrow.png
--------------------------------------------------------------------------------
/samples/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/samples/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/samples/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/res/drawable-mdpi/menu_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/samples/res/drawable-mdpi/menu_arrow.png
--------------------------------------------------------------------------------
/samples/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/samples/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/res/drawable-xhdpi/menu_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimonVT/ThreePaneLayout/f64fa1766adc76878c02df6cd1dbefbfc884ed0a/samples/res/drawable-xhdpi/menu_arrow.png
--------------------------------------------------------------------------------
/samples/res/layout/fragment_rightpane.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/samples/res/layout/left_pane.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/samples/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/samples/res/layout/middle_pane.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/samples/res/layout/right_pane.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/samples/res/values-land/bools.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
--------------------------------------------------------------------------------
/samples/res/values-v14/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/samples/res/values/bools.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 |
6 |
--------------------------------------------------------------------------------
/samples/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ThreePaneLayout Sample
4 |
5 |
--------------------------------------------------------------------------------
/samples/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/src/net/simonvt/threepanelayout/samples/LeftPaneFragment.java:
--------------------------------------------------------------------------------
1 | package net.simonvt.threepanelayout.samples;
2 |
3 | import android.app.Activity;
4 | import android.app.ListFragment;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ArrayAdapter;
9 | import android.widget.ListView;
10 |
11 | public class LeftPaneFragment extends ListFragment {
12 |
13 | public interface OnLeftPaneListListener {
14 |
15 | void onLeftItemClicked(View v, int position);
16 | }
17 |
18 | private static final String[] ENTRIES;
19 |
20 | static {
21 | final int count = 10;
22 | ENTRIES = new String[count];
23 |
24 | for (int i = 0; i < count; i++) {
25 | ENTRIES[i] = "Item " + i;
26 | }
27 | }
28 |
29 | private OnLeftPaneListListener mListener;
30 |
31 | private ArrayAdapter mAdapter;
32 |
33 | @Override
34 | public void onAttach(Activity activity) {
35 | super.onAttach(activity);
36 | mListener = (OnLeftPaneListListener) activity;
37 | }
38 |
39 | @Override
40 | public void onViewCreated(View view, Bundle savedInstanceState) {
41 | super.onViewCreated(view, savedInstanceState);
42 | if (mAdapter == null) {
43 | mAdapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, ENTRIES) {
44 | @Override
45 | public View getView(int position, View convertView, ViewGroup parent) {
46 | View v = super.getView(position, convertView, parent);
47 | v.setTag(R.id.tplActiveViewPosition, position);
48 | return v;
49 | }
50 | };
51 | }
52 |
53 | setListAdapter(mAdapter);
54 | }
55 |
56 | @Override
57 | public void onListItemClick(ListView l, View v, int position, long id) {
58 | mListener.onLeftItemClicked(v, position);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/samples/src/net/simonvt/threepanelayout/samples/MiddlePaneFragment.java:
--------------------------------------------------------------------------------
1 | package net.simonvt.threepanelayout.samples;
2 |
3 | import android.app.Activity;
4 | import android.app.ListFragment;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ArrayAdapter;
9 | import android.widget.ListView;
10 |
11 | public class MiddlePaneFragment extends ListFragment {
12 |
13 | public interface OnMiddlePaneListListener {
14 |
15 | void onMiddleItemClicked(View v, int position);
16 | }
17 |
18 | private static final String[] ENTRIES;
19 |
20 | static {
21 | final int count = 20;
22 | ENTRIES = new String[count];
23 |
24 | for (int i = 0; i < count; i++) {
25 | ENTRIES[i] = "Item " + i;
26 | }
27 | }
28 |
29 | private OnMiddlePaneListListener mListener;
30 |
31 | private ArrayAdapter mAdapter;
32 |
33 | @Override
34 | public void onAttach(Activity activity) {
35 | super.onAttach(activity);
36 | mListener = (OnMiddlePaneListListener) activity;
37 | }
38 |
39 | @Override
40 | public void onViewCreated(View view, Bundle savedInstanceState) {
41 | super.onViewCreated(view, savedInstanceState);
42 | if (mAdapter == null) {
43 | mAdapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, ENTRIES) {
44 | @Override
45 | public View getView(int position, View convertView, ViewGroup parent) {
46 | View v = super.getView(position, convertView, parent);
47 | v.setTag(R.id.tplActiveViewPosition, position);
48 | return v;
49 | }
50 | };
51 | }
52 |
53 | setListAdapter(mAdapter);
54 | }
55 |
56 | @Override
57 | public void onListItemClick(ListView l, View v, int position, long id) {
58 | mListener.onMiddleItemClicked(v, position);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/samples/src/net/simonvt/threepanelayout/samples/RightPaneFragment.java:
--------------------------------------------------------------------------------
1 | package net.simonvt.threepanelayout.samples;
2 |
3 | import android.app.Fragment;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | public class RightPaneFragment extends Fragment {
10 |
11 | @Override
12 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
13 | return inflater.inflate(R.layout.fragment_rightpane, container, false);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/samples/src/net/simonvt/threepanelayout/samples/SamplesActivity.java:
--------------------------------------------------------------------------------
1 | package net.simonvt.threepanelayout.samples;
2 |
3 | import net.simonvt.threepanelayout.ThreePaneLayout;
4 |
5 | import android.app.Activity;
6 | import android.os.Bundle;
7 | import android.view.MenuItem;
8 | import android.view.View;
9 |
10 | public class SamplesActivity extends Activity implements LeftPaneFragment.OnLeftPaneListListener,
11 | MiddlePaneFragment.OnMiddlePaneListListener {
12 |
13 | private ThreePaneLayout mThreePaneLayout;
14 |
15 | int i = 0;
16 |
17 | @Override
18 | public void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setContentView(R.layout.main);
21 |
22 | mThreePaneLayout = (ThreePaneLayout) findViewById(R.id.threePaneLayout);
23 | }
24 |
25 | @Override
26 | public void onBackPressed() {
27 | if (mThreePaneLayout.isRightPaneVisible()) {
28 | mThreePaneLayout.showLeftPane();
29 | getActionBar().setDisplayHomeAsUpEnabled(false);
30 | return;
31 | }
32 |
33 | super.onBackPressed();
34 | }
35 |
36 | @Override
37 | public boolean onOptionsItemSelected(MenuItem item) {
38 | switch (item.getItemId()) {
39 | case android.R.id.home:
40 | mThreePaneLayout.showLeftPane();
41 | getActionBar().setDisplayHomeAsUpEnabled(false);
42 | return true;
43 | }
44 | return super.onOptionsItemSelected(item);
45 | }
46 |
47 | @Override
48 | public void onLeftItemClicked(View v, int position) {
49 | mThreePaneLayout.setLeftActiveView(v, position);
50 | }
51 |
52 | @Override
53 | public void onMiddleItemClicked(View v, int position) {
54 | mThreePaneLayout.setMiddleActiveView(v, position);
55 | mThreePaneLayout.showRightPane();
56 | getActionBar().setDisplayHomeAsUpEnabled(true);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------