ActivityOptions.makeSceneTransitionAnimation
を使用して、TextViewを2つのアクティビティ間で完全に遷移させることができます。しかし、私はそれが移行するときにテキストを拡大したいと思います。 マテリアルデザインの例 連絡先カードの遷移で「Alphonso Engelking」というテキストを拡大できます。
目的のTextViewにスケール属性を設定し、changeTransform共有要素のトランジションを使用しようとしましたが、スケールせず、トランジション時にテキストが切り捨てられます。
共有要素の遷移を使用してTextViewをスケーリングするにはどうすればよいですか?
以下のコメントでKiryl Tkachが指摘しているように、 このGoogle I/Oトーク で説明されているより良い解決策があります。
次のように、TextView
のテキストサイズをアニメーション化するカスタム遷移を作成できます。
public class TextSizeTransition extends Transition {
private static final String PROPNAME_TEXT_SIZE = "alexjlockwood:transition:textsize";
private static final String[] TRANSITION_PROPERTIES = { PROPNAME_TEXT_SIZE };
private static final Property<TextView, Float> TEXT_SIZE_PROPERTY =
new Property<TextView, Float>(Float.class, "textSize") {
@Override
public Float get(TextView textView) {
return textView.getTextSize();
}
@Override
public void set(TextView textView, Float textSizePixels) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePixels);
}
};
public TextSizeTransition() {
}
public TextSizeTransition(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public String[] getTransitionProperties() {
return TRANSITION_PROPERTIES;
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
private void captureValues(TransitionValues transitionValues) {
if (transitionValues.view instanceof TextView) {
TextView textView = (TextView) transitionValues.view;
transitionValues.values.put(PROPNAME_TEXT_SIZE, textView.getTextSize());
}
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
Float startSize = (Float) startValues.values.get(PROPNAME_TEXT_SIZE);
Float endSize = (Float) endValues.values.get(PROPNAME_TEXT_SIZE);
if (startSize == null || endSize == null ||
startSize.floatValue() == endSize.floatValue()) {
return null;
}
TextView view = (TextView) endValues.view;
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, startSize);
return ObjectAnimator.ofFloat(view, TEXT_SIZE_PROPERTY, startSize, endSize);
}
}
TextView
のテキストサイズを変更すると、アニメーションの途中でレイアウトの境界が変更されるため、トランジションを適切に機能させるには、単にChangeBounds
トランジションを同じTransitionSet
。代わりに、SharedElementCallback
でビューを最終状態で手動で測定/レイアウトする必要があります。
GitHub にプロジェクトの例を公開しました(プロジェクトは2つのGradle製品フレーバーを定義していることに注意してください)。 。1つはアクティビティ遷移を使用し、もう1つはフラグメント遷移を使用します。
私はAlex Lockwoodのソリューションを使用し、使用を簡略化しました(これはTextViewのTextSizeのみです)。これが役立つことを願っています。
public class Activity2 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity2);
EnterSharedElementTextSizeHandler handler = new EnterSharedElementTextSizeHandler(this);
handler.addTextViewSizeResource((TextView) findViewById(R.id.timer),
R.dimen.small_text_size, R.dimen.large_text_size);
}
}
そしてクラスEnterSharedElementTextSizeHandler:
public class EnterSharedElementTextSizeHandler extends SharedElementCallback {
private final TransitionSet mTransitionSet;
private final Activity mActivity;
public Map<TextView, Pair<Integer, Integer>> textViewList = new HashMap<>();
public EnterSharedElementTextSizeHandler(Activity activity) {
mActivity = activity;
Transition transitionWindow = activity.getWindow().getSharedElementEnterTransition();
if (!(transitionWindow instanceof TransitionSet)) {
mTransitionSet = new TransitionSet();
mTransitionSet.addTransition(transitionWindow);
} else {
mTransitionSet = (TransitionSet) transitionWindow;
}
activity.setEnterSharedElementCallback(this);
}
public void addTextViewSizeResource(TextView tv, int sizeBegin, int sizeEnd) {
Resources res = mActivity.getResources();
addTextView(tv,
res.getDimensionPixelSize(sizeBegin),
res.getDimensionPixelSize(sizeEnd));
}
public void addTextView(TextView tv, int sizeBegin, int sizeEnd) {
Transition textSize = new TextSizeTransition();
textSize.addTarget(tv.getId());
textSize.addTarget(tv.getText().toString());
mTransitionSet.addTransition(textSize);
textViewList.put(tv, new Pair<>(sizeBegin, sizeEnd));
}
@Override
public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
for (View v : sharedElements) {
if (!textViewList.containsKey(v)) {
continue;
}
((TextView) v).setTextSize(TypedValue.COMPLEX_UNIT_PX, textViewList.get(v).first);
}
}
@Override
public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
for (View v : sharedElements) {
if (!textViewList.containsKey(v)) {
continue;
}
TextView textView = (TextView) v;
// Record the TextView's old width/height.
int oldWidth = textView.getMeasuredWidth();
int oldHeight = textView.getMeasuredHeight();
// Setup the TextView's end values.
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textViewList.get(v).second);
// Re-measure the TextView (since the text size has changed).
int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
textView.measure(widthSpec, heightSpec);
// Record the TextView's new width/height.
int newWidth = textView.getMeasuredWidth();
int newHeight = textView.getMeasuredHeight();
// Layout the TextView in the center of its container, accounting for its new width/height.
int widthDiff = newWidth - oldWidth;
int heightDiff = newHeight - oldHeight;
textView.layout(textView.getLeft() - widthDiff / 2, textView.getTop() - heightDiff / 2,
textView.getRight() + widthDiff / 2, textView.getBottom() + heightDiff / 2);
}
}
}
これは Google I/O 2016の講演 の1つで取り上げられました。コードにコピーできる遷移のソースは here にあります。 IDEがaddTarget(TextView.class);
にAPI 21が必要であると不平を言っている場合は、コンストラクタを削除して、ターゲットを動的にまたはXMLに追加します。
つまり(これはKotlinにあることに注意してください)
val textResizeTransition = TextResize().addTarget(view.findViewById(R.id.text_view))
ChangeBounds
のしくみを見ると、ビューの左/右/上/下のプロパティで動作します。
2つのアクティビティで同じテキストサイズを使用し、起動したアクティビティのscaleX
プロパティとscaleY
プロパティを使用して、必要に応じてテキストサイズを変更する必要があると思います。次に、ChangeBounds
でChangeTransform
とTransitionSet
の組み合わせを使用します。
TransitionAnimationの私の解決策は、かなり話題に上っているわけではありませんが、おそらく修正を加えるか、誰かが役に立つだけです。
package com.example.Android.basictransition
import Android.animation.Animator
import Android.animation.AnimatorListenerAdapter
import Android.animation.ObjectAnimator
import Android.animation.PropertyValuesHolder.ofFloat
import Android.content.Context
import Android.transition.Transition
import Android.transition.TransitionValues
import Android.util.AttributeSet
import Android.view.View
import Android.view.ViewGroup
class ScaleTransition : Transition {
companion object {
private const val LAYOUT_WIDTH = "ScaleTransition:layout_width"
private const val LAYOUT_HEIGHT = "ScaleTransition:layout_height"
private const val POSITION_X = "ScaleTransition:position_x"
private const val POSITION_Y = "ScaleTransition:position_y"
private const val SCALE_X = "ScaleTransition:scale_x"
private const val SCALE_Y = "ScaleTransition:scale_y"
private val PROPERTIES = arrayOf(
LAYOUT_WIDTH,
LAYOUT_HEIGHT,
POSITION_X,
POSITION_Y,
SCALE_X,
SCALE_Y
)
}
constructor() : super()
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun getTransitionProperties(): Array<String> {
return PROPERTIES
}
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
resetValues(transitionValues.view)
captureValues(transitionValues)
}
private fun captureValues(transitionValues: TransitionValues) = with(transitionValues.view) {
transitionValues.values[LAYOUT_WIDTH] = width.toFloat()
transitionValues.values[LAYOUT_HEIGHT] = height.toFloat()
transitionValues.values[POSITION_X] = x
transitionValues.values[POSITION_Y] = y
transitionValues.values[SCALE_X] = scaleX
transitionValues.values[SCALE_Y] = scaleY
}
private fun resetValues(view: View) = with(view) {
translationX = 0f
translationY = 0f
scaleX = 1f
scaleY = 1f
}
override fun createAnimator(
sceneRoot: ViewGroup,
start: TransitionValues?,
end: TransitionValues?
): Animator? {
if (start == null || end == null) {
return null
}
val startWidth = start.values[LAYOUT_WIDTH] as Float
val endWidth = end.values[LAYOUT_WIDTH] as Float
val startHeight = start.values[LAYOUT_HEIGHT] as Float
val endHeight = end.values[LAYOUT_HEIGHT] as Float
val startX = start.values[POSITION_X] as Float
val endX = end.values[POSITION_X] as Float
val startY = start.values[POSITION_Y] as Float
val endY = end.values[POSITION_Y] as Float
val startScaleX = start.values[SCALE_X] as Float
val startScaleY = start.values[SCALE_Y] as Float
end.view.translationX = (startX - endX) - (endWidth - startWidth) / 2
end.view.translationY = (startY - endY) - (endHeight - startHeight) / 2
end.view.scaleX = (startWidth / endWidth) * startScaleX
end.view.scaleY = (startHeight / endHeight) * startScaleY
return ObjectAnimator.ofPropertyValuesHolder(end.view,
ofFloat(View.TRANSLATION_X, 0f),
ofFloat(View.TRANSLATION_Y, 0f),
ofFloat(View.SCALE_X, 1f),
ofFloat(View.SCALE_Y, 1f)).apply {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
resetValues(start.view)
resetValues(end.view)
}
})
}
}
}