Merge changes from topic "tr-anim" into sc-dev
* changes: Translate: Text alpha animation on toggling translated state. Translate: Ignore duplicate translations.
This commit is contained in:
@@ -389,6 +389,14 @@ public class UiTranslationController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
mActivity.runOnUiThread(() -> {
|
mActivity.runOnUiThread(() -> {
|
||||||
|
if (view.getViewTranslationResponse() != null
|
||||||
|
&& view.getViewTranslationResponse().equals(response)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
|
||||||
|
+ ". Ignoring.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
ViewTranslationCallback callback = view.getViewTranslationCallback();
|
ViewTranslationCallback callback = view.getViewTranslationCallback();
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
if (view instanceof TextView) {
|
if (view instanceof TextView) {
|
||||||
@@ -396,9 +404,6 @@ public class UiTranslationController {
|
|||||||
// implememtation.
|
// implememtation.
|
||||||
callback = new TextViewTranslationCallback();
|
callback = new TextViewTranslationCallback();
|
||||||
view.setViewTranslationCallback(callback);
|
view.setViewTranslationCallback(callback);
|
||||||
if (mViewsToPadContent.contains(autofillId)) {
|
|
||||||
callback.enableContentPadding();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, view + " doesn't support showing translation because of "
|
Log.d(TAG, view + " doesn't support showing translation because of "
|
||||||
@@ -407,6 +412,10 @@ public class UiTranslationController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
callback.setAnimationDurationMillis(ANIMATION_DURATION_MILLIS);
|
||||||
|
if (mViewsToPadContent.contains(autofillId)) {
|
||||||
|
callback.enableContentPadding();
|
||||||
|
}
|
||||||
view.onViewTranslationResponse(response);
|
view.onViewTranslationResponse(response);
|
||||||
callback.onShowTranslation(view);
|
callback.onShowTranslation(view);
|
||||||
});
|
});
|
||||||
@@ -414,6 +423,9 @@ public class UiTranslationController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use a device config value.
|
||||||
|
private static final int ANIMATION_DURATION_MILLIS = 250;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Translator for the given source and target translation specs and start the ui
|
* Creates a Translator for the given source and target translation specs and start the ui
|
||||||
* translation when the Translator is created successfully.
|
* translation when the Translator is created successfully.
|
||||||
|
|||||||
@@ -64,4 +64,12 @@ public interface ViewTranslationCallback {
|
|||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
default void enableContentPadding() {}
|
default void enableContentPadding() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration for animations while transitioning the view between the original and
|
||||||
|
* translated contents.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
default void setAnimationDurationMillis(int durationMillis) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,15 @@
|
|||||||
|
|
||||||
package android.widget;
|
package android.widget;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.text.method.TransformationMethod;
|
||||||
import android.text.method.TranslationTransformationMethod;
|
import android.text.method.TranslationTransformationMethod;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -47,6 +52,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
|
|||||||
private boolean mIsShowingTranslation = false;
|
private boolean mIsShowingTranslation = false;
|
||||||
private boolean mIsTextPaddingEnabled = false;
|
private boolean mIsTextPaddingEnabled = false;
|
||||||
private CharSequence mPaddedText;
|
private CharSequence mPaddedText;
|
||||||
|
private int mAnimationDurationMillis = 250; // default value
|
||||||
|
|
||||||
private CharSequence mContentDescription;
|
private CharSequence mContentDescription;
|
||||||
|
|
||||||
@@ -82,14 +88,19 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onShowTranslation(@NonNull View view) {
|
public boolean onShowTranslation(@NonNull View view) {
|
||||||
mIsShowingTranslation = true;
|
|
||||||
if (view.getViewTranslationResponse() == null) {
|
if (view.getViewTranslationResponse() == null) {
|
||||||
Log.wtf(TAG, "onShowTranslation() shouldn't be called before "
|
Log.wtf(TAG, "onShowTranslation() shouldn't be called before "
|
||||||
+ "onViewTranslationResponse().");
|
+ "onViewTranslationResponse().");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (mTranslationTransformation != null) {
|
if (mTranslationTransformation != null) {
|
||||||
((TextView) view).setTransformationMethod(mTranslationTransformation);
|
final TransformationMethod transformation = mTranslationTransformation;
|
||||||
|
runWithAnimation(
|
||||||
|
(TextView) view,
|
||||||
|
() -> {
|
||||||
|
mIsShowingTranslation = true;
|
||||||
|
((TextView) view).setTransformationMethod(transformation);
|
||||||
|
});
|
||||||
ViewTranslationResponse response = view.getViewTranslationResponse();
|
ViewTranslationResponse response = view.getViewTranslationResponse();
|
||||||
if (response.getKeys().contains(ViewTranslationRequest.ID_CONTENT_DESCRIPTION)) {
|
if (response.getKeys().contains(ViewTranslationRequest.ID_CONTENT_DESCRIPTION)) {
|
||||||
CharSequence translatedContentDescription =
|
CharSequence translatedContentDescription =
|
||||||
@@ -114,7 +125,6 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onHideTranslation(@NonNull View view) {
|
public boolean onHideTranslation(@NonNull View view) {
|
||||||
mIsShowingTranslation = false;
|
|
||||||
if (view.getViewTranslationResponse() == null) {
|
if (view.getViewTranslationResponse() == null) {
|
||||||
Log.wtf(TAG, "onHideTranslation() shouldn't be called before "
|
Log.wtf(TAG, "onHideTranslation() shouldn't be called before "
|
||||||
+ "onViewTranslationResponse().");
|
+ "onViewTranslationResponse().");
|
||||||
@@ -122,8 +132,14 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
|
|||||||
}
|
}
|
||||||
// Restore to original text content.
|
// Restore to original text content.
|
||||||
if (mTranslationTransformation != null) {
|
if (mTranslationTransformation != null) {
|
||||||
((TextView) view).setTransformationMethod(
|
final TransformationMethod transformation =
|
||||||
mTranslationTransformation.getOriginalTransformationMethod());
|
mTranslationTransformation.getOriginalTransformationMethod();
|
||||||
|
runWithAnimation(
|
||||||
|
(TextView) view,
|
||||||
|
() -> {
|
||||||
|
mIsShowingTranslation = false;
|
||||||
|
((TextView) view).setTransformationMethod(transformation);
|
||||||
|
});
|
||||||
if (!TextUtils.isEmpty(mContentDescription)) {
|
if (!TextUtils.isEmpty(mContentDescription)) {
|
||||||
view.setContentDescription(mContentDescription);
|
view.setContentDescription(mContentDescription);
|
||||||
}
|
}
|
||||||
@@ -212,4 +228,64 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final char COMPAT_PAD_CHARACTER = '\u2002';
|
private static final char COMPAT_PAD_CHARACTER = '\u2002';
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAnimationDurationMillis(int durationMillis) {
|
||||||
|
mAnimationDurationMillis = durationMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a simple text alpha animation when toggling between original and translated text. The
|
||||||
|
* text is fully faded out, then swapped to the new text, then the fading is reversed.
|
||||||
|
*
|
||||||
|
* @param runnable the operation to run on the view after the text is faded out, to change to
|
||||||
|
* displaying the original or translated text.
|
||||||
|
*/
|
||||||
|
private void runWithAnimation(TextView view, Runnable runnable) {
|
||||||
|
if (mAnimator != null) {
|
||||||
|
mAnimator.end();
|
||||||
|
// Note: mAnimator is now null; do not use again here.
|
||||||
|
}
|
||||||
|
int fadedOutColor = colorWithAlpha(view.getCurrentTextColor(), 0);
|
||||||
|
mAnimator = ValueAnimator.ofArgb(view.getCurrentTextColor(), fadedOutColor);
|
||||||
|
mAnimator.addUpdateListener(
|
||||||
|
// Note that if the text has a ColorStateList, this replaces it with a single color
|
||||||
|
// for all states. The original ColorStateList is restored when the animation ends
|
||||||
|
// (see below).
|
||||||
|
(valueAnimator) -> view.setTextColor((Integer) valueAnimator.getAnimatedValue()));
|
||||||
|
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||||
|
mAnimator.setRepeatCount(1);
|
||||||
|
mAnimator.setDuration(mAnimationDurationMillis);
|
||||||
|
final ColorStateList originalColors = view.getTextColors();
|
||||||
|
mAnimator.addListener(new Animator.AnimatorListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
view.setTextColor(originalColors);
|
||||||
|
mAnimator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueAnimator mAnimator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code color} with alpha changed to {@code newAlpha}
|
||||||
|
*/
|
||||||
|
private static int colorWithAlpha(int color, int newAlpha) {
|
||||||
|
return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user