19
19
import android .support .annotation .Nullable ;
20
20
import android .support .v7 .widget .RecyclerView ;
21
21
import android .util .AttributeSet ;
22
+ import android .util .Log ;
22
23
import android .view .View ;
23
24
import android .view .ViewGroup ;
24
25
import android .view .ViewParent ;
32
33
*/
33
34
34
35
public class ExpandableLayout extends LinearLayout {
36
+ private static final String TAG = "ExpandableLayout" ;
35
37
private final int ANIMATION_DURATION = 300 ;
36
38
39
+ /**
40
+ * Height in collapsed state
41
+ * 收缩起来的高度
42
+ */
37
43
private int mCollapsedHeight ;
44
+ /**
45
+ * Height in expanded state
46
+ * 展开的高度
47
+ */
38
48
private int mExpandedHeight ;
39
- private int mCurrentHeight = -1 ; // Used for animating
40
-
41
- private boolean mCollapsed = true ; // current state: true -> collapsed
42
- private boolean mInitialMeasure = true ;
43
-
49
+ /**
50
+ * Used in OnMeasure method
51
+ */
52
+ private int mCurrentHeight = -1 ;
53
+ /**
54
+ * current state: true -> collapsed
55
+ * 标记当前的状态 true -> 收缩
56
+ */
57
+ private boolean mCollapsed = true ;
58
+ /**
59
+ * The parent which can scroll (RecyclerView, ListView)
60
+ * 可滚动的父布局
61
+ */
44
62
private ViewGroup mScrolledParent ;
45
- private View mLastView ; // Last view in collapsed state
63
+ /**
64
+ * Last visible view in collapsed state
65
+ * 收缩后最下面的View,通过这个view计算得到收缩后的高度
66
+ */
67
+ private View mLastView ;
46
68
private View mAnchorView ;
69
+ /**
70
+ * Offset for collapsed height
71
+ * 收缩起来高度的偏移
72
+ */
47
73
private int mCollapsedOffset ;
74
+ /**
75
+ * Offset for expanded height
76
+ *
77
+ */
48
78
private int mExpandedOffset ;
49
79
private boolean mExpandWithScroll ; // true to scroll parent if expanded content exceeds parent's bottom edge
50
80
private boolean mCollapseWithScroll ; // true to scroll to ExpandableLayout's head
51
81
private int mExpandScrollOffset ;
52
82
private int mCollapseScrollOffset ;
53
83
private boolean mAnimating ;
54
84
85
+ private ExpandCollapseAnimation mAnimation ;
86
+
55
87
private OnExpandListener mOnExpandListener ;
56
88
57
89
public ExpandableLayout (Context context ) {
@@ -61,6 +93,51 @@ public ExpandableLayout(Context context) {
61
93
public ExpandableLayout (Context context , @ Nullable AttributeSet attrs ) {
62
94
super (context , attrs );
63
95
setOrientation (VERTICAL );
96
+
97
+ mAnimation = new ExpandCollapseAnimation ();
98
+ mAnimation .setFillAfter (true );
99
+ mAnimation .setAnimationListener (new Animation .AnimationListener () {
100
+ @ Override
101
+ public void onAnimationStart (Animation animation ) {
102
+ mAnimating = true ;
103
+ }
104
+
105
+ @ Override
106
+ public void onAnimationEnd (Animation animation ) {
107
+ // clear animation here to avoid repeated applyTransformation() calls
108
+ clearAnimation ();
109
+
110
+ if (mOnExpandListener != null ) {
111
+ mOnExpandListener .onExpand (!mCollapsed );
112
+ }
113
+
114
+ if (mScrolledParent != null ) {
115
+ int scrollDistanceY = 0 ;
116
+ if (mExpandWithScroll && !mCollapsed && expandShouldScrollParent ()) {
117
+ scrollDistanceY = getDescendantBottom ((ViewGroup ) mScrolledParent .getParent (), ExpandableLayout .this )
118
+ - mScrolledParent .getBottom () + mExpandScrollOffset ;
119
+ } else if (mCollapseWithScroll && collapsedShouldScrollParent ()) {
120
+ scrollDistanceY = getDescendantTop ((ViewGroup ) mScrolledParent .getParent (), ExpandableLayout .this )
121
+ - mScrolledParent .getTop () - mCollapseScrollOffset ;
122
+ }
123
+
124
+ if (mScrolledParent instanceof RecyclerView ) {
125
+ RecyclerView recyclerView = (RecyclerView ) mScrolledParent ;
126
+ recyclerView .smoothScrollBy (0 , scrollDistanceY );
127
+ } else if (mScrolledParent instanceof AbsListView ) {
128
+ AbsListView listView = (AbsListView ) mScrolledParent ;
129
+ listView .smoothScrollBy (scrollDistanceY , ANIMATION_DURATION );
130
+ }
131
+ }
132
+
133
+ mAnimating = false ;
134
+ }
135
+
136
+ @ Override
137
+ public void onAnimationRepeat (Animation animation ) {
138
+
139
+ }
140
+ });
64
141
}
65
142
66
143
@ Override
@@ -71,15 +148,34 @@ protected void onAttachedToWindow() {
71
148
}
72
149
73
150
/**
74
- * Set a View, we will collapsed Views after this View .
151
+ * Set a View, we will collapse views under this view in the screen .
75
152
* @param view Last view in collapsed state
76
153
*/
77
154
public void setCollapsedEdgeView (View view ){
78
- if (view == null ){
155
+ if (view == null || view . equals ( mLastView ) ){
79
156
return ;
80
157
}
81
158
mLastView = view ;
82
- requestLayout ();
159
+
160
+ measure (0 , 0 );
161
+ int width = getMeasuredWidth ();
162
+ int height = getMeasuredHeight ();
163
+ layout (0 , 0 , width , height );
164
+
165
+ int collapsedHeight = getDescendantBottom (ExpandableLayout .this , mLastView ) + mCollapsedOffset ;
166
+ if (collapsedHeight > 0 && mCollapsedHeight != collapsedHeight ) {
167
+ Log .d (TAG , "collapsedHeight ->>>>> " + collapsedHeight );
168
+ mCollapsedHeight = collapsedHeight ;
169
+ mCurrentHeight = collapsedHeight ;
170
+ }
171
+ }
172
+
173
+ public void setCollapsedEdgeView (View view , int collapsedOffset ) {
174
+ if (view == null ) {
175
+ return ;
176
+ }
177
+ mCollapsedOffset = collapsedOffset ;
178
+ setCollapsedEdgeView (view );
83
179
}
84
180
85
181
// Not Supported
@@ -119,133 +215,75 @@ public void setCollapseWithScroll(boolean scroll, int offset) {
119
215
}
120
216
121
217
public void initState (boolean collapsed ){
122
- if (collapsed ){
123
- mCurrentHeight = mCollapsedHeight ;
218
+ if (collapsed ){ // init as collapsed
219
+ collapse ( false ) ;
124
220
}else {
125
- mCurrentHeight = mExpandedHeight ;
221
+ expand ( false ) ;
126
222
}
127
- mCollapsed = collapsed ;
128
- requestLayout ();
129
223
}
130
224
131
225
@ Override
132
226
protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) {
133
227
super .onMeasure (widthMeasureSpec , heightMeasureSpec );
134
228
135
- int width = getMeasuredWidth ();
136
- int height = getMeasuredHeight ();
137
-
138
- if (mExpandedHeight == 0 || mExpandedHeight > height ) {
139
- mExpandedHeight = height ;
140
- }
141
-
142
- if (mCurrentHeight >= 0 && height != mCurrentHeight ) {
229
+ mExpandedHeight = getMeasuredHeight ();
230
+ if (mCurrentHeight >= 0 && mExpandedHeight != mCurrentHeight ) {
231
+ int width = getMeasuredWidth ();
143
232
setMeasuredDimension (width , mCurrentHeight );
233
+ Log .d (TAG , "current height ->>>>>> " + mCurrentHeight );
144
234
}
145
235
}
146
236
147
- @ Override
148
- protected void onLayout (boolean changed , int l , int t , int r , int b ) {
149
- super .onLayout (changed , l , t , r , b );
150
- if (!mAnimating && mCollapsed ) {
151
- if (mLastView != null ) {
152
- int collapsedHeight = getDescendantBottom (ExpandableLayout .this , mLastView );
153
- if (collapsedHeight > 0 && mCollapsedHeight != collapsedHeight ) {
154
- mCollapsedHeight = collapsedHeight ;
155
- mCurrentHeight = collapsedHeight ;
156
- mLastView .post (new Runnable () {
157
- @ Override
158
- public void run () {
159
- requestLayout ();
160
- }
161
- });
162
- }
163
- } else {
164
- mCollapsedHeight = 0 ;
165
- mCurrentHeight = 0 ;
166
- }
237
+ public void toggle (boolean withAnimation ){
238
+ if (mAnimating ) {
239
+ return ;
167
240
}
168
-
169
- }
170
-
171
- public void toggle (){
172
- mInitialMeasure = false ;
173
-
174
- clearAnimation ();
175
- Animation animation ;
176
- if (mCollapsed ){ // need expand
177
- animation = new ExpandCollapseAnimation (getHeight (), mExpandedHeight + mExpandedOffset );
178
- }else { // need collapsed
179
- animation = new ExpandCollapseAnimation (getHeight (), mCollapsedHeight + mCollapsedOffset );
241
+ if (mCollapsed ) { // need expand
242
+ expand (withAnimation );
243
+ } else { // need collapsed
244
+ collapse (withAnimation );
180
245
}
246
+ }
181
247
182
- mCollapsed = !mCollapsed ;
183
- animation .setFillAfter (true );
184
- animation .setAnimationListener (new Animation .AnimationListener () {
185
- @ Override
186
- public void onAnimationStart (Animation animation ) {
187
- mAnimating = true ;
188
- }
189
-
190
- @ Override
191
- public void onAnimationEnd (Animation animation ) {
192
- // clear animation here to avoid repeated applyTransformation() calls
193
- clearAnimation ();
194
-
195
- if (mOnExpandListener != null ) {
196
- mOnExpandListener .onExpand (!mCollapsed );
197
- }
198
-
199
- if (mScrolledParent != null ){
200
- int scrollDistanceY = 0 ;
201
- if (mExpandWithScroll && !mCollapsed && expandShouldScrollParent ()){
202
- scrollDistanceY = getDescendantBottom ((ViewGroup ) mScrolledParent .getParent (), ExpandableLayout .this )
203
- - mScrolledParent .getBottom () + mExpandScrollOffset ;
204
- }else if (mCollapseWithScroll && collapsedShouldScrollParent ()){
205
- scrollDistanceY = getDescendantTop ((ViewGroup ) mScrolledParent .getParent (), ExpandableLayout .this )
206
- - mScrolledParent .getTop () - mCollapseScrollOffset ;
207
- }
208
-
209
- if (mScrolledParent instanceof RecyclerView ){
210
- RecyclerView recyclerView = (RecyclerView ) mScrolledParent ;
211
- recyclerView .smoothScrollBy (0 , scrollDistanceY );
212
- }else if (mScrolledParent instanceof AbsListView ) {
213
- AbsListView listView = (AbsListView ) mScrolledParent ;
214
- listView .smoothScrollBy (scrollDistanceY , ANIMATION_DURATION );
215
- }
216
- }
217
-
218
- mAnimating = false ;
219
- }
220
-
221
- @ Override
222
- public void onAnimationRepeat (Animation animation ) {
223
-
224
- }
225
- });
226
-
227
- startAnimation (animation );
228
-
229
- // Collapse/Expand with no animation
230
- // ViewGroup.LayoutParams lp = getLayoutParams();
231
- // if(mCollapsed){
232
- // lp.height = mExpandedHeight;
233
- // }else{
234
- // lp.height = mCollapsedHeight;
235
- // }
236
- // setLayoutParams(lp);
237
- // mCollapsed = !mCollapsed;
248
+ public void toggleWithAnimation () {
249
+ toggle (true );
238
250
}
239
251
240
252
/**
241
253
* Toggle with offsets
242
254
* @param expandedOffset Add offset to expanded height
243
255
* @param collapsedOffset Add offset to collapsed height
244
256
*/
245
- public void toggle (int expandedOffset , int collapsedOffset ){
257
+ public void toggleWithOffset (int expandedOffset , int collapsedOffset ){
246
258
mExpandedOffset = expandedOffset ;
247
259
mCollapsedOffset = collapsedOffset ;
248
- toggle ();
260
+ mExpandedHeight += mExpandedOffset ;
261
+ mCollapsedHeight += mCollapsedOffset ;
262
+ toggle (true );
263
+ }
264
+
265
+ public void expand (boolean withAnimation ) {
266
+ if (withAnimation && mAnimation != null ) {
267
+ clearAnimation ();
268
+ mAnimation .setData (getHeight (), mExpandedHeight );
269
+ startAnimation (mAnimation );
270
+ } else {
271
+ mCurrentHeight = mExpandedHeight ;
272
+ requestLayout ();
273
+ }
274
+ mCollapsed = false ;
275
+ }
276
+
277
+ public void collapse (boolean withAnimation ) {
278
+ if (withAnimation && mAnimation != null ) {
279
+ clearAnimation ();
280
+ mAnimation .setData (getHeight (), mCollapsedHeight );
281
+ startAnimation (mAnimation );
282
+ } else {
283
+ mCurrentHeight = mCollapsedHeight ;
284
+ requestLayout ();
285
+ }
286
+ mCollapsed = true ;
249
287
}
250
288
251
289
private boolean expandShouldScrollParent () {
@@ -315,20 +353,27 @@ private int getDescendantTop(ViewGroup parent, View view) {
315
353
}else {
316
354
return top + getDescendantTop (parent , (View ) view .getParent ());
317
355
}
318
- } else { // view.getParent() is a ViewRootImpl instance means that parent does not contains view
356
+ } else { // view.getParent() is a ViewRootImpl instance means that parent does not contain this view
319
357
throw new RuntimeException ("view must be included in the parent" );
320
358
}
321
-
322
359
}
323
360
324
- class ExpandCollapseAnimation extends Animation {
325
- private final int mStartHeight ;
326
- private final int mEndHeight ;
361
+ private class ExpandCollapseAnimation extends Animation {
362
+ private int mStartHeight ;
363
+ private int mEndHeight ;
364
+
365
+ public ExpandCollapseAnimation () {
366
+ setDuration (ANIMATION_DURATION );
367
+ }
327
368
328
369
public ExpandCollapseAnimation (int startHeight , int endHeight ) {
370
+ setData (startHeight , endHeight );
371
+ setDuration (ANIMATION_DURATION );
372
+ }
373
+
374
+ public void setData (int startHeight , int endHeight ) {
329
375
mStartHeight = startHeight ;
330
376
mEndHeight = endHeight ;
331
- setDuration (ANIMATION_DURATION );
332
377
}
333
378
334
379
@ Override
@@ -342,11 +387,12 @@ protected void applyTransformation(float interpolatedTime, Transformation t) {
342
387
}
343
388
344
389
mCurrentHeight = newHeight + heightOffset ;
390
+
345
391
requestLayout ();
346
392
}
347
393
}
348
394
349
- public interface OnExpandListener {
395
+ interface OnExpandListener {
350
396
void onExpand (boolean expand );
351
397
352
398
/**
0 commit comments