aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Huang <yabinh@google.com>2020-05-12 23:55:03 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-05-12 23:55:03 +0000
commita4aa5d5675762a5c8480a30a494eeab646b69a69 (patch)
tree11b8493afcac5898dd83f7f37035a3f7749f0395
parent778d489585053d9219466ce1371915e18a0b7069 (diff)
parentb5b8a5d6a783e12ca0ee06e7770b94dae44a1d9a (diff)
downloadtests-a4aa5d5675762a5c8480a30a494eeab646b69a69.tar.gz
Add a custom DirectManipulationView am: b5b8a5d6a7
Change-Id: I2c27037b69e2e24852f34c95bf2e526b3dc18b0e
-rw-r--r--RotaryPlayground/res/layout/rotary_direct_manipulation.xml46
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationView.java116
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java111
3 files changed, 259 insertions, 14 deletions
diff --git a/RotaryPlayground/res/layout/rotary_direct_manipulation.xml b/RotaryPlayground/res/layout/rotary_direct_manipulation.xml
index 914a6e3..6203cdd 100644
--- a/RotaryPlayground/res/layout/rotary_direct_manipulation.xml
+++ b/RotaryPlayground/res/layout/rotary_direct_manipulation.xml
@@ -21,25 +21,38 @@
<!-- Split the screen in half horizontally. -->
<!-- Two time pickers formatted differently. -->
- <com.android.car.ui.FocusArea
+ <LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_weight="1">
- <TimePicker
+ <!-- Put each TimePicker into a separate FocusArea. A TimePicker has several focusable views
+ and it's difficult to move to another TimePicker via rotation. Let's wrap each TimePicker
+ with a FocusArea so that we can use nudge to move to another TimePicker. -->
+ <com.android.car.ui.FocusArea
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <TimePicker
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:timePickerMode="spinner">
+ </TimePicker>
+ </com.android.car.ui.FocusArea>
+ <com.android.car.ui.FocusArea
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:timePickerMode="spinner">
- </TimePicker>
- <TimePicker
- android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1"
- android:timePickerMode="clock">
- </TimePicker>
- </com.android.car.ui.FocusArea>
+ android:layout_weight="1">
+ <TimePicker
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:timePickerMode="clock">
+ </TimePicker>
+ </com.android.car.ui.FocusArea>
+ </LinearLayout>
- <!-- A seek bar and a radial time picker. -->
+ <!-- A seek bar, a radial time picker, and a custom DirectManipulationView. -->
<com.android.car.ui.FocusArea
android:layout_width="0dp"
android:layout_height="match_parent"
@@ -52,7 +65,14 @@
<RadialTimePickerView
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1">
+ android:layout_weight="1"
+ android:focusable="true">
</RadialTimePickerView>
+ <com.android.car.rotaryplayground.DirectManipulationView
+ android:id="@+id/direct_manipulation_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ </com.android.car.rotaryplayground.DirectManipulationView>
</com.android.car.ui.FocusArea>
</LinearLayout>
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationView.java b/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationView.java
new file mode 100644
index 0000000..6c37556
--- /dev/null
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationView.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 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.car.rotaryplayground;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import static java.lang.Math.min;
+
+/**
+ * A {@link View} used to demonstrate direct manipulation mode.
+ * <p>
+ * This view draws nothing but a circle. It provides APIs to change the center and the radius of the
+ * circle.
+ */
+public class DirectManipulationView extends View {
+
+ /**
+ * How many pixels do we want to move the center of the circle horizontally from its initial
+ * position.
+ */
+ private float mDeltaX;
+ /**
+ * How many pixels do we want to move the center of the circle vertically from its initial
+ * position.
+ */
+ private float mDeltaY;
+ /** How many pixels do we want change the radius of the circle from its initial radius. */
+ private float mDeltaRadius;
+
+ private Paint mPaint;
+
+ public DirectManipulationView(Context context) {
+ super(context);
+ init();
+ }
+
+ public DirectManipulationView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public DirectManipulationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ public DirectManipulationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Draw the circle. Initially the circle is in the center of the canvas, and its radius is
+ // min(getWidth(), getHeight()) / 4. We need to translate it and scale it.
+ canvas.drawCircle(
+ /* cx= */getWidth() / 2 + mDeltaX,
+ /* cy= */getHeight() / 2 + mDeltaY,
+ /* radius= */min(getWidth(), getHeight()) / 4 + mDeltaRadius,
+ mPaint);
+
+ }
+
+ /**
+ * Moves the center of the circle by {@code dx} horizontally and by {@code dy} vertically, then
+ * redraws it.
+ */
+ void move(float dx, float dy) {
+ mDeltaX += dx;
+ mDeltaY += dy;
+ invalidate();
+ }
+
+ /** Changes the radius of the circle by {@code dr} then redraws it. */
+ void zoom(float dr) {
+ mDeltaRadius += dr;
+ invalidate();
+ }
+
+ private void init() {
+ // The view must be focusable to enter direct manipulation mode.
+ setFocusable(View.FOCUSABLE);
+
+ // Set up paint with color and stroke styles.
+ mPaint = new Paint();
+ mPaint.setColor(Color.GREEN);
+ mPaint.setAntiAlias(true);
+ mPaint.setStrokeWidth(5);
+ mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mPaint.setStrokeJoin(Paint.Join.ROUND);
+ mPaint.setStrokeCap(Paint.Cap.ROUND);
+ }
+}
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java
index 34f9f60..fb02105 100644
--- a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java
@@ -16,14 +16,20 @@
package com.android.car.rotaryplayground;
+import android.graphics.Color;
import android.os.Bundle;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import com.android.car.ui.utils.DirectManipulationHelper;
+
/**
* Fragment that demos rotary interactions directly manipulating the state of UI widgets such as a
* {@link android.widget.SeekBar}, {@link android.widget.DatePicker}, and
@@ -32,9 +38,112 @@ import androidx.fragment.app.Fragment;
public class RotaryDirectManipulationWidgets extends Fragment {
// TODO(agathaman): refactor a common class that takes in a fragment xml id and inflates it, to
// share between this and RotaryCards.
+
+ /** How many pixels do we want to move the {@link DirectManipulationView} for nudge. */
+ private static final float DIRECT_MANIPULATION_VIEW_PX_PER_NUDGE = 10f;
+
+ /** How many pixels do we want to zoom the {@link DirectManipulationView} for a rotation. */
+ private static final float DIRECT_MANIPULATION_VIEW_PX_PER_ROTATION = 10f;
+
+ /** Background color of {@link DirectManipulationView} when it's in direct manipulation mode. */
+ private static final int BACKGROUND_COLOR_IN_DIRECT_MANIPULATION_MODE = Color.BLUE;
+
+ /**
+ * Background color of {@link DirectManipulationView} when it's not in direct manipulation
+ * mode.
+ */
+ private static final int BACKGROUND_COLOR_NOT_IN_DIRECT_MANIPULATION_MODE = Color.TRANSPARENT;
+
+ /** Whether any view in this Fragment is in direct manipulation mode. */
+ private boolean mInDirectManipulationMode;
+
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.rotary_direct_manipulation, container, false);
+ View view = inflater.inflate(R.layout.rotary_direct_manipulation, container, false);
+ DirectManipulationView directManipulationView =
+ view.findViewById(R.id.direct_manipulation_view);
+ initDirectManipulationView(directManipulationView);
+ return view;
+ }
+
+ /**
+ * Initializes the {@link DirectManipulationView} so that it can enter/exit direct manipulation
+ * mode and interact with the rotary controller directly. In direct manipulation mode, the
+ * circle of the DirectManipulationView can move when the controller nudges, and the circle of
+ * the DirectManipulationView can zoom when the controller rotates.
+ */
+ private void initDirectManipulationView(@NonNull DirectManipulationView dmv) {
+ dmv.setOnKeyListener((view, keyCode, keyEvent) -> {
+ boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP;
+ switch (keyCode) {
+ // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK event.
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (!mInDirectManipulationMode && isActionUp) {
+ mInDirectManipulationMode = true;
+ dmv.setBackgroundColor(BACKGROUND_COLOR_IN_DIRECT_MANIPULATION_MODE);
+ dmv.invalidate();
+ DirectManipulationHelper.enableDirectManipulationMode(dmv, true);
+ }
+ return true;
+ case KeyEvent.KEYCODE_BACK:
+ if (mInDirectManipulationMode && isActionUp) {
+ mInDirectManipulationMode = false;
+ dmv.setBackgroundColor(BACKGROUND_COLOR_NOT_IN_DIRECT_MANIPULATION_MODE);
+ dmv.invalidate();
+ DirectManipulationHelper.enableDirectManipulationMode(dmv, false);
+ }
+ return true;
+ // Consume controller nudge event (KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN,
+ // KEYCODE_DPAD_LEFT, or KEYCODE_DPAD_RIGHT) only when in direct manipulation mode.
+ // When handling nudge event, move the circle of the DirectManipulationView.
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (!mInDirectManipulationMode) {
+ return false;
+ }
+ if (isActionUp) {
+ dmv.move(0f, -DIRECT_MANIPULATION_VIEW_PX_PER_NUDGE);
+ }
+ return true;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (!mInDirectManipulationMode) {
+ return false;
+ }
+ if (isActionUp) {
+ dmv.move(0f, DIRECT_MANIPULATION_VIEW_PX_PER_NUDGE);
+ }
+ return true;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (!mInDirectManipulationMode) {
+ return false;
+ }
+ if (isActionUp) {
+ dmv.move(-DIRECT_MANIPULATION_VIEW_PX_PER_NUDGE, 0f);
+ }
+ return true;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (!mInDirectManipulationMode) {
+ return false;
+ }
+ if (isActionUp) {
+ dmv.move(DIRECT_MANIPULATION_VIEW_PX_PER_NUDGE, 0f);
+ }
+ return true;
+ // Don't consume other key events.
+ default:
+ return false;
+ }
+ });
+
+ // When in direct manipulation mode, zoom the circle of the DirectManipulationView on
+ // controller rotate event.
+ dmv.setOnGenericMotionListener(((view, motionEvent) -> {
+ if (!mInDirectManipulationMode) {
+ return false;
+ }
+ float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL);
+ dmv.zoom(DIRECT_MANIPULATION_VIEW_PX_PER_ROTATION * scroll);
+ return true;
+ }));
}
}