SettingsLayoutFragment.java revision 01a5b81394670c6c5c2011673ed48c4fee1cf77b
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 17package com.android.tv.settings.dialog; 18 19import android.animation.Animator; 20import android.animation.AnimatorInflater; 21import android.animation.AnimatorListenerAdapter; 22import android.animation.AnimatorSet; 23import android.animation.ObjectAnimator; 24import android.animation.TimeInterpolator; 25import android.animation.ValueAnimator; 26import android.app.Activity; 27import android.app.Fragment; 28import android.app.FragmentManager; 29import android.app.FragmentTransaction; 30import android.content.Context; 31import android.content.Intent; 32import android.content.pm.PackageManager; 33import android.content.res.Resources; 34import android.graphics.Bitmap; 35import android.graphics.Color; 36import android.graphics.drawable.ColorDrawable; 37import android.graphics.drawable.Drawable; 38import android.net.Uri; 39import android.os.Bundle; 40import android.os.Handler; 41import android.os.Parcel; 42import android.os.Parcelable; 43import android.support.v17.leanback.R; 44import android.support.v17.leanback.widget.VerticalGridView; 45import android.support.v7.widget.RecyclerView; 46import android.util.Log; 47import android.view.LayoutInflater; 48import android.view.View; 49import android.view.ViewGroup; 50import android.view.ViewPropertyAnimator; 51import android.view.ViewTreeObserver; 52import android.view.ViewGroup.LayoutParams; 53import android.view.animation.DecelerateInterpolator; 54import android.view.animation.Interpolator; 55import android.widget.ImageView; 56import android.widget.RelativeLayout; 57import android.widget.TextView; 58 59import java.util.ArrayList; 60import java.util.List; 61 62import com.android.tv.settings.dialog.Layout; 63import com.android.tv.settings.util.AccessibilityHelper; 64 65/** 66 * Displays content on the left and actions on the right. 67 */ 68public class SettingsLayoutFragment extends Fragment implements Layout.LayoutNodeRefreshListener { 69 70 private static final String TAG_LEAN_BACK_DIALOG_FRAGMENT = "leanBackSettingsLayoutFragment"; 71 private static final String EXTRA_CONTENT_TITLE = "title"; 72 private static final String EXTRA_CONTENT_BREADCRUMB = "breadcrumb"; 73 private static final String EXTRA_CONTENT_DESCRIPTION = "description"; 74 private static final String EXTRA_CONTENT_ICON = "icon"; 75 private static final String EXTRA_CONTENT_ICON_URI = "iconUri"; 76 private static final String EXTRA_CONTENT_ICON_BITMAP = "iconBitmap"; 77 private static final String EXTRA_CONTENT_ICON_BACKGROUND = "iconBackground"; 78 private static final String EXTRA_ACTION_NAME = "name"; 79 private static final String EXTRA_ACTION_LAYOUT = "layout"; 80 private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex"; 81 private static final String EXTRA_ENTRY_TRANSITION_PERFORMED = "entryTransitionPerformed"; 82 private static final int ANIMATION_FRAGMENT_ENTER = 1; 83 private static final int ANIMATION_FRAGMENT_EXIT = 2; 84 private static final int ANIMATION_FRAGMENT_ENTER_POP = 3; 85 private static final int ANIMATION_FRAGMENT_EXIT_POP = 4; 86 private static final float WINDOW_ALIGNMENT_OFFSET_PERCENT = 50f; 87 private static final float FADE_IN_ALPHA_START = 0f; 88 private static final float FADE_IN_ALPHA_FINISH = 1f; 89 private static final float SLIDE_OUT_ANIMATOR_LEFT = 0f; 90 private static final float SLIDE_OUT_ANIMATOR_RIGHT = 200f; 91 private static final float SLIDE_OUT_ANIMATOR_START_ALPHA = 0f; 92 private static final float SLIDE_OUT_ANIMATOR_END_ALPHA = 1f; 93 94 public interface Listener { 95 void onActionClicked(Layout.Action action); 96 }; 97 98 /** 99 * Builds a SettingsLayoutFragment object. 100 */ 101 public static class Builder { 102 103 private String mContentTitle; 104 private String mContentBreadcrumb; 105 private String mContentDescription; 106 private Drawable mIcon; 107 private Uri mIconUri; 108 private Bitmap mIconBitmap; 109 private int mIconBackgroundColor = Color.TRANSPARENT; 110 private Layout mLayout; 111 private String mName; 112 113 public SettingsLayoutFragment build() { 114 SettingsLayoutFragment fragment = new SettingsLayoutFragment(); 115 Bundle args = new Bundle(); 116 args.putString(EXTRA_CONTENT_TITLE, mContentTitle); 117 args.putString(EXTRA_CONTENT_BREADCRUMB, mContentBreadcrumb); 118 args.putString(EXTRA_CONTENT_DESCRIPTION, mContentDescription); 119 //args.putParcelable(EXTRA_CONTENT_ICON, mIcon); 120 fragment.mIcon = mIcon; 121 args.putParcelable(EXTRA_CONTENT_ICON_URI, mIconUri); 122 args.putParcelable(EXTRA_CONTENT_ICON_BITMAP, mIconBitmap); 123 args.putInt(EXTRA_CONTENT_ICON_BACKGROUND, mIconBackgroundColor); 124 args.putParcelable(EXTRA_ACTION_LAYOUT, mLayout); 125 mLayout.setRefreshViewListener(fragment); 126 args.putString(EXTRA_ACTION_NAME, mName); 127 fragment.setArguments(args); 128 return fragment; 129 } 130 131 public Builder title(String title) { 132 mContentTitle = title; 133 return this; 134 } 135 136 public Builder breadcrumb(String breadcrumb) { 137 mContentBreadcrumb = breadcrumb; 138 return this; 139 } 140 141 public Builder description(String description) { 142 mContentDescription = description; 143 return this; 144 } 145 146 public Builder icon(Drawable icon) { 147 mIcon = icon; 148 return this; 149 } 150 151 public Builder iconUri(Uri iconUri) { 152 mIconUri = iconUri; 153 return this; 154 } 155 156 public Builder iconBitmap(Bitmap iconBitmap) { 157 mIconBitmap = iconBitmap; 158 return this; 159 } 160 161 public Builder iconBackgroundColor(int iconBackgroundColor) { 162 mIconBackgroundColor = iconBackgroundColor; 163 return this; 164 } 165 166 public Builder layout(Layout layout) { 167 mLayout = layout; 168 return this; 169 } 170 171 public Builder name(String name) { 172 mName = name; 173 return this; 174 } 175 } 176 177 public static void add(FragmentManager fm, SettingsLayoutFragment f) { 178 boolean hasDialog = fm.findFragmentByTag(TAG_LEAN_BACK_DIALOG_FRAGMENT) != null; 179 FragmentTransaction ft = fm.beginTransaction(); 180 181 if (hasDialog) { 182 ft.setCustomAnimations(ANIMATION_FRAGMENT_ENTER, 183 ANIMATION_FRAGMENT_EXIT, ANIMATION_FRAGMENT_ENTER_POP, 184 ANIMATION_FRAGMENT_EXIT_POP); 185 ft.addToBackStack(null); 186 } 187 ft.replace(android.R.id.content, f, TAG_LEAN_BACK_DIALOG_FRAGMENT).commit(); 188 } 189 190 private SettingsLayoutAdapter mAdapter; 191 private VerticalGridView mListView; 192 private String mTitle; 193 private String mBreadcrumb; 194 private String mDescription; 195 private Drawable mIcon; 196 private Uri mIconUri; 197 private Bitmap mIconBitmap; 198 private int mIconBackgroundColor = Color.TRANSPARENT; 199 private Layout mLayout; 200 private String mName; 201 private int mSelectedIndex = -1; 202 private boolean mEntryTransitionPerformed; 203 private boolean mIntroAnimationInProgress; 204 private int mAnimateInDuration; 205 private int mAnimateDelay; 206 private int mSecondaryAnimateDelay; 207 private int mSlideInStagger; 208 private int mSlideInDistance; 209 private Handler refreshViewHandler = new Handler(); 210 211 private final Runnable mRefreshViewRunnable = new Runnable() { 212 @Override 213 public void run() { 214 mLayout.setSelectedIndex(mListView.getSelectedPosition()); 215 mLayout.reloadLayoutRows(); 216 mAdapter.setLayoutRows(mLayout.getLayoutRows()); 217 mAdapter.setNoAnimateMode(); 218 mAdapter.notifyDataSetChanged(); 219 mListView.setSelectedPositionSmooth(mLayout.getSelectedIndex()); 220 } 221 }; 222 223 private final SettingsLayoutAdapter.Listener mLayoutViewRowClicked = 224 new SettingsLayoutAdapter.Listener() { 225 @Override 226 public void onRowClicked(Layout.LayoutRow layoutRow) { 227 onRowViewClicked(layoutRow); 228 } 229 }; 230 231 private final SettingsLayoutAdapter.OnFocusListener mLayoutViewOnFocus = 232 new SettingsLayoutAdapter.OnFocusListener() { 233 @Override 234 public void onActionFocused(Layout.LayoutRow action) { 235 if (getActivity() instanceof SettingsLayoutAdapter.OnFocusListener) { 236 SettingsLayoutAdapter.OnFocusListener listener = 237 (SettingsLayoutAdapter.OnFocusListener) getActivity(); 238 listener.onActionFocused(action); 239 } 240 } 241 }; 242 243 @Override 244 public void onCreate(Bundle savedInstanceState) { 245 android.util.Log.v("SettingsLayoutFragment", "onCreate"); 246 super.onCreate(savedInstanceState); 247 Bundle state = (savedInstanceState != null) ? savedInstanceState : getArguments(); 248 mTitle = state.getString(EXTRA_CONTENT_TITLE); 249 mBreadcrumb = state.getString(EXTRA_CONTENT_BREADCRUMB); 250 mDescription = state.getString(EXTRA_CONTENT_DESCRIPTION); 251 //mIcon = state.getParcelable(EXTRA_CONTENT_ICON_RESOURCE_ID, 0); 252 mIconUri = state.getParcelable(EXTRA_CONTENT_ICON_URI); 253 mIconBitmap = state.getParcelable(EXTRA_CONTENT_ICON_BITMAP); 254 mIconBackgroundColor = state.getInt(EXTRA_CONTENT_ICON_BACKGROUND, Color.TRANSPARENT); 255 mLayout = state.getParcelable(EXTRA_ACTION_LAYOUT); 256 mName = state.getString(EXTRA_ACTION_NAME); 257 mSelectedIndex = state.getInt(EXTRA_ACTION_SELECTED_INDEX, -1); 258 mEntryTransitionPerformed = state.getBoolean(EXTRA_ENTRY_TRANSITION_PERFORMED, false); 259 } 260 261 @Override 262 public View onCreateView(LayoutInflater inflater, ViewGroup container, 263 Bundle savedInstanceState) { 264 265 View v = inflater.inflate(R.layout.lb_dialog_fragment, container, false); 266 267 View contentContainer = v.findViewById(R.id.content_fragment); 268 View content = inflater.inflate(R.layout.lb_dialog_content, container, false); 269 ((ViewGroup) contentContainer).addView(content); 270 initializeContentView(content); 271 v.setTag(R.id.content_fragment, content); 272 273 View actionContainer = v.findViewById(R.id.action_fragment); 274 View action = inflater.inflate(R.layout.lb_dialog_action_list, container, false); 275 ((ViewGroup) actionContainer).addView(action); 276 setActionView(action); 277 v.setTag(R.id.action_fragment, action); 278 279 Resources res = getActivity().getResources(); 280 mAnimateInDuration = res.getInteger(R.integer.animate_in_duration); 281 mAnimateDelay = res.getInteger(R.integer.animate_delay); 282 mSecondaryAnimateDelay = res.getInteger(R.integer.secondary_animate_delay); 283 mSlideInStagger = res.getInteger(R.integer.slide_in_stagger); 284 mSlideInDistance = res.getInteger(R.integer.slide_in_distance); 285 286 return v; 287 } 288 289 @Override 290 public void onSaveInstanceState(Bundle outState) { 291 super.onSaveInstanceState(outState); 292 outState.putString(EXTRA_CONTENT_TITLE, mTitle); 293 outState.putString(EXTRA_CONTENT_BREADCRUMB, mBreadcrumb); 294 outState.putString(EXTRA_CONTENT_DESCRIPTION, mDescription); 295 //outState.putInt(EXTRA_CONTENT_ICON_RESOURCE_ID, mIconResourceId); 296 outState.putParcelable(EXTRA_CONTENT_ICON_URI, mIconUri); 297 outState.putParcelable(EXTRA_CONTENT_ICON_BITMAP, mIconBitmap); 298 outState.putInt(EXTRA_CONTENT_ICON_BACKGROUND, mIconBackgroundColor); 299 outState.putParcelable(EXTRA_ACTION_LAYOUT, mLayout); 300 outState.putInt(EXTRA_ACTION_SELECTED_INDEX, 301 (mListView != null) ? mListView.getSelectedPosition() : -1); 302 outState.putString(EXTRA_ACTION_NAME, mName); 303 outState.putBoolean(EXTRA_ENTRY_TRANSITION_PERFORMED, mEntryTransitionPerformed); 304 } 305 306 @Override 307 public void onStart() { 308 super.onStart(); 309 if (!mEntryTransitionPerformed) { 310 mEntryTransitionPerformed = true; 311 performEntryTransition(); 312 } 313 } 314 315 // TODO refactor to get this call as the result of a callback from the Layout. 316 private void updateViews() { 317 View dialogView = getView(); 318 View contentView = (View) dialogView.getTag(R.id.content_fragment); 319 320 mBreadcrumb = mLayout.getBreadcrumb(); 321 TextView breadcrumbView = (TextView) contentView.getTag(R.id.breadcrumb); 322 breadcrumbView.setText(mBreadcrumb); 323 324 mTitle = mLayout.getTitle(); 325 TextView titleView = (TextView) contentView.getTag(R.id.title); 326 titleView.setText(mTitle); 327 328 mDescription = mLayout.getDescription(); 329 TextView descriptionView = (TextView) contentView.getTag(R.id.description); 330 descriptionView.setText(mDescription); 331 332 mAdapter.setLayoutRows(mLayout.getLayoutRows()); 333 mAdapter.notifyDataSetChanged(); 334 mAdapter.setFocusListenerEnabled(false); 335 mListView.setSelectedPositionSmooth(mLayout.getSelectedIndex()); 336 mAdapter.setFocusListenerEnabled(true); 337 } 338 339 public void setIcon(int resId) { 340 View dialogView = getView(); 341 View contentView = (View) dialogView.getTag(R.id.content_fragment); 342 ImageView iconView = (ImageView) contentView.findViewById(R.id.icon); 343 if (iconView != null) { 344 iconView.setImageResource(resId); 345 } 346 } 347 348 /** 349 * Notification that a part of the model antecedent to the visibile view has changed. 350 */ 351 @Override 352 public void onRefreshView() { 353 refreshViewHandler.removeCallbacks(mRefreshViewRunnable); 354 refreshViewHandler.post(mRefreshViewRunnable); 355 } 356 357 /** 358 * Return the currently selected node. The return value may be null, if this is called before 359 * the layout has been rendered for the first time. Clients should check the return value 360 * before using. 361 */ 362 @Override 363 public Layout.Node getSelectedNode() { 364 int index = mListView.getSelectedPosition(); 365 ArrayList<Layout.LayoutRow> layoutRows = mLayout.getLayoutRows(); 366 if (index < layoutRows.size()) { 367 return layoutRows.get(index).getNode(); 368 } else { 369 return null; 370 } 371 } 372 373 /** 374 * Process forward key press. 375 */ 376 void onRowViewClicked(Layout.LayoutRow layoutRow) { 377 if (layoutRow.isGoBack()) { 378 onBackPressed(); 379 } else { 380 Layout.Action action = layoutRow.getUserAction(); 381 if (action != null) { 382 Listener actionListener = (Listener) getActivity(); 383 if (actionListener != null) { 384 actionListener.onActionClicked(action); 385 } 386 } else if (mLayout.onClickNavigate(layoutRow)) { 387 mLayout.setParentSelectedIndex(mListView.getSelectedPosition()); 388 updateViews(); 389 } 390 } 391 } 392 393 /** 394 * Process back key press. 395 */ 396 public boolean onBackPressed() { 397 if (mLayout.goBack()) { 398 updateViews(); 399 return true; 400 } else { 401 return false; 402 } 403 } 404 405 /** 406 * Client has requested header with {@param title} be selected. If there is no such header 407 * return to the first row. 408 */ 409 protected void goBackToTitle(String title) { 410 mLayout.goToTitle(title); 411 updateViews(); 412 } 413 414 @Override 415 public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) { 416 View dialogView = getView(); 417 View contentView = (View) dialogView.getTag(R.id.content_fragment); 418 View actionView = (View) dialogView.getTag(R.id.action_fragment); 419 View actionContainerView = dialogView.findViewById(R.id.action_fragment); 420 View titleView = (View) contentView.getTag(R.id.title); 421 View breadcrumbView = (View) contentView.getTag(R.id.breadcrumb); 422 View descriptionView = (View) contentView.getTag(R.id.description); 423 View iconView = (View) contentView.getTag(R.id.icon); 424 View listView = (View) actionView.getTag(R.id.list); 425 View selectorView = (View) actionView.getTag(R.id.selector); 426 427 ArrayList<Animator> animators = new ArrayList<Animator>(); 428 429 switch (nextAnim) { 430 case ANIMATION_FRAGMENT_ENTER: 431 animators.add(createSlideLeftInAnimator(titleView)); 432 animators.add(createSlideLeftInAnimator(breadcrumbView)); 433 animators.add(createSlideLeftInAnimator(descriptionView)); 434 animators.add(createSlideLeftInAnimator(iconView)); 435 animators.add(createSlideLeftInAnimator(listView)); 436 animators.add(createSlideLeftInAnimator(selectorView)); 437 break; 438 case ANIMATION_FRAGMENT_EXIT: 439 animators.add(createSlideLeftOutAnimator(titleView)); 440 animators.add(createSlideLeftOutAnimator(breadcrumbView)); 441 animators.add(createSlideLeftOutAnimator(descriptionView)); 442 animators.add(createSlideLeftOutAnimator(iconView)); 443 animators.add(createSlideLeftOutAnimator(listView)); 444 animators.add(createSlideLeftOutAnimator(selectorView)); 445 animators.add(createFadeOutAnimator(actionContainerView)); 446 break; 447 case ANIMATION_FRAGMENT_ENTER_POP: 448 animators.add(createSlideRightInAnimator(titleView)); 449 animators.add(createSlideRightInAnimator(breadcrumbView)); 450 animators.add(createSlideRightInAnimator(descriptionView)); 451 animators.add(createSlideRightInAnimator(iconView)); 452 animators.add(createSlideRightInAnimator(listView)); 453 animators.add(createSlideRightInAnimator(selectorView)); 454 break; 455 case ANIMATION_FRAGMENT_EXIT_POP: 456 animators.add(createSlideRightOutAnimator(titleView)); 457 animators.add(createSlideRightOutAnimator(breadcrumbView)); 458 animators.add(createSlideRightOutAnimator(descriptionView)); 459 animators.add(createSlideRightOutAnimator(iconView)); 460 animators.add(createSlideRightOutAnimator(listView)); 461 animators.add(createSlideRightOutAnimator(selectorView)); 462 animators.add(createFadeOutAnimator(actionContainerView)); 463 break; 464 default: 465 return super.onCreateAnimator(transit, enter, nextAnim); 466 } 467 468 mEntryTransitionPerformed = true; 469 return createDummyAnimator(dialogView, animators); 470 } 471 472 /** 473 * Called when intro animation is finished. 474 * <p> 475 * If a subclass is going to alter the view, should wait until this is 476 * called. 477 */ 478 public void onIntroAnimationFinished() { 479 mIntroAnimationInProgress = false; 480 481 // Display the selector view. 482 View focusedChild = mListView.getFocusedChild(); 483 if (focusedChild != null) { 484 View actionView = (View) getView().getTag(R.id.action_fragment); 485 int height = focusedChild.getHeight (); 486 View selectorView = actionView.findViewById(R.id.selector); 487 LayoutParams lp = selectorView.getLayoutParams(); 488 lp.height = height; 489 selectorView.setLayoutParams(lp); 490 selectorView.setAlpha (1f); 491 } 492 } 493 494 public boolean isIntroAnimationInProgress() { 495 return mIntroAnimationInProgress; 496 } 497 498 private void initializeContentView(View content) { 499 TextView titleView = (TextView) content.findViewById(R.id.title); 500 TextView breadcrumbView = (TextView) content.findViewById(R.id.breadcrumb); 501 TextView descriptionView = (TextView) content.findViewById(R.id.description); 502 titleView.setText(mTitle); 503 breadcrumbView.setText(mBreadcrumb); 504 descriptionView.setText(mDescription); 505 final ImageView iconImageView = (ImageView) content.findViewById(R.id.icon); 506 iconImageView.setBackgroundColor(mIconBackgroundColor); 507 508 // Force text fields to be focusable when accessibility is enabled. 509 if (AccessibilityHelper.forceFocusableViews(getActivity())) { 510 titleView.setFocusable(true); 511 titleView.setFocusableInTouchMode(true); 512 descriptionView.setFocusable(true); 513 descriptionView.setFocusableInTouchMode(true); 514 breadcrumbView.setFocusable(true); 515 breadcrumbView.setFocusableInTouchMode(true); 516 } 517 518 if (mIcon != null) { 519 iconImageView.setImageDrawable(mIcon); 520 updateViewSize(iconImageView); 521 } else if (mIconBitmap != null) { 522 iconImageView.setImageBitmap(mIconBitmap); 523 updateViewSize(iconImageView); 524 } else if (mIconUri != null) { 525 iconImageView.setVisibility(View.INVISIBLE); 526 /* 527 528 BitmapDownloader bitmapDownloader = BitmapDownloader.getInstance( 529 content.getContext()); 530 mBitmapCallBack = new BitmapCallback() { 531 @Override 532 public void onBitmapRetrieved(Bitmap bitmap) { 533 if (bitmap != null) { 534 mIconBitmap = bitmap; 535 iconImageView.setVisibility(View.VISIBLE); 536 iconImageView.setImageBitmap(bitmap); 537 updateViewSize(iconImageView); 538 } 539 } 540 }; 541 542 bitmapDownloader.getBitmap(new BitmapWorkerOptions.Builder( 543 content.getContext()).resource(mIconUri) 544 .width(iconImageView.getLayoutParams().width).build(), 545 mBitmapCallBack); 546 */ 547 } else { 548 iconImageView.setVisibility(View.GONE); 549 } 550 551 content.setTag(R.id.title, titleView); 552 content.setTag(R.id.breadcrumb, breadcrumbView); 553 content.setTag(R.id.description, descriptionView); 554 content.setTag(R.id.icon, iconImageView); 555 } 556 557 private void setActionView(View action) { 558 mAdapter = new SettingsLayoutAdapter(mLayoutViewRowClicked, mLayoutViewOnFocus); 559 mAdapter.setLayoutRows(mLayout.getLayoutRows()); 560 if (action instanceof VerticalGridView) { 561 mListView = (VerticalGridView) action; 562 } else { 563 mListView = (VerticalGridView) action.findViewById(R.id.list); 564 if (mListView == null) { 565 throw new IllegalArgumentException("No ListView exists."); 566 } 567 mListView.setWindowAlignmentOffset(0); 568 mListView.setWindowAlignmentOffsetPercent(WINDOW_ALIGNMENT_OFFSET_PERCENT); 569 mListView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 570 View selectorView = action.findViewById(R.id.selector); 571 if (selectorView != null) { 572 mListView.setOnScrollListener(new SelectorAnimator(selectorView, mListView)); 573 } 574 } 575 576 mListView.requestFocusFromTouch(); 577 mListView.setAdapter(mAdapter); 578 int initialSelectedIndex; 579 if (mSelectedIndex >= 0 && mSelectedIndex < mLayout.getLayoutRows().size()) { 580 // "mSelectedIndex" is a valid index and so must have been initialized from a Bundle in 581 // the "onCreate" member and the only way it could be a valid index is if it was saved 582 // by "onSaveInstanceState" since it is initialized to "-1" (an invalid value) in the 583 // constructor. 584 initialSelectedIndex = mSelectedIndex; 585 } else { 586 // First time this fragment is being instantiated, i.e. did not reach here via the 587 // "onSaveInstanceState" route. Initialize the index from the starting index defined 588 // in the "Layout". 589 initialSelectedIndex = mLayout.getSelectedIndex(); 590 } 591 mListView.setSelectedPositionSmooth(initialSelectedIndex); 592 action.setTag(R.id.list, mListView); 593 action.setTag(R.id.selector, action.findViewById(R.id.selector)); 594 } 595 596 private void updateViewSize(ImageView iconView) { 597 int intrinsicWidth = iconView.getDrawable().getIntrinsicWidth(); 598 LayoutParams lp = iconView.getLayoutParams(); 599 if (intrinsicWidth > 0) { 600 lp.height = lp.width * iconView.getDrawable().getIntrinsicHeight() 601 / intrinsicWidth; 602 } else { 603 // If no intrinsic width, then just mke this a square. 604 lp.height = lp.width; 605 } 606 } 607 608 private void fadeIn(View v) { 609 ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha", FADE_IN_ALPHA_START, 610 FADE_IN_ALPHA_FINISH); 611 alphaAnimator.setDuration(v.getContext().getResources().getInteger( 612 android.R.integer.config_mediumAnimTime)); 613 alphaAnimator.start(); 614 } 615 616 private void performEntryTransition() { 617 final View dialogView = getView(); 618 final View contentView = (View) dialogView.getTag(R.id.content_fragment); 619 final View actionContainerView = dialogView.findViewById(R.id.action_fragment); 620 621 mIntroAnimationInProgress = true; 622 623 // Fade out the old activity. 624 getActivity().overridePendingTransition(0, R.anim.lb_dialog_fade_out); 625 626 int bgColor = contentView.getContext().getResources() 627 .getColor(R.color.lb_dialog_activity_background); 628 final ColorDrawable bgDrawable = new ColorDrawable(); 629 bgDrawable.setColor(bgColor); 630 bgDrawable.setAlpha(0); 631 dialogView.setBackground(bgDrawable); 632 dialogView.setVisibility(View.INVISIBLE); 633 634 // We need to defer the remainder of the animation preparation until the first layout has 635 // occurred, as we don't yet know the final location of the icon. 636 contentView.getViewTreeObserver().addOnGlobalLayoutListener( 637 new ViewTreeObserver.OnGlobalLayoutListener() { 638 @Override 639 public void onGlobalLayout() { 640 contentView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 641 // if we buildLayer() at this time, the texture is 642 // actually not created delay a little so we can make 643 // sure all hardware layer is created before animation, 644 // in that way we can avoid the jittering of start 645 // animation 646 contentView.postOnAnimationDelayed(mEntryAnimationRunnable, mAnimateDelay); 647 } 648 649 Runnable mEntryAnimationRunnable = new Runnable() { 650 @Override 651 public void run() { 652 if (!isAdded()) { 653 // We have been detached before this could run, so just bail. 654 return; 655 } 656 657 dialogView.setVisibility(View.VISIBLE); 658 659 // Fade in the activity background protection 660 ObjectAnimator oa = ObjectAnimator.ofInt(bgDrawable, "alpha", 255); 661 oa.setDuration(mAnimateInDuration); 662 oa.setStartDelay(mSecondaryAnimateDelay); 663 oa.setInterpolator(new DecelerateInterpolator(1.0f)); 664 oa.start(); 665 666 // Fade in and slide in the ContentFragment TextViews from the left. 667 prepareAndAnimateView((View) contentView.getTag(R.id.title), 668 -mSlideInDistance, false); 669 prepareAndAnimateView((View) contentView.getTag(R.id.breadcrumb), 670 -mSlideInDistance, false); 671 prepareAndAnimateView((View) contentView.getTag(R.id.description), 672 -mSlideInDistance, false); 673 674 // Fade in and slide in the ActionFragment from the right. 675 prepareAndAnimateView(actionContainerView, 676 actionContainerView.getMeasuredWidth(), false); 677 prepareAndAnimateView((View) contentView.getTag(R.id.icon), 678 -mSlideInDistance, true); 679 } 680 }; 681 }); 682 } 683 684 private void prepareAndAnimateView(final View v, float initTransX, 685 final boolean notifyAnimationFinished) { 686 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 687 v.buildLayer(); 688 v.setAlpha(0); 689 v.setTranslationX(initTransX); 690 v.animate().alpha(1f).translationX(0).setDuration(mAnimateInDuration) 691 .setStartDelay(mSecondaryAnimateDelay); 692 v.animate().setInterpolator(new DecelerateInterpolator(1.0f)); 693 v.animate().setListener(new AnimatorListenerAdapter() { 694 @Override 695 public void onAnimationEnd(Animator animation) { 696 v.setLayerType(View.LAYER_TYPE_NONE, null); 697 if (notifyAnimationFinished) { 698 onIntroAnimationFinished(); 699 } 700 } 701 }); 702 v.animate().start(); 703 } 704 705 private Animator createDummyAnimator(final View v, ArrayList<Animator> animators) { 706 final AnimatorSet animatorSet = new AnimatorSet(); 707 animatorSet.playTogether(animators); 708 return new UntargetableAnimatorSet(animatorSet); 709 } 710 711 private Animator createAnimator(View v, int resourceId) { 712 Animator animator = AnimatorInflater.loadAnimator(v.getContext(), resourceId); 713 animator.setTarget(v); 714 return animator; 715 } 716 717 private Animator createSlideLeftOutAnimator(View v) { 718 return createTranslateAlphaAnimator(v, SLIDE_OUT_ANIMATOR_LEFT, -SLIDE_OUT_ANIMATOR_RIGHT, 719 SLIDE_OUT_ANIMATOR_END_ALPHA, SLIDE_OUT_ANIMATOR_START_ALPHA); 720 } 721 722 private Animator createSlideLeftInAnimator(View v) { 723 return createTranslateAlphaAnimator(v, SLIDE_OUT_ANIMATOR_RIGHT, SLIDE_OUT_ANIMATOR_LEFT, 724 SLIDE_OUT_ANIMATOR_START_ALPHA, SLIDE_OUT_ANIMATOR_END_ALPHA); 725 } 726 727 private Animator createSlideRightInAnimator(View v) { 728 return createTranslateAlphaAnimator(v, -SLIDE_OUT_ANIMATOR_RIGHT, SLIDE_OUT_ANIMATOR_LEFT, 729 SLIDE_OUT_ANIMATOR_START_ALPHA, SLIDE_OUT_ANIMATOR_END_ALPHA); 730 } 731 732 private Animator createSlideRightOutAnimator(View v) { 733 return createTranslateAlphaAnimator(v, SLIDE_OUT_ANIMATOR_LEFT, SLIDE_OUT_ANIMATOR_RIGHT, 734 SLIDE_OUT_ANIMATOR_END_ALPHA, SLIDE_OUT_ANIMATOR_START_ALPHA); 735 } 736 737 private Animator createFadeOutAnimator(View v) { 738 return createAlphaAnimator(v, SLIDE_OUT_ANIMATOR_END_ALPHA, SLIDE_OUT_ANIMATOR_START_ALPHA); 739 } 740 741 private Animator createTranslateAlphaAnimator(View v, float fromTranslateX, float toTranslateX, 742 float fromAlpha, float toAlpha) { 743 ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(v, "translationX", fromTranslateX, 744 toTranslateX); 745 translateAnimator.setDuration( 746 getResources().getInteger(android.R.integer.config_longAnimTime)); 747 Animator alphaAnimator = createAlphaAnimator(v, fromAlpha, toAlpha); 748 AnimatorSet animatorSet = new AnimatorSet(); 749 animatorSet.play(translateAnimator).with(alphaAnimator); 750 return animatorSet; 751 } 752 753 private Animator createAlphaAnimator(View v, float fromAlpha, float toAlpha) { 754 ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha", fromAlpha, toAlpha); 755 alphaAnimator.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime)); 756 return alphaAnimator; 757 } 758 759 private static class SelectorAnimator extends RecyclerView.OnScrollListener { 760 761 private final View mSelectorView; 762 private final ViewGroup mParentView; 763 private final int mAnimationDuration; 764 private volatile boolean mFadedOut = true; 765 766 SelectorAnimator(View selectorView, ViewGroup parentView) { 767 mSelectorView = selectorView; 768 mParentView = parentView; 769 mAnimationDuration = selectorView.getContext() 770 .getResources().getInteger(R.integer.lb_dialog_animation_duration); 771 } 772 773 /** 774 * We want to fade in the selector if we've stopped scrolling on it. If we're scrolling, we 775 * want to ensure to dim the selector if we haven't already. We dim the last highlighted 776 * view so that while a user is scrolling, nothing is highlighted. 777 */ 778 @Override 779 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 780 if (newState == RecyclerView.SCROLL_STATE_IDLE) { 781 // The selector starts with a height of 0. In order to scale up from 0 we first 782 // need the set the height to 1 and scale form there. 783 int selectorHeight = mSelectorView.getHeight(); 784 if (selectorHeight == 0) { 785 LayoutParams lp = mSelectorView.getLayoutParams(); 786 lp.height = selectorHeight = mSelectorView.getContext().getResources() 787 .getDimensionPixelSize(R.dimen.lb_action_fragment_selector_min_height); 788 mSelectorView.setLayoutParams(lp); 789 } 790 View focusedChild = mParentView.getFocusedChild(); 791 if (focusedChild != null) { 792 float scaleY = (float) focusedChild.getHeight() / selectorHeight; 793 ViewPropertyAnimator animation = mSelectorView.animate() 794 .alpha(1f) 795 .setDuration(mAnimationDuration) 796 .setInterpolator(new DecelerateInterpolator(2f)); 797 if (mFadedOut) { 798 // Selector is completely faded out, so we can just scale before fading in. 799 mSelectorView.setScaleY(scaleY); 800 } else { 801 // Selector is not faded out, so we must animate the scale as we fade in. 802 animation.scaleY(scaleY); 803 } 804 animation.start(); 805 } 806 } else { 807 mSelectorView.animate() 808 .alpha(0f) 809 .setDuration(mAnimationDuration) 810 .setInterpolator(new DecelerateInterpolator(2f)) 811 .start(); 812 } 813 } 814 } 815 816 private static class UntargetableAnimatorSet extends Animator { 817 818 private final AnimatorSet mAnimatorSet; 819 820 UntargetableAnimatorSet(AnimatorSet animatorSet) { 821 mAnimatorSet = animatorSet; 822 } 823 824 @Override 825 public void addListener(Animator.AnimatorListener listener) { 826 mAnimatorSet.addListener(listener); 827 } 828 829 @Override 830 public void cancel() { 831 mAnimatorSet.cancel(); 832 } 833 834 @Override 835 public Animator clone() { 836 return mAnimatorSet.clone(); 837 } 838 839 @Override 840 public void end() { 841 mAnimatorSet.end(); 842 } 843 844 @Override 845 public long getDuration() { 846 return mAnimatorSet.getDuration(); 847 } 848 849 @Override 850 public ArrayList<Animator.AnimatorListener> getListeners() { 851 return mAnimatorSet.getListeners(); 852 } 853 854 @Override 855 public long getStartDelay() { 856 return mAnimatorSet.getStartDelay(); 857 } 858 859 @Override 860 public boolean isRunning() { 861 return mAnimatorSet.isRunning(); 862 } 863 864 @Override 865 public boolean isStarted() { 866 return mAnimatorSet.isStarted(); 867 } 868 869 @Override 870 public void removeAllListeners() { 871 mAnimatorSet.removeAllListeners(); 872 } 873 874 @Override 875 public void removeListener(Animator.AnimatorListener listener) { 876 mAnimatorSet.removeListener(listener); 877 } 878 879 @Override 880 public Animator setDuration(long duration) { 881 return mAnimatorSet.setDuration(duration); 882 } 883 884 @Override 885 public void setInterpolator(TimeInterpolator value) { 886 mAnimatorSet.setInterpolator(value); 887 } 888 889 @Override 890 public void setStartDelay(long startDelay) { 891 mAnimatorSet.setStartDelay(startDelay); 892 } 893 894 @Override 895 public void setTarget(Object target) { 896 // ignore 897 } 898 899 @Override 900 public void setupEndValues() { 901 mAnimatorSet.setupEndValues(); 902 } 903 904 @Override 905 public void setupStartValues() { 906 mAnimatorSet.setupStartValues(); 907 } 908 909 @Override 910 public void start() { 911 mAnimatorSet.start(); 912 } 913 } 914 915} 916