Skip to content

Most visited

Recently visited

navigation
BatchStepSensor / src / com.example.android.batchstepsensor / cardstream /

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
package com.example.android.batchstepsensor.cardstream;
19
 
20
import android.animation.Animator;
21
import android.animation.LayoutTransition;
22
import android.animation.ObjectAnimator;
23
import android.annotation.SuppressLint;
24
import android.annotation.TargetApi;
25
import android.content.Context;
26
import android.content.res.TypedArray;
27
import android.graphics.Rect;
28
import android.os.Build;
29
import android.util.AttributeSet;
30
import android.view.MotionEvent;
31
import android.view.View;
32
import android.view.ViewConfiguration;
33
import android.view.ViewGroup;
34
import android.view.ViewParent;
35
import android.widget.LinearLayout;
36
import android.widget.ScrollView;
37
 
38
import com.example.android.common.logger.Log;
39
import com.example.android.batchstepsensor.R;
40
 
41
import java.util.ArrayList;
42
 
43
/**
44
 * A Layout that contains a stream of card views.
45
 */
46
public class CardStreamLinearLayout extends LinearLayout {
47
 
48
    public static final int ANIMATION_SPEED_SLOW = 1001;
49
    public static final int ANIMATION_SPEED_NORMAL = 1002;
50
    public static final int ANIMATION_SPEED_FAST = 1003;
51
 
52
    private static final String TAG = "CardStreamLinearLayout";
53
    private final ArrayList<View> mFixedViewList = new ArrayList<View>();
54
    private final Rect mChildRect = new Rect();
55
    private CardStreamAnimator mAnimators;
56
    private OnDissmissListener mDismissListener = null;
57
    private boolean mLayouted = false;
58
    private boolean mSwiping = false;
59
    private String mFirstVisibleCardTag = null;
60
    private boolean mShowInitialAnimation = false;
61
 
62
    /**
63
     * Handle touch events to fade/move dragged items as they are swiped out
64
     */
65
    private OnTouchListener mTouchListener = new OnTouchListener() {
66
 
67
        private float mDownX;
68
        private float mDownY;
69
 
70
        @Override
71
        public boolean onTouch(final View v, MotionEvent event) {
72
 
73
            switch (event.getAction()) {
74
                case MotionEvent.ACTION_DOWN:
75
                    mDownX = event.getX();
76
                    mDownY = event.getY();
77
                    break;
78
                case MotionEvent.ACTION_CANCEL:
79
                    resetAnimatedView(v);
80
                    mSwiping = false;
81
                    mDownX = 0.f;
82
                    mDownY = 0.f;
83
                    break;
84
                case MotionEvent.ACTION_MOVE: {
85
 
86
                    float x = event.getX() + v.getTranslationX();
87
                    float y = event.getY() + v.getTranslationY();
88
 
89
                    mDownX = mDownX == 0.f ? x : mDownX;
90
                    mDownY = mDownY == 0.f ? x : mDownY;
91
 
92
                    float deltaX = x - mDownX;
93
                    float deltaY = y - mDownY;
94
 
95
                    if (!mSwiping && isSwiping(deltaX, deltaY)) {
96
                        mSwiping = true;
97
                        v.getParent().requestDisallowInterceptTouchEvent(true);
98
                    } else {
99
                        swipeView(v, deltaX, deltaY);
100
                    }
101
                }
102
                break;
103
                case MotionEvent.ACTION_UP: {
104
                    // User let go - figure out whether to animate the view out, or back into place
105
                    if (mSwiping) {
106
                        float x = event.getX() + v.getTranslationX();
107
                        float y = event.getY() + v.getTranslationY();
108
 
109
                        float deltaX = x - mDownX;
110
                        float deltaY = y - mDownX;
111
                        float deltaXAbs = Math.abs(deltaX);
112
 
113
                        // User let go - figure out whether to animate the view out, or back into place
114
                        boolean remove = deltaXAbs > v.getWidth() / 4 && !isFixedView(v);
115
                        if( remove )
116
                            handleViewSwipingOut(v, deltaX, deltaY);
117
                        else
118
                            handleViewSwipingIn(v, deltaX, deltaY);
119
                    }
120
                    mDownX = 0.f;
121
                    mDownY = 0.f;
122
                    mSwiping = false;
123
                }
124
                break;
125
                default:
126
                    return false;
127
            }
128
            return false;
129
        }
130
    };
131
    private int mSwipeSlop = -1;
132
    /**
133
     * Handle end-transition animation event of each child and launch a following animation.
134
     */
135
    private LayoutTransition.TransitionListener mTransitionListener
136
            = new LayoutTransition.TransitionListener() {
137
 
138
        @Override
139
        public void startTransition(LayoutTransition transition, ViewGroup container, View
140
                view, int transitionType) {
141
            Log.d(TAG, "Start LayoutTransition animation:" + transitionType);
142
        }
143
 
144
        @Override
145
        public void endTransition(LayoutTransition transition, ViewGroup container,
146
                                  final View view, int transitionType) {
147
 
148
            Log.d(TAG, "End LayoutTransition animation:" + transitionType);
149
            if (transitionType == LayoutTransition.APPEARING) {
150
                final View area = view.findViewById(R.id.card_actionarea);
151
                if (area != null) {
152
                    runShowActionAreaAnimation(container, area);
153
                }
154
            }
155
        }
156
    };
157
    /**
158
     * Handle a hierarchy change event
159
     * when a new child is added, scroll to bottom and hide action area..
160
     */
161
    private OnHierarchyChangeListener mOnHierarchyChangeListener
162
            = new OnHierarchyChangeListener() {
163
        @Override
164
        public void onChildViewAdded(final View parent, final View child) {
165
 
166
            Log.d(TAG, "child is added: " + child);
167
 
168
            ViewParent scrollView = parent.getParent();
169
            if (scrollView != null && scrollView instanceof ScrollView) {
170
                ((ScrollView) scrollView).fullScroll(FOCUS_DOWN);
171
            }
172
 
173
            if (getLayoutTransition() != null) {
174
                View view = child.findViewById(R.id.card_actionarea);
175
                if (view != null)
176
                    view.setAlpha(0.f);
177
            }
178
        }
179
 
180
        @Override
181
        public void onChildViewRemoved(View parent, View child) {
182
            Log.d(TAG, "child is removed: " + child);
183
            mFixedViewList.remove(child);
184
        }
185
    };
186
    private int mLastDownX;
187
 
188
    public CardStreamLinearLayout(Context context) {
189
        super(context);
190
        initialize(null, 0);
191
    }
192
 
193
    public CardStreamLinearLayout(Context context, AttributeSet attrs) {
194
        super(context, attrs);
195
        initialize(attrs, 0);
196
    }
197
 
198
    @SuppressLint("NewApi")
199
    public CardStreamLinearLayout(Context context, AttributeSet attrs, int defStyle) {
200
        super(context, attrs, defStyle);
201
        initialize(attrs, defStyle);
202
    }
203
 
204
    /**
205
     * add a card view w/ canDismiss flag.
206
     *
207
     * @param cardView   a card view
208
     * @param canDismiss flag to indicate this card is dismissible or not.
209
     */
210
    public void addCard(View cardView, boolean canDismiss) {
211
        if (cardView.getParent() == null) {
212
            initCard(cardView, canDismiss);
213
 
214
            ViewGroup.LayoutParams param = cardView.getLayoutParams();
215
            if(param == null)
216
                param = generateDefaultLayoutParams();
217
 
218
            super.addView(cardView, -1, param);
219
        }
220
    }
221
 
222
    @Override
223
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
224
        if (child.getParent() == null) {
225
            initCard(child, true);
226
            super.addView(child, index, params);
227
        }
228
    }
229
 
230
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
231
    @Override
232
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
233
        super.onLayout(changed, l, t, r, b);
234
        Log.d(TAG, "onLayout: " + changed);
235
 
236
        if( changed && !mLayouted ){
237
            mLayouted = true;
238
 
239
            ObjectAnimator animator;
240
            LayoutTransition layoutTransition = new LayoutTransition();
241
 
242
            animator = mAnimators.getDisappearingAnimator(getContext());
243
            layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animator);
244
 
245
            animator = mAnimators.getAppearingAnimator(getContext());
246
            layoutTransition.setAnimator(LayoutTransition.APPEARING, animator);
247
 
248
            layoutTransition.addTransitionListener(mTransitionListener);
249
 
250
            if( animator != null )
251
                layoutTransition.setDuration(animator.getDuration());
252
 
253
            setLayoutTransition(layoutTransition);
254
 
255
            if( mShowInitialAnimation )
256
                runInitialAnimations();
257
 
258
            if (mFirstVisibleCardTag != null) {
259
                scrollToCard(mFirstVisibleCardTag);
260
                mFirstVisibleCardTag = null;
261
            }
262
        }
263
    }
264
 
265
    /**
266
     * Check whether a user moved enough distance to start a swipe action or not.
267
     *
268
     * @param deltaX
269
     * @param deltaY
270
     * @return true if a user is swiping.
271
     */
272
    protected boolean isSwiping(float deltaX, float deltaY) {
273
 
274
        if (mSwipeSlop < 0) {
275
            //get swipping slop from ViewConfiguration;
276
            mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
277
        }
278
 
279
        boolean swipping = false;
280
        float absDeltaX = Math.abs(deltaX);
281
 
282
        if( absDeltaX > mSwipeSlop )
283
            return true;
284
 
285
        return swipping;
286
    }
287
 
288
    /**
289
     * Swipe a view by moving distance
290
     *
291
     * @param child a target view
292
     * @param deltaX x moving distance by x-axis.
293
     * @param deltaY y moving distance by y-axis.
294
     */
295
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
296
    protected void swipeView(View child, float deltaX, float deltaY) {
297
        if (isFixedView(child)){
298
            deltaX = deltaX / 4;
299
        }
300
 
301
        float deltaXAbs = Math.abs(deltaX);
302
        float fractionCovered = deltaXAbs / (float) child.getWidth();
303
 
304
        child.setTranslationX(deltaX);
305
        child.setAlpha(1.f - fractionCovered);
306
 
307
        if (deltaX > 0)
308
            child.setRotationY(-15.f * fractionCovered);
309
        else
310
            child.setRotationY(15.f * fractionCovered);
311
    }
312
 
313
    protected void notifyOnDismissEvent( View child ){
314
        if( child == null || mDismissListener == null )
315
            return;
316
 
317
        mDismissListener.onDismiss((String) child.getTag());
318
    }
319