Improve stopwatch tab layout for multiwindow
Bug: 26840878
Change-Id: Ic0e15f3394c74c9998001797e24af91406321996
diff --git a/Android.mk b/Android.mk
index 906dab8..884085a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,6 +6,7 @@
ifeq ($(TARGET_BUILD_APPS),)
LOCAL_RESOURCE_DIR += frameworks/support/design/res
+LOCAL_RESOURCE_DIR += frameworks/support/v14/percent/res
LOCAL_RESOURCE_DIR += frameworks/support/v14/preference/res
LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res
LOCAL_RESOURCE_DIR += frameworks/support/v7/gridlayout/res
@@ -13,6 +14,7 @@
LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res
else
LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/design/res
+LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v14/percent/res
LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v14/preference/res
LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/appcompat/res
LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/gridlayout/res
@@ -31,6 +33,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-opt-datetimepicker
LOCAL_STATIC_JAVA_LIBRARIES += android-support-design
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v13
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v14-percent
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v14-preference
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-gridlayout
@@ -39,6 +42,7 @@
LOCAL_AAPT_FLAGS := --auto-add-overlay
LOCAL_AAPT_FLAGS += --extra-packages android.support.design
+LOCAL_AAPT_FLAGS += --extra-packages android.support.v14.percent
LOCAL_AAPT_FLAGS += --extra-packages android.support.v14.preference
LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat
LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.gridlayout
diff --git a/res/layout-land/stopwatch_fragment.xml b/res/layout-land/stopwatch_fragment.xml
index 779aba4..ca87895 100644
--- a/res/layout-land/stopwatch_fragment.xml
+++ b/res/layout-land/stopwatch_fragment.xml
@@ -13,47 +13,51 @@
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:layout_height="match_parent"
android:baselineAligned="false"
- android:orientation="horizontal"
- android:paddingBottom="@dimen/fab_height">
+ android:orientation="horizontal">
- <!-- This FrameLayout reserves half the screen for the stopwatch. -->
- <FrameLayout
+ <!-- Left gutter. -->
+ <Space
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1">
+ android:layout_weight="@integer/gutter_width_percent" />
- <!-- This FrameLayout draws the stopwatch centered within its half of the screen. -->
+ <!-- Guttered content. -->
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="@integer/guttered_content_width_percent"
+ android:orientation="horizontal">
+
<FrameLayout
- android:layout_width="@dimen/circle_size"
- android:layout_height="@dimen/circle_size"
- android:layout_gravity="center"
- android:gravity="center"
- android:padding="16dp">
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:paddingBottom="@dimen/fab_height">
- <com.android.deskclock.timer.CountingTimerView
- android:id="@+id/stopwatch_time_text"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ <include layout="@layout/stopwatch_view" />
- <com.android.deskclock.stopwatch.StopwatchCircleView
- android:id="@+id/stopwatch_time"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@null" />
</FrameLayout>
- </FrameLayout>
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/laps_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:clipToPadding="false"
+ android:paddingBottom="@dimen/fab_height" />
- <android.support.v7.widget.RecyclerView
- android:id="@+id/laps_list"
+ </LinearLayout>
+
+ <!-- Right gutter. -->
+ <Space
android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_weight="1" />
+ android:layout_height="match_parent"
+ android:layout_weight="@integer/gutter_width_percent" />
</LinearLayout>
\ No newline at end of file
diff --git a/res/layout-sw320dp/stopwatch_view.xml b/res/layout-sw320dp/stopwatch_view.xml
new file mode 100644
index 0000000..5a75818
--- /dev/null
+++ b/res/layout-sw320dp/stopwatch_view.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<!-- Sufficient space exists to include the bounding stopwatch circle. -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <com.android.deskclock.timer.CountingTimerView
+ android:id="@+id/stopwatch_time_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <com.android.deskclock.stopwatch.StopwatchCircleView
+ android:id="@+id/stopwatch_time"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null" />
+
+</merge>
\ No newline at end of file
diff --git a/res/layout/stopwatch_fragment.xml b/res/layout/stopwatch_fragment.xml
index 5ce57af..0be82f1 100644
--- a/res/layout/stopwatch_fragment.xml
+++ b/res/layout/stopwatch_fragment.xml
@@ -13,38 +13,39 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+
<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:baselineAligned="false"
android:gravity="center"
- android:orientation="vertical"
- android:paddingBottom="@dimen/fab_height">
+ android:baselineAligned="false"
+ android:orientation="vertical">
- <FrameLayout
- android:layout_width="@dimen/circle_size"
- android:layout_height="@dimen/circle_size"
- android:layout_gravity="center"
- android:padding="16dp">
+ <android.support.percent.PercentFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
- <com.android.deskclock.timer.CountingTimerView
- android:id="@+id/stopwatch_time_text"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ <FrameLayout
+ android:layout_gravity="center"
+ app:layout_aspectRatio="100%"
+ app:layout_widthPercent="60%">
- <com.android.deskclock.stopwatch.StopwatchCircleView
- android:id="@+id/stopwatch_time"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@null" />
- </FrameLayout>
+ <include layout="@layout/stopwatch_view" />
+
+ </FrameLayout>
+
+ </android.support.percent.PercentFrameLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/laps_list"
android:layout_width="match_parent"
android:layout_height="0dp"
+ android:layout_weight="1"
android:layout_gravity="center"
- android:layout_weight="1" />
+ android:clipToPadding="false"
+ android:paddingBottom="@dimen/fab_height" />
</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/stopwatch_view.xml b/res/layout/stopwatch_view.xml
new file mode 100644
index 0000000..ecc4322
--- /dev/null
+++ b/res/layout/stopwatch_view.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<!-- Insufficient space exists to include the bounding stopwatch circle. -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <com.android.deskclock.timer.CountingTimerView
+ android:id="@+id/stopwatch_time_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</merge>
\ No newline at end of file
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index ff7fef4..bbec0e7 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -42,9 +42,6 @@
<dimen name="medium_font_size">48sp</dimen>
<dimen name="circle_size">190dip</dimen>
- <dimen name="stopwatch_circle_margin_bottom">80dip</dimen>
-
- <dimen name="sw_padding_end">8dp</dimen>
<dimen name="alarm_alert_clock_padding_left">32dp</dimen>
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index d31c950..06650e6 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -36,9 +36,6 @@
<dimen name="analog_clock_margin">48dp</dimen>
<dimen name="circle_size">360dip</dimen>
- <dimen name="stopwatch_circle_margin_bottom">112dip</dimen>
-
- <dimen name="sw_padding_end">48dp</dimen>
<!-- The maximum size of the font for the time in widgets. -->
<dimen name="widget_max_clock_font_size">100dp</dimen>
diff --git a/src/com/android/alarmclock/WidgetUtils.java b/src/com/android/alarmclock/WidgetUtils.java
index e3099cc..be7bf33 100644
--- a/src/com/android/alarmclock/WidgetUtils.java
+++ b/src/com/android/alarmclock/WidgetUtils.java
@@ -18,11 +18,11 @@
import android.appwidget.AppWidgetManager;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import com.android.deskclock.R;
+import com.android.deskclock.Utils;
public final class WidgetUtils {
@@ -44,7 +44,7 @@
// No data , do no scaling
return 1f;
}
- Resources res = context.getResources();
+ final Resources res = context.getResources();
float density = res.getDisplayMetrics().density;
float ratio = (density * minWidth) / res.getDimension(R.dimen.min_digital_widget_width);
ratio = Math.min(ratio, getHeightScaleRatio(context, options, id));
@@ -55,7 +55,7 @@
}
ratio = Math.min(ratio, 1.6f);
- if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ if (Utils.isPortrait(context)) {
ratio = Math.max(ratio, .71f);
}
else {
@@ -82,10 +82,10 @@
// No data , do no scaling
return 1f;
}
- Resources res = context.getResources();
+ final Resources res = context.getResources();
float density = res.getDisplayMetrics().density;
float ratio = density * minHeight / res.getDimension(R.dimen.min_digital_widget_height);
- if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ if (Utils.isPortrait(context)) {
return ratio * 1.75f;
}
return ratio;
diff --git a/src/com/android/deskclock/Utils.java b/src/com/android/deskclock/Utils.java
index 381db21..148a2bd 100644
--- a/src/com/android/deskclock/Utils.java
+++ b/src/com/android/deskclock/Utils.java
@@ -77,6 +77,8 @@
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY;
import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.graphics.Bitmap.Config.ARGB_8888;
public class Utils {
@@ -653,4 +655,20 @@
return PreferenceManager.getDefaultSharedPreferences(storageContext);
}
+
+ /**
+ * @param context from which to query the current device configuration
+ * @return {@code true} if the device is currently in portrait or reverse portrait orientation
+ */
+ public static boolean isPortrait(Context context) {
+ return context.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
+ }
+
+ /**
+ * @param context from which to query the current device configuration
+ * @return {@code true} if the device is currently in landscape or reverse landscape orientation
+ */
+ public static boolean isLandscape(Context context) {
+ return context.getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ }
}
\ No newline at end of file
diff --git a/src/com/android/deskclock/stopwatch/StopwatchFragment.java b/src/com/android/deskclock/stopwatch/StopwatchFragment.java
index 420bab3..333ce42 100644
--- a/src/com/android/deskclock/stopwatch/StopwatchFragment.java
+++ b/src/com/android/deskclock/stopwatch/StopwatchFragment.java
@@ -20,6 +20,7 @@
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -106,10 +107,19 @@
mLapsList = (RecyclerView) v.findViewById(R.id.laps_list);
((SimpleItemAnimator) mLapsList.getItemAnimator()).setSupportsChangeAnimations(false);
mLapsList.setLayoutManager(mLapsLayoutManager);
+
+ // In landscape layouts, the laps list can reach the top of the screen and thus can cause
+ // a drop shadow to appear. The same is not true for portrait landscapes.
+ if (Utils.isLandscape(getActivity())) {
+ final ScrollPositionWatcher scrollPositionWatcher = new ScrollPositionWatcher();
+ mLapsList.addOnLayoutChangeListener(scrollPositionWatcher);
+ mLapsList.addOnScrollListener(scrollPositionWatcher);
+ }
mLapsList.setAdapter(mLapsAdapter);
// Timer text serves as a virtual start/stop button.
mTimeText = (CountingTimerView) v.findViewById(R.id.stopwatch_time_text);
+ mTimeText.setShowBoundingCircle(mTime != null);
mTimeText.setVirtualButtonEnabled(true);
mTimeText.registerVirtualButtonAction(new ToggleStopwatchRunnable());
@@ -119,8 +129,8 @@
}
@Override
- public void onResume() {
- super.onResume();
+ public void onStart() {
+ super.onStart();
// Conservatively assume the data in the adapter has changed while the fragment was paused.
mLapCount = mLapsAdapter.getItemCount();
@@ -134,8 +144,8 @@
}
@Override
- public void onPause() {
- super.onPause();
+ public void onStop() {
+ super.onStop();
// Stop all updates while the fragment is not visible.
stopUpdatingTime();
@@ -350,8 +360,10 @@
// to ensure they aren't seen as the first lap is drawn.
mLapsList.removeAllViewsInLayout();
- // Start animating the reference lap.
- mTime.update();
+ if (mTime != null) {
+ // Start animating the reference lap.
+ mTime.update();
+ }
// Recording the first lap transitions the UI to display the laps list.
showOrHideLaps(false);
@@ -367,6 +379,10 @@
*/
private void showOrHideLaps(boolean clearLaps) {
final ViewGroup sceneRoot = (ViewGroup) getView();
+ if (sceneRoot == null) {
+ return;
+ }
+
TransitionManager.beginDelayedTransition(sceneRoot);
if (clearLaps) {
@@ -375,6 +391,17 @@
final boolean lapsVisible = mLapsAdapter.getItemCount() > 0;
mLapsList.setVisibility(lapsVisible ? VISIBLE : GONE);
+
+ if (Utils.isPortrait(getActivity())) {
+ // When the lap list is visible, it includes the bottom padding. When it is absent the
+ // appropriate bottom padding must be applied to the container.
+ final Resources res = getResources();
+ final int bottom = lapsVisible ? 0 : res.getDimensionPixelSize(R.dimen.fab_height);
+ final int top = sceneRoot.getPaddingTop();
+ final int left = sceneRoot.getPaddingLeft();
+ final int right = sceneRoot.getPaddingRight();
+ sceneRoot.setPadding(left, top, right, bottom);
+ }
}
private void adjustWakeLock() {
@@ -415,14 +442,14 @@
private void startUpdatingTime() {
// Ensure only one copy of the runnable is ever scheduled by first stopping updates.
stopUpdatingTime();
- mTime.post(mTimeUpdateRunnable);
+ mTimeText.post(mTimeUpdateRunnable);
}
/**
* Remove the runnable that updates times within the UI.
*/
private void stopUpdatingTime() {
- mTime.removeCallbacks(mTimeUpdateRunnable);
+ mTimeText.removeCallbacks(mTimeUpdateRunnable);
}
/**
@@ -454,7 +481,9 @@
// Draw the latest stopwatch and current lap times.
updateTime();
- mTime.update();
+ if (mTime != null) {
+ mTime.update();
+ }
// Start updates if the stopwatch is running.
final Stopwatch stopwatch = getStopwatch();
@@ -488,7 +517,7 @@
final long endTime = SystemClock.elapsedRealtime();
final long delay = Math.max(0, startTime + REDRAW_PERIOD - endTime);
- mTime.postDelayed(this, delay);
+ mTimeText.postDelayed(this, delay);
}
}
}
@@ -531,4 +560,22 @@
public void lapAdded(Lap lap) {
}
}
-}
+
+ /**
+ * Updates the vertical scroll state of this tab in the {@link UiDataModel} as the user scrolls
+ * the recyclerview or when the size/position of elements within the recyclerview changes.
+ */
+ private final class ScrollPositionWatcher extends RecyclerView.OnScrollListener
+ implements View.OnLayoutChangeListener {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ setTabScrolledToTop(Utils.isScrolledToTop(mLapsList));
+ }
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ setTabScrolledToTop(Utils.isScrolledToTop(mLapsList));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/timer/CountingTimerView.java b/src/com/android/deskclock/timer/CountingTimerView.java
index b87c644..e276543 100644
--- a/src/com/android/deskclock/timer/CountingTimerView.java
+++ b/src/com/android/deskclock/timer/CountingTimerView.java
@@ -85,6 +85,10 @@
private boolean mVirtualButtonEnabled = false;
private boolean mVirtualButtonPressedOn = false;
+ // Whether or not a bounding circle exists into which the text must be made to fit.
+ // If no such circle exists, the entire width of this component is available for text display.
+ private boolean mShowBoundingCircle;
+
Runnable mBlinkThread = new Runnable() {
private boolean mVisible = true;
@Override
@@ -310,6 +314,11 @@
mPaintMed.setColor(textColor);
}
+ public void setShowBoundingCircle(boolean showBoundingCircle) {
+ mShowBoundingCircle = showBoundingCircle;
+ requestLayout();
+ }
+
/**
* Update the time to display. Separates that time into the hours, minutes, seconds and
* hundredths. If update is true, the view is invalidated so that it will draw again.
@@ -420,10 +429,18 @@
*/
private void setTotalTextWidth() {
calcTotalTextWidth();
- // To determine the maximum width, we find the minimum of the height and width (since the
- // circle we are trying to fit the text into has its radius sized to the smaller of the
- // two.
- int width = Math.min(getWidth(), getHeight());
+
+ int width;
+ if (mShowBoundingCircle) {
+ // A bounding circle exists, so the available width in which to fit the timer text is
+ // the smaller of the width or height, which is also equal to the circle's diameter.
+ width = Math.min(getWidth(), getHeight());
+ } else {
+ // A bounding circle does not exist, so pretend that the entire width of this component
+ // is the diameter of a theoretical bounding circle.
+ width = getWidth();
+ }
+
if (width != 0) {
// Shrink 'width' to account for circle stroke and other painted objects.
// Note on the "4 *": (1) To reduce divisions, using the diameter instead of the radius.