Skip to content

Most visited

Recently visited

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

Card.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.AnimatorListenerAdapter;
22
import android.animation.ObjectAnimator;
23
import android.app.Activity;
24
import android.graphics.Color;
25
import android.view.LayoutInflater;
26
import android.view.View;
27
import android.view.ViewGroup;
28
import android.widget.Button;
29
import android.widget.ProgressBar;
30
import android.widget.TextView;
31
 
32
import com.example.android.batchstepsensor.R;
33
 
34
import java.util.ArrayList;
35
 
36
/**
37
 * A Card contains a description and has a visual state. Optionally a card also contains a title,
38
 * progress indicator and zero or more actions. It is constructed through the {@link Builder}.
39
 */
40
public class Card {
41
 
42
    public static final int ACTION_POSITIVE = 1;
43
    public static final int ACTION_NEGATIVE = 2;
44
    public static final int ACTION_NEUTRAL = 3;
45
 
46
    public static final int PROGRESS_TYPE_NO_PROGRESS = 0;
47
    public static final int PROGRESS_TYPE_NORMAL = 1;
48
    public static final int PROGRESS_TYPE_INDETERMINATE = 2;
49
    public static final int PROGRESS_TYPE_LABEL = 3;
50
 
51
    private OnCardClickListener mClickListener;
52
 
53
 
54
    // The card model contains a reference to its desired layout (for extensibility), title,
55
    // description, zero to many action buttons, and zero or 1 progress indicators.
56
    private int mLayoutId = R.layout.card;
57
 
58
    /**
59
     * Tag that uniquely identifies this card.
60
     */
61
    private String mTag = null;
62
 
63
    private String mTitle = null;
64
    private String mDescription = null;
65
 
66
    private View mCardView = null;
67
    private View mOverlayView = null;
68
    private TextView mTitleView = null;
69
    private TextView mDescView = null;
70
    private View mActionAreaView = null;
71
 
72
    private Animator mOngoingAnimator = null;
73
 
74
    /**
75
     * Visual state, either {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED} or
76
     * {@link #CARD_STATE_INACTIVE}.
77
     */
78
    private int mCardState = CARD_STATE_NORMAL;
79
    public static final int CARD_STATE_NORMAL = 1;
80
    public static final int CARD_STATE_FOCUSED = 2;
81
    public static final int CARD_STATE_INACTIVE = 3;
82
 
83
    /**
84
     * Represent actions that can be taken from the card.  Stylistically the developer can
85
     * designate the action as positive, negative (ok/cancel, for instance), or neutral.
86
     * This "type" can be used as a UI hint.
87
     * @see com.example.android.sensors.batchstepsensor.Card.CardAction
88
     */
89
    private ArrayList<CardAction> mCardActions = new ArrayList<CardAction>();
90
 
91
    /**
92
     * Some cards will have a sense of "progress" which should be associated with, but separated
93
     * from its "parent" card.  To push for simplicity in samples, Cards are designed to have
94
     * a maximum of one progress indicator per Card.
95
     */
96
    private CardProgress mCardProgress = null;
97
 
98
    public Card() {
99
    }
100
 
101
    public String getTag() {
102
        return mTag;
103
    }
104
 
105
    public View getView() {
106
        return mCardView;
107
    }
108
 
109
 
110
    public Card setDescription(String desc) {
111
        if (mDescView != null) {
112
            mDescription = desc;
113
            mDescView.setText(desc);
114
        }
115
        return this;
116
    }
117
 
118
    public Card setTitle(String title) {
119
        if (mTitleView != null) {
120
            mTitle = title;
121
            mTitleView.setText(title);
122
        }
123
        return this;
124
    }
125
 
126
 
127
    /**
128
     * Return the UI state, either {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED}
129
     * or {@link #CARD_STATE_INACTIVE}.
130
     */
131
    public int getState() {
132
        return mCardState;
133
    }
134
 
135
    /**
136
     * Set the UI state. The parameter describes the state and must be either
137
     * {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED} or {@link #CARD_STATE_INACTIVE}.
138
     * Note: This method must be called from the UI Thread.
139
     * @param state
140
     * @return The card itself, allows for chaining of calls
141
     */
142
    public Card setState(int state) {
143
        mCardState = state;
144
        if (null != mOverlayView) {
145
            if (null != mOngoingAnimator) {
146
                mOngoingAnimator.end();
147
                mOngoingAnimator = null;
148
            }
149
            switch (state) {
150
                case CARD_STATE_NORMAL: {
151
                    mOverlayView.setVisibility(View.GONE);
152
                    mOverlayView.setAlpha(1.f);
153
                    break;
154
                }
155
                case CARD_STATE_FOCUSED: {
156
                    mOverlayView.setVisibility(View.VISIBLE);
157
                    mOverlayView.setBackgroundResource(R.drawable.card_overlay_focused);
158
                    ObjectAnimator animator = ObjectAnimator.ofFloat(mOverlayView, "alpha", 0.f);
159
                    animator.setRepeatMode(ObjectAnimator.REVERSE);
160
                    animator.setRepeatCount(ObjectAnimator.INFINITE);
161
                    animator.setDuration(1000);
162
                    animator.start();
163
                    mOngoingAnimator = animator;
164
                    break;
165
                }
166
                case CARD_STATE_INACTIVE: {
167
                    mOverlayView.setVisibility(View.VISIBLE);
168
                    mOverlayView.setAlpha(1.f);
169
                    mOverlayView.setBackgroundColor(Color.argb(0xaa, 0xcc, 0xcc, 0xcc));
170
                    break;
171
                }
172
            }
173
        }
174
        return this;
175
    }
176
 
177
    /**
178
     * Set the type of progress indicator.
179
     * The progress type can only be changed if the Card was initially build with a progress
180
     * indicator.
181
     * See {@link Builder#setProgressType(int)}.
182
     * Must be a value of either {@link #PROGRESS_TYPE_NORMAL},
183
     * {@link #PROGRESS_TYPE_INDETERMINATE}, {@link #PROGRESS_TYPE_LABEL} or
184
     * {@link #PROGRESS_TYPE_NO_PROGRESS}.
185
     * @param progressType
186
     * @return The card itself, allows for chaining of calls
187
     */
188
    public Card setProgressType(int progressType) {
189
        if (mCardProgress == null) {
190
            mCardProgress = new CardProgress();
191
        }
192
        mCardProgress.setProgressType(progressType);
193
        return this;
194
    }
195
 
196
    /**
197
     * Return the progress indicator type. A value of either {@link #PROGRESS_TYPE_NORMAL},
198
     * {@link #PROGRESS_TYPE_INDETERMINATE}, {@link #PROGRESS_TYPE_LABEL}. Otherwise if no progress
199
     * indicator is enabled, {@link #PROGRESS_TYPE_NO_PROGRESS} is returned.
200
     * @return
201
     */
202
    public int getProgressType() {
203
        if (mCardProgress == null) {
204
            return PROGRESS_TYPE_NO_PROGRESS;
205
        }
206
        return mCardProgress.progressType;
207
    }
208
 
209
    /**
210
     * Set the progress to the specified value. Only applicable if the card has a
211
     * {@link #PROGRESS_TYPE_NORMAL} progress type.
212
     * @param progress
213
     * @return
214
     * @see #setMaxProgress(int)
215
     */
216
    public Card setProgress(int progress) {
217
        if (mCardProgress != null) {
218
            mCardProgress.setProgress(progress);
219
        }
220
        return this;
221
    }
222
 
223
    /**
224
     * Set the range of the progress to 0...max. Only applicable if the card has a
225
     * {@link #PROGRESS_TYPE_NORMAL} progress type.
226
     * @return
227
     */
228
    public Card setMaxProgress(int max){
229
        if (mCardProgress != null) {
230
            mCardProgress.setMax(max);
231
        }
232
        return this;
233
    }
234
 
235
    /**
236
     * Set the label text for the progress if the card has a progress type of
237
     * {@link #PROGRESS_TYPE_NORMAL}, {@link #PROGRESS_TYPE_INDETERMINATE} or
238
     * {@link #PROGRESS_TYPE_LABEL}
239
     * @param text
240
     * @return
241
     */
242
    public Card setProgressLabel(String text) {
243
        if (mCardProgress != null) {
244
            mCardProgress.setProgressLabel(text);
245
        }
246
        return this;
247
    }
248
 
249
    /**
250
     * Toggle the visibility of the progress section of the card. Only applicable if
251
     * the card has a progress type of
252
     * {@link #PROGRESS_TYPE_NORMAL}, {@link #PROGRESS_TYPE_INDETERMINATE} or
253
     * {@link #PROGRESS_TYPE_LABEL}.
254
     * @param isVisible
255
     * @return
256
     */
257
    public Card setProgressVisibility(boolean isVisible) {
258
        if (mCardProgress.progressView == null) {
259
            return this; // Card does not have progress
260
        }
261
        mCardProgress.progressView.setVisibility(isVisible ? View.VISIBLE : View.GONE);
262
 
263
        return this;
264
    }
265
 
266
    /**
267
     * Adds an action to this card during build time.
268
     *
269
     * @param label
270
     * @param id
271
     * @param type
272
     */
273
    private void addAction(String label, int id, int type) {
274
        CardAction cardAction = new CardAction();
275
        cardAction.label = label;
276
        cardAction.id = id;
277
        cardAction.type = type;
278
        mCardActions.add(cardAction);
279
    }
280
 
281
    /**
282
     * Toggles the visibility of a card action.
283
     * @param actionId
284
     * @param isVisible
285
     * @return
286
     */
287
    public Card setActionVisibility(int actionId, boolean isVisible) {
288
        int visibilityFlag = isVisible ? View.VISIBLE : View.GONE;
289
        for (CardAction action : mCardActions) {
290
            if (action.id == actionId && action.actionView != null) {
291
                action.actionView.setVisibility(visibilityFlag);
292
            }
293
        }
294
        return this;
295
    }
296
 
297
    /**
298
     * Toggles visibility of the action area of this Card through an animation.
299
     * @param isVisible
300
     * @return
301
     */
302
    public Card setActionAreaVisibility(boolean isVisible) {
303
        if (mActionAreaView == null) {
304
            return this; // Card does not have an action area
305
        }
306
 
307
        if (isVisible) {
308
            // Show the action area
309
            mActionAreaView.setVisibility(View.VISIBLE);
310
            mActionAreaView.setPivotY(0.f);
311
            mActionAreaView.setPivotX(mCardView.getWidth() / 2.f);
312
            mActionAreaView.setAlpha(0.5f);
313
            mActionAreaView.setRotationX(-90.f);
314
            mActionAreaView.animate().rotationX(0.f).alpha(1.f).setDuration(400);
315
        } else {
316
            // Hide the action area
317
            mActionAreaView.setPivotY(0.f);
318
            mActionAreaView.setPivotX(mCardView.getWidth() / 2.f);
319
            mActionAreaView.animate().rotationX(-90.f).alpha(0.f).setDuration(400).setListener(
320
                    new AnimatorListenerAdapter() {
321
                        @Override
322
                        public void onAnimationEnd(Animator animation) {
323
                            mActionAreaView.setVisibility(View.GONE);
324
                        }
325
                    });
326
        }
327
        return this;
328
    }
329
 
330
 
331
    /**
332
     * Creates a shallow clone of the card.  Shallow means all values are present, but no views.
333
     * This is useful for saving/restoring in the case of configuration changes, like screen
334
     * rotation.
335
     *
336
     * @return A shallow clone of the card instance
337
     */
338
    public Card createShallowClone() {
339
        Card cloneCard = new Card();
340
 
341
        // Outer card values
342
        cloneCard.mTitle = mTitle;
343
        cloneCard.mDescription = mDescription;
344
        cloneCard.mTag = mTag;
345
        cloneCard.mLayoutId = mLayoutId;
346
        cloneCard.mCardState = mCardState;
347
 
348
        // Progress
349
        if (mCardProgress != null) {
350
            cloneCard.mCardProgress = mCardProgress.createShallowClone();
351
        }
352
 
353
        // Actions
354
        for (CardAction action : mCardActions) {
355
            cloneCard.mCardActions.add(action.createShallowClone());
356
        }
357
 
358
        return cloneCard;
359
    }
360
 
361
 
362
    /**
363
     * Prepare the card to be stored for configuration change.
364
     */
365
    public void prepareForConfigurationChange() {
366
        // Null out views.
367
        mCardView = null;
368
        for (CardAction action : mCardActions) {
369
            action.actionView = null;
370
        }
371
        mCardProgress.progressView = null;
372
    }
373
 
374
    /**
375
     * Creates a new {@link #Card}.
376
     */
377
    public static class Builder {
378
        private Card mCard;
379
 
380
        /**
381
         * Instantiate the builder with data from a shallow clone.
382
         * @param listener
383
         * @param card
384
         * @see Card#createShallowClone()
385
         */
386
        protected Builder(OnCardClickListener listener, Card card) {
387
            mCard = card;
388
            mCard.mClickListener = listener;
389
        }
390
 
391
        /**
392
         * Instantiate the builder with the tag of the card.
393
         * @param listener
394
         * @param tag
395
         */
396
        public Builder(OnCardClickListener listener, String tag) {
397
            mCard = new Card();
398
            mCard.mTag = tag;
399
            mCard.mClickListener = listener;
400
        }
401
 
402
        public Builder setTitle(String title) {
403
            mCard.mTitle = title;
404
            return this;
405
        }
406
 
407
        public Builder setDescription(String desc) {
408
            mCard.mDescription = desc;
409
            return this;
410
        }
411
 
412
        /**
413
         * Add an action.
414
         * The type describes how this action will be displayed. Accepted values are
415
         * {@link #ACTION_NEUTRAL}, {@link #ACTION_POSITIVE} or {@link #ACTION_NEGATIVE}.
416
         *
417
         * @param label The text to display for this action
418
         * @param id Identifier for this action, supplied in the click listener
419
         * @param type UI style of action
420
         * @return
421
         */
422
        public Builder addAction(String label, int id, int type) {
423
            mCard.addAction(label, id, type);
424
            return this;
425
        }
426
 
427
        /**
428
         * Override the default layout.
429
         * The referenced layout file has to contain the same identifiers as defined in the default
430
         * layout configuration.
431
         * @param layout
432
         * @return
433
         * @see R.layout.card
434
         */
435
        public Builder setLayout(int layout) {
436
            mCard.mLayoutId = layout;
437
            return this;
438
        }
439
 
440
        /**
441
         * Set the type of progress bar to display.
442
         * Accepted values are:
443
         * <ul>
444
         *     <li>{@link #PROGRESS_TYPE_NO_PROGRESS} disables the progress indicator</li>
445
         *     <li>{@link #PROGRESS_TYPE_NORMAL} 
446
         *     displays a standard, linear progress indicator.</li>
447
         *     <li>{@link #PROGRESS_TYPE_INDETERMINATE} displays an indeterminate (infite) progress
448
         *     indicator.</li>
449
         *     <li>{@link #PROGRESS_TYPE_LABEL} only displays a label text in the progress area
450
         *     of the card.</li>
451
         * </ul>
452
         *
453
         * @param progressType
454
         * @return
455
         */
456
        public Builder setProgressType(int progressType) {
457
            mCard.setProgressType(progressType);
458
            return this;
459
        }
460
 
461
        public Builder setProgressLabel(String label) {
462
            // ensure the progress layout has been initialized, use 'no progress' by default
463
            if (mCard.mCardProgress == null) {
464
                mCard.setProgressType(PROGRESS_TYPE_NO_PROGRESS);
465
            }
466
            mCard.mCardProgress.label = label;
467
            return this;
468
        }
469
 
470
        public Builder setProgressMaxValue(int maxValue) {
471
            // ensure the progress layout has been initialized, use 'no progress' by default
472
            if (mCard.mCardProgress == null) {
473
                mCard.setProgressType(PROGRESS_TYPE_NO_PROGRESS);
474
            }
475
            mCard.mCardProgress.maxValue = maxValue;
476
            return this;
477
        }
478
 
479
        public Builder setStatus(int status) {
480
            mCard.setState(status);
481
            return this;
482
        }
483
 
484
        public Card build(Activity activity) {
485
            LayoutInflater inflater = activity.getLayoutInflater();
486
            // Inflating the card.
487
            ViewGroup cardView = (ViewGroup) inflater.inflate(mCard.mLayoutId,
488
                    (ViewGroup) activity.findViewById(R.id.card_stream), false);
489
 
490
            // Check that the layout contains a TextView with the card_title id
491
            View viewTitle = cardView.findViewById(R.id.card_title);
492
            if (mCard.mTitle != null && viewTitle != null) {
493
                mCard.mTitleView = (TextView) viewTitle;
494
                mCard.mTitleView.setText(mCard.mTitl