加载中...
The Wayback Machine - https://sup1a9wrlpyh5li9ro.vcoronado.top/web/20140305055250/http://developer.android.com:80/samples/BatchStepSensor/src/com.example.android.batchstepsensor/CardStreamLinearLayout.html
to top
BatchStepSensor / src / com.example.android.batchstepsensor /

CardStreamLinearLayout.java

1
/*
2
* Copyright 2013 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
 
18
 
19
 
20
package com.example.android.batchstepsensor;
21
 
22
import android.animation.Animator;
23
import android.animation.LayoutTransition;
24
import android.animation.ObjectAnimator;
25
import android.annotation.SuppressLint;
26
import android.annotation.TargetApi;
27
import android.content.Context;
28
import android.content.res.TypedArray;
29
import android.graphics.Rect;
30
import android.os.Build;
31
import android.util.AttributeSet;
32
import android.view.MotionEvent;
33
import android.view.View;
34
import android.view.ViewConfiguration;
35
import android.view.ViewGroup;
36
import android.view.ViewParent;
37
import android.widget.LinearLayout;
38
import android.widget.ScrollView;
39
 
40
import com.example.android.common.logger.Log;
41
 
42
import java.util.ArrayList;
43
 
44
/**
45
 * A Layout that contains a stream of card views.
46
 */
47
public class CardStreamLinearLayout extends LinearLayout {
48
 
49
    public static final int ANIMATION_SPEED_SLOW = 1001;
50
    public static final int ANIMATION_SPEED_NORMAL = 1002;
51
    public static final int ANIMATION_SPEED_FAST = 1003;
52
 
53
    private static final String TAG = "CardStreamLinearLayout";
54
    private final ArrayList<View> mFixedViewList = new ArrayList<View>();
55
    private final Rect mChildRect = new Rect();
56
    private CardStreamAnimator mAnimators;
57
    private OnDissmissListener mDismissListener = null;
58
    private boolean mLayouted = false;
59
    private boolean mSwiping = false;
60
    private String mFirstVisibleCardTag = null;
61
    private boolean mShowInitialAnimation = false;
62
 
63
    /**
64
     * Handle touch events to fade/move dragged items as they are swiped out
65
     */
66
    private OnTouchListener mTouchListener = new OnTouchListener() {
67
 
68
        private float mDownX;
69
        private float mDownY;
70
 
71
        @Override
72
        public boolean onTouch(final View v, MotionEvent event) {
73
 
74
            switch (event.getAction()) {
75
                case MotionEvent.ACTION_DOWN:
76
                    mDownX = event.getX();
77
                    mDownY = event.getY();
78
                    break;
79
                case MotionEvent.ACTION_CANCEL:
80
                    resetAnimatedView(v);
81
                    mSwiping = false;
82
                    mDownX = 0.f;
83
                    mDownY = 0.f;
84
                    break;
85
                case MotionEvent.ACTION_MOVE: {
86
 
87
                    float x = event.getX() + v.getTranslationX();
88
                    float y = event.getY() + v.getTranslationY();
89
 
90
                    mDownX = mDownX == 0.f ? x : mDownX;
91
                    mDownY = mDownY == 0.f ? x : mDownY;
92
 
93
                    float deltaX = x - mDownX;
94
                    float deltaY = y - mDownY;
95
 
96
                    if (!mSwiping && isSwiping(deltaX, deltaY)) {
97
                        mSwiping = true;
98
                        v.getParent().requestDisallowInterceptTouchEvent(true);
99
                    } else {
100
                        swipeView(v, deltaX, deltaY);
101
                    }
102
                }
103
                break;
104
                case MotionEvent.ACTION_UP: {
105
                    // User let go - figure out whether to animate the view out, or back into place
106
                    if (mSwiping) {
107
                        float x = event.getX() + v.getTranslationX();
108
                        float y = event.getY() + v.getTranslationY();
109
 
110
                        float deltaX = x - mDownX;
111
                        float deltaY = y - mDownX;
112
                        float deltaXAbs = Math.abs(deltaX);
113
 
114
                        // User let go - figure out whether to animate the view out, or back into place
115
                        boolean remove = deltaXAbs > v.getWidth() / 4 && !isFixedView(v);
116
                        if( remove )
117
                            handleViewSwipingOut(v, deltaX, deltaY);
118
                        else
119
                            handleViewSwipingIn(v, deltaX, deltaY);
120
                    }
121
                    mDownX = 0.f;
122
                    mDownY = 0.f;
123
                    mSwiping = false;
124
                }
125
                break;
126
                default:
127
                    return false;
128
            }
129
            return false;
130
        }
131
    };
132
    private int mSwipeSlop = -1;
133
    /**
134
     * Handle end-transition animation event of each child and launch a following animation.
135
     */
136
    private LayoutTransition.TransitionListener mTransitionListener
137
            = new LayoutTransition.TransitionListener() {
138
 
139
        @Override
140
        public void startTransition(LayoutTransition transition, ViewGroup container, View
141
                view, int transitionType) {
142
            Log.d(TAG, "Start LayoutTransition animation:" + transitionType);
143
        }
144
 
145
        @Override
146
        public void endTransition(LayoutTransition transition, ViewGroup container,
147
                                  final View view, int transitionType) {
148
 
149
            Log.d(TAG, "End LayoutTransition animation:" + transitionType);
150
            if (transitionType == LayoutTransition.APPEARING) {
151
                final View area = view.findViewById(R.id.card_actionarea);
152
                if (area != null) {
153
                    runShowActionAreaAnimation(container, area);
154
                }
155
            }
156
        }
157
    };
158
    /**
159
     * Handle a hierarchy change event
160
     * when a new child is added, scroll to bottom and hide action area..
161
     */
162
    private OnHierarchyChangeListener mOnHierarchyChangeListener
163
            = new OnHierarchyChangeListener() {
164
        @Override
165
        public void onChildViewAdded(final View parent, final View child) {
166
 
167
            Log.d(TAG, "child is added: " + child);
168
 
169
            ViewParent scrollView = parent.getParent();
170
            if (scrollView != null && scrollView instanceof ScrollView) {
171
                ((ScrollView) scrollView).fullScroll(FOCUS_DOWN);
172
            }
173
 
174
            if (getLayoutTransition() != null) {
175
                View view = child.findViewById(R.id.card_actionarea);
176
                if (view != null)
177
                    view.setAlpha(0.f);
178
            }
179
        }
180
 
181
        @Override
182
        public void onChildViewRemoved(View parent, View child) {
183
            Log.d(TAG, "child is removed: " + child);
184
            mFixedViewList.remove(child);
185
        }
186
    };
187
    private int mLastDownX;
188
 
189
    public CardStreamLinearLayout(Context context) {
190
        super(context);
191
        initialize(null, 0);
192
    }
193
 
194
    public CardStreamLinearLayout(Context context, AttributeSet attrs) {
195
        super(context, attrs);
196
        initialize(attrs, 0);
197
    }
198
 
199
    @SuppressLint("NewApi")
200
    public CardStreamLinearLayout(Context context, AttributeSet attrs, int defStyle) {
201
        super(context, attrs, defStyle);
202
        initialize(attrs, defStyle);
203
    }
204
 
205
    /**
206
     * add a card view w/ canDismiss flag.
207
     *
208
     * @param cardView   a card view
209
     * @param canDismiss flag to indicate this card is dismissible or not.
210
     */
211
    public void addCard(View cardView, boolean canDismiss) {
212
        if (cardView.getParent() == null) {
213
            initCard(cardView, canDismiss);
214
 
215
            ViewGroup.LayoutParams param = cardView.getLayoutParams();
216
            if(param == null)
217
                param = generateDefaultLayoutParams();
218
 
219
            super.addView(cardView, -1, param);
220
        }
221
    }
222
 
223
    @Override
224
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
225
        if (child.getParent() == null) {
226
            initCard(child, true);
227
            super.addView(child, index, params);
228
        }
229
    }
230
 
231
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
232
    @Override
233
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
234
        super.onLayout(changed, l, t, r, b);
235
        Log.d(TAG, "onLayout: " + changed);
236
 
237
        if( changed && !mLayouted ){
238
            mLayouted = true;
239
 
240
            ObjectAnimator animator;
241
            LayoutTransition layoutTransition = new LayoutTransition();
242
 
243
            animator = mAnimators.getDisappearingAnimator(getContext());
244
            layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animator);
245
 
246
            animator = mAnimators.getAppearingAnimator(getContext());
247
            layoutTransition.setAnimator(LayoutTransition.APPEARING, animator);
248
 
249
            layoutTransition.addTransitionListener(mTransitionListener);
250
 
251
            if( animator != null )
252
                layoutTransition.setDuration(animator.getDuration());
253
 
254
            setLayoutTransition(layoutTransition);
255
 
256
            if( mShowInitialAnimation )
257
                runInitialAnimations();
258
 
259
            if (mFirstVisibleCardTag != null) {
260
                scrollToCard(mFirstVisibleCardTag);
261
                mFirstVisibleCardTag = null;
262
            }
263
        }
264
    }
265
 
266
    /**
267
     * Check whether a user moved enough distance to start a swipe action or not.
268
     *
269
     * @param deltaX
270
     * @param deltaY
271
     * @return true if a user is swiping.
272
     */
273
    protected boolean isSwiping(float deltaX, float deltaY) {
274
 
275
        if (mSwipeSlop < 0) {
276
            //get swipping slop from ViewConfiguration;
277
            mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
278
        }
279
 
280
        boolean swipping = false;
281
        float absDeltaX = Math.abs(deltaX);
282
 
283
        if( absDeltaX > mSwipeSlop )
284
            return true;
285
 
286
        return swipping;
287
    }
288
 
289
    /**
290
     * Swipe a view by moving distance
291
     *
292
     * @param child a target view
293
     * @param deltaX x moving distance by x-axis.
294
     * @param deltaY y moving distance by y-axis.
295
     */
296
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
297
    protected void swipeView(View child, float deltaX, float deltaY) {
298
        if (isFixedView(child)){
299
            deltaX = deltaX / 4;
300
        }
301
 
302
        float deltaXAbs = Math.abs(deltaX);
303
        float fractionCovered = deltaXAbs / (float) child.getWidth();
304
 
305
        child.setTranslationX(deltaX);
306
        child.setAlpha(1.f - fractionCovered);
307
 
308
        if (deltaX > 0)
309
            child.setRotationY(-15.f * fractionCovered);
310
        else
311
            child.setRotationY(15.f * fractionCovered);
312
    }
313
 
314
    protected void notifyOnDismissEvent( View child ){
315
        if( child == null || mDismissListener == null )
316
            return;
317
 
318
        mDismissListener.onDismiss((String) child.getTag());
319
    }
320
 
321
    /**
322
     * get the tag of the first visible child in this layout
323
     *
324
     * @return tag of the first visible child or null
325
     */
326
    public String getFirstVisibleCardTag() {
327
 
328
        final int count = getChildCount();
329
 
330
        if (count == 0)
331
            return null;
332
 
333
        for (int index = 0; index < count; ++index) {
334
            //check the position of each view.
335
            View child = getChildAt(index);
336
            if (child.getGlobalVisibleRect(mChildRect) == true)
337
                return (String) child.getTag();
338
        }
339
 
340
        return null;
341
    }
342
 
343
    /**
344
     * Set the first visible card of this linear layout.
345
     *
346
     * @param tag tag of a card which should already added to this layout.
347
     */
348
    public void setFirstVisibleCard(String tag) {
349
        if (tag == null)
350
            return; //do nothing.
351
 
352
        if (mLayouted) {
353
            scrollToCard(tag);
354
        } else {
355
            //keep the tag for next use.
356
            mFirstVisibleCardTag = tag;
357
        }
358
    }
359
 
360
    /**
361
     * If this flag is set,
362
     * after finishing initial onLayout event, an initial animation which is defined in DefaultCardStreamAnimator is launched.
363
     */
364
    public void triggerShowInitialAnimation(){
365
        mShowInitialAnimation = true;
366
    }
367
 
368
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
369
    public void setCardStreamAnimator( CardStreamAnimator animators ){
370
 
371
        if( animators == null )
372
            mAnimators = new CardStreamAnimator.EmptyAnimator();
373
        else
374
            mAnimators = animators;
375
 
376
        LayoutTransition layoutTransition = getLayoutTransition();
377
 
378
        if( layoutTransition != null ){
379
            layoutTransition.setAnimator( LayoutTransition.APPEARING,
380
                    mAnimators.getAppearingAnimator(getContext()) );
381
            layoutTransition.setAnimator( LayoutTransition.DISAPPEARING,
382
                    mAnimators.getDisappearingAnimator(getContext()) );
383
        }
384
    }
385
 
386
    /**
387
     * set a OnDismissListener which called when user dismiss a card.
388
     *
389
     * @param listener
390
     */
391
    public void setOnDismissListener(OnDissmissListener listener) {
392
        mDismissListener = listener;
393
    }
394
 
395
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
396
    private void initialize(AttributeSet attrs, int defStyle) {
397
 
398
        float speedFactor = 1.f;
399
 
400
        if (attrs != null) {
401
            TypedArray a = getContext().obtainStyledAttributes(attrs,
402
                    R.styleable.CardStream, defStyle, 0);
403
 
404
            if( a != null ){
405
                int speedType = a.getInt(R.styleable.CardStream_animationDuration, 1001);
406
                switch (speedType){
407
                    case ANIMATION_SPEED_FAST:
408
                        speedFactor = 0.5f;
409
                        break;
410
                    case ANIMATION_SPEED_NORMAL:
411
                        speedFactor = 1.f;
412
                        break;
413
                    case ANIMATION_SPEED_SLOW:
414
                        speedFactor = 2.f;
415
                        break;
416
                }
417
 
418
                String animatorName = a.getString(R.styleable.CardStream_animators);
419
 
420
                try {
421
                    if( animatorName != null )
422
                        mAnimators = (CardStreamAnimator) getClass().getClassLoader()
423
                                .loadClass(animatorName).newInstance();
424
                } catch (Exception e) {
425
                    Log.e(TAG, "Fail to load animator:" + animatorName, e);
426
                } finally {
427
                    if(mAnimators == null)
428
                        mAnimators = new DefaultCardStreamAnimator();
429
                }
430
                a.recycle();
431
            }
432
        }
433
 
434
        mAnimators.setSpeedFactor(speedFactor);
435
        mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
436
        setOnHierarchyChangeListener(mOnHierarchyChangeListener);
437
    }
438
 
439
    private void initCard(View cardView, boolean canDismiss) {
440
        resetAnimatedView(cardView);
441
        cardView.setOnTouchListener(mTouchListener);
442
        if (!canDismiss)
443
            mFixedViewList.add(cardView);
444
    }
445
 
446
    private boolean isFixedView(View v) {
447
        return mFixedViewList.contains(v);
448
    }
449
 
450
    private void resetAnimatedView(View child) {
451
        child.setAlpha(1.f);
452
        child.setTranslationX(0.f);
453
        child.setTranslationY(0.f);
454
        child.setRotation(0.f);
455
        child.setRotationY(0.f);
456
        child.setRotationX(0.f);
457
        child.setScaleX(1.f);
458
        child.setScaleY(1.f);
459
    }
460
 
461
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
462
    private void runInitialAnimations() {
463
        if( mAnimators == null )
464
            return;
465
 
466
        final int count = getChildCount();
467
 
468
        for (int index = 0; index < count; ++index) {
469
            final View child = getChildAt(index);
470
            ObjectAnimator animator =  mAnimators.getInitalAnimator(getContext());
471
            if( animator != null ){
472
                animator.setTarget(child);
473
                animator.start();
474
            }
475
        }
476
    }
477
 
478
    private void runShowActionAreaAnimation(View parent, View area) {
479
        area.setPivotY(0.f);
480
        area.setPivotX(parent.getWidth() / 2.f);
481
 
482
        area.setAlpha(0.5f);
483
        area.setRotationX(-90.f);
484
        area.animate().rotationX(0.f).alpha(1.f).setDuration(400);
485
    }
486
 
487
    private void handleViewSwipingOut(final View child, float deltaX, float deltaY) {
488
        ObjectAnimator animator = mAnimators.getSwipeOutAnimator(child, deltaX, deltaY);
489
        if( animator != null ){
490
            animator.addListener(new EndAnimationWrapper() {
491
                @Override
492
                public void onAnimationEnd(Animator animation) {
493
                    removeView(child);
494
                    notifyOnDismissEvent(child);
495
                }
496
            });
497
        } else {
498
            removeView(child);
499
            notifyOnDismissEvent(child);
500
        }
501
 
502
        if( animator != null ){
503
            animator.setTarget(child);
504
            animator.start();
505
        }
506
    }
507
 
508
    private void handleViewSwipingIn(final View child, float deltaX, float deltaY) {
509
        ObjectAnimator animator = mAnimators.getSwipeInAnimator(child, deltaX, deltaY);
510
        if( animator != null ){
511
            animator.addListener(new EndAnimationWrapper() {
512
                @Override
513
                public void onAnimationEnd(Animator animation) {
514
                    child.setTranslationY(0.f);
515
                    child.setTranslationX(0.f);
516
                }
517
            });
518
        } else {
519
            child.setTranslationY(0.f);
520
            child.setTranslationX(0.f);
521
        }
522
 
523
        if( animator != null ){
524
            animator.setTarget(child);
525
            animator.start();
526
        }
527
    }
528
 
529
    private void scrollToCard(String tag) {
530
 
531
 
532
        final int count = getChildCount();
533
        for (int index = 0; index < count; ++index) {
534
            View child = getChildAt(index);
535
 
536
            if (tag.equals(child.getTag())) {
537
 
538
                ViewParent parent = getParent();
539
                if( parent != null && parent instanceof ScrollView ){
540
                    ((ScrollView)parent).smoothScrollTo(
541
                            0, child.getTop() - getPaddingTop() - child.getPaddingTop());
542
                }
543
                return;
544
            }
545
        }
546
    }
547
 
548
    public interface OnDissmissListener {
549
        public void onDismiss(String tag);
550
    }
551
 
552
    /**
553
     * Empty default AnimationListener
554
     */
555
    private abstract class EndAnimationWrapper implements Animator.AnimatorListener {
556
 
557
        @Override
558
        public void onAnimationStart(Animator animation) {
559
        }
560
 
561
        @Override
562
        public void onAnimationCancel(Animator animation) {
563
        }
564
 
565
        @Override
566
        public void onAnimationRepeat(Animator animation) {
567
        }
568
    }//end of inner class
569
}