Search code examples
androidandroid-transitionsandroid-viewgroup

Does Recolor transition not work for VIewGroup?


I have a scene transition and I want to use Recolor animation to change background of a ViewGroup from transparent to another color. Here is my login_to_register.xml:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
   <fade android:fadingMode="fade_out"/>
   <fade android:fadingMode="fade_in">
      <targets>
         <target android:targetId="@id/passwordEditor" />
         <target android:targetId="@id/loginNameEditor" />
      </targets>
   </fade>
   <!--<changeTransform />-->
   <changeBounds />
   <recolor>
      <targets>
         <target android:targetId="@id/loginOptions" />
      </targets>
   </recolor>
</transitionSet>

This is the XML for the layout of the fragment, which is inside the ViewPager.

<FrameLayout
      android:layout_width="match_parent" android:layout_height="match_parent">

      <ImageView
         android:layout_width="match_parent" android:layout_height="match_parent"
         android:scaleType="centerCrop"
         android:src="@drawable/onboarding_bg"
         />

      <LinearLayout
         android:id="@+id/loginOptions"
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginLeft="@dimen/activity_horizontal_margin"
         android:layout_marginRight="@dimen/activity_horizontal_margin"
         android:background="@android:color/transparent"
         android:layout_gravity="bottom"
         android:gravity="center_horizontal"
         >
         <android.support.design.widget.TextInputLayout
            android:id="@+id/loginNameEditor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/email"
            app:errorEnabled="true"
            app:hintTextAppearance="@style/BlackFloatingTextTextAppearance"
            android:visibility="gone"
            tools:visibility="visible"
            >
            <android.support.design.widget.TextInputEditText
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:text="@={regModel.emailAddress, default=`twinkle`}"
               android:inputType="textEmailAddress"
               android:maxLength="254"
               android:onFocusChange="@{(v, focus) -> context.loginFocusChanged(true, focus)}"
               android:afterTextChanged="@{() -> context.loginTextChanged()}"
               />
         </android.support.design.widget.TextInputLayout>

         <android.support.design.widget.TextInputLayout
            android:id="@+id/passwordEditor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/password"
            app:passwordToggleEnabled="true"
            app:errorEnabled="true"
            app:hintTextAppearance="@style/BlackFloatingTextTextAppearance"
            app:counterEnabled="true"
            app:counterMaxLength="50"
            android:visibility="gone"
            tools:visibility="visible"
            >
            <android.support.design.widget.TextInputEditText
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:text="@={regModel.password}"
               android:inputType="textPassword"
               android:fontFamily="monospace"
               android:onFocusChange="@{(v, focus) -> context.loginFocusChanged(false, focus)}"
               android:afterTextChanged="@{() -> context.loginTextChanged()}"
               />
         </android.support.design.widget.TextInputLayout>

         <TextView
            android:id="@+id/error"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingStart="4dp"
            android:paddingEnd="4dp"
            android:textAppearance="@style/TextAppearance.Design.Error"
            android:gravity="start"
            android:visibility="gone"
            tools:visibility="visible"
            />

         <!--<Button-->
         <!--android:id="emailAuthButton"-->
         <!--android:layout_width="wrap_content"-->
         <!--android:layout_height="wrap_content"-->
         <!--android:layout_marginTop="@dimen/loginButtonSpacing"-->
         <!--android:text="@string/login"-->
         <!--style="@style/PrimaryColorButtonBorderlessStyle"-->
         <!--android:onClick="loginEmail"-->
         <!--/>-->

         <com.google.android.gms.common.SignInButton
            android:id="@+id/googButton"
            android:layout_width="230dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/loginButtonSpacing"
            android:layout_marginTop="@dimen/loginButtonSpacing"
            android:onClick="loginGoogle"
            app:buttonSize="wide"
            />

         <com.facebook.login.widget.LoginButton
            android:id="@+id/fbButton"
            android:layout_width="220dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/loginButtonSpacing"
            app:com_facebook_login_text="@string/com_facebook_loginview_log_in_button"
            android:layout_marginBottom="@dimen/loginButtonSpacing"
            android:paddingTop="12dp"
            android:paddingBottom="12dp"
            android:onClick="loginFB"
            />

         <TextView
            android:id="@+id/emailInfo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:layout_marginTop="@dimen/register_row_spacing"
            android:layout_marginStart="10dp"
            android:text="@string/unauth_email_prompt"
            android:textColor="?android:attr/textColorPrimaryInverse"
            />

         <Button
            android:id="@+id/unfederatedButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/loginButtonSpacing"
            android:layout_marginBottom="@dimen/loginButtonSpacing"
            android:text="@string/create_account"
            style="@style/ScalableBorderlessButton"
            android:textColor="@color/colorAccent"
            android:minHeight="53dp"
            android:onClick="showUnfederatedUI"
            />
         <TextView
            android:id="@+id/forgotPassword"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="start"
            android:layout_marginTop="15dp"
            android:paddingTop="@dimen/smallLinkTopPadding"
            android:paddingBottom="@dimen/smallLinkBottomPadding"
            android:paddingLeft="@dimen/smallLinkHPadding"
            android:paddingRight="@dimen/smallLinkHPadding"
            app:hypertext="@{R.string.forgot_pass}"
            android:background="?android:attr/selectableItemBackground"
            tools:text="@string/forgot_pass"
            android:textSize="@dimen/smallLinkTextSize"
            android:onClick="recoverPassword"
            android:visibility="gone"
         />
      </LinearLayout>
   </FrameLayout>

Now the view that I want to use Recolor is the id loginOptions. You can see that I am giving its specific targetId in the transition. And it is defaulting to transparent in case there are any issues with starting from null. So here is the code to start the scene transition

    val trans = TransitionInflater.from(this).inflateTransition(R.transition.login_to_register)
    TransitionManager.beginDelayedTransition(this.dataBinding.root, trans)
    this.loginBinding!!.loginNameEditor.visibility = View.VISIBLE
    this.loginBinding!!.passwordEditor.visibility = View.VISIBLE
    this.loginBinding!!.error.visibility = View.VISIBLE
    this.loginBinding!!.googButton.visibility = View.GONE
    this.loginBinding!!.fbButton.visibility = View.GONE
    this.loginBinding!!.emailInfo.visibility = View.GONE
    this.loginBinding!!.forgotPassword.visibility = View.VISIBLE

    val color = this.resources.getColor(R.color.almostWhite)
    this.loginBinding!!.loginOptions.background = ColorDrawable(color)

I am not including the activity layout of the ViewPager, it is straightforward, and probably doesn't have any issues, because all the other transitions work! Yes, all the VISIBILITY changes are working properly, and the bounds changes. What's wrong with the Recolor? Edit: Let me add that, if I remove <recolor>, the background of @id/loginOptions does change to almostWhite.


Solution

  • This reported to Google issue tracker,there has a solution that works for me, which is why I'm accepting this.

    To use a custom transition in a transition xml do :

    <transition 
            class="com.example.BackgroundRecolor" 
            android:duration="200"/> 
    

    Add constructors from Transition in your custom transition to support xml attributes :

    public BackgroundRecolor() {} 
    
    public BackgroundRecolor(Context context, AttributeSet attrs) { 
        super(context, attrs); 
    }
    

    [Update] The code file on the issuetracker is "deleted", here is the full contents of this code as it is in my project:

    public class BackgroundRecolor extends Transition {
        public BackgroundRecolor() {}
    
        public BackgroundRecolor(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        private static final String PROPNAME_BACKGROUND = "com.example:recolor:background";
    
        private void captureValues(TransitionValues transitionValues) {
            Drawable bg = transitionValues.view.getBackground();
            if (bg != null && bg instanceof ColorDrawable) {
                transitionValues.values.put(PROPNAME_BACKGROUND, ((ColorDrawable) bg).getColor());
            }
        }
    
        @Override
        public void captureStartValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }
    
        @Override
        public void captureEndValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }
    
        @Override
        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
            if (startValues == null || endValues == null) {
                return null;
            }
    
            if (startValues.values.get(PROPNAME_BACKGROUND) == null || !(startValues.values.get(PROPNAME_BACKGROUND) instanceof Integer))
                return null;
            if (endValues.values.get(PROPNAME_BACKGROUND) == null || !(endValues.values.get(PROPNAME_BACKGROUND) instanceof Integer))
                return null;
    
            int colorStart = (int) startValues.values.get(PROPNAME_BACKGROUND);
            int colorEnd = (int) endValues.values.get(PROPNAME_BACKGROUND);
    
            Drawable bg = endValues.view.getBackground();
            if (!(bg instanceof ColorDrawable))
                return null;
    
            ColorDrawable bgc = (ColorDrawable) bg;
            bgc.setColor(colorStart);
            return ObjectAnimator.ofObject(bgc, "color", new ArgbEvaluator(), colorStart, colorEnd);
        }
    
    }