Updated browseable samples for april push

Change-Id: Idd8cc6b7c43ab93f05f0a5d69d5379631721d185
This commit is contained in:
Alexander Lucas
2014-04-10 15:44:01 -07:00
parent f03f35fe24
commit fb5e574e79
309 changed files with 12685 additions and 105 deletions

View File

@@ -0,0 +1,752 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.android.batchstepsensor.R;
import java.util.ArrayList;
/**
* A Card contains a description and has a visual state. Optionally a card also contains a title,
* progress indicator and zero or more actions. It is constructed through the {@link Builder}.
*/
public class Card {
public static final int ACTION_POSITIVE = 1;
public static final int ACTION_NEGATIVE = 2;
public static final int ACTION_NEUTRAL = 3;
public static final int PROGRESS_TYPE_NO_PROGRESS = 0;
public static final int PROGRESS_TYPE_NORMAL = 1;
public static final int PROGRESS_TYPE_INDETERMINATE = 2;
public static final int PROGRESS_TYPE_LABEL = 3;
private OnCardClickListener mClickListener;
// The card model contains a reference to its desired layout (for extensibility), title,
// description, zero to many action buttons, and zero or 1 progress indicators.
private int mLayoutId = R.layout.card;
/**
* Tag that uniquely identifies this card.
*/
private String mTag = null;
private String mTitle = null;
private String mDescription = null;
private View mCardView = null;
private View mOverlayView = null;
private TextView mTitleView = null;
private TextView mDescView = null;
private View mActionAreaView = null;
private Animator mOngoingAnimator = null;
/**
* Visual state, either {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED} or
* {@link #CARD_STATE_INACTIVE}.
*/
private int mCardState = CARD_STATE_NORMAL;
public static final int CARD_STATE_NORMAL = 1;
public static final int CARD_STATE_FOCUSED = 2;
public static final int CARD_STATE_INACTIVE = 3;
/**
* Represent actions that can be taken from the card. Stylistically the developer can
* designate the action as positive, negative (ok/cancel, for instance), or neutral.
* This "type" can be used as a UI hint.
* @see com.example.android.sensors.batchstepsensor.Card.CardAction
*/
private ArrayList<CardAction> mCardActions = new ArrayList<CardAction>();
/**
* Some cards will have a sense of "progress" which should be associated with, but separated
* from its "parent" card. To push for simplicity in samples, Cards are designed to have
* a maximum of one progress indicator per Card.
*/
private CardProgress mCardProgress = null;
public Card() {
}
public String getTag() {
return mTag;
}
public View getView() {
return mCardView;
}
public Card setDescription(String desc) {
if (mDescView != null) {
mDescription = desc;
mDescView.setText(desc);
}
return this;
}
public Card setTitle(String title) {
if (mTitleView != null) {
mTitle = title;
mTitleView.setText(title);
}
return this;
}
/**
* Return the UI state, either {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED}
* or {@link #CARD_STATE_INACTIVE}.
*/
public int getState() {
return mCardState;
}
/**
* Set the UI state. The parameter describes the state and must be either
* {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED} or {@link #CARD_STATE_INACTIVE}.
* Note: This method must be called from the UI Thread.
* @param state
* @return The card itself, allows for chaining of calls
*/
public Card setState(int state) {
mCardState = state;
if (null != mOverlayView) {
if (null != mOngoingAnimator) {
mOngoingAnimator.end();
mOngoingAnimator = null;
}
switch (state) {
case CARD_STATE_NORMAL: {
mOverlayView.setVisibility(View.GONE);
mOverlayView.setAlpha(1.f);
break;
}
case CARD_STATE_FOCUSED: {
mOverlayView.setVisibility(View.VISIBLE);
mOverlayView.setBackgroundResource(R.drawable.card_overlay_focused);
ObjectAnimator animator = ObjectAnimator.ofFloat(mOverlayView, "alpha", 0.f);
animator.setRepeatMode(ObjectAnimator.REVERSE);
animator.setRepeatCount(ObjectAnimator.INFINITE);
animator.setDuration(1000);
animator.start();
mOngoingAnimator = animator;
break;
}
case CARD_STATE_INACTIVE: {
mOverlayView.setVisibility(View.VISIBLE);
mOverlayView.setAlpha(1.f);
mOverlayView.setBackgroundColor(Color.argb(0xaa, 0xcc, 0xcc, 0xcc));
break;
}
}
}
return this;
}
/**
* Set the type of progress indicator.
* The progress type can only be changed if the Card was initially build with a progress
* indicator.
* See {@link Builder#setProgressType(int)}.
* Must be a value of either {@link #PROGRESS_TYPE_NORMAL},
* {@link #PROGRESS_TYPE_INDETERMINATE}, {@link #PROGRESS_TYPE_LABEL} or
* {@link #PROGRESS_TYPE_NO_PROGRESS}.
* @param progressType
* @return The card itself, allows for chaining of calls
*/
public Card setProgressType(int progressType) {
if (mCardProgress == null) {
mCardProgress = new CardProgress();
}
mCardProgress.setProgressType(progressType);
return this;
}
/**
* Return the progress indicator type. A value of either {@link #PROGRESS_TYPE_NORMAL},
* {@link #PROGRESS_TYPE_INDETERMINATE}, {@link #PROGRESS_TYPE_LABEL}. Otherwise if no progress
* indicator is enabled, {@link #PROGRESS_TYPE_NO_PROGRESS} is returned.
* @return
*/
public int getProgressType() {
if (mCardProgress == null) {
return PROGRESS_TYPE_NO_PROGRESS;
}
return mCardProgress.progressType;
}
/**
* Set the progress to the specified value. Only applicable if the card has a
* {@link #PROGRESS_TYPE_NORMAL} progress type.
* @param progress
* @return
* @see #setMaxProgress(int)
*/
public Card setProgress(int progress) {
if (mCardProgress != null) {
mCardProgress.setProgress(progress);
}
return this;
}
/**
* Set the range of the progress to 0...max. Only applicable if the card has a
* {@link #PROGRESS_TYPE_NORMAL} progress type.
* @return
*/
public Card setMaxProgress(int max){
if (mCardProgress != null) {
mCardProgress.setMax(max);
}
return this;
}
/**
* Set the label text for the progress if the card has a progress type of
* {@link #PROGRESS_TYPE_NORMAL}, {@link #PROGRESS_TYPE_INDETERMINATE} or
* {@link #PROGRESS_TYPE_LABEL}
* @param text
* @return
*/
public Card setProgressLabel(String text) {
if (mCardProgress != null) {
mCardProgress.setProgressLabel(text);
}
return this;
}
/**
* Toggle the visibility of the progress section of the card. Only applicable if
* the card has a progress type of
* {@link #PROGRESS_TYPE_NORMAL}, {@link #PROGRESS_TYPE_INDETERMINATE} or
* {@link #PROGRESS_TYPE_LABEL}.
* @param isVisible
* @return
*/
public Card setProgressVisibility(boolean isVisible) {
if (mCardProgress.progressView == null) {
return this; // Card does not have progress
}
mCardProgress.progressView.setVisibility(isVisible ? View.VISIBLE : View.GONE);
return this;
}
/**
* Adds an action to this card during build time.
*
* @param label
* @param id
* @param type
*/
private void addAction(String label, int id, int type) {
CardAction cardAction = new CardAction();
cardAction.label = label;
cardAction.id = id;
cardAction.type = type;
mCardActions.add(cardAction);
}
/**
* Toggles the visibility of a card action.
* @param actionId
* @param isVisible
* @return
*/
public Card setActionVisibility(int actionId, boolean isVisible) {
int visibilityFlag = isVisible ? View.VISIBLE : View.GONE;
for (CardAction action : mCardActions) {
if (action.id == actionId && action.actionView != null) {
action.actionView.setVisibility(visibilityFlag);
}
}
return this;
}
/**
* Toggles visibility of the action area of this Card through an animation.
* @param isVisible
* @return
*/
public Card setActionAreaVisibility(boolean isVisible) {
if (mActionAreaView == null) {
return this; // Card does not have an action area
}
if (isVisible) {
// Show the action area
mActionAreaView.setVisibility(View.VISIBLE);
mActionAreaView.setPivotY(0.f);
mActionAreaView.setPivotX(mCardView.getWidth() / 2.f);
mActionAreaView.setAlpha(0.5f);
mActionAreaView.setRotationX(-90.f);
mActionAreaView.animate().rotationX(0.f).alpha(1.f).setDuration(400);
} else {
// Hide the action area
mActionAreaView.setPivotY(0.f);
mActionAreaView.setPivotX(mCardView.getWidth() / 2.f);
mActionAreaView.animate().rotationX(-90.f).alpha(0.f).setDuration(400).setListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mActionAreaView.setVisibility(View.GONE);
}
});
}
return this;
}
/**
* Creates a shallow clone of the card. Shallow means all values are present, but no views.
* This is useful for saving/restoring in the case of configuration changes, like screen
* rotation.
*
* @return A shallow clone of the card instance
*/
public Card createShallowClone() {
Card cloneCard = new Card();
// Outer card values
cloneCard.mTitle = mTitle;
cloneCard.mDescription = mDescription;
cloneCard.mTag = mTag;
cloneCard.mLayoutId = mLayoutId;
cloneCard.mCardState = mCardState;
// Progress
if (mCardProgress != null) {
cloneCard.mCardProgress = mCardProgress.createShallowClone();
}
// Actions
for (CardAction action : mCardActions) {
cloneCard.mCardActions.add(action.createShallowClone());
}
return cloneCard;
}
/**
* Prepare the card to be stored for configuration change.
*/
public void prepareForConfigurationChange() {
// Null out views.
mCardView = null;
for (CardAction action : mCardActions) {
action.actionView = null;
}
mCardProgress.progressView = null;
}
/**
* Creates a new {@link #Card}.
*/
public static class Builder {
private Card mCard;
/**
* Instantiate the builder with data from a shallow clone.
* @param listener
* @param card
* @see Card#createShallowClone()
*/
protected Builder(OnCardClickListener listener, Card card) {
mCard = card;
mCard.mClickListener = listener;
}
/**
* Instantiate the builder with the tag of the card.
* @param listener
* @param tag
*/
public Builder(OnCardClickListener listener, String tag) {
mCard = new Card();
mCard.mTag = tag;
mCard.mClickListener = listener;
}
public Builder setTitle(String title) {
mCard.mTitle = title;
return this;
}
public Builder setDescription(String desc) {
mCard.mDescription = desc;
return this;
}
/**
* Add an action.
* The type describes how this action will be displayed. Accepted values are
* {@link #ACTION_NEUTRAL}, {@link #ACTION_POSITIVE} or {@link #ACTION_NEGATIVE}.
*
* @param label The text to display for this action
* @param id Identifier for this action, supplied in the click listener
* @param type UI style of action
* @return
*/
public Builder addAction(String label, int id, int type) {
mCard.addAction(label, id, type);
return this;
}
/**
* Override the default layout.
* The referenced layout file has to contain the same identifiers as defined in the default
* layout configuration.
* @param layout
* @return
* @see R.layout.card
*/
public Builder setLayout(int layout) {
mCard.mLayoutId = layout;
return this;
}
/**
* Set the type of progress bar to display.
* Accepted values are:
* <ul>
* <li>{@link #PROGRESS_TYPE_NO_PROGRESS} disables the progress indicator</li>
* <li>{@link #PROGRESS_TYPE_NORMAL}
* displays a standard, linear progress indicator.</li>
* <li>{@link #PROGRESS_TYPE_INDETERMINATE} displays an indeterminate (infite) progress
* indicator.</li>
* <li>{@link #PROGRESS_TYPE_LABEL} only displays a label text in the progress area
* of the card.</li>
* </ul>
*
* @param progressType
* @return
*/
public Builder setProgressType(int progressType) {
mCard.setProgressType(progressType);
return this;
}
public Builder setProgressLabel(String label) {
// ensure the progress layout has been initialized, use 'no progress' by default
if (mCard.mCardProgress == null) {
mCard.setProgressType(PROGRESS_TYPE_NO_PROGRESS);
}
mCard.mCardProgress.label = label;
return this;
}
public Builder setProgressMaxValue(int maxValue) {
// ensure the progress layout has been initialized, use 'no progress' by default
if (mCard.mCardProgress == null) {
mCard.setProgressType(PROGRESS_TYPE_NO_PROGRESS);
}
mCard.mCardProgress.maxValue = maxValue;
return this;
}
public Builder setStatus(int status) {
mCard.setState(status);
return this;
}
public Card build(Activity activity) {
LayoutInflater inflater = activity.getLayoutInflater();
// Inflating the card.
ViewGroup cardView = (ViewGroup) inflater.inflate(mCard.mLayoutId,
(ViewGroup) activity.findViewById(R.id.card_stream), false);
// Check that the layout contains a TextView with the card_title id
View viewTitle = cardView.findViewById(R.id.card_title);
if (mCard.mTitle != null && viewTitle != null) {
mCard.mTitleView = (TextView) viewTitle;
mCard.mTitleView.setText(mCard.mTitle);
} else if (viewTitle != null) {
viewTitle.setVisibility(View.GONE);
}
// Check that the layout contains a TextView with the card_content id
View viewDesc = cardView.findViewById(R.id.card_content);
if (mCard.mDescription != null && viewDesc != null) {
mCard.mDescView = (TextView) viewDesc;
mCard.mDescView.setText(mCard.mDescription);
} else if (viewDesc != null) {
cardView.findViewById(R.id.card_content).setVisibility(View.GONE);
}
ViewGroup actionArea = (ViewGroup) cardView.findViewById(R.id.card_actionarea);
// Inflate Progress
initializeProgressView(inflater, actionArea);
// Inflate all action views.
initializeActionViews(inflater, cardView, actionArea);
mCard.mCardView = cardView;
mCard.mOverlayView = cardView.findViewById(R.id.card_overlay);
return mCard;
}
/**
* Initialize data from the given card.
* @param card
* @return
* @see Card#createShallowClone()
*/
public Builder cloneFromCard(Card card) {
mCard = card.createShallowClone();
return this;
}
/**
* Build the action views by inflating the appropriate layouts and setting the text and
* values.
* @param inflater
* @param cardView
* @param actionArea
*/
private void initializeActionViews(LayoutInflater inflater, ViewGroup cardView,
ViewGroup actionArea) {
if (!mCard.mCardActions.isEmpty()) {
// Set action area to visible only when actions are visible
actionArea.setVisibility(View.VISIBLE);
mCard.mActionAreaView = actionArea;
}
// Inflate all card actions
for (final CardAction action : mCard.mCardActions) {
int useActionLayout = 0;
switch (action.type) {
case Card.ACTION_POSITIVE:
useActionLayout = R.layout.card_button_positive;
break;
case Card.ACTION_NEGATIVE:
useActionLayout = R.layout.card_button_negative;
break;
case Card.ACTION_NEUTRAL:
default:
useActionLayout = R.layout.card_button_neutral;
break;
}
action.actionView = inflater.inflate(useActionLayout, actionArea, false);
Button actionButton = (Button) action.actionView.findViewById(R.id.card_button);
actionButton.setText(action.label);
actionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCard.mClickListener.onCardClick(action.id, mCard.mTag);
}
});
actionArea.addView(action.actionView);
}
}
/**
* Build the progress view into the given ViewGroup.
*
* @param inflater
* @param actionArea
*/
private void initializeProgressView(LayoutInflater inflater, ViewGroup actionArea) {
// Only inflate progress layout if a progress type other than NO_PROGRESS was set.
if (mCard.mCardProgress != null) {
//Setup progress card.
View progressView = inflater.inflate(R.layout.card_progress, actionArea, false);
ProgressBar progressBar =
(ProgressBar) progressView.findViewById(R.id.card_progress);
((TextView) progressView.findViewById(R.id.card_progress_text))
.setText(mCard.mCardProgress.label);
progressBar.setMax(mCard.mCardProgress.maxValue);
progressBar.setProgress(0);
mCard.mCardProgress.progressView = progressView;
mCard.mCardProgress.setProgressType(mCard.getProgressType());
actionArea.addView(progressView);
}
}
}
/**
* Represents a clickable action, accessible from the bottom of the card.
* Fields include the label, an ID to specify the action that was performed in the callback,
* an action type (positive, negative, neutral), and the callback.
*/
public class CardAction {
public String label;
public int id;
public int type;
public View actionView;
public CardAction createShallowClone() {
CardAction actionClone = new CardAction();
actionClone.label = label;
actionClone.id = id;
actionClone.type = type;
return actionClone;
// Not the view. Never the view (don't want to hold view references for
// onConfigurationChange.
}
}
/**
* Describes the progress of a {@link Card}.
* Three types of progress are supported:
* <ul><li>{@link Card#PROGRESS_TYPE_NORMAL: Standard progress bar with label text</li>
* <li>{@link Card#PROGRESS_TYPE_INDETERMINATE}: Indeterminate progress bar with label txt</li>
* <li>{@link Card#PROGRESS_TYPE_LABEL}: Label only, no progresss bar</li>
* </ul>
*/
public class CardProgress {
private int progressType = Card.PROGRESS_TYPE_NO_PROGRESS;
private String label = "";
private int currProgress = 0;
private int maxValue = 100;
public View progressView = null;
private ProgressBar progressBar = null;
private TextView progressLabel = null;
public CardProgress createShallowClone() {
CardProgress progressClone = new CardProgress();
progressClone.label = label;
progressClone.currProgress = currProgress;
progressClone.maxValue = maxValue;
progressClone.progressType = progressType;
return progressClone;
}
/**
* Set the progress. Only useful for the type {@link #PROGRESS_TYPE_NORMAL}.
* @param progress
* @see android.widget.ProgressBar#setProgress(int)
*/
public void setProgress(int progress) {
currProgress = progress;
final ProgressBar bar = getProgressBar();
if (bar != null) {
bar.setProgress(currProgress);
bar.invalidate();
}
}
/**
* Set the range of the progress to 0...max.
* Only useful for the type {@link #PROGRESS_TYPE_NORMAL}.
* @param max
* @see android.widget.ProgressBar#setMax(int)
*/
public void setMax(int max) {
maxValue = max;
final ProgressBar bar = getProgressBar();
if (bar != null) {
bar.setMax(maxValue);
}
}
/**
* Set the label text that appears near the progress indicator.
* @param text
*/
public void setProgressLabel(String text) {
label = text;
final TextView labelView = getProgressLabel();
if (labelView != null) {
labelView.setText(text);
}
}
/**
* Set how progress is displayed. The parameter must be one of three supported types:
* <ul><li>{@link Card#PROGRESS_TYPE_NORMAL: Standard progress bar with label text</li>
* <li>{@link Card#PROGRESS_TYPE_INDETERMINATE}:
* Indeterminate progress bar with label txt</li>
* <li>{@link Card#PROGRESS_TYPE_LABEL}: Label only, no progresss bar</li>
* @param type
*/
public void setProgressType(int type) {
progressType = type;
if (progressView != null) {
switch (type) {
case PROGRESS_TYPE_NO_PROGRESS: {
progressView.setVisibility(View.GONE);
break;
}
case PROGRESS_TYPE_NORMAL: {
progressView.setVisibility(View.VISIBLE);
getProgressBar().setIndeterminate(false);
break;
}
case PROGRESS_TYPE_INDETERMINATE: {
progressView.setVisibility(View.VISIBLE);
getProgressBar().setIndeterminate(true);
break;
}
}
}
}
private TextView getProgressLabel() {
if (progressLabel != null) {
return progressLabel;
} else if (progressView != null) {
progressLabel = (TextView) progressView.findViewById(R.id.card_progress_text);
return progressLabel;
} else {
return null;
}
}
private ProgressBar getProgressBar() {
if (progressBar != null) {
return progressBar;
} else if (progressView != null) {
progressBar = (ProgressBar) progressView.findViewById(R.id.card_progress);
return progressBar;
} else {
return null;
}
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
/**
* Custom Button with a special 'pressed' effect for touch events.
*/
public class CardActionButton extends Button {
public CardActionButton(Context context) {
super(context);
}
public CardActionButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CardActionButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
setPressed(true);
animate().scaleX(0.98f).scaleY(0.98f).alpha(0.8f).setDuration(100).
setInterpolator(new DecelerateInterpolator());
break;
case MotionEvent.ACTION_UP:
animate().scaleX(1.0f).scaleY(1.f).alpha(1.0f).setDuration(50).
setInterpolator(new BounceInterpolator());
break;
case MotionEvent.ACTION_CANCEL:
animate().scaleX(1.0f).scaleY(1.f).alpha(1.0f).setDuration(50).
setInterpolator(new BounceInterpolator());
break;
}
return super.onTouchEvent(event);
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.RelativeLayout;
/**
* Custom Button with a special 'pressed' effect for touch events.
*/
public class CardLayout extends RelativeLayout {
private boolean mSwiping = false;
private float mDownX = 0.f;
private float mDownY = 0.f;
private float mTouchSlop = 0.f;
public CardLayout(Context context) {
super(context);
init();
}
public CardLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CardLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
setClickable(true);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop() * 2.f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mSwiping = false;
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:
if( !mSwiping ){
mSwiping = Math.abs(mDownX - event.getX()) > mTouchSlop;
}
break;
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
mSwiping = false;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mSwiping = false;
break;
}
return mSwiping;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
public interface CardStream {
public CardStreamFragment getCardStream();
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.view.View;
/**
* An abstract class which defines animators for CardStreamLinearLayout.
*/
abstract class CardStreamAnimator {
protected float mSpeedFactor = 1.f;
/**
* Set speed factor of animations. Higher value means longer duration & slow animation.
*
* @param speedFactor speed type 1: SLOW, 2: NORMAL, 3:FAST
*/
public void setSpeedFactor(float speedFactor) {
mSpeedFactor = speedFactor;
}
/**
* Define initial animation of each child which fired when a user rotate a screen.
*
* @param context
* @return ObjectAnimator for initial animation
*/
public abstract ObjectAnimator getInitalAnimator(Context context);
/**
* Define disappearing animation of a child which fired when a view is removed programmatically
*
* @param context
* @return ObjectAnimator for disappearing animation
*/
public abstract ObjectAnimator getDisappearingAnimator(Context context);
/**
* Define appearing animation of a child which fired when a view is added programmatically
*
* @param context
* @return ObjectAnimator for appearing animation
*/
public abstract ObjectAnimator getAppearingAnimator(Context context);
/**
* Define swipe-in (back to the origin position) animation of a child
* which fired when a view is not moved enough to be removed.
*
* @param view target view
* @param deltaX delta distance by x-axis
* @param deltaY delta distance by y-axis
* @return ObjectAnimator for swipe-in animation
*/
public abstract ObjectAnimator getSwipeInAnimator(View view, float deltaX, float deltaY);
/**
* Define swipe-out animation of a child
* which fired when a view is removing by a user swipe action.
*
* @param view target view
* @param deltaX delta distance by x-axis
* @param deltaY delta distance by y-axis
* @return ObjectAnimator for swipe-out animation
*/
public abstract ObjectAnimator getSwipeOutAnimator(View view, float deltaX, float deltaY);
/**
* A simple CardStreamAnimator implementation which is used to turn animations off.
*/
public static class EmptyAnimator extends CardStreamAnimator {
@Override
public ObjectAnimator getInitalAnimator(Context context) {
return null;
}
@Override
public ObjectAnimator getDisappearingAnimator(Context context) {
return null;
}
@Override
public ObjectAnimator getAppearingAnimator(Context context) {
return null;
}
@Override
public ObjectAnimator getSwipeInAnimator(View view, float deltaX, float deltaY) {
return null;
}
@Override
public ObjectAnimator getSwipeOutAnimator(View view, float deltaX, float deltaY) {
return null;
}
}
}

View File

@@ -0,0 +1,276 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import com.example.android.batchstepsensor.R;
/**
* A Fragment that handles a stream of cards.
* Cards can be shown or hidden. When a card is shown it can also be marked as not-dismissible, see
* {@link CardStreamLinearLayout#addCard(android.view.View, boolean)}.
*/
public class CardStreamFragment extends Fragment {
private static final int INITIAL_SIZE = 15;
private CardStreamLinearLayout mLayout = null;
private LinkedHashMap<String, Card> mVisibleCards = new LinkedHashMap<String, Card>(INITIAL_SIZE);
private HashMap<String, Card> mHiddenCards = new HashMap<String, Card>(INITIAL_SIZE);
private HashSet<String> mDismissibleCards = new HashSet<String>(INITIAL_SIZE);
// Set the listener to handle dismissed cards by moving them to the hidden cards map.
private CardStreamLinearLayout.OnDissmissListener mCardDismissListener =
new CardStreamLinearLayout.OnDissmissListener() {
@Override
public void onDismiss(String tag) {
dismissCard(tag);
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.cardstream, container, false);
mLayout = (CardStreamLinearLayout) view.findViewById(R.id.card_stream);
mLayout.setOnDismissListener(mCardDismissListener);
return view;
}
/**
* Add a visible, dismissible card to the card stream.
*
* @param card
*/
public void addCard(Card card) {
final String tag = card.getTag();
if (!mVisibleCards.containsKey(tag) && !mHiddenCards.containsKey(tag)) {
final View view = card.getView();
view.setTag(tag);
mHiddenCards.put(tag, card);
}
}
/**
* Add and show a card.
*
* @param card
* @param show
*/
public void addCard(Card card, boolean show) {
addCard(card);
if (show) {
showCard(card.getTag());
}
}
/**
* Remove a card and return true if it has been successfully removed.
*
* @param tag
* @return
*/
public boolean removeCard(String tag) {
// Attempt to remove a visible card first
Card card = mVisibleCards.get(tag);
if (card != null) {
// Card is visible, also remove from layout
mVisibleCards.remove(tag);
mLayout.removeView(card.getView());
return true;
} else {
// Card is hidden, no need to remove from layout
card = mHiddenCards.remove(tag);
return card != null;
}
}
/**
* Show a dismissible card, returns false if the card could not be shown.
*
* @param tag
* @return
*/
public boolean showCard(String tag) {
return showCard(tag, true);
}
/**
* Show a card, returns false if the card could not be shown.
*
* @param tag
* @param dismissible
* @return
*/
public boolean showCard(String tag, boolean dismissible) {
final Card card = mHiddenCards.get(tag);
// ensure the card is hidden and not already visible
if (card != null && !mVisibleCards.containsValue(tag)) {
mHiddenCards.remove(tag);
mVisibleCards.put(tag, card);
mLayout.addCard(card.getView(), dismissible);
if (dismissible) {
mDismissibleCards.add(tag);
}
return true;
}
return false;
}
/**
* Hides the card, returns false if the card could not be hidden.
*
* @param tag
* @return
*/
public boolean hideCard(String tag) {
final Card card = mVisibleCards.get(tag);
if (card != null) {
mVisibleCards.remove(tag);
mDismissibleCards.remove(tag);
mHiddenCards.put(tag, card);
mLayout.removeView(card.getView());
return true;
}
return mHiddenCards.containsValue(tag);
}
private void dismissCard(String tag) {
final Card card = mVisibleCards.get(tag);
if (card != null) {
mDismissibleCards.remove(tag);
mVisibleCards.remove(tag);
mHiddenCards.put(tag, card);
}
}
public boolean isCardVisible(String tag) {
return mVisibleCards.containsValue(tag);
}
/**
* Returns true if the card is shown and is dismissible.
*
* @param tag
* @return
*/
public boolean isCardDismissible(String tag) {
return mDismissibleCards.contains(tag);
}
/**
* Returns the Card for this tag.
*
* @param tag
* @return
*/
public Card getCard(String tag) {
final Card card = mVisibleCards.get(tag);
if (card != null) {
return card;
} else {
return mHiddenCards.get(tag);
}
}
/**
* Moves the view port to show the card with this tag.
*
* @param tag
* @see CardStreamLinearLayout#setFirstVisibleCard(String)
*/
public void setFirstVisibleCard(String tag) {
final Card card = mVisibleCards.get(tag);
if (card != null) {
mLayout.setFirstVisibleCard(tag);
}
}
public int getVisibleCardCount() {
return mVisibleCards.size();
}
public Collection<Card> getVisibleCards() {
return mVisibleCards.values();
}
public void restoreState(CardStreamState state, OnCardClickListener callback) {
// restore hidden cards
for (Card c : state.hiddenCards) {
Card card = new Card.Builder(callback,c).build(getActivity());
mHiddenCards.put(card.getTag(), card);
}
// temporarily set up list of dismissible
final HashSet<String> dismissibleCards = state.dismissibleCards;
//restore shown cards
for (Card c : state.visibleCards) {
Card card = new Card.Builder(callback,c).build(getActivity());
addCard(card);
final String tag = card.getTag();
showCard(tag, dismissibleCards.contains(tag));
}
// move to first visible card
final String firstShown = state.shownTag;
if (firstShown != null) {
mLayout.setFirstVisibleCard(firstShown);
}
mLayout.triggerShowInitialAnimation();
}
public CardStreamState dumpState() {
final Card[] visible = cloneCards(mVisibleCards.values());
final Card[] hidden = cloneCards(mHiddenCards.values());
final HashSet<String> dismissible = new HashSet<String>(mDismissibleCards);
final String firstVisible = mLayout.getFirstVisibleCardTag();
return new CardStreamState(visible, hidden, dismissible, firstVisible);
}
private Card[] cloneCards(Collection<Card> cards) {
Card[] cardArray = new Card[cards.size()];
int i = 0;
for (Card c : cards) {
cardArray[i++] = c.createShallowClone();
}
return cardArray;
}
}

View File

@@ -0,0 +1,570 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import android.animation.Animator;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import com.example.android.common.logger.Log;
import com.example.android.batchstepsensor.R;
import java.util.ArrayList;
/**
* A Layout that contains a stream of card views.
*/
public class CardStreamLinearLayout extends LinearLayout {
public static final int ANIMATION_SPEED_SLOW = 1001;
public static final int ANIMATION_SPEED_NORMAL = 1002;
public static final int ANIMATION_SPEED_FAST = 1003;
private static final String TAG = "CardStreamLinearLayout";
private final ArrayList<View> mFixedViewList = new ArrayList<View>();
private final Rect mChildRect = new Rect();
private CardStreamAnimator mAnimators;
private OnDissmissListener mDismissListener = null;
private boolean mLayouted = false;
private boolean mSwiping = false;
private String mFirstVisibleCardTag = null;
private boolean mShowInitialAnimation = false;
/**
* Handle touch events to fade/move dragged items as they are swiped out
*/
private OnTouchListener mTouchListener = new OnTouchListener() {
private float mDownX;
private float mDownY;
@Override
public boolean onTouch(final View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
break;
case MotionEvent.ACTION_CANCEL:
resetAnimatedView(v);
mSwiping = false;
mDownX = 0.f;
mDownY = 0.f;
break;
case MotionEvent.ACTION_MOVE: {
float x = event.getX() + v.getTranslationX();
float y = event.getY() + v.getTranslationY();
mDownX = mDownX == 0.f ? x : mDownX;
mDownY = mDownY == 0.f ? x : mDownY;
float deltaX = x - mDownX;
float deltaY = y - mDownY;
if (!mSwiping && isSwiping(deltaX, deltaY)) {
mSwiping = true;
v.getParent().requestDisallowInterceptTouchEvent(true);
} else {
swipeView(v, deltaX, deltaY);
}
}
break;
case MotionEvent.ACTION_UP: {
// User let go - figure out whether to animate the view out, or back into place
if (mSwiping) {
float x = event.getX() + v.getTranslationX();
float y = event.getY() + v.getTranslationY();
float deltaX = x - mDownX;
float deltaY = y - mDownX;
float deltaXAbs = Math.abs(deltaX);
// User let go - figure out whether to animate the view out, or back into place
boolean remove = deltaXAbs > v.getWidth() / 4 && !isFixedView(v);
if( remove )
handleViewSwipingOut(v, deltaX, deltaY);
else
handleViewSwipingIn(v, deltaX, deltaY);
}
mDownX = 0.f;
mDownY = 0.f;
mSwiping = false;
}
break;
default:
return false;
}
return false;
}
};
private int mSwipeSlop = -1;
/**
* Handle end-transition animation event of each child and launch a following animation.
*/
private LayoutTransition.TransitionListener mTransitionListener
= new LayoutTransition.TransitionListener() {
@Override
public void startTransition(LayoutTransition transition, ViewGroup container, View
view, int transitionType) {
Log.d(TAG, "Start LayoutTransition animation:" + transitionType);
}
@Override
public void endTransition(LayoutTransition transition, ViewGroup container,
final View view, int transitionType) {
Log.d(TAG, "End LayoutTransition animation:" + transitionType);
if (transitionType == LayoutTransition.APPEARING) {
final View area = view.findViewById(R.id.card_actionarea);
if (area != null) {
runShowActionAreaAnimation(container, area);
}
}
}
};
/**
* Handle a hierarchy change event
* when a new child is added, scroll to bottom and hide action area..
*/
private OnHierarchyChangeListener mOnHierarchyChangeListener
= new OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(final View parent, final View child) {
Log.d(TAG, "child is added: " + child);
ViewParent scrollView = parent.getParent();
if (scrollView != null && scrollView instanceof ScrollView) {
((ScrollView) scrollView).fullScroll(FOCUS_DOWN);
}
if (getLayoutTransition() != null) {
View view = child.findViewById(R.id.card_actionarea);
if (view != null)
view.setAlpha(0.f);
}
}
@Override
public void onChildViewRemoved(View parent, View child) {
Log.d(TAG, "child is removed: " + child);
mFixedViewList.remove(child);
}
};
private int mLastDownX;
public CardStreamLinearLayout(Context context) {
super(context);
initialize(null, 0);
}
public CardStreamLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(attrs, 0);
}
@SuppressLint("NewApi")
public CardStreamLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(attrs, defStyle);
}
/**
* add a card view w/ canDismiss flag.
*
* @param cardView a card view
* @param canDismiss flag to indicate this card is dismissible or not.
*/
public void addCard(View cardView, boolean canDismiss) {
if (cardView.getParent() == null) {
initCard(cardView, canDismiss);
ViewGroup.LayoutParams param = cardView.getLayoutParams();
if(param == null)
param = generateDefaultLayoutParams();
super.addView(cardView, -1, param);
}
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child.getParent() == null) {
initCard(child, true);
super.addView(child, index, params);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.d(TAG, "onLayout: " + changed);
if( changed && !mLayouted ){
mLayouted = true;
ObjectAnimator animator;
LayoutTransition layoutTransition = new LayoutTransition();
animator = mAnimators.getDisappearingAnimator(getContext());
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animator);
animator = mAnimators.getAppearingAnimator(getContext());
layoutTransition.setAnimator(LayoutTransition.APPEARING, animator);
layoutTransition.addTransitionListener(mTransitionListener);
if( animator != null )
layoutTransition.setDuration(animator.getDuration());
setLayoutTransition(layoutTransition);
if( mShowInitialAnimation )
runInitialAnimations();
if (mFirstVisibleCardTag != null) {
scrollToCard(mFirstVisibleCardTag);
mFirstVisibleCardTag = null;
}
}
}
/**
* Check whether a user moved enough distance to start a swipe action or not.
*
* @param deltaX
* @param deltaY
* @return true if a user is swiping.
*/
protected boolean isSwiping(float deltaX, float deltaY) {
if (mSwipeSlop < 0) {
//get swipping slop from ViewConfiguration;
mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
boolean swipping = false;
float absDeltaX = Math.abs(deltaX);
if( absDeltaX > mSwipeSlop )
return true;
return swipping;
}
/**
* Swipe a view by moving distance
*
* @param child a target view
* @param deltaX x moving distance by x-axis.
* @param deltaY y moving distance by y-axis.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
protected void swipeView(View child, float deltaX, float deltaY) {
if (isFixedView(child)){
deltaX = deltaX / 4;
}
float deltaXAbs = Math.abs(deltaX);
float fractionCovered = deltaXAbs / (float) child.getWidth();
child.setTranslationX(deltaX);
child.setAlpha(1.f - fractionCovered);
if (deltaX > 0)
child.setRotationY(-15.f * fractionCovered);
else
child.setRotationY(15.f * fractionCovered);
}
protected void notifyOnDismissEvent( View child ){
if( child == null || mDismissListener == null )
return;
mDismissListener.onDismiss((String) child.getTag());
}
/**
* get the tag of the first visible child in this layout
*
* @return tag of the first visible child or null
*/
public String getFirstVisibleCardTag() {
final int count = getChildCount();
if (count == 0)
return null;
for (int index = 0; index < count; ++index) {
//check the position of each view.
View child = getChildAt(index);
if (child.getGlobalVisibleRect(mChildRect) == true)
return (String) child.getTag();
}
return null;
}
/**
* Set the first visible card of this linear layout.
*
* @param tag tag of a card which should already added to this layout.
*/
public void setFirstVisibleCard(String tag) {
if (tag == null)
return; //do nothing.
if (mLayouted) {
scrollToCard(tag);
} else {
//keep the tag for next use.
mFirstVisibleCardTag = tag;
}
}
/**
* If this flag is set,
* after finishing initial onLayout event, an initial animation which is defined in DefaultCardStreamAnimator is launched.
*/
public void triggerShowInitialAnimation(){
mShowInitialAnimation = true;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setCardStreamAnimator( CardStreamAnimator animators ){
if( animators == null )
mAnimators = new CardStreamAnimator.EmptyAnimator();
else
mAnimators = animators;
LayoutTransition layoutTransition = getLayoutTransition();
if( layoutTransition != null ){
layoutTransition.setAnimator( LayoutTransition.APPEARING,
mAnimators.getAppearingAnimator(getContext()) );
layoutTransition.setAnimator( LayoutTransition.DISAPPEARING,
mAnimators.getDisappearingAnimator(getContext()) );
}
}
/**
* set a OnDismissListener which called when user dismiss a card.
*
* @param listener
*/
public void setOnDismissListener(OnDissmissListener listener) {
mDismissListener = listener;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void initialize(AttributeSet attrs, int defStyle) {
float speedFactor = 1.f;
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.CardStream, defStyle, 0);
if( a != null ){
int speedType = a.getInt(R.styleable.CardStream_animationDuration, 1001);
switch (speedType){
case ANIMATION_SPEED_FAST:
speedFactor = 0.5f;
break;
case ANIMATION_SPEED_NORMAL:
speedFactor = 1.f;
break;
case ANIMATION_SPEED_SLOW:
speedFactor = 2.f;
break;
}
String animatorName = a.getString(R.styleable.CardStream_animators);
try {
if( animatorName != null )
mAnimators = (CardStreamAnimator) getClass().getClassLoader()
.loadClass(animatorName).newInstance();
} catch (Exception e) {
Log.e(TAG, "Fail to load animator:" + animatorName, e);
} finally {
if(mAnimators == null)
mAnimators = new DefaultCardStreamAnimator();
}
a.recycle();
}
}
mAnimators.setSpeedFactor(speedFactor);
mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
setOnHierarchyChangeListener(mOnHierarchyChangeListener);
}
private void initCard(View cardView, boolean canDismiss) {
resetAnimatedView(cardView);
cardView.setOnTouchListener(mTouchListener);
if (!canDismiss)
mFixedViewList.add(cardView);
}
private boolean isFixedView(View v) {
return mFixedViewList.contains(v);
}
private void resetAnimatedView(View child) {
child.setAlpha(1.f);
child.setTranslationX(0.f);
child.setTranslationY(0.f);
child.setRotation(0.f);
child.setRotationY(0.f);
child.setRotationX(0.f);
child.setScaleX(1.f);
child.setScaleY(1.f);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void runInitialAnimations() {
if( mAnimators == null )
return;
final int count = getChildCount();
for (int index = 0; index < count; ++index) {
final View child = getChildAt(index);
ObjectAnimator animator = mAnimators.getInitalAnimator(getContext());
if( animator != null ){
animator.setTarget(child);
animator.start();
}
}
}
private void runShowActionAreaAnimation(View parent, View area) {
area.setPivotY(0.f);
area.setPivotX(parent.getWidth() / 2.f);
area.setAlpha(0.5f);
area.setRotationX(-90.f);
area.animate().rotationX(0.f).alpha(1.f).setDuration(400);
}
private void handleViewSwipingOut(final View child, float deltaX, float deltaY) {
ObjectAnimator animator = mAnimators.getSwipeOutAnimator(child, deltaX, deltaY);
if( animator != null ){
animator.addListener(new EndAnimationWrapper() {
@Override
public void onAnimationEnd(Animator animation) {
removeView(child);
notifyOnDismissEvent(child);
}
});
} else {
removeView(child);
notifyOnDismissEvent(child);
}
if( animator != null ){
animator.setTarget(child);
animator.start();
}
}
private void handleViewSwipingIn(final View child, float deltaX, float deltaY) {
ObjectAnimator animator = mAnimators.getSwipeInAnimator(child, deltaX, deltaY);
if( animator != null ){
animator.addListener(new EndAnimationWrapper() {
@Override
public void onAnimationEnd(Animator animation) {
child.setTranslationY(0.f);
child.setTranslationX(0.f);
}
});
} else {
child.setTranslationY(0.f);
child.setTranslationX(0.f);
}
if( animator != null ){
animator.setTarget(child);
animator.start();
}
}
private void scrollToCard(String tag) {
final int count = getChildCount();
for (int index = 0; index < count; ++index) {
View child = getChildAt(index);
if (tag.equals(child.getTag())) {
ViewParent parent = getParent();
if( parent != null && parent instanceof ScrollView ){
((ScrollView)parent).smoothScrollTo(
0, child.getTop() - getPaddingTop() - child.getPaddingTop());
}
return;
}
}
}
public interface OnDissmissListener {
public void onDismiss(String tag);
}
/**
* Empty default AnimationListener
*/
private abstract class EndAnimationWrapper implements Animator.AnimatorListener {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}//end of inner class
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import java.util.HashSet;
/**
* A struct object that holds the state of a {@link CardStreamFragment}.
*/
public class CardStreamState {
protected Card[] visibleCards;
protected Card[] hiddenCards;
protected HashSet<String> dismissibleCards;
protected String shownTag;
protected CardStreamState(Card[] visible, Card[] hidden, HashSet<String> dismissible, String shownTag) {
visibleCards = visible;
hiddenCards = hidden;
dismissibleCards = dismissible;
this.shownTag = shownTag;
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Point;
import android.os.Build;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.BounceInterpolator;
class DefaultCardStreamAnimator extends CardStreamAnimator {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public ObjectAnimator getDisappearingAnimator(Context context){
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(new Object(),
PropertyValuesHolder.ofFloat("alpha", 1.f, 0.f),
PropertyValuesHolder.ofFloat("scaleX", 1.f, 0.f),
PropertyValuesHolder.ofFloat("scaleY", 1.f, 0.f),
PropertyValuesHolder.ofFloat("rotation", 0.f, 270.f));
animator.setDuration((long) (200 * mSpeedFactor));
return animator;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
@Override
public ObjectAnimator getAppearingAnimator(Context context){
final Point outPoint = new Point();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getSize(outPoint);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(new Object(),
PropertyValuesHolder.ofFloat("alpha", 0.f, 1.f),
PropertyValuesHolder.ofFloat("translationY", outPoint.y / 2.f, 0.f),
PropertyValuesHolder.ofFloat("rotation", -45.f, 0.f));
animator.setDuration((long) (200 * mSpeedFactor));
return animator;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
@Override
public ObjectAnimator getInitalAnimator(Context context){
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(new Object(),
PropertyValuesHolder.ofFloat("alpha", 0.5f, 1.f),
PropertyValuesHolder.ofFloat("rotation", 60.f, 0.f));
animator.setDuration((long) (200 * mSpeedFactor));
return animator;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public ObjectAnimator getSwipeInAnimator(View view, float deltaX, float deltaY){
float deltaXAbs = Math.abs(deltaX);
float fractionCovered = 1.f - (deltaXAbs / view.getWidth());
long duration = Math.abs((int) ((1 - fractionCovered) * 200 * mSpeedFactor));
// Animate position and alpha of swiped item
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat("alpha", 1.f),
PropertyValuesHolder.ofFloat("translationX", 0.f),
PropertyValuesHolder.ofFloat("rotationY", 0.f));
animator.setDuration(duration).setInterpolator(new BounceInterpolator());
return animator;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public ObjectAnimator getSwipeOutAnimator(View view, float deltaX, float deltaY){
float endX;
float endRotationY;
float deltaXAbs = Math.abs(deltaX);
float fractionCovered = 1.f - (deltaXAbs / view.getWidth());
long duration = Math.abs((int) ((1 - fractionCovered) * 200 * mSpeedFactor));
endX = deltaX < 0 ? -view.getWidth() : view.getWidth();
if (deltaX > 0)
endRotationY = -15.f;
else
endRotationY = 15.f;
// Animate position and alpha of swiped item
return ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat("alpha", 0.f),
PropertyValuesHolder.ofFloat("translationX", endX),
PropertyValuesHolder.ofFloat("rotationY", endRotationY)).setDuration(duration);
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
public interface OnCardClickListener {
public void onCardClick(int cardActionId, String cardTag);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.batchstepsensor.cardstream;
import android.os.Bundle;
import android.support.v4.app.Fragment;
public class StreamRetentionFragment extends Fragment {
CardStreamState mState;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
}
public void storeCardStream(CardStreamState state) {
mState = state;
}
public CardStreamState getCardStream() {
return mState;
}
}