diff options
author | Danny Baumann <dannybaumann@web.de> | 2014-12-08 09:18:06 +0100 |
---|---|---|
committer | Rajesh Yengisetty <rajesh@cyngn.com> | 2014-12-18 17:43:26 +0000 |
commit | b5ad7c99c3e990868042b13cd31c532b6102e0cd (patch) | |
tree | 470f13bba80aa1f6525788b9d04f19a64986e92e | |
parent | 5022a2e5a6bac602462c8b24c1ee450c992c4086 (diff) | |
download | android_packages_apps_ContactsCommon-b5ad7c99c3e990868042b13cd31c532b6102e0cd.tar.gz android_packages_apps_ContactsCommon-b5ad7c99c3e990868042b13cd31c532b6102e0cd.tar.bz2 android_packages_apps_ContactsCommon-b5ad7c99c3e990868042b13cd31c532b6102e0cd.zip |
Add a checkable QuickContactBadge.
To be used from multi picker UI.
FlipDrawable and CheckableFlipDrawable were kanged from UnifiedEmail.
Change-Id: Ic910071da9314526c3d75ab354b62645399824ce
(cherry picked from commit 5d3fe79d9ce4800084d5826041b13f0ae01408a6)
-rw-r--r-- | res/drawable-hdpi/ic_check_wht_24dp.png | bin | 0 -> 341 bytes | |||
-rw-r--r-- | res/drawable-mdpi/ic_check_wht_24dp.png | bin | 0 -> 295 bytes | |||
-rw-r--r-- | res/drawable-xhdpi/ic_check_wht_24dp.png | bin | 0 -> 402 bytes | |||
-rw-r--r-- | res/drawable-xxhdpi/ic_check_wht_24dp.png | bin | 0 -> 449 bytes | |||
-rw-r--r-- | res/values/attrs.xml | 1 | ||||
-rwxr-xr-x | src/com/android/contacts/common/list/ContactListItemView.java | 19 | ||||
-rw-r--r-- | src/com/android/contacts/common/widget/CheckableFlipDrawable.java | 220 | ||||
-rw-r--r-- | src/com/android/contacts/common/widget/CheckableImageView.java | 103 | ||||
-rw-r--r-- | src/com/android/contacts/common/widget/CheckableQuickContactBadge.java | 103 | ||||
-rw-r--r-- | src/com/android/contacts/common/widget/FlipDrawable.java | 276 |
10 files changed, 718 insertions, 4 deletions
diff --git a/res/drawable-hdpi/ic_check_wht_24dp.png b/res/drawable-hdpi/ic_check_wht_24dp.png Binary files differnew file mode 100644 index 00000000..12ce8e0d --- /dev/null +++ b/res/drawable-hdpi/ic_check_wht_24dp.png diff --git a/res/drawable-mdpi/ic_check_wht_24dp.png b/res/drawable-mdpi/ic_check_wht_24dp.png Binary files differnew file mode 100644 index 00000000..c7de7050 --- /dev/null +++ b/res/drawable-mdpi/ic_check_wht_24dp.png diff --git a/res/drawable-xhdpi/ic_check_wht_24dp.png b/res/drawable-xhdpi/ic_check_wht_24dp.png Binary files differnew file mode 100644 index 00000000..e34b73e5 --- /dev/null +++ b/res/drawable-xhdpi/ic_check_wht_24dp.png diff --git a/res/drawable-xxhdpi/ic_check_wht_24dp.png b/res/drawable-xxhdpi/ic_check_wht_24dp.png Binary files differnew file mode 100644 index 00000000..4c6a653f --- /dev/null +++ b/res/drawable-xxhdpi/ic_check_wht_24dp.png diff --git a/res/values/attrs.xml b/res/values/attrs.xml index b098c0b5..921469ff 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -17,6 +17,7 @@ <resources> <declare-styleable name="Theme"> <attr name="android:textColorSecondary" /> + <attr name="android:colorPrimary" /> </declare-styleable> <declare-styleable name="ContactsDataKind"> diff --git a/src/com/android/contacts/common/list/ContactListItemView.java b/src/com/android/contacts/common/list/ContactListItemView.java index 0f523d66..91f063ff 100755 --- a/src/com/android/contacts/common/list/ContactListItemView.java +++ b/src/com/android/contacts/common/list/ContactListItemView.java @@ -52,6 +52,8 @@ import com.android.contacts.common.R; import com.android.contacts.common.format.TextHighlighter; import com.android.contacts.common.util.SearchUtil; import com.android.contacts.common.util.ViewUtil; +import com.android.contacts.common.widget.CheckableImageView; +import com.android.contacts.common.widget.CheckableQuickContactBadge; import com.google.common.collect.Lists; @@ -152,9 +154,9 @@ public class ContactListItemView extends ViewGroup // The views inside the contact view private boolean mQuickContactEnabled = true; private boolean mQuickCallButtonEnabled = false; - private QuickContactBadge mQuickContact; + private CheckableQuickContactBadge mQuickContact; private ImageView mQuickCallView; - private ImageView mPhotoView; + private CheckableImageView mPhotoView; private TextView mNameTextView; private TextView mPhoneticNameTextView; private TextView mLabelView; @@ -849,7 +851,7 @@ public class ContactListItemView extends ViewGroup throw new IllegalStateException("QuickContact is disabled for this view"); } if (mQuickContact == null) { - mQuickContact = new QuickContactBadge(getContext()); + mQuickContact = new CheckableQuickContactBadge(getContext()); mQuickContact.setOverlay(null); mQuickContact.setLayoutParams(getDefaultPhotoLayoutParams()); if (mNameTextView != null) { @@ -863,12 +865,21 @@ public class ContactListItemView extends ViewGroup return mQuickContact; } + public void setChecked(boolean checked, boolean animate) { + if (mQuickContact != null) { + mQuickContact.setChecked(checked, animate); + } + if (mPhotoView != null) { + mPhotoView.setChecked(checked, animate); + } + } + /** * Returns the photo view, creating it if necessary. */ public ImageView getPhotoView() { if (mPhotoView == null) { - mPhotoView = new ImageView(getContext()); + mPhotoView = new CheckableImageView(getContext()); mPhotoView.setLayoutParams(getDefaultPhotoLayoutParams()); // Quick contact style used above will set a background - remove it mPhotoView.setBackground(null); diff --git a/src/com/android/contacts/common/widget/CheckableFlipDrawable.java b/src/com/android/contacts/common/widget/CheckableFlipDrawable.java new file mode 100644 index 00000000..cca82886 --- /dev/null +++ b/src/com/android/contacts/common/widget/CheckableFlipDrawable.java @@ -0,0 +1,220 @@ +package com.android.contacts.common.widget; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.Checkable; +import android.widget.QuickContactBadge; + +import com.android.contacts.common.R; + +public class CheckableFlipDrawable extends FlipDrawable implements + ValueAnimator.AnimatorUpdateListener { + + private final CheckmarkDrawable mCheckmarkDrawable; + + private final ValueAnimator mCheckmarkScaleAnimator; + private final ValueAnimator mCheckmarkAlphaAnimator; + + private static final int POST_FLIP_DURATION_MS = 150; + + private static final float CHECKMARK_SCALE_BEGIN_VALUE = 0.2f; + private static final float CHECKMARK_ALPHA_BEGIN_VALUE = 0f; + + /** Must be <= 1f since the animation value is used as a percentage. */ + private static final float END_VALUE = 1f; + + public CheckableFlipDrawable(Drawable front, final Resources res, + final int checkBackgroundColor, final int flipDurationMs) { + super(front, new CheckmarkDrawable(res, checkBackgroundColor), + flipDurationMs, 0 /* preFlipDurationMs */, POST_FLIP_DURATION_MS); + + mCheckmarkDrawable = (CheckmarkDrawable) mBack; + + // We will create checkmark animations that are synchronized with the + // flipping animation. The entire delay + duration of the checkmark animation + // needs to equal the entire duration of the flip animation (where delay is 0). + + // The checkmark animation is in effect only when the back drawable is being shown. + // For the flip animation duration <pre>[_][]|[][_]<post> + // The checkmark animation will be |--delay--|-duration-| + + // Need delay to skip the first half of the flip duration. + final long animationDelay = mPreFlipDurationMs + mFlipDurationMs / 2; + // Actual duration is the second half of the flip duration. + final long animationDuration = mFlipDurationMs / 2 + mPostFlipDurationMs; + + mCheckmarkScaleAnimator = ValueAnimator.ofFloat(CHECKMARK_SCALE_BEGIN_VALUE, END_VALUE) + .setDuration(animationDuration); + mCheckmarkScaleAnimator.setStartDelay(animationDelay); + mCheckmarkScaleAnimator.addUpdateListener(this); + + mCheckmarkAlphaAnimator = ValueAnimator.ofFloat(CHECKMARK_ALPHA_BEGIN_VALUE, END_VALUE) + .setDuration(animationDuration); + mCheckmarkAlphaAnimator.setStartDelay(animationDelay); + mCheckmarkAlphaAnimator.addUpdateListener(this); + } + + public void setFront(Drawable front) { + mFront.setCallback(null); + + mFront = front; + + mFront.setCallback(this); + mFront.setBounds(getBounds()); + mFront.setAlpha(getAlpha()); + mFront.setColorFilter(getColorFilter()); + mFront.setLevel(getLevel()); + + reset(); + invalidateSelf(); + } + + public void setCheckMarkBackgroundColor(int color) { + mCheckmarkDrawable.setBackgroundColor(color); + invalidateSelf(); + } + + @Override + public void reset() { + super.reset(); + if (mCheckmarkScaleAnimator == null) { + // Call from super's constructor. Not yet initialized. + return; + } + mCheckmarkScaleAnimator.cancel(); + mCheckmarkAlphaAnimator.cancel(); + boolean side = getSideFlippingTowards(); + mCheckmarkDrawable.setScaleAnimatorValue(side ? CHECKMARK_SCALE_BEGIN_VALUE : END_VALUE); + mCheckmarkDrawable.setAlphaAnimatorValue(side ? CHECKMARK_ALPHA_BEGIN_VALUE : END_VALUE); + } + + @Override + public void flip() { + super.flip(); + // Keep the checkmark animators in sync with the flip animator. + if (mCheckmarkScaleAnimator.isStarted()) { + mCheckmarkScaleAnimator.reverse(); + mCheckmarkAlphaAnimator.reverse(); + } else { + if (!getSideFlippingTowards() /* front to back */) { + mCheckmarkScaleAnimator.start(); + mCheckmarkAlphaAnimator.start(); + } else /* back to front */ { + mCheckmarkScaleAnimator.reverse(); + mCheckmarkAlphaAnimator.reverse(); + } + } + } + + @Override + public void onAnimationUpdate(final ValueAnimator animation) { + //noinspection ConstantConditions + final float value = (Float) animation.getAnimatedValue(); + + if (animation == mCheckmarkScaleAnimator) { + mCheckmarkDrawable.setScaleAnimatorValue(value); + } else if (animation == mCheckmarkAlphaAnimator) { + mCheckmarkDrawable.setAlphaAnimatorValue(value); + } + } + + private static class CheckmarkDrawable extends Drawable { + private static Bitmap sCheckMark; + + private final Paint mPaint; + + private float mScaleFraction; + private float mAlphaFraction; + + private static final Matrix sMatrix = new Matrix(); + + public CheckmarkDrawable(final Resources res, int backgroundColor) { + if (sCheckMark == null) { + sCheckMark = BitmapFactory.decodeResource(res, R.drawable.ic_check_wht_24dp); + } + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setFilterBitmap(true); + mPaint.setColor(backgroundColor); + } + + public void setBackgroundColor(int color) { + mPaint.setColor(color); + } + + @Override + public void draw(final Canvas canvas) { + final Rect bounds = getBounds(); + if (!isVisible() || bounds.isEmpty()) { + return; + } + + canvas.drawCircle(bounds.centerX(), bounds.centerY(), bounds.width() / 2, mPaint); + + // Scale the checkmark. + sMatrix.reset(); + sMatrix.setScale(mScaleFraction, mScaleFraction, sCheckMark.getWidth() / 2, + sCheckMark.getHeight() / 2); + sMatrix.postTranslate(bounds.centerX() - sCheckMark.getWidth() / 2, + bounds.centerY() - sCheckMark.getHeight() / 2); + + // Fade the checkmark. + final int oldAlpha = mPaint.getAlpha(); + // Interpolate the alpha. + mPaint.setAlpha((int) (oldAlpha * mAlphaFraction)); + canvas.drawBitmap(sCheckMark, sMatrix, mPaint); + // Restore the alpha. + mPaint.setAlpha(oldAlpha); + } + + @Override + public void setAlpha(final int alpha) { + mPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(final ColorFilter cf) { + mPaint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + // Always a gray background. + return PixelFormat.OPAQUE; + } + + /** + * Set value as a fraction from 0f to 1f. + */ + public void setScaleAnimatorValue(final float value) { + final float old = mScaleFraction; + mScaleFraction = value; + if (old != mScaleFraction) { + invalidateSelf(); + } + } + + /** + * Set value as a fraction from 0f to 1f. + */ + public void setAlphaAnimatorValue(final float value) { + final float old = mAlphaFraction; + mAlphaFraction = value; + if (old != mAlphaFraction) { + invalidateSelf(); + } + } + } +} diff --git a/src/com/android/contacts/common/widget/CheckableImageView.java b/src/com/android/contacts/common/widget/CheckableImageView.java new file mode 100644 index 00000000..914d4eaf --- /dev/null +++ b/src/com/android/contacts/common/widget/CheckableImageView.java @@ -0,0 +1,103 @@ +package com.android.contacts.common.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.Checkable; +import android.widget.ImageView; + +import com.android.contacts.common.R; + +public class CheckableImageView extends ImageView implements Checkable { + private boolean mChecked = false; + private int mCheckMarkBackgroundColor; + private CheckableFlipDrawable mDrawable; + + public CheckableImageView(Context context) { + super(context); + init(context); + } + + public CheckableImageView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public CheckableImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + public CheckableImageView(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context); + } + + private void init(Context context) { + TypedArray a = context.obtainStyledAttributes(android.R.styleable.Theme); + setCheckMarkBackgroundColor(a.getColor(android.R.styleable.Theme_colorPrimary, + context.getResources().getColor(R.color.people_app_theme_color))); + a.recycle(); + } + + public void setCheckMarkBackgroundColor(int color) { + mCheckMarkBackgroundColor = color; + if (mDrawable != null) { + mDrawable.setCheckMarkBackgroundColor(color); + } + } + + public void toggle() { + setChecked(!mChecked); + } + + @Override + public boolean isChecked() { + return mChecked; + } + + @Override + public void setChecked(boolean checked) { + setChecked(checked, true); + } + + public void setChecked(boolean checked, boolean animate) { + if (mChecked == checked) { + return; + } + + mChecked = checked; + + Drawable d = getDrawable(); + if (d instanceof CheckableFlipDrawable) { + CheckableFlipDrawable cfd = (CheckableFlipDrawable) d; + cfd.flipTo(!mChecked); + if (!animate) { + cfd.reset(); + } + } + } + + @Override + public void setImageDrawable(Drawable d) { + if (d != null) { + if (mDrawable == null) { + mDrawable = new CheckableFlipDrawable(d, getResources(), + mCheckMarkBackgroundColor, 150); + } else { + int oldWidth = mDrawable.getIntrinsicWidth(); + int oldHeight = mDrawable.getIntrinsicHeight(); + mDrawable.setFront(d); + if (oldWidth != mDrawable.getIntrinsicWidth() + || oldHeight != mDrawable.getIntrinsicHeight()) { + // enforce drawable size update + layout + super.setImageDrawable(null); + } + } + d = mDrawable; + } + super.setImageDrawable(d); + } +} diff --git a/src/com/android/contacts/common/widget/CheckableQuickContactBadge.java b/src/com/android/contacts/common/widget/CheckableQuickContactBadge.java new file mode 100644 index 00000000..85160569 --- /dev/null +++ b/src/com/android/contacts/common/widget/CheckableQuickContactBadge.java @@ -0,0 +1,103 @@ +package com.android.contacts.common.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.Checkable; +import android.widget.QuickContactBadge; + +import com.android.contacts.common.R; + +public class CheckableQuickContactBadge extends QuickContactBadge implements Checkable { + private boolean mChecked = false; + private int mCheckMarkBackgroundColor; + private CheckableFlipDrawable mDrawable; + + public CheckableQuickContactBadge(Context context) { + super(context); + init(context); + } + + public CheckableQuickContactBadge(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public CheckableQuickContactBadge(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + public CheckableQuickContactBadge(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context); + } + + private void init(Context context) { + TypedArray a = context.obtainStyledAttributes(android.R.styleable.Theme); + setCheckMarkBackgroundColor(a.getColor(android.R.styleable.Theme_colorPrimary, + context.getResources().getColor(R.color.people_app_theme_color))); + a.recycle(); + } + + public void setCheckMarkBackgroundColor(int color) { + mCheckMarkBackgroundColor = color; + if (mDrawable != null) { + mDrawable.setCheckMarkBackgroundColor(color); + } + } + + public void toggle() { + setChecked(!mChecked); + } + + @Override + public boolean isChecked() { + return mChecked; + } + + @Override + public void setChecked(boolean checked) { + setChecked(checked, true); + } + + public void setChecked(boolean checked, boolean animate) { + if (mChecked == checked) { + return; + } + + mChecked = checked; + + Drawable d = getDrawable(); + if (d instanceof CheckableFlipDrawable) { + CheckableFlipDrawable cfd = (CheckableFlipDrawable) d; + cfd.flipTo(!mChecked); + if (!animate) { + cfd.reset(); + } + } + } + + @Override + public void setImageDrawable(Drawable d) { + if (d != null) { + if (mDrawable == null) { + mDrawable = new CheckableFlipDrawable(d, getResources(), + mCheckMarkBackgroundColor, 150); + } else { + int oldWidth = mDrawable.getIntrinsicWidth(); + int oldHeight = mDrawable.getIntrinsicHeight(); + mDrawable.setFront(d); + if (oldWidth != mDrawable.getIntrinsicWidth() + || oldHeight != mDrawable.getIntrinsicHeight()) { + // enforce drawable size update + layout + super.setImageDrawable(null); + } + } + d = mDrawable; + } + super.setImageDrawable(d); + } +} diff --git a/src/com/android/contacts/common/widget/FlipDrawable.java b/src/com/android/contacts/common/widget/FlipDrawable.java new file mode 100644 index 00000000..ff14b508 --- /dev/null +++ b/src/com/android/contacts/common/widget/FlipDrawable.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.contacts.common.widget; + +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +/** + * A drawable that wraps two other drawables and allows flipping between them. The flipping + * animation is a 2D rotation around the y axis. + * + * <p/> + * The 3 durations are: (best viewed in documentation form) + * <pre> + * <pre>[_][]|[][_]<post> + * | | | + * V V V + * <pre>< flip ><post> + * </pre> + */ +public class FlipDrawable extends Drawable implements Drawable.Callback { + + /** + * The inner drawables. + */ + protected Drawable mFront; + protected final Drawable mBack; + + protected final int mFlipDurationMs; + protected final int mPreFlipDurationMs; + protected final int mPostFlipDurationMs; + private final ValueAnimator mFlipAnimator; + + private static final float END_VALUE = 2f; + + /** + * From 0f to END_VALUE. Determines the flip progress between mFront and mBack. 0f means + * mFront is fully shown, while END_VALUE means mBack is fully shown. + */ + private float mFlipFraction = 0f; + + /** + * True if flipping towards front, false if flipping towards back. + */ + private boolean mFlipToSide = true; + + /** + * Create a new FlipDrawable. The front is fully shown by default. + * + * <p/> + * The 3 durations are: (best viewed in documentation form) + * <pre> + * <pre>[_][]|[][_]<post> + * | | | + * V V V + * <pre>< flip ><post> + * </pre> + * + * @param front The front drawable. + * @param back The back drawable. + * @param flipDurationMs The duration of the actual flip. This duration includes both + * animating away one side and showing the other. + * @param preFlipDurationMs The duration before the actual flip begins. Subclasses can use this + * to add flourish. + * @param postFlipDurationMs The duration after the actual flip begins. Subclasses can use this + * to add flourish. + */ + public FlipDrawable(final Drawable front, final Drawable back, final int flipDurationMs, + final int preFlipDurationMs, final int postFlipDurationMs) { + if (front == null || back == null) { + throw new IllegalArgumentException("Front and back drawables must not be null."); + } + mFront = front; + mBack = back; + + mFront.setCallback(this); + mBack.setCallback(this); + + mFlipDurationMs = flipDurationMs; + mPreFlipDurationMs = preFlipDurationMs; + mPostFlipDurationMs = postFlipDurationMs; + + mFlipAnimator = ValueAnimator.ofFloat(0f, END_VALUE) + .setDuration(mPreFlipDurationMs + mFlipDurationMs + mPostFlipDurationMs); + mFlipAnimator.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(final ValueAnimator animation) { + final float old = mFlipFraction; + //noinspection ConstantConditions + mFlipFraction = (Float) animation.getAnimatedValue(); + if (old != mFlipFraction) { + invalidateSelf(); + } + } + }); + + reset(); + } + + @Override + public int getIntrinsicWidth() { + return mFront.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mFront.getIntrinsicHeight(); + } + + @Override + protected void onBoundsChange(final Rect bounds) { + super.onBoundsChange(bounds); + if (bounds.isEmpty()) { + mFront.setBounds(0, 0, 0, 0); + mBack.setBounds(0, 0, 0, 0); + } else { + mFront.setBounds(bounds); + mBack.setBounds(bounds); + } + } + + @Override + public void draw(final Canvas canvas) { + final Rect bounds = getBounds(); + if (!isVisible() || bounds.isEmpty()) { + return; + } + + final Drawable inner = getSideShown() /* == front */ ? mFront : mBack; + + final float totalDurationMs = mPreFlipDurationMs + mFlipDurationMs + mPostFlipDurationMs; + + final float scaleX; + if (mFlipFraction / 2 <= mPreFlipDurationMs / totalDurationMs) { + // During pre-flip. + scaleX = 1; + } else if (mFlipFraction / 2 >= (totalDurationMs - mPostFlipDurationMs) / totalDurationMs) { + // During post-flip. + scaleX = 1; + } else { + // During flip. + final float flipFraction = mFlipFraction / 2; + final float flipMiddle = (mPreFlipDurationMs / totalDurationMs + + (totalDurationMs - mPostFlipDurationMs) / totalDurationMs) / 2; + final float distFraction = Math.abs(flipFraction - flipMiddle); + final float multiplier = 1 / (flipMiddle - (mPreFlipDurationMs / totalDurationMs)); + scaleX = distFraction * multiplier; + } + + canvas.save(); + // The flip is a simple 1 dimensional scale. + canvas.scale(scaleX, 1, bounds.exactCenterX(), bounds.exactCenterY()); + inner.draw(canvas); + canvas.restore(); + } + + @Override + public void setAlpha(final int alpha) { + mFront.setAlpha(alpha); + mBack.setAlpha(alpha); + } + + @Override + public void setColorFilter(final ColorFilter cf) { + mFront.setColorFilter(cf); + mBack.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return resolveOpacity(mFront.getOpacity(), mBack.getOpacity()); + } + + @Override + protected boolean onLevelChange(final int level) { + return mFront.setLevel(level) || mBack.setLevel(level); + } + + @Override + public void invalidateDrawable(final Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(final Drawable who, final Runnable what, final long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(final Drawable who, final Runnable what) { + unscheduleSelf(what); + } + + /** + * Stop animating the flip and reset to one side. + * @param side Pass true if reset to front, false if reset to back. + */ + public void reset() { + final float old = mFlipFraction; + mFlipAnimator.cancel(); + mFlipFraction = mFlipToSide ? 0f : 2f; + if (mFlipFraction != old) { + invalidateSelf(); + } + } + + /** + * Returns true if the front is shown. Returns false if the back is shown. + */ + public boolean getSideShown() { + final float totalDurationMs = mPreFlipDurationMs + mFlipDurationMs + mPostFlipDurationMs; + final float middleFraction = (mPreFlipDurationMs / totalDurationMs + + (totalDurationMs - mPostFlipDurationMs) / totalDurationMs) / 2; + return mFlipFraction / 2 < middleFraction; + } + + /** + * Returns true if the front is being flipped towards. Returns false if the back is being + * flipped towards. + */ + public boolean getSideFlippingTowards() { + return mFlipToSide; + } + + /** + * Starts an animated flip to the other side. If a flip animation is currently started, + * it will be reversed. + */ + public void flip() { + mFlipToSide = !mFlipToSide; + if (mFlipAnimator.isStarted()) { + mFlipAnimator.reverse(); + } else { + if (!mFlipToSide /* front to back */) { + mFlipAnimator.start(); + } else /* back to front */ { + mFlipAnimator.reverse(); + } + } + } + + /** + * Start an animated flip to a side. This works regardless of whether a flip animation is + * currently started. + * @param side Pass true if flip to front, false if flip to back. + */ + public void flipTo(final boolean side) { + if (mFlipToSide != side) { + flip(); + } + } + + /** + * Returns whether flipping is in progress. + */ + public boolean isFlipping() { + return mFlipAnimator.isStarted(); + } +} |