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