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.old; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.app.Activity; 23import android.content.Intent; 24import android.graphics.drawable.ColorDrawable; 25import android.support.v4.view.ViewCompat; 26import android.view.View; 27import android.view.ViewGroup; 28import android.view.ViewTreeObserver; 29import android.view.animation.DecelerateInterpolator; 30import android.view.animation.Interpolator; 31import android.widget.ImageView; 32import android.widget.TextView; 33 34import com.android.tv.settings.R; 35import com.android.tv.settings.widget.FrameLayoutWithShadows; 36 37/** 38 * This class exists to make extending both v4 DialogFragments and regular DialogFragments easy 39 */ 40public class BaseDialogFragment { 41 42 private static final int ANIMATE_IN_DURATION = 250; 43 private static final int ANIMATE_DELAY = 550; 44 private static final int SLIDE_IN_DISTANCE = 120; 45 46 public static final String TAG_CONTENT = "content"; 47 public static final String TAG_ACTION = "action"; 48 49 public int mContentAreaId = R.id.content_fragment; 50 public int mActionAreaId = R.id.action_fragment; 51 52 private FrameLayoutWithShadows mShadowLayer; 53 public boolean mFirstOnStart = true; 54 private boolean mIntroAnimationInProgress = false; 55 56 private final LiteFragment mFragment; 57 58 // Related to activity entry transition 59 private ColorDrawable mBgDrawable = new ColorDrawable(); 60 61 public BaseDialogFragment(LiteFragment fragment) { 62 mFragment = fragment; 63 } 64 65 public void onActionClicked(Activity activity, Action action) { 66 if (activity instanceof ActionAdapter.Listener) { 67 ((ActionAdapter.Listener) activity).onActionClicked(action); 68 } else { 69 Intent intent = action.getIntent(); 70 if (intent != null) { 71 activity.startActivity(intent); 72 activity.finish(); 73 } 74 } 75 } 76 77 public void disableEntryAnimation() { 78 mFirstOnStart = false; 79 } 80 81 /** 82 * This method sets the layout property of this class. <br/> 83 * Activities extending {@link DialogFragment} should call this method 84 * before calling {@link #onCreate(Bundle)} if they want to have a 85 * custom view. 86 * 87 * @param contentAreaId id of the content area 88 * @param actionAreaId id of the action area 89 */ 90 public void setLayoutProperties(int contentAreaId, int actionAreaId) { 91 mContentAreaId = contentAreaId; 92 mActionAreaId = actionAreaId; 93 } 94 95 public void performEntryTransition(final Activity activity, final ViewGroup contentView, 96 final ImageView icon, final TextView title, 97 final TextView description, final TextView breadcrumb) { 98 // Pull out the root layout of the dialog and set the background drawable, to be 99 // faded in during the transition. 100 final ViewGroup twoPane = (ViewGroup) contentView.getChildAt(0); 101 twoPane.setVisibility(View.INVISIBLE); 102 103 // If the appropriate data is embedded in the intent and there is an icon specified 104 // in the content fragment, we animate the icon from its initial position to the final 105 // position. Otherwise, we perform a simpler transition in which the ActionFragment 106 // slides in and the ContentFragment text fields slide in. 107 mIntroAnimationInProgress = true; 108 109 // Fade out the old activity, and hard cut the new activity. 110 activity.overridePendingTransition(R.anim.hard_cut_in, R.anim.fade_out); 111 112 int bgColor = mFragment.getActivity().getColor(R.color.dialog_activity_background); 113 mBgDrawable.setColor(bgColor); 114 mBgDrawable.setAlpha(0); 115 twoPane.setBackground(mBgDrawable); 116 117 // If we're animating the icon, we create a new ImageView in which to place the embedded 118 // bitmap. We place it in the root layout to match its location in the previous activity. 119 mShadowLayer = (FrameLayoutWithShadows) twoPane.findViewById(R.id.shadow_layout); 120 121 // We need to defer the remainder of the animation preparation until the first 122 // layout has occurred, as we don't yet know the final location of the icon. 123 twoPane.getViewTreeObserver().addOnGlobalLayoutListener( 124 new ViewTreeObserver.OnGlobalLayoutListener() { 125 @Override 126 public void onGlobalLayout() { 127 twoPane.getViewTreeObserver().removeOnGlobalLayoutListener(this); 128 // if we buildLayer() at this time, the texture is actually not created 129 // delay a little so we can make sure all hardware layer is created before 130 // animation, in that way we can avoid the jittering of start animation 131 twoPane.postOnAnimationDelayed(mEntryAnimationRunnable, ANIMATE_DELAY); 132 } 133 134 final Runnable mEntryAnimationRunnable = new Runnable() { 135 @Override 136 public void run() { 137 if (!mFragment.isAdded()) { 138 // We have been detached before this could run, so just bail 139 return; 140 } 141 142 twoPane.setVisibility(View.VISIBLE); 143 final int secondaryDelay = SLIDE_IN_DISTANCE; 144 145 // Fade in the activity background protection 146 ObjectAnimator oa = ObjectAnimator.ofInt(mBgDrawable, "alpha", 255); 147 oa.setDuration(ANIMATE_IN_DURATION); 148 oa.setStartDelay(secondaryDelay); 149 oa.setInterpolator(new DecelerateInterpolator(1.0f)); 150 oa.start(); 151 152 View actionFragmentView = activity.findViewById(mActionAreaId); 153 boolean isRtl = ViewCompat.getLayoutDirection(contentView) == 154 ViewCompat.LAYOUT_DIRECTION_RTL; 155 int startDist = isRtl ? SLIDE_IN_DISTANCE : -SLIDE_IN_DISTANCE; 156 int endDist = isRtl ? -actionFragmentView.getMeasuredWidth() : 157 actionFragmentView.getMeasuredWidth(); 158 159 // Fade in and slide in the ContentFragment TextViews from the start. 160 prepareAndAnimateView(title, 0, startDist, 161 secondaryDelay, ANIMATE_IN_DURATION, 162 new DecelerateInterpolator(1.0f), 163 false); 164 prepareAndAnimateView(breadcrumb, 0, startDist, 165 secondaryDelay, ANIMATE_IN_DURATION, 166 new DecelerateInterpolator(1.0f), 167 false); 168 prepareAndAnimateView(description, 0, 169 startDist, 170 secondaryDelay, ANIMATE_IN_DURATION, 171 new DecelerateInterpolator(1.0f), 172 false); 173 174 // Fade in and slide in the ActionFragment from the end. 175 prepareAndAnimateView(actionFragmentView, 0, 176 endDist, secondaryDelay, 177 ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f), 178 false); 179 180 if (icon != null) { 181 prepareAndAnimateView(icon, 0, startDist, 182 secondaryDelay, ANIMATE_IN_DURATION, 183 new DecelerateInterpolator(1.0f), true /* is the icon */); 184 if (mShadowLayer != null) { 185 mShadowLayer.setShadowsAlpha(0f); 186 } 187 } 188 } 189 }; 190 }); 191 } 192 193 /** 194 * Animates a view. 195 * 196 * @param v view to animate 197 * @param initAlpha initial alpha 198 * @param initTransX initial translation in the X 199 * @param delay delay in ms 200 * @param duration duration in ms 201 * @param interpolator interpolator to be used, can be null 202 * @param isIcon if {@code true}, this is the main icon being moved 203 */ 204 public void prepareAndAnimateView(final View v, float initAlpha, float initTransX, int delay, 205 int duration, Interpolator interpolator, final boolean isIcon) { 206 if (v != null && v.getWindowToken() != null) { 207 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 208 v.buildLayer(); 209 v.setAlpha(initAlpha); 210 v.setTranslationX(initTransX); 211 v.animate().alpha(1f).translationX(0).setDuration(duration).setStartDelay(delay); 212 if (interpolator != null) { 213 v.animate().setInterpolator(interpolator); 214 } 215 v.animate().setListener(new AnimatorListenerAdapter() { 216 @Override 217 public void onAnimationEnd(Animator animation) { 218 v.setLayerType(View.LAYER_TYPE_NONE, null); 219 if (isIcon) { 220 if (mShadowLayer != null) { 221 mShadowLayer.setShadowsAlpha(1f); 222 } 223 onIntroAnimationFinished(); 224 } 225 } 226 }); 227 v.animate().start(); 228 } 229 } 230 231 /** 232 * Called when intro animation is finished. 233 * <p> 234 * If a subclass is going to alter the view, should wait until this is called. 235 */ 236 public void onIntroAnimationFinished() { 237 mIntroAnimationInProgress = false; 238 } 239 240 public boolean isIntroAnimationInProgress() { 241 return mIntroAnimationInProgress; 242 } 243 244 public ColorDrawable getBackgroundDrawable() { 245 return mBgDrawable; 246 } 247 248 public void setBackgroundDrawable(ColorDrawable drawable) { 249 mBgDrawable = drawable; 250 } 251 252} 253