Animate CalculatorDisplay text on pulldown.
Bug: 31623549
Bug: 32584801
Change-Id: I07a54cad38c026357082b86ad026392f72693e22
diff --git a/res/layout/activity_calculator_port.xml b/res/layout/activity_calculator_port.xml
index eff5c96..30aaf00 100644
--- a/res/layout/activity_calculator_port.xml
+++ b/res/layout/activity_calculator_port.xml
@@ -43,4 +43,4 @@
</com.android.calculator2.CalculatorPadViewPager>
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/res/layout/display_two_line.xml b/res/layout/display_two_line.xml
index 55e6f7b..3f7338d 100644
--- a/res/layout/display_two_line.xml
+++ b/res/layout/display_two_line.xml
@@ -38,12 +38,9 @@
style="@style/DisplayTextStyle.Formula"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_gravity="bottom|end"
android:ellipsize="none"
- android:longClickable="true"
- android:singleLine="true"
- android:textColor="@color/display_formula_text_color"
- android:textIsSelectable="false" />
+ android:maxLines="1"
+ android:textColor="@color/display_formula_text_color" />
</com.android.calculator2.CalculatorScrollView>
diff --git a/res/layout/fragment_history.xml b/res/layout/fragment_history.xml
index 35d2691..8bb7218 100644
--- a/res/layout/fragment_history.xml
+++ b/res/layout/fragment_history.xml
@@ -16,10 +16,10 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/display_background_color"
- android:clickable="true"
android:orientation="vertical">
<Toolbar
@@ -34,10 +34,12 @@
android:theme="@android:style/ThemeOverlay.Material.Dark.ActionBar"
android:title="@string/title_history" />
- <TextView
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/history_recycler_view"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:text="No History" />
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ app:layoutManager="LinearLayoutManager"
+ app:stackFromEnd="true" />
</LinearLayout>
diff --git a/res/layout/history_item.xml b/res/layout/history_item.xml
new file mode 100644
index 0000000..1d45a14
--- /dev/null
+++ b/res/layout/history_item.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/history_date"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fontFamily="sans-serif-medium"
+ android:text="@string/title_current_expression"
+ android:textColor="?android:attr/colorAccent"
+ android:textSize="14dp" />
+
+ <com.android.calculator2.CalculatorScrollView
+ android:id="@+id/history_formula_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:overScrollMode="never"
+ android:scrollbars="none">
+
+ <com.android.calculator2.CalculatorFormula
+ android:id="@+id/history_formula"
+ style="@style/HistoryItemTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:ellipsize="none"
+ android:textColor="@color/display_formula_text_color" />
+
+ </com.android.calculator2.CalculatorScrollView>
+
+ <com.android.calculator2.CalculatorResult
+ android:id="@+id/history_result"
+ style="@style/HistoryItemTextStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:bufferType="spannable"
+ android:textColor="@color/display_result_text_color" />
+
+</LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5218acd..9de65d5 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,4 +20,9 @@
<!-- The margin between the pad pages when displayed using a view pager. -->
<dimen name="pad_page_margin">24dip</dimen>
+ <dimen name="history_item_text_padding_top">8dip</dimen>
+ <dimen name="history_item_text_padding_bottom">16dip</dimen>
+ <dimen name="history_item_text_padding_start">16dip</dimen>
+ <dimen name="history_item_text_padding_end">24dip</dimen>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f6cfad1..8a053cc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -299,4 +299,7 @@
<!-- Title for alert dialog when calculation takes too long (timeout). [CHAR_LIMIT=30] -->
<string name="dialog_timeout">Timeout</string>
+ <!-- Title for "current expression" in history page. [CHAR_LIMIT=40] -->
+ <string name="title_current_expression">Current Expression</string>
+
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a15cc31..25a3cd0 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -18,7 +18,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="DisplayTextStyle" parent="@android:style/Widget.Material.Light.TextView">
- <item name="android:background">@android:color/transparent</item>
<item name="android:cursorVisible">false</item>
<item name="android:fontFamily">sans-serif-light</item>
<item name="android:includeFontPadding">false</item>
@@ -46,6 +45,14 @@
<item name="android:textSize">@dimen/result_textsize</item>
</style>
+ <style name="HistoryItemTextStyle" parent="DisplayTextStyle">
+ <item name="android:paddingTop">@dimen/history_item_text_padding_top</item>
+ <item name="android:paddingBottom">@dimen/history_item_text_padding_bottom</item>
+ <item name="android:paddingStart">@dimen/history_item_text_padding_start</item>
+ <item name="android:paddingEnd">@dimen/history_item_text_padding_end</item>
+ <item name="android:textSize">@dimen/result_textsize</item>
+ </style>
+
<style name="PadButtonStyle" parent="@android:style/Widget.Material.Light.Button.Borderless">
<item name="android:layout_width">0dip</item>
<item name="android:layout_height">0dip</item>
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index 8e4b240..8aac061 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -57,12 +57,14 @@
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewAnimationUtils;
import android.view.ViewGroupOverlay;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.TextView;
import android.widget.Toolbar;
@@ -174,6 +176,44 @@
}
};
+ private final DragLayout.DragCallback mDragCallback = new DragLayout.DragCallback() {
+ @Override
+ public void onStartDragging() {
+ showHistoryFragment(FragmentTransaction.TRANSIT_NONE);
+ }
+
+ @Override
+ public void whileDragging(float yFraction) {
+ // no-op
+ }
+
+ @Override
+ public void onClosed() {
+ getFragmentManager().popBackStack();
+ }
+
+ @Override
+ public boolean allowDrag(MotionEvent event) {
+ return isViewTarget(mHistoryFrame, event) || isViewTarget(mDisplayView, event);
+ }
+
+ @Override
+ public boolean shouldInterceptTouchEvent(MotionEvent event) {
+ return isViewTarget(mHistoryFrame, event) || isViewTarget(mDisplayView, event);
+ }
+
+ @Override
+ public int getDisplayHeight() {
+ return mDisplayView.getMeasuredHeight();
+ }
+
+ public void onLayout(int translation) {
+ mHistoryFrame.setTranslationY(translation + mDisplayView.getBottom());
+ }
+ };
+
+ private final Rect mHitRect = new Rect();
+
private CalculatorState mCurrentState;
private Evaluator mEvaluator;
@@ -183,6 +223,7 @@
private CalculatorResult mResultText;
private HorizontalScrollView mFormulaContainer;
private DragLayout mDragLayout;
+ private FrameLayout mHistoryFrame;
private ViewPager mPadViewPager;
private View mDeleteButton;
@@ -272,17 +313,10 @@
KeyMaps.setActivity(this);
mDragLayout = (DragLayout) findViewById(R.id.drag_layout);
- mDragLayout.setOnDragCallback(new DragLayout.OnDragCallback() {
- @Override
- public void onStartDragging() {
- showHistoryFragment(FragmentTransaction.TRANSIT_NONE);
- }
+ mDragLayout.removeDragCallback(mDragCallback);
+ mDragLayout.addDragCallback(mDragCallback);
- @Override
- public void onDragToClose() {
- getFragmentManager().popBackStack();
- }
- });
+ mHistoryFrame = (FrameLayout) findViewById(R.id.history_frame);
if (savedInstanceState != null) {
setState(CalculatorState.values()[
@@ -422,6 +456,12 @@
}
@Override
+ protected void onDestroy() {
+ mDragLayout.removeDragCallback(mDragCallback);
+ super.onDestroy();
+ }
+
+ @Override
public void onActionModeStarted(ActionMode mode) {
super.onActionModeStarted(mode);
if (mode.getTag() == CalculatorFormula.TAG_ACTION_MODE) {
@@ -1136,11 +1176,13 @@
}
private void showHistoryFragment(int transit) {
- getFragmentManager().beginTransaction()
- .replace(R.id.history_frame, mHistoryFragment, HistoryFragment.TAG)
- .setTransition(transit)
- .addToBackStack(HistoryFragment.TAG)
- .commit();
+ if (!mDragLayout.isOpen()) {
+ getFragmentManager().beginTransaction()
+ .replace(R.id.history_frame, mHistoryFragment, HistoryFragment.TAG)
+ .setTransition(transit)
+ .addToBackStack(HistoryFragment.TAG)
+ .commit();
+ }
}
private void displayMessage(String title, String message) {
@@ -1263,6 +1305,12 @@
}
}
+ private boolean isViewTarget(View view, MotionEvent event) {
+ mHitRect.set(0, 0, view.getWidth(), view.getHeight());
+ mDragLayout.offsetDescendantRectToMyCoords(view, mHitRect);
+ return mHitRect.contains((int) event.getX(), (int) event.getY());
+ }
+
@Override
public boolean onPaste(ClipData clip) {
final ClipData.Item item = clip.getItemCount() == 0 ? null : clip.getItemAt(0);
diff --git a/src/com/android/calculator2/DragController.java b/src/com/android/calculator2/DragController.java
new file mode 100644
index 0000000..d448cbc
--- /dev/null
+++ b/src/com/android/calculator2/DragController.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016 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.android.calculator2;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Contains the logic for animating the recyclerview elements on drag.
+ */
+public final class DragController {
+
+ // References to views from the Calculator Display.
+ private CalculatorFormula mDisplayFormula;
+ private CalculatorResult mDisplayResult;
+ private View mToolbar;
+
+ private int mFormulaTranslationY;
+ private int mFormulaTranslationX;
+ private float mFormulaScale;
+
+ private int mResultTranslationY;
+ private int mResultTranslationX;
+
+ private boolean mAnimationInitialized;
+
+ public void setDisplayFormula(CalculatorFormula formula) {
+ mDisplayFormula = formula;
+ }
+
+ public void setDisplayResult(CalculatorResult result) {
+ mDisplayResult = result;
+ }
+
+ public void setToolbar(View toolbar) {
+ mToolbar = toolbar;
+ }
+
+ public void animateViews(float yFraction, RecyclerView recyclerView, int itemCount) {
+ final HistoryAdapter.ViewHolder vh = (HistoryAdapter.ViewHolder)
+ recyclerView.findViewHolderForAdapterPosition(itemCount - 1);
+ if (vh != null) {
+ final CalculatorFormula formula = vh.getFormula();
+ final CalculatorResult result = vh.getResult();
+ final TextView date = vh.getDate();
+
+ if (!mAnimationInitialized) {
+ // Calculate the scale for the text
+ mFormulaScale = (mDisplayFormula.getTextSize() * 1.0f) / formula.getTextSize();
+
+ // Baseline of formula moves by the difference in formula bottom padding.
+ mFormulaTranslationY =
+ mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
+ + mDisplayResult.getHeight() - result.getHeight();
+
+ // Right border of formula moves by the difference in formula end padding.
+ mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
+
+ // Baseline of result moves by the difference in result bottom padding.
+ mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom();
+
+ mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
+
+ mAnimationInitialized = true;
+ }
+
+ if (mAnimationInitialized) {
+ formula.setPivotX(formula.getWidth() - formula.getPaddingEnd());
+ formula.setPivotY(formula.getHeight() - formula.getPaddingBottom());
+
+ result.setPivotX(result.getWidth() - result.getPaddingEnd());
+ result.setPivotY(result.getHeight() - result.getPaddingBottom());
+
+ final float resultTranslationX = (mResultTranslationX * yFraction)
+ - mResultTranslationX;
+ result.setTranslationX(resultTranslationX);
+
+ // Scale linearly between -mResultTranslationY and 0.
+ final float resultTranslationY =
+ (mResultTranslationY * yFraction) - mResultTranslationY;
+ result.setTranslationY(resultTranslationY);
+
+ final float scale = mFormulaScale - (mFormulaScale * yFraction) + yFraction;
+ formula.setScaleY(scale);
+ formula.setScaleX(scale);
+
+ final float formulaTranslationX = (mFormulaTranslationX * yFraction)
+ - mFormulaTranslationX;
+ formula.setTranslationX(formulaTranslationX);
+
+ // Scale linearly between -FormulaTranslationY and 0.
+ final float formulaTranslationY =
+ (mFormulaTranslationY * yFraction) - mFormulaTranslationY;
+ formula.setTranslationY(formulaTranslationY);
+
+ // We want the date to start out above the visible screen with
+ // this distance decreasing as it's pulled down.
+ final float dateTranslationY =
+ - mToolbar.getHeight() * (1 - yFraction)
+ + formulaTranslationY
+ - mDisplayFormula.getPaddingTop()
+ + (mDisplayFormula.getPaddingTop() * yFraction);
+ date.setTranslationY(dateTranslationY);
+
+ // Move up all ViewHolders above the current expression.
+ for (int i = recyclerView.getChildCount() - 2; i >= 0; --i) {
+ final RecyclerView.ViewHolder vh2 =
+ recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
+ if (vh2 != null) {
+ final View view = vh2.itemView;
+ if (view != null){
+ view.setTranslationY(dateTranslationY);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/calculator2/DragLayout.java b/src/com/android/calculator2/DragLayout.java
index 93a5974..101b62e 100644
--- a/src/com/android/calculator2/DragLayout.java
+++ b/src/com/android/calculator2/DragLayout.java
@@ -17,7 +17,6 @@
package com.android.calculator2;
import android.content.Context;
-import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
@@ -28,25 +27,30 @@
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
+import java.util.ArrayList;
+import java.util.List;
+
public class DragLayout extends RelativeLayout {
+ private static final String TAG = "DragLayout";
private static final double AUTO_OPEN_SPEED_LIMIT = 800.0;
private static final String KEY_IS_OPEN = "IS_OPEN";
private static final String KEY_SUPER_STATE = "SUPER_STATE";
- private final Rect mHitRect = new Rect();
-
- private CalculatorDisplay mCalculatorDisplay;
private FrameLayout mHistoryFrame;
private ViewDragHelper mDragHelper;
- private OnDragCallback mOnDragCallback;
+ private final List<DragCallback> mDragCallbacks = new ArrayList<>();
private int mDraggingState = ViewDragHelper.STATE_IDLE;
private int mDraggingBorder;
private int mVerticalRange;
private boolean mIsOpen;
+ // Used to determine whether a touch event should be intercepted.
+ private float mInitialDownX;
+ private float mInitialDownY;
+
public DragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -55,7 +59,6 @@
protected void onFinishInflate() {
mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
mHistoryFrame = (FrameLayout) findViewById(R.id.history_frame);
- mCalculatorDisplay = (CalculatorDisplay) findViewById(R.id.display);
super.onFinishInflate();
}
@@ -63,17 +66,24 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
- mHistoryFrame.setTranslationY(-(b - t) + mCalculatorDisplay.getBottom());
-
+ for (DragCallback c : mDragCallbacks) {
+ c.onLayout(t-b);
+ }
if (mIsOpen) {
setOpen();
+ } else {
+ setClosed();
}
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mVerticalRange = h - mCalculatorDisplay.getMeasuredHeight();
+ int height = 0;
+ for (DragCallback c : mDragCallbacks) {
+ height += c.getDisplayHeight();
+ }
+ mVerticalRange = h - height;
super.onSizeChanged(w, h, oldw, oldh);
}
@@ -97,13 +107,41 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- return (isDisplayTarget(event) || isHistoryTarget(event))
- && mDragHelper.shouldInterceptTouchEvent(event);
+ // First verify that we don't have a large deltaX (that the user is not trying to
+ // horizontally scroll).
+ final float x = event.getX();
+ final float y = event.getY();
+ final int action = event.getAction();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mInitialDownX = x;
+ mInitialDownY = y;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final float deltaX = Math.abs(x - mInitialDownX);
+ final float deltaY = Math.abs(y - mInitialDownY);
+ final int slop = mDragHelper.getTouchSlop();
+ if (deltaY > slop && deltaX > deltaY) {
+ mDragHelper.cancel();
+ return false;
+ }
+ }
+
+ boolean doDrag = true;
+ for (DragCallback c : mDragCallbacks) {
+ doDrag &= c.allowDrag(event);
+ }
+ return doDrag && mDragHelper.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (isDisplayTarget(event) || isHistoryTarget(event) || isMoving()) {
+ boolean doIntercept = true;
+ for (DragCallback c : mDragCallbacks) {
+ doIntercept &= c.shouldInterceptTouchEvent(event);
+ }
+ if (doIntercept || isMoving()) {
mDragHelper.processTouchEvent(event);
return true;
} else {
@@ -119,24 +157,12 @@
}
private void onStartDragging() {
- mOnDragCallback.onStartDragging();
+ for (DragCallback c : mDragCallbacks) {
+ c.onStartDragging();
+ }
mHistoryFrame.setVisibility(VISIBLE);
}
- private boolean isViewTarget(View view, MotionEvent event) {
- view.getHitRect(mHitRect);
- offsetDescendantRectToMyCoords(view, mHitRect);
- return mHitRect.contains((int) event.getRawX(), (int) event.getRawY());
- }
-
- private boolean isDisplayTarget(MotionEvent event) {
- return isViewTarget(mCalculatorDisplay, event);
- }
-
- private boolean isHistoryTarget(MotionEvent event) {
- return isViewTarget(mHistoryFrame, event);
- }
-
public boolean isMoving() {
return mDraggingState == ViewDragHelper.STATE_DRAGGING
|| mDraggingState == ViewDragHelper.STATE_SETTLING;
@@ -153,22 +179,45 @@
}
public void setClosed() {
+ // Scroll the RecyclerView to the bottom.
+ for (DragCallback c : mDragCallbacks) {
+ c.onClosed();
+ }
mDragHelper.smoothSlideViewTo(mHistoryFrame, 0, 0);
- mIsOpen = false;
- mOnDragCallback.onDragToClose();
mHistoryFrame.setVisibility(GONE);
+ mIsOpen = false;
}
- public void setOnDragCallback(OnDragCallback callback) {
- mOnDragCallback = callback;
+ public void addDragCallback(DragCallback callback) {
+ mDragCallbacks.add(callback);
}
- public interface OnDragCallback {
+ public void removeDragCallback(DragCallback callback) {
+ mDragCallbacks.remove(callback);
+ }
+
+ /**
+ * Callbacks for coordinating with the RecyclerView or HistoryFragment.
+ */
+ public interface DragCallback {
// Callback when a drag in any direction begins.
void onStartDragging();
- // Callback when a drag is used to close.
- void onDragToClose();
+ // Animate the RecyclerView text.
+ void whileDragging(float yFraction);
+
+ // Scroll the RecyclerView to the bottom before closing the frame.
+ void onClosed();
+
+ // Whether we should allow the drag to happen
+ boolean allowDrag(MotionEvent event);
+
+ // Whether we should intercept the touch event
+ boolean shouldInterceptTouchEvent(MotionEvent event);
+
+ int getDisplayHeight();
+
+ void onLayout(int translation);
}
public class DragHelperCallback extends ViewDragHelper.Callback {
@@ -197,6 +246,11 @@
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
mDraggingBorder = top;
+
+ // Animate RecyclerView text.
+ for (DragCallback c : mDragCallbacks) {
+ c.whileDragging(top / (mVerticalRange * 1.0f));
+ }
}
@Override
@@ -218,14 +272,6 @@
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
- if (mDraggingBorder == 0) {
- setClosed();
- return;
- }
- if (mDraggingBorder == mVerticalRange) {
- setOpen();
- return;
- }
boolean settleToOpen = false;
final float threshold = mVerticalRange / 2;
if (yvel > AUTO_OPEN_SPEED_LIMIT) {
diff --git a/src/com/android/calculator2/HistoryAdapter.java b/src/com/android/calculator2/HistoryAdapter.java
new file mode 100644
index 0000000..6a630d9
--- /dev/null
+++ b/src/com/android/calculator2/HistoryAdapter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 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.android.calculator2;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+/**
+ * Adapter for RecyclerView of HistoryItems.
+ */
+public class HistoryAdapter extends RecyclerView.Adapter<HistoryAdapter.ViewHolder> {
+
+ private final List<HistoryItem> mDataSet = new ArrayList<>();
+ private String mCurrentExpression;
+
+ public HistoryAdapter(int[] dataset, String currentExpression) {
+ mCurrentExpression = currentExpression;
+ // Temporary dataset
+ final Calendar calendar = Calendar.getInstance();
+ for (int i: dataset) {
+ calendar.set(2016, 10, i);
+ mDataSet.add(new HistoryItem(calendar.getTimeInMillis(), Integer.toString(i) + "+1",
+ Integer.toString(i+1)));
+ }
+ }
+
+ @Override
+ public HistoryAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.history_item, parent, false);
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(HistoryAdapter.ViewHolder holder, int position) {
+ final HistoryItem item = mDataSet.get(position);
+
+ if (!isCurrentExpressionItem(position)) {
+ holder.mDate.setText(item.getDateString());
+ holder.mDate.setContentDescription(item.getDateDescription());
+ } else {
+ holder.mDate.setText(mCurrentExpression);
+ holder.mDate.setContentDescription(mCurrentExpression);
+ }
+ holder.mFormula.setText(item.getFormula());
+ holder.mResult.setText(item.getResult());
+ }
+
+ @Override
+ public void onViewRecycled(ViewHolder holder) {
+ holder.mDate.setContentDescription(null);
+ holder.mDate.setText(null);
+ holder.mFormula.setText(null);
+ holder.mResult.setText(null);
+
+ super.onViewRecycled(holder);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mDataSet.size();
+ }
+
+ private boolean isCurrentExpressionItem(int position) {
+ return position == mDataSet.size() - 1;
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+
+ private final TextView mDate;
+ private final CalculatorFormula mFormula;
+ private final CalculatorResult mResult;
+
+ public ViewHolder(View v) {
+ super(v);
+ mDate = (TextView) v.findViewById(R.id.history_date);
+ mFormula = (CalculatorFormula) v.findViewById(R.id.history_formula);
+ mResult = (CalculatorResult) v.findViewById(R.id.history_result);
+ }
+
+ public CalculatorFormula getFormula() {
+ return mFormula;
+ }
+
+ public CalculatorResult getResult() {
+ return mResult;
+ }
+
+ public TextView getDate() {
+ return mDate;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/calculator2/HistoryFragment.java b/src/com/android/calculator2/HistoryFragment.java
index ef75ed6..3fc8e89 100644
--- a/src/com/android/calculator2/HistoryFragment.java
+++ b/src/com/android/calculator2/HistoryFragment.java
@@ -21,9 +21,11 @@
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
+import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toolbar;
@@ -32,12 +34,72 @@
public static final String TAG = "HistoryFragment";
+ private final DragLayout.DragCallback mDragCallback =
+ new DragLayout.DragCallback() {
+ @Override
+ public void onStartDragging() {
+ // no-op
+ }
+
+ @Override
+ public void whileDragging(float yFraction) {
+ mDragController.animateViews(yFraction, mRecyclerView, mAdapter.getItemCount());
+ }
+
+ @Override
+ public void onClosed() {
+ mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1);
+ }
+
+ @Override
+ public boolean allowDrag(MotionEvent event) {
+ // Do not allow drag if the recycler view can move down more
+ return !mRecyclerView.canScrollVertically(1);
+ }
+
+ @Override
+ public boolean shouldInterceptTouchEvent(MotionEvent event) {
+ return true;
+ }
+
+ @Override
+ public int getDisplayHeight() {
+ return 0;
+ }
+
+ @Override
+ public void onLayout(int translation) {
+ // no-op
+ }
+ };
+
+ private final DragController mDragController = new DragController();
+
+ private RecyclerView mRecyclerView;
+ private HistoryAdapter mAdapter;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Temporary data
+ final int[] testArray = {1, 2, 3, 4, 5, 6, 7};
+ mAdapter = new HistoryAdapter(testArray,
+ getContext().getResources().getString(R.string.title_current_expression));
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(
R.layout.fragment_history, container, false /* attachToRoot */);
+ mRecyclerView = (RecyclerView) view.findViewById(R.id.history_recycler_view);
+
+ // The size of the RecyclerView is not affected by the adapter's contents.
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setAdapter(mAdapter);
+
final Toolbar toolbar = (Toolbar) view.findViewById(R.id.history_toolbar);
toolbar.inflateMenu(R.menu.fragment_history);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@@ -61,6 +123,16 @@
}
@Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ initializeController();
+ final DragLayout dragLayout = (DragLayout) getActivity().findViewById(R.id.drag_layout);
+ dragLayout.removeDragCallback(mDragCallback);
+ dragLayout.addDragCallback(mDragCallback);
+ }
+
+ @Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
final View view = getView();
final int height = getResources().getDisplayMetrics().heightPixels;
@@ -72,6 +144,29 @@
return null;
}
+ @Override
+ public void onDestroyView() {
+ final DragLayout dragLayout = (DragLayout) getActivity().findViewById(R.id.drag_layout);
+ if (dragLayout != null) {
+ dragLayout.removeDragCallback(mDragCallback);
+ }
+ super.onDestroy();
+ }
+
+ private void initializeController() {
+ mDragController.setDisplayFormula(
+ (CalculatorFormula) getActivity().findViewById(R.id.formula));
+
+ mDragController.setDisplayResult(
+ (CalculatorResult) getActivity().findViewById(R.id.result));
+
+ mDragController.setToolbar(getActivity().findViewById(R.id.toolbar));
+
+ // Initialize the current expression element to dimensions that match the display to avoid
+ // flickering and scrolling when elements expand on drag start.
+ mDragController.animateViews(1.0f, mRecyclerView, mAdapter.getItemCount());
+ }
+
private void clearHistory() {
Log.d(TAG, "Dropping history table");
}
diff --git a/src/com/android/calculator2/HistoryItem.java b/src/com/android/calculator2/HistoryItem.java
new file mode 100644
index 0000000..0e46293
--- /dev/null
+++ b/src/com/android/calculator2/HistoryItem.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 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.android.calculator2;
+
+import android.text.format.DateFormat;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class HistoryItem {
+
+ private static final String dateFormat = "EEEMMMd";
+ private static final String descriptionFormat = "EEEEMMMMd";
+
+ private Date mDate;
+ private String mFormula;
+ private String mResult;
+
+ public HistoryItem(long millis, String formula, String result) {
+ mDate = new Date(millis);
+ mFormula = formula;
+ mResult = result;
+ }
+
+ public String getDateString() {
+ // TODO: Use DateUtils?
+ final Locale l = Locale.getDefault();
+ final String datePattern = DateFormat.getBestDateTimePattern(l, dateFormat);
+ return new SimpleDateFormat(datePattern, l).format(mDate);
+ }
+
+ public String getDateDescription() {
+ final Locale l = Locale.getDefault();
+ final String descriptionPattern = DateFormat.getBestDateTimePattern(l, descriptionFormat);
+ return new SimpleDateFormat(descriptionPattern, l).format(mDate);
+ }
+
+ public String getFormula() {
+ return mFormula;
+ }
+
+ public String getResult() {
+ return mResult;
+ }
+}
\ No newline at end of file