How can I scale textviews using shared element transitions?

后端 未结 5 1276
广开言路
广开言路 2020-12-07 17:58

I am able to get TextViews to transition perfectly between two activities using ActivityOptions.makeSceneTransitionAnimation. However I want to make the text sc

相关标签:
5条回答
  • 2020-12-07 18:27

    I used solution from Alex Lockwood and simplified the use (it's only for TextSize of a TextView), I hope this will help:

    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);
        }
    }
    

    and the class 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);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-07 18:42

    This was covered in one of the Google I/O 2016 talks. The source for the transition which you can copy into your code is found here. If your IDE complains the addTarget(TextView.class); requires API 21, just remove the constructor and add the target either dynamically or in your xml.

    i.e. (note this is in Kotlin)

    val textResizeTransition = TextResize().addTarget(view.findViewById(R.id.text_view))
    
    0 讨论(0)
  • 2020-12-07 18:42

    If you look at how ChangeBounds works, it operates on the left/right/top/bottom properties of the view.

    What I expect is that you'll need to use the same text size across the two activities, and use the scaleX and scaleY properties in your launched activity to modify the text size as needed. Then, use a combination of ChangeBounds and ChangeTransform in your TransitionSet.

    0 讨论(0)
  • 2020-12-07 18:46

    Edit:

    As pointed out by Kiryl Tkach in the comments below, there is a better solution described in this Google I/O talk.


    You can create a custom transition that animates a TextView's text size as follows:

    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);
        }
    }
    

    Since changing the TextView's text size will cause its layout bounds to change during the course of the animation, getting the transition to work properly will take a little more effort than simply throwing a ChangeBounds transition into the same TransitionSet. What you will need to do instead is manually measure/layout the view in its end state in a SharedElementCallback.

    I've published an example project on GitHub that illustrates the concept (note that the project defines two Gradle product flavors... one uses Activity Transitions and the other uses Fragment Transitions).

    0 讨论(0)
  • 2020-12-07 18:46

    My solutions for the TransitionAnimation, not quite on the topic but close, perhaps with modifications or just someone come in handy.

    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)
                    }
                })
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题