Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(996)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogView.java

Issue 14886012: Implement layout transition animations for the Autofill dialog (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Got rid of TABs Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogView.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogContentView.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogView.java
similarity index 66%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogContentView.java
rename to chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogView.java
index a98585d083178ca7f10e920374dcdfe4c1ff7438..419fb30c19536e3b1edc87b5d43b5778759604e6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogContentView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogView.java
@@ -4,11 +4,29 @@
package org.chromium.chrome.browser.autofill;
+import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.NUM_SECTIONS;
+import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_BILLING;
+import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_CC;
+import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_CC_BILLING;
+import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_EMAIL;
+import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_SHIPPING;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
@@ -18,21 +36,14 @@ import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
+import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
-import android.widget.AdapterView.OnItemSelectedListener;
-
-import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.NUM_SECTIONS;
-import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_EMAIL;
-import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_CC;
-import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_CC_BILLING;
-import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_BILLING;
-import static org.chromium.chrome.browser.autofill.AutofillDialogConstants.SECTION_SHIPPING;
import org.chromium.chrome.R;
@@ -46,7 +57,7 @@ import java.util.List;
* actual workflow, but rather respond to any UI update messages coming to it
* from the AutofillDialog.
*/
-public class AutofillDialogContentView extends LinearLayout {
+public class AutofillDialogView extends FrameLayout {
private static final int ANIMATION_DURATION_MS = 1000;
static final int INVALID_LAYOUT = -1;
static final int LAYOUT_EDITING_EMAIL = 0;
@@ -72,6 +83,18 @@ public class AutofillDialogContentView extends LinearLayout {
private Bitmap mCVCIcon;
private OnItemSelectedListener mOnItemSelectedListener;
private OnItemEditButtonClickedListener mOnItemEditButtonClickedListener;
+ private View mTitle;
+ private View mContent;
+ private View mFooter;
+ private boolean mTransitionAnimationPlaying;
+ private float mVisibleContentHeight;
+ private ObjectAnimator mCurrentHeightAnimator;
+ private AnimationSet mCurrentLayoutAnimation;
+ private Drawable mBackground = new ColorDrawable(Color.LTGRAY);
+ private boolean mAnimateOnNextLayoutChange = false;
+ private int mCurrentOrientation;
+ private int mFrameHeight;
+ private int mFrameWidth;
/**
* Interface definition for a callback to be invoked when an "Edit" button
@@ -86,13 +109,17 @@ public class AutofillDialogContentView extends LinearLayout {
void onItemEditButtonClicked(int section, int position);
}
- public AutofillDialogContentView(Context context, AttributeSet attrs) {
+ public AutofillDialogView(Context context, AttributeSet attrs) {
super(context, attrs);
+ setWillNotDraw(false);
}
@Override
protected void onFinishInflate () {
mSteadyLayout = (ViewGroup) findViewById(R.id.general_layout);
+ mTitle = findViewById(R.id.title);
+ mContent = findViewById(R.id.content);
+ mFooter = findViewById(R.id.footer);
for (int i = 0; i < AutofillDialogConstants.NUM_SECTIONS; i++) {
mEditLayouts[i] = (ViewGroup) findViewById(
@@ -101,15 +128,107 @@ public class AutofillDialogContentView extends LinearLayout {
mSpinners[i] = (Spinner) findViewById(id);
}
+ int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ for (int i = 0; i < NUM_SECTIONS; i++) {
Ted C 2013/05/07 21:49:07 should these go in measure instead?
Yusuf 2013/05/08 21:39:00 This is just to avoid getting to a state where we
+ (mEditLayouts[i]).measure(spec, spec);
+ }
+ findViewById(R.id.loading_icon).measure(spec, spec);
+
+ mContent.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View arg0, int arg1, int arg2, int arg3, int arg4, int arg5,
Ted C 2013/05/07 21:49:07 get the argument names from the sdk so they're not
Yusuf 2013/05/08 21:39:00 Done.
+ int arg6, int arg7, int arg8) {
+ if (mAnimateOnNextLayoutChange && mCurrentHeightAnimator != null
+ && !mCurrentHeightAnimator.isStarted()) {
+ mCurrentHeightAnimator.start();
+ mAnimateOnNextLayoutChange = false;
+ if (mCurrentLayoutAnimation != null && !mCurrentLayoutAnimation.hasStarted()) {
+ mCurrentLayoutAnimation.start();
+ }
+ }
+ }
+ });
+
+ mVisibleContentHeight = getAdjustedContentLayoutHeight(LAYOUT_FETCHING, null);
changeLayoutTo(LAYOUT_FETCHING);
}
/**
- * Sets the controller dialog for this content view.
+ * Sets the controller dialog for this content view and initializes the view.
* @param dialog The autofill Dialog that should be set as the controller.
*/
- public void setAutofillDialog(AutofillDialog dialog) {
+ public void initialize(AutofillDialog dialog) {
mDialog = dialog;
+
+ mDialog.getWindow().getDecorView().setBackground(null);
+
+ FrameLayout.LayoutParams layoutParams =
+ (android.widget.FrameLayout.LayoutParams) getLayoutParams();
+
+ mCurrentOrientation = getResources().getConfiguration().orientation;
+ mFrameHeight = getMaxHeightForOrientation(mCurrentOrientation);
+ layoutParams.height = mFrameHeight;
+ mFrameWidth = getResources().getDimensionPixelSize(R.dimen.autofill_dialog_width);
+ layoutParams.width = mFrameWidth;
+ requestLayout();
+ }
+
+ private int getMaxHeightForOrientation(int orientation) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ return getResources().getDimensionPixelSize(
+ R.dimen.autofill_dialog_max_height_portrait);
Ted C 2013/05/07 21:49:07 Hmm...can you use the same dimension name but use
Yusuf 2013/05/08 21:39:00 Done.
+ } else {
+ return getResources().getDimensionPixelSize(
+ R.dimen.autofill_dialog_max_height_landscape);
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged (Configuration newConfig) {
+ if (mCurrentOrientation != newConfig.orientation) {
Ted C 2013/05/07 21:49:07 I would check for equality and early out instead s
Yusuf 2013/05/08 21:39:00 Done.
+ mCurrentOrientation = newConfig.orientation;
+ FrameLayout.LayoutParams layoutParams =
+ (android.widget.FrameLayout.LayoutParams) getLayoutParams();
+ mFrameHeight = getMaxHeightForOrientation(mCurrentOrientation);
+ layoutParams.height = mFrameHeight;
+ requestLayout();
+ }
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (!mTransitionAnimationPlaying) return super.drawChild(canvas, child, drawingTime);
Ted C 2013/05/07 21:49:07 Maybe make this: !mTransitionAnimationPlaying ||
Yusuf 2013/05/08 21:39:00 Done.
+
+ float top = mContent.getTop()
Ted C 2013/05/07 21:49:07 These aren't used outside of the if block, so prob
Yusuf 2013/05/08 21:39:00 Done.
+ + mContent.getMeasuredHeight() / 2 - mVisibleContentHeight / 2;
+ float bottom = mContent.getBottom()
+ - mContent.getMeasuredHeight() / 2 + mVisibleContentHeight / 2;
+ if (child.getId() == R.id.content) {
+ canvas.save();
+ canvas.clipRect(getLeft(), top, getRight(), bottom);
+ boolean result = super.drawChild(canvas, child, drawingTime);
+ canvas.restore();
+ return result;
+ }
+
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ if (mTransitionAnimationPlaying) {
+ mTitle.setTranslationY(mContent.getMeasuredHeight() / 2 - mVisibleContentHeight / 2);
Ted C 2013/05/07 21:49:07 maybe extra some of these as variables and share t
Yusuf 2013/05/08 21:39:00 Done.
+ mContent.setTranslationY(mContent.getMeasuredHeight() / 2 - mVisibleContentHeight / 2);
+ mFooter.setTranslationY(- mContent.getMeasuredHeight() / 2 + mVisibleContentHeight / 2);
Ted C 2013/05/07 21:49:07 unnecessary space between - and mContent
Yusuf 2013/05/08 21:39:00 Done.
+ }
+
+ canvas.save();
+ mBackground.setBounds(getLeft(),(int) (mTitle.getTop() + mTitle.getTranslationY())
Ted C 2013/05/07 21:49:07 space after getLeft()
Yusuf 2013/05/08 21:39:00 Done.
+ , getRight(),(int) (mFooter.getBottom()+mFooter.getTranslationY()));
Ted C 2013/05/07 21:49:07 leading comma should go on the previous line. Spa
Yusuf 2013/05/08 21:39:00 Done.
+ mBackground.draw(canvas);
+ canvas.restore();
+
+ super.onDraw(canvas);
}
/**
@@ -158,15 +277,76 @@ public class AutofillDialogContentView extends LinearLayout {
}
}
+ private int getMeasuredTitleHeight() {
+ return mTitle.getMeasuredHeight();
+ }
+
+ private int getMeasuredFooterHeight() {
+ return mFooter.getMeasuredHeight();
+ }
+
+ private int getAdjustedContentLayoutHeight(int mode, View layout) {
+ assert mode != INVALID_LAYOUT;
+ int measuredContentHeight;
+ if (mode == LAYOUT_STEADY) {
+ measuredContentHeight = mSteadyLayout.getMeasuredHeight();
Ted C 2013/05/07 21:49:07 What happens if you rotate? Will all these measur
Yusuf 2013/05/08 21:39:00 Yes ,they are always in the hierarchy. This measur
+ } else if (mode == LAYOUT_FETCHING) {
+ measuredContentHeight = findViewById(R.id.loading_icon).getMeasuredHeight();
+ } else {
+ if (mode == LAYOUT_EDITING_CC_BILLING) {
+ // For CC_BILLING we have to measure again since it contains both CC and
+ // BILLING also. The last measure may be for just one of them.
+ int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
+ int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ layout.measure(widthSpec, heightSpec);
Ted C 2013/05/07 21:49:07 Maybe a TODO to only do this if the mode that was
Yusuf 2013/05/08 21:39:00 Done.
+ }
+ measuredContentHeight = layout.getMeasuredHeight();
+ }
+ return Math.min(measuredContentHeight,
+ getMeasuredHeight() - getMeasuredTitleHeight() - getMeasuredFooterHeight());
+ }
+
+ @Override
+ public void requestLayout () {
+ Exception e = new Exception();
+ e.printStackTrace();
Ted C 2013/05/07 21:49:07 probably should remove this :-) and for future re
Yusuf 2013/05/08 21:39:00 Done.
+ super.requestLayout();
+ }
+
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mTitle.setTranslationY(0);
+ mContent.setTranslationY(0);
Ted C 2013/05/07 21:49:07 why do you need to reset these here? Maybe commen
Yusuf 2013/05/08 21:39:00 Done.
+ mFooter.setTranslationY(0);
+
+ int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
+ int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ measureChild(mTitle, widthSpec, heightSpec);
+ measureChild(mFooter, widthSpec, heightSpec);
+ heightSpec = MeasureSpec.makeMeasureSpec(
+ getHeight() - getMeasuredTitleHeight() - getMeasuredFooterHeight(),
+ MeasureSpec.AT_MOST);
+ mContent.measure(widthSpec, heightSpec);
+ long content = SystemClock.uptimeMillis();
Ted C 2013/05/07 21:49:07 not used?
Yusuf 2013/05/08 21:39:00 Done.
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
for (int i = 0; i < NUM_SECTIONS; i++) {
mSpinners[i].setDropDownWidth(mSpinners[i].getMeasuredWidth());
mSpinners[i].setDropDownVerticalOffset(-mSpinners[i].getMeasuredHeight());
}
}
+ setMeasuredDimension(mFrameWidth, mFrameHeight);
Ted C 2013/05/07 21:49:07 why use getWidth() above and mFrameWidth here?
Yusuf 2013/05/08 21:39:00 Done.
+ }
+
+ @Override
+ protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
+ int totalHeight = getMeasuredTitleHeight()
+ + getMeasuredFooterHeight() + mContent.getMeasuredHeight();
+ int space = (getMeasuredHeight() - totalHeight) / 2 ;
Ted C 2013/05/07 21:49:07 I would rename space to specify something about th
Yusuf 2013/05/08 21:39:00 Done.
+ mTitle.layout(left, space, right, getMeasuredTitleHeight() + space);
+ mContent.layout(left, mTitle.getBottom(), right,
+ mTitle.getBottom() + mContent.getMeasuredHeight());
+ mFooter.layout(left, mContent.getBottom(), right, bottom - space);
}
/**
@@ -262,10 +442,10 @@ public class AutofillDialogContentView extends LinearLayout {
AutofillDialogMenuAdapter adapter = mAdapters[section];
adapter.clear();
adapter.addAll(items);
+ spinner.setSelection(selectedMenuItem);
spinner.post(new Runnable() {
@Override
public void run() {
- spinner.setSelection(selectedMenuItem);
spinner.setOnItemSelectedListener(mOnItemSelectedListener);
}
});
@@ -301,21 +481,43 @@ public class AutofillDialogContentView extends LinearLayout {
mSteadyLayout.setVisibility(VISIBLE);
if (mode == LAYOUT_STEADY) return;
- addTranslateAnimations(mode);
- addDisappearAnimationForSteadyLayout();
View centeredLayout = mEditLayouts[getSectionForLayoutMode(mode)];
- addAppearAnimationForEditLayout(mode, centeredLayout);
- centeredLayout.setVisibility(VISIBLE);
if (mode == LAYOUT_EDITING_CC_BILLING) {
mEditLayouts[SECTION_CC].setVisibility(VISIBLE);
mEditLayouts[SECTION_BILLING].setVisibility(VISIBLE);
+ } else if (mode == LAYOUT_EDITING_CC || mode == LAYOUT_EDITING_BILLING) {
+ mEditLayouts[SECTION_CC_BILLING].setVisibility(VISIBLE);
}
- ((View) centeredLayout.getParent()).setVisibility(VISIBLE);
- centeredLayout.animate();
- mSteadyLayout.animate();
- postDelayed(mDismissSteadyLayoutRunnable, ANIMATION_DURATION_MS);
+ centeredLayout.setVisibility(VISIBLE);
+ final float fromHeight = mSteadyLayout.getMeasuredHeight();
+ final float toHeight = getAdjustedContentLayoutHeight(mode, centeredLayout);
+ mCurrentHeightAnimator =
+ ObjectAnimator.ofFloat(this, "VisibleContentHeight", fromHeight, toHeight);
Ted C 2013/05/07 21:49:07 Maybe use a Property to avoid the reflection?
Yusuf 2013/05/08 21:39:00 I am not sure if the objectAnimator framework uses
David Trainor- moved to gerrit 2013/05/10 22:59:55 It uses reflection by looking for a method called
Yusuf 2013/05/14 02:15:10 Done.
+ mCurrentHeightAnimator.setDuration(ANIMATION_DURATION_MS);
+ mCurrentHeightAnimator.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ invalidate();
Ted C 2013/05/07 21:49:07 If you can, it should be "slightly" faster to inva
Yusuf 2013/05/08 21:39:00 The header and the footer is also moving, so every
+ }
+ });
+ mCurrentHeightAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ mTransitionAnimationPlaying = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mTransitionAnimationPlaying = false;
+ mVisibleContentHeight = toHeight;
+ }
+ });
+ mCurrentLayoutAnimation = addTranslateAnimations(mode);
+ mCurrentLayoutAnimation.addAnimation(addAppearAnimationForEditLayout(mode, centeredLayout));
+ mAnimateOnNextLayoutChange = true;
+
+ postDelayed(mDismissSteadyLayoutRunnable, ANIMATION_DURATION_MS);
mCurrentLayout = mode;
}
@@ -338,7 +540,7 @@ public class AutofillDialogContentView extends LinearLayout {
if (labelView != null) labelView.setVisibility(visibility);
}
- private void addAppearAnimationForEditLayout(int mode, View layout) {
+ private AnimationSet addAppearAnimationForEditLayout(int mode, View layout) {
View centerView = mSpinners[getSectionForLayoutMode(mode)];
float yOffset = centerView.getY() - (float) centerView.getHeight() / 2;
@@ -347,25 +549,16 @@ public class AutofillDialogContentView extends LinearLayout {
ScaleAnimation scaleAnimation = new ScaleAnimation(1, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(ANIMATION_DURATION_MS);
- scaleAnimation.setStartOffset(ANIMATION_DURATION_MS / 4);
AnimationSet appearAnimation = new AnimationSet(true);
appearAnimation.addAnimation(toLocationAnimation);
appearAnimation.addAnimation(scaleAnimation);
layout.setAnimation(appearAnimation);
+ return appearAnimation;
}
- private void addDisappearAnimationForSteadyLayout() {
- ScaleAnimation scaleDownAnimation = new ScaleAnimation(1, 1, 1, 0,
- Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0.5f);
- scaleDownAnimation.setDuration(ANIMATION_DURATION_MS / 2);
- scaleDownAnimation.setStartOffset(ANIMATION_DURATION_MS / 2);
-
- mSteadyLayout.setAnimation(scaleDownAnimation);
- }
-
- private void addTranslateAnimations(int mode) {
+ private AnimationSet addTranslateAnimations(int mode) {
float distance = getResources().
getDimensionPixelSize(R.dimen.autofill_translation_anim_distance);
TranslateAnimation toTopAnimation = new TranslateAnimation(0, 0, 0, -distance);
@@ -379,13 +572,14 @@ public class AutofillDialogContentView extends LinearLayout {
if (currentChild.getTop() <=
mSpinners[getSectionForLayoutMode(mode)].getTop()) {
currentChild.setAnimation(toTopAnimation);
- currentChild.animate();
- } else if (currentChild.getTop() >
- mSpinners[getSectionForLayoutMode(mode)].getTop()) {
+ } else {
currentChild.setAnimation(toBottomAnimation);
- currentChild.animate();
}
}
+ AnimationSet combined = new AnimationSet(true);
+ combined.addAnimation(toBottomAnimation);
+ combined.addAnimation(toTopAnimation);
+ return combined;
}
private static int getSectionForLayoutMode(int mode) {
@@ -420,6 +614,22 @@ public class AutofillDialogContentView extends LinearLayout {
return INVALID_LAYOUT;
}
+ /**
+ * @return The current visible content height.
+ */
+ public float getVisibleContentHeight() {
+ return mVisibleContentHeight;
+ }
+
+ /**
+ * Sets the visible content height. This value is used for setting the clipRect for the canvas
+ * in layout transitions.
+ * @param height The value to use for the visible content height.
+ */
+ public void setVisibleContentHeight(float height) {
+ mVisibleContentHeight = height;
+ }
+
private static class AutofillDialogMenuAdapter extends ArrayAdapter<AutofillDialogMenuItem> {
protected boolean mShouldShowCVC = true;
private int mSection;
@@ -446,19 +656,20 @@ public class AutofillDialogContentView extends LinearLayout {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- return initView(position, convertView, parent, false);
+ return initView(position, convertView, parent, false,
+ AutofillDialogUtils.getItemLayoutIDForSection(mSection));
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
- return initView(position, convertView, parent, true);
+ return initView(position, convertView, parent, true, R.layout.autofill_menu_item);
}
private View initView(
- final int position, View convertView, final ViewGroup parent, boolean showButton) {
+ final int position, View convertView, final ViewGroup parent, boolean showButton,
+ int layoutID) {
Ted C 2013/05/07 21:49:07 lowercase d in layoutId
Yusuf 2013/05/08 21:39:00 Done.
if (convertView == null) {
- convertView = View.inflate(getContext(),
- R.layout.autofill_menu_item, null);
+ convertView = View.inflate(getContext(), layoutID, null);
mShouldShowCVC = true;
}

Powered by Google App Engine
This is Rietveld 408576698