Adding new samples to browseable section of DAC

Change-Id: I58e10e787f5df668331fc04e97a6c2efcd75f76f
This commit is contained in:
Alexander Lucas
2014-02-06 15:38:51 -08:00
parent 01d72b37a8
commit 0b3758ea4e
399 changed files with 16010 additions and 369 deletions

View File

@@ -0,0 +1,588 @@
/*
* Copyright 2014 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;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import com.example.android.common.logger.Log;
public class BatchStepSensorFragment extends Fragment implements OnCardClickListener {
public static final String TAG = "StepSensorSample";
// Cards
private CardStreamFragment mCards = null;
// Card tags
public static final String CARD_INTRO = "intro";
public static final String CARD_REGISTER_DETECTOR = "register_detector";
public static final String CARD_REGISTER_COUNTER = "register_counter";
public static final String CARD_BATCHING_DESCRIPTION = "register_batching_description";
public static final String CARD_COUNTING = "counting";
public static final String CARD_EXPLANATION = "explanation";
public static final String CARD_NOBATCHSUPPORT = "error";
// Actions from REGISTER cards
public static final int ACTION_REGISTER_DETECT_NOBATCHING = 10;
public static final int ACTION_REGISTER_DETECT_BATCHING_5s = 11;
public static final int ACTION_REGISTER_DETECT_BATCHING_10s = 12;
public static final int ACTION_REGISTER_COUNT_NOBATCHING = 21;
public static final int ACTION_REGISTER_COUNT_BATCHING_5s = 22;
public static final int ACTION_REGISTER_COUNT_BATCHING_10s = 23;
// Action from COUNTING card
public static final int ACTION_UNREGISTER = 1;
// Actions from description cards
private static final int ACTION_BATCHING_DESCRIPTION_DISMISS = 2;
private static final int ACTION_EXPLANATION_DISMISS = 3;
// State of application, used to register for sensors when app is restored
public static final int STATE_OTHER = 0;
public static final int STATE_COUNTER = 1;
public static final int STATE_DETECTOR = 2;
// Bundle tags used to store data when restoring application state
private static final String BUNDLE_STATE = "state";
private static final String BUNDLE_LATENCY = "latency";
private static final String BUNDLE_STEPS = "steps";
// max batch latency is specified in microseconds
private static final int BATCH_LATENCY_0 = 0; // no batching
private static final int BATCH_LATENCY_10s = 10000000;
private static final int BATCH_LATENCY_5s = 5000000;
/*
For illustration we keep track of the last few events and show their delay from when the
event occurred until it was received by the event listener.
These variables keep track of the list of timestamps and the number of events.
*/
// Number of events to keep in queue and display on card
private static final int EVENT_QUEUE_LENGTH = 10;
// List of timestamps when sensor events occurred
private float[] mEventDelays = new float[EVENT_QUEUE_LENGTH];
// number of events in event list
private int mEventLength = 0;
// pointer to next entry in sensor event list
private int mEventData = 0;
// Steps counted in current session
private int mSteps = 0;
// Value of the step counter sensor when the listener was registered.
// (Total steps are calculated from this value.)
private int mCounterSteps = 0;
// Steps counted by the step counter previously. Used to keep counter consistent across rotation
// changes
private int mPreviousCounterSteps = 0;
// State of the app (STATE_OTHER, STATE_COUNTER or STATE_DETECTOR)
private int mState = STATE_OTHER;
// When a listener is registered, the batch sensor delay in microseconds
private int mMaxDelay = 0;
@Override
public void onResume() {
super.onResume();
CardStreamFragment stream = getCardStream();
if (stream.getVisibleCardCount() < 1) {
// No cards are visible, started for the first time
// Prepare all cards and show the intro card.
initialiseCards();
showIntroCard();
// Show the registration card if the hardware is supported, show an error otherwise
if (isKitkatWithStepSensor()) {
showRegisterCard();
} else {
showErrorCard();
}
}
}
@Override
public void onPause() {
super.onPause();
// BEGIN_INCLUDE(onpause)
// Unregister the listener when the application is paused
unregisterListeners();
// END_INCLUDE(onpause)
}
/**
* Returns true if this device is supported. It needs to be running Android KitKat (4.4) or
* higher and has a step counter and step detector sensor.
* This check is useful when an app provides an alternative implementation or different
* functionality if the step sensors are not available or this code runs on a platform version
* below Android KitKat. If this functionality is required, then the minSDK parameter should
* be specified appropriately in the AndroidManifest.
*
* @return True iff the device can run this sample
*/
private boolean isKitkatWithStepSensor() {
// BEGIN_INCLUDE(iskitkatsensor)
// Require at least Android KitKat
int currentApiVersion = android.os.Build.VERSION.SDK_INT;
// Check that the device supports the step counter and detector sensors
PackageManager packageManager = getActivity().getPackageManager();
return currentApiVersion >= android.os.Build.VERSION_CODES.KITKAT
&& packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_COUNTER)
&& packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_DETECTOR);
// END_INCLUDE(iskitkatsensor)
}
/**
* Handles a click on a card action.
* Registers a SensorEventListener (see {@link #registerEventListener(int, int)}) with the
* selected delay, dismisses cards and unregisters the listener
* (see {@link #unregisterListeners()}).
* Actions are defined when a card is created.
*
* @param cardActionId
* @param cardTag
*/
@Override
public void onCardClick(int cardActionId, String cardTag) {
switch (cardActionId) {
// BEGIN_INCLUDE(onclick)
// Register Step Counter card
case ACTION_REGISTER_COUNT_NOBATCHING:
registerEventListener(BATCH_LATENCY_0, Sensor.TYPE_STEP_COUNTER);
break;
case ACTION_REGISTER_COUNT_BATCHING_5s:
registerEventListener(BATCH_LATENCY_5s, Sensor.TYPE_STEP_COUNTER);
break;
case ACTION_REGISTER_COUNT_BATCHING_10s:
registerEventListener(BATCH_LATENCY_10s, Sensor.TYPE_STEP_COUNTER);
break;
// Register Step Detector card
case ACTION_REGISTER_DETECT_NOBATCHING:
registerEventListener(BATCH_LATENCY_0, Sensor.TYPE_STEP_DETECTOR);
break;
case ACTION_REGISTER_DETECT_BATCHING_5s:
registerEventListener(BATCH_LATENCY_5s, Sensor.TYPE_STEP_DETECTOR);
break;
case ACTION_REGISTER_DETECT_BATCHING_10s:
registerEventListener(BATCH_LATENCY_10s, Sensor.TYPE_STEP_DETECTOR);
break;
// Unregister card
case ACTION_UNREGISTER:
showRegisterCard();
unregisterListeners();
// reset the application state when explicitly unregistered
mState = STATE_OTHER;
break;
// END_INCLUDE(onclick)
// Explanation cards
case ACTION_BATCHING_DESCRIPTION_DISMISS:
// permanently remove the batch description card, it will not be shown again
getCardStream().removeCard(CARD_BATCHING_DESCRIPTION);
break;
case ACTION_EXPLANATION_DISMISS:
// permanently remove the explanation card, it will not be shown again
getCardStream().removeCard(CARD_EXPLANATION);
}
// For register cards, display the counting card
if (cardTag.equals(CARD_REGISTER_COUNTER) || cardTag.equals(CARD_REGISTER_DETECTOR)) {
showCountingCards();
}
}
/**
* Register a {@link android.hardware.SensorEventListener} for the sensor and max batch delay.
* The maximum batch delay specifies the maximum duration in microseconds for which subsequent
* sensor events can be temporarily stored by the sensor before they are delivered to the
* registered SensorEventListener. A larger delay allows the system to handle sensor events more
* efficiently, allowing the system to switch to a lower power state while the sensor is
* capturing events. Once the max delay is reached, all stored events are delivered to the
* registered listener. Note that this value only specifies the maximum delay, the listener may
* receive events quicker. A delay of 0 disables batch mode and registers the listener in
* continuous mode.
* The optimium batch delay depends on the application. For example, a delay of 5 seconds or
* higher may be appropriate for an application that does not update the UI in real time.
*
* @param maxdelay
* @param sensorType
*/
private void registerEventListener(int maxdelay, int sensorType) {
// BEGIN_INCLUDE(register)
// Keep track of state so that the correct sensor type and batch delay can be set up when
// the app is restored (for example on screen rotation).
mMaxDelay = maxdelay;
if (sensorType == Sensor.TYPE_STEP_COUNTER) {
mState = STATE_COUNTER;
/*
Reset the initial step counter value, the first event received by the event listener is
stored in mCounterSteps and used to calculate the total number of steps taken.
*/
mCounterSteps = 0;
Log.i(TAG, "Event listener for step counter sensor registered with a max delay of "
+ mMaxDelay);
} else {
mState = STATE_DETECTOR;
Log.i(TAG, "Event listener for step detector sensor registered with a max delay of "
+ mMaxDelay);
}
// Get the default sensor for the sensor type from the SenorManager
SensorManager sensorManager =
(SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE);
// sensorType is either Sensor.TYPE_STEP_COUNTER or Sensor.TYPE_STEP_DETECTOR
Sensor sensor = sensorManager.getDefaultSensor(sensorType);
// Register the listener for this sensor in batch mode.
// If the max delay is 0, events will be delivered in continuous mode without batching.
final boolean batchMode = sensorManager.registerListener(
mListener, sensor, SensorManager.SENSOR_DELAY_NORMAL, maxdelay);
if (!batchMode) {
// Batch mode could not be enabled, show a warning message and switch to continuous mode
getCardStream().getCard(CARD_NOBATCHSUPPORT)
.setDescription(getString(R.string.warning_nobatching));
getCardStream().showCard(CARD_NOBATCHSUPPORT);
Log.w(TAG, "Could not register sensor listener in batch mode, " +
"falling back to continuous mode.");
}
if (maxdelay > 0 && batchMode) {
// Batch mode was enabled successfully, show a description card
getCardStream().showCard(CARD_BATCHING_DESCRIPTION);
}
// Show the explanation card
getCardStream().showCard(CARD_EXPLANATION);
// END_INCLUDE(register)
}
/**
* Unregisters the sensor listener if it is registered.
*/
private void unregisterListeners() {
// BEGIN_INCLUDE(unregister)
SensorManager sensorManager =
(SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE);
sensorManager.unregisterListener(mListener);
Log.i(TAG, "Sensor listener unregistered.");
// END_INCLUDE(unregister)
}
/**
* Resets the step counter by clearing all counting variables and lists.
*/
private void resetCounter() {
// BEGIN_INCLUDE(reset)
mSteps = 0;
mCounterSteps = 0;
mEventLength = 0;
mEventDelays = new float[EVENT_QUEUE_LENGTH];
mPreviousCounterSteps = 0;
// END_INCLUDE(reset)
}
/**
* Listener that handles step sensor events for step detector and step counter sensors.
*/
private final SensorEventListener mListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
// BEGIN_INCLUDE(sensorevent)
// store the delay of this event
recordDelay(event);
final String delayString = getDelayString();
if (event.sensor.getType() == Sensor.TYPE_STEP_DETECTOR) {
// A step detector event is received for each step.
// This means we need to count steps ourselves
mSteps += event.values.length;
// Update the card with the latest step count
getCardStream().getCard(CARD_COUNTING)
.setTitle(getString(R.string.counting_title, mSteps))
.setDescription(getString(R.string.counting_description,
getString(R.string.sensor_detector), mMaxDelay, delayString));
Log.i(TAG,
"New step detected by STEP_DETECTOR sensor. Total step count: " + mSteps);
} else if (event.sensor.getType() == Sensor.TYPE_STEP_COUNTER) {
/*
A step counter event contains the total number of steps since the listener
was first registered. We need to keep track of this initial value to calculate the
number of steps taken, as the first value a listener receives is undefined.
*/
if (mCounterSteps < 1) {
// initial value
mCounterSteps = (int) event.values[0];
}
// Calculate steps taken based on first counter value received.
mSteps = (int) event.values[0] - mCounterSteps;
// Add the number of steps previously taken, otherwise the counter would start at 0.
// This is needed to keep the counter consistent across rotation changes.
mSteps = mSteps + mPreviousCounterSteps;
// Update the card with the latest step count
getCardStream().getCard(CARD_COUNTING)
.setTitle(getString(R.string.counting_title, mSteps))
.setDescription(getString(R.string.counting_description,
getString(R.string.sensor_counter), mMaxDelay, delayString));
Log.i(TAG, "New step detected by STEP_COUNTER sensor. Total step count: " + mSteps);
// END_INCLUDE(sensorevent)
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
/**
* Records the delay for the event.
*
* @param event
*/
private void recordDelay(SensorEvent event) {
// Calculate the delay from when event was recorded until it was received here in ms
// Event timestamp is recorded in us accuracy, but ms accuracy is sufficient here
mEventDelays[mEventData] = System.currentTimeMillis() - (event.timestamp / 1000000L);
// Increment length counter
mEventLength = Math.min(EVENT_QUEUE_LENGTH, mEventLength + 1);
// Move pointer to the next (oldest) location
mEventData = (mEventData + 1) % EVENT_QUEUE_LENGTH;
}
private final StringBuffer mDelayStringBuffer = new StringBuffer();
/**
* Returns a string describing the sensor delays recorded in
* {@link #recordDelay(android.hardware.SensorEvent)}.
*
* @return
*/
private String getDelayString() {
// Empty the StringBuffer
mDelayStringBuffer.setLength(0);
// Loop over all recorded delays and append them to the buffer as a decimal
for (int i = 0; i < mEventLength; i++) {
if (i > 0) {
mDelayStringBuffer.append(", ");
}
final int index = (mEventData + i) % EVENT_QUEUE_LENGTH;
final float delay = mEventDelays[index] / 1000f; // convert delay from ms into s
mDelayStringBuffer.append(String.format("%1.1f", delay));
}
return mDelayStringBuffer.toString();
}
/**
* Records the state of the application into the {@link android.os.Bundle}.
*
* @param outState
*/
@Override
public void onSaveInstanceState(Bundle outState) {
// BEGIN_INCLUDE(saveinstance)
super.onSaveInstanceState(outState);
// Store all variables required to restore the state of the application
outState.putInt(BUNDLE_LATENCY, mMaxDelay);
outState.putInt(BUNDLE_STATE, mState);
outState.putInt(BUNDLE_STEPS, mSteps);
// END_INCLUDE(saveinstance)
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// BEGIN_INCLUDE(restore)
// Fragment is being restored, reinitialise its state with data from the bundle
if (savedInstanceState != null) {
resetCounter();
mSteps = savedInstanceState.getInt(BUNDLE_STEPS);
mState = savedInstanceState.getInt(BUNDLE_STATE);
mMaxDelay = savedInstanceState.getInt(BUNDLE_LATENCY);
// Register listeners again if in detector or counter states with restored delay
if (mState == STATE_DETECTOR) {
registerEventListener(mMaxDelay, Sensor.TYPE_STEP_DETECTOR);
} else if (mState == STATE_COUNTER) {
// store the previous number of steps to keep step counter count consistent
mPreviousCounterSteps = mSteps;
registerEventListener(mMaxDelay, Sensor.TYPE_STEP_COUNTER);
}
}
// END_INCLUDE(restore)
}
/**
* Hides the registration cards, reset the counter and show the step counting card.
*/
private void showCountingCards() {
// Hide the registration cards
getCardStream().hideCard(CARD_REGISTER_DETECTOR);
getCardStream().hideCard(CARD_REGISTER_COUNTER);
// Show the explanation card if it has not been dismissed
getCardStream().showCard(CARD_EXPLANATION);
// Reset the step counter, then show the step counting card
resetCounter();
// Set the inital text for the step counting card before a step is recorded
String sensor = "-";
if (mState == STATE_COUNTER) {
sensor = getString(R.string.sensor_counter);
} else if (mState == STATE_DETECTOR) {
sensor = getString(R.string.sensor_detector);
}
// Set initial text
getCardStream().getCard(CARD_COUNTING)
.setTitle(getString(R.string.counting_title, 0))
.setDescription(getString(R.string.counting_description, sensor, mMaxDelay, "-"));
// Show the counting card and make it undismissable
getCardStream().showCard(CARD_COUNTING, false);
}
/**
* Show the introduction card
*/
private void showIntroCard() {
Card c = new Card.Builder(this, CARD_INTRO)
.setTitle(getString(R.string.intro_title))
.setDescription(getString(R.string.intro_message))
.build(getActivity());
getCardStream().addCard(c, true);
}
/**
* Show two registration cards, one for the step detector and counter sensors.
*/
private void showRegisterCard() {
// Hide the counting and explanation cards
getCardStream().hideCard(CARD_BATCHING_DESCRIPTION);
getCardStream().hideCard(CARD_EXPLANATION);
getCardStream().hideCard(CARD_COUNTING);
// Show two undismissable registration cards, one for each step sensor
getCardStream().showCard(CARD_REGISTER_DETECTOR, false);
getCardStream().showCard(CARD_REGISTER_COUNTER, false);
}
/**
* Show the error card.
*/
private void showErrorCard() {
getCardStream().showCard(CARD_NOBATCHSUPPORT, false);
}
/**
* Initialise Cards.
*/
private void initialiseCards() {
// Step counting
Card c = new Card.Builder(this, CARD_COUNTING)
.setTitle("Steps")
.setDescription("")
.addAction("Unregister Listener", ACTION_UNREGISTER, Card.ACTION_NEGATIVE)
.build(getActivity());
getCardStream().addCard(c);
// Register step detector listener
c = new Card.Builder(this, CARD_REGISTER_DETECTOR)
.setTitle(getString(R.string.register_detector_title))
.setDescription(getString(R.string.register_detector_description))
.addAction(getString(R.string.register_0),
ACTION_REGISTER_DETECT_NOBATCHING, Card.ACTION_NEUTRAL)
.addAction(getString(R.string.register_5),
ACTION_REGISTER_DETECT_BATCHING_5s, Card.ACTION_NEUTRAL)
.addAction(getString(R.string.register_10),
ACTION_REGISTER_DETECT_BATCHING_10s, Card.ACTION_NEUTRAL)
.build(getActivity());
getCardStream().addCard(c);
// Register step counter listener
c = new Card.Builder(this, CARD_REGISTER_COUNTER)
.setTitle(getString(R.string.register_counter_title))
.setDescription(getString(R.string.register_counter_description))
.addAction(getString(R.string.register_0),
ACTION_REGISTER_COUNT_NOBATCHING, Card.ACTION_NEUTRAL)
.addAction(getString(R.string.register_5),
ACTION_REGISTER_COUNT_BATCHING_5s, Card.ACTION_NEUTRAL)
.addAction(getString(R.string.register_10),
ACTION_REGISTER_COUNT_BATCHING_10s, Card.ACTION_NEUTRAL)
.build(getActivity());
getCardStream().addCard(c);
// Batching description
c = new Card.Builder(this, CARD_BATCHING_DESCRIPTION)
.setTitle(getString(R.string.batching_queue_title))
.setDescription(getString(R.string.batching_queue_description))
.addAction(getString(R.string.action_notagain),
ACTION_BATCHING_DESCRIPTION_DISMISS, Card.ACTION_POSITIVE)
.build(getActivity());
getCardStream().addCard(c);
// Explanation
c = new Card.Builder(this, CARD_EXPLANATION)
.setDescription(getString(R.string.explanation_description))
.addAction(getString(R.string.action_notagain),
ACTION_EXPLANATION_DISMISS, Card.ACTION_POSITIVE)
.build(getActivity());
getCardStream().addCard(c);
// Error
c = new Card.Builder(this, CARD_NOBATCHSUPPORT)
.setTitle(getString(R.string.error_title))
.setDescription(getString(R.string.error_nosensor))
.build(getActivity());
getCardStream().addCard(c);
}
/**
* Returns the cached CardStreamFragment used to show cards.
*
* @return
*/
private CardStreamFragment getCardStream() {
if (mCards == null) {
mCards = ((CardStream) getActivity()).getCardStream();
}
return mCards;
}
}

View File

@@ -0,0 +1,750 @@
/*
* 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;
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 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;
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;
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;
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;
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,275 @@
/*
* 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;
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;
/**
* 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,569 @@
/*
* 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;
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 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;
import java.util.HashSet;
/**
* A struct object that holds the state of a {@link CardStreamFragment}.
*/
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;
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,91 @@
/*
* 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;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.Menu;
import com.example.android.common.activities.SampleActivityBase;
import com.example.android.common.logger.Log;
public class MainActivity extends SampleActivityBase implements CardStream {
public static final String TAG = "MainActivity";
public static final String FRAGTAG = "BatchStepSensorFragment";
private CardStreamFragment mCardStreamFragment;
private StreamRetentionFragment mRetentionFragment;
private static final String RETENTION_TAG = "retention";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
BatchStepSensorFragment fragment =
(BatchStepSensorFragment) fm.findFragmentByTag(FRAGTAG);
if (fragment == null) {
FragmentTransaction transaction = fm.beginTransaction();
fragment = new BatchStepSensorFragment();
transaction.add(fragment, FRAGTAG);
transaction.commit();
}
// Use fragment as click listener for cards, but must implement correct interface
if(!(fragment instanceof OnCardClickListener)){
throw new ClassCastException("BatchStepSensorFragment must " +
"implement OnCardClickListener interface.");
}
OnCardClickListener clickListener = (OnCardClickListener) fm.findFragmentByTag(FRAGTAG);
mRetentionFragment = (StreamRetentionFragment) fm.findFragmentByTag(RETENTION_TAG);
if (mRetentionFragment == null) {
mRetentionFragment = new StreamRetentionFragment();
fm.beginTransaction().add(mRetentionFragment, RETENTION_TAG).commit();
} else {
// If the retention fragment already existed, we need to pull some state.
// pull state out
CardStreamState state = mRetentionFragment.getCardStream();
// dump it in CardStreamFragment.
mCardStreamFragment =
(CardStreamFragment) fm.findFragmentById(R.id.fragment_cardstream);
mCardStreamFragment.restoreState(state, clickListener);
}
}
public CardStreamFragment getCardStream() {
if (mCardStreamFragment == null) {
mCardStreamFragment = (CardStreamFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_cardstream);
}
return mCardStreamFragment;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
CardStreamState state = getCardStream().dumpState();
mRetentionFragment.storeCardStream(state);
}
}

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;
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;
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;
}
}