How can I scale textviews using shared element transitions? How can I scale textviews using shared element transitions? android android

How can I scale textviews using shared element transitions?


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).


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


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