Adding new samples to browseable section of DAC
Change-Id: I58e10e787f5df668331fc04e97a6c2efcd75f76f
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user