1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.wearable.watchface.config; 18 19 import android.animation.AnimatorSet; 20 import android.animation.ObjectAnimator; 21 import android.app.Activity; 22 import android.content.Context; 23 import android.graphics.Color; 24 import android.os.Bundle; 25 import android.support.v7.widget.RecyclerView; 26 import android.support.wearable.view.BoxInsetLayout; 27 import android.support.wearable.view.CircledImageView; 28 import android.support.wearable.view.WearableListView; 29 import android.util.Log; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.WindowInsets; 33 import android.widget.LinearLayout; 34 import android.widget.TextView; 35 36 import com.google.android.gms.common.ConnectionResult; 37 import com.google.android.gms.common.api.GoogleApiClient; 38 import com.google.android.gms.wearable.DataMap; 39 import com.google.android.gms.wearable.Wearable; 40 41 import com.example.android.wearable.watchface.watchface.DigitalWatchFaceService; 42 import com.example.android.wearable.watchface.util.DigitalWatchFaceUtil; 43 import com.example.android.wearable.watchface.R; 44 45 /** 46 * The watch-side config activity for {@link DigitalWatchFaceService}, which allows for setting the 47 * background color. 48 */ 49 public class DigitalWatchFaceWearableConfigActivity extends Activity implements 50 WearableListView.ClickListener, WearableListView.OnScrollListener { 51 private static final String TAG = "DigitalWatchFaceConfig"; 52 53 private GoogleApiClient mGoogleApiClient; 54 private TextView mHeader; 55 56 @Override 57 protected void onCreate(Bundle savedInstanceState) { 58 super.onCreate(savedInstanceState); 59 setContentView(R.layout.activity_digital_config); 60 61 mHeader = (TextView) findViewById(R.id.header); 62 WearableListView listView = (WearableListView) findViewById(R.id.color_picker); 63 BoxInsetLayout content = (BoxInsetLayout) findViewById(R.id.content); 64 // BoxInsetLayout adds padding by default on round devices. Add some on square devices. 65 content.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { 66 @Override 67 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 68 if (!insets.isRound()) { 69 v.setPaddingRelative( 70 (int) getResources().getDimensionPixelSize(R.dimen.content_padding_start), 71 v.getPaddingTop(), 72 v.getPaddingEnd(), 73 v.getPaddingBottom()); 74 } 75 return v.onApplyWindowInsets(insets); 76 } 77 }); 78 79 listView.setHasFixedSize(true); 80 listView.setClickListener(this); 81 listView.addOnScrollListener(this); 82 83 String[] colors = getResources().getStringArray(R.array.color_array); 84 listView.setAdapter(new ColorListAdapter(colors)); 85 86 mGoogleApiClient = new GoogleApiClient.Builder(this) 87 .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { 88 @Override 89 public void onConnected(Bundle connectionHint) { 90 if (Log.isLoggable(TAG, Log.DEBUG)) { 91 Log.d(TAG, "onConnected: " + connectionHint); 92 } 93 } 94 95 @Override 96 public void onConnectionSuspended(int cause) { 97 if (Log.isLoggable(TAG, Log.DEBUG)) { 98 Log.d(TAG, "onConnectionSuspended: " + cause); 99 } 100 } 101 }) 102 .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() { 103 @Override 104 public void onConnectionFailed(ConnectionResult result) { 105 if (Log.isLoggable(TAG, Log.DEBUG)) { 106 Log.d(TAG, "onConnectionFailed: " + result); 107 } 108 } 109 }) 110 .addApi(Wearable.API) 111 .build(); 112 } 113 114 @Override 115 protected void onStart() { 116 super.onStart(); 117 mGoogleApiClient.connect(); 118 } 119 120 @Override 121 protected void onStop() { 122 if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { 123 mGoogleApiClient.disconnect(); 124 } 125 super.onStop(); 126 } 127 128 @Override // WearableListView.ClickListener 129 public void onClick(WearableListView.ViewHolder viewHolder) { 130 ColorItemViewHolder colorItemViewHolder = (ColorItemViewHolder) viewHolder; 131 updateConfigDataItem(colorItemViewHolder.mColorItem.getColor()); 132 finish(); 133 } 134 135 @Override // WearableListView.ClickListener 136 public void onTopEmptyRegionClick() {} 137 138 @Override // WearableListView.OnScrollListener 139 public void onScroll(int scroll) {} 140 141 @Override // WearableListView.OnScrollListener 142 public void onAbsoluteScrollChange(int scroll) { 143 float newTranslation = Math.min(-scroll, 0); 144 mHeader.setTranslationY(newTranslation); 145 } 146 147 @Override // WearableListView.OnScrollListener 148 public void onScrollStateChanged(int scrollState) {} 149 150 @Override // WearableListView.OnScrollListener 151 public void onCentralPositionChanged(int centralPosition) {} 152 153 private void updateConfigDataItem(final int backgroundColor) { 154 DataMap configKeysToOverwrite = new DataMap(); 155 configKeysToOverwrite.putInt(DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR, 156 backgroundColor); 157 DigitalWatchFaceUtil.overwriteKeysInConfigDataMap(mGoogleApiClient, configKeysToOverwrite); 158 } 159 160 private class ColorListAdapter extends WearableListView.Adapter { 161 private final String[] mColors; 162 163 public ColorListAdapter(String[] colors) { 164 mColors = colors; 165 } 166 167 @Override 168 public ColorItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 169 return new ColorItemViewHolder(new ColorItem(parent.getContext())); 170 } 171 172 @Override 173 public void onBindViewHolder(WearableListView.ViewHolder holder, int position) { 174 ColorItemViewHolder colorItemViewHolder = (ColorItemViewHolder) holder; 175 String colorName = mColors[position]; 176 colorItemViewHolder.mColorItem.setColor(colorName); 177 178 RecyclerView.LayoutParams layoutParams = 179 new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 180 ViewGroup.LayoutParams.WRAP_CONTENT); 181 int colorPickerItemMargin = (int) getResources() 182 .getDimension(R.dimen.digital_config_color_picker_item_margin); 183 // Add margins to first and last item to make it possible for user to tap on them. 184 if (position == 0) { 185 layoutParams.setMargins(0, colorPickerItemMargin, 0, 0); 186 } else if (position == mColors.length - 1) { 187 layoutParams.setMargins(0, 0, 0, colorPickerItemMargin); 188 } else { 189 layoutParams.setMargins(0, 0, 0, 0); 190 } 191 colorItemViewHolder.itemView.setLayoutParams(layoutParams); 192 } 193 194 @Override 195 public int getItemCount() { 196 return mColors.length; 197 } 198 } 199 200 /** The layout of a color item including image and label. */ 201 private static class ColorItem extends LinearLayout implements 202 WearableListView.OnCenterProximityListener { 203 /** The duration of the expand/shrink animation. */ 204 private static final int ANIMATION_DURATION_MS = 150; 205 /** The ratio for the size of a circle in shrink state. */ 206 private static final float SHRINK_CIRCLE_RATIO = .75f; 207 208 private static final float SHRINK_LABEL_ALPHA = .5f; 209 private static final float EXPAND_LABEL_ALPHA = 1f; 210 211 private final TextView mLabel; 212 private final CircledImageView mColor; 213 214 private final float mExpandCircleRadius; 215 private final float mShrinkCircleRadius; 216 217 private final ObjectAnimator mExpandCircleAnimator; 218 private final ObjectAnimator mExpandLabelAnimator; 219 private final AnimatorSet mExpandAnimator; 220 221 private final ObjectAnimator mShrinkCircleAnimator; 222 private final ObjectAnimator mShrinkLabelAnimator; 223 private final AnimatorSet mShrinkAnimator; 224 225 public ColorItem(Context context) { 226 super(context); 227 View.inflate(context, R.layout.digital_color_picker_item, this); 228 229 mLabel = (TextView) findViewById(R.id.label); 230 mColor = (CircledImageView) findViewById(R.id.color); 231 232 mExpandCircleRadius = mColor.getCircleRadius(); 233 mShrinkCircleRadius = mExpandCircleRadius * SHRINK_CIRCLE_RATIO; 234 235 mShrinkCircleAnimator = ObjectAnimator.ofFloat(mColor, "circleRadius", 236 mExpandCircleRadius, mShrinkCircleRadius); 237 mShrinkLabelAnimator = ObjectAnimator.ofFloat(mLabel, "alpha", 238 EXPAND_LABEL_ALPHA, SHRINK_LABEL_ALPHA); 239 mShrinkAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS); 240 mShrinkAnimator.playTogether(mShrinkCircleAnimator, mShrinkLabelAnimator); 241 242 mExpandCircleAnimator = ObjectAnimator.ofFloat(mColor, "circleRadius", 243 mShrinkCircleRadius, mExpandCircleRadius); 244 mExpandLabelAnimator = ObjectAnimator.ofFloat(mLabel, "alpha", 245 SHRINK_LABEL_ALPHA, EXPAND_LABEL_ALPHA); 246 mExpandAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS); 247 mExpandAnimator.playTogether(mExpandCircleAnimator, mExpandLabelAnimator); 248 } 249 250 @Override 251 public void onCenterPosition(boolean animate) { 252 if (animate) { 253 mShrinkAnimator.cancel(); 254 if (!mExpandAnimator.isRunning()) { 255 mExpandCircleAnimator.setFloatValues(mColor.getCircleRadius(), mExpandCircleRadius); 256 mExpandLabelAnimator.setFloatValues(mLabel.getAlpha(), EXPAND_LABEL_ALPHA); 257 mExpandAnimator.start(); 258 } 259 } else { 260 mExpandAnimator.cancel(); 261 mColor.setCircleRadius(mExpandCircleRadius); 262 mLabel.setAlpha(EXPAND_LABEL_ALPHA); 263 } 264 } 265 266 @Override 267 public void onNonCenterPosition(boolean animate) { 268 if (animate) { 269 mExpandAnimator.cancel(); 270 if (!mShrinkAnimator.isRunning()) { 271 mShrinkCircleAnimator.setFloatValues(mColor.getCircleRadius(), mShrinkCircleRadius); 272 mShrinkLabelAnimator.setFloatValues(mLabel.getAlpha(), SHRINK_LABEL_ALPHA); 273 mShrinkAnimator.start(); 274 } 275 } else { 276 mShrinkAnimator.cancel(); 277 mColor.setCircleRadius(mShrinkCircleRadius); 278 mLabel.setAlpha(SHRINK_LABEL_ALPHA); 279 } 280 } 281 282 private void setColor(String colorName) { 283 mLabel.setText(colorName); 284 mColor.setCircleColor(Color.parseColor(colorName)); 285 } 286 287 private int getColor() { 288 return mColor.getDefaultCircleColor(); 289 } 290 } 291 292 private static class ColorItemViewHolder extends WearableListView.ViewHolder { 293 private final ColorItem mColorItem; 294 295 public ColorItemViewHolder(ColorItem colorItem) { 296 super(colorItem); 297 mColorItem = colorItem; 298 } 299 } 300 } 301