2016-05-29 77 views
6

我试图使用转换API来为两个ViewGroups之间的共享元素设置动画效果。目标是绿色观点“走出其父母的界限”走向新的位置。使用嵌套共享元素的场景转换

enter image description hereenter image description hereenter image description here

我有以下布局:

first.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 

    <FrameLayout 
    android:layout_width="200dp" 
    android:layout_height="200dp" 
    android:background="#f00" /> 

    <FrameLayout 
    android:layout_width="200dp" 
    android:layout_height="200dp" 
    android:layout_alignParentBottom="true" 
    android:layout_alignParentRight="true" 
    android:background="#00f"> 

    <View 
     android:id="@+id/myview" 
     android:layout_width="100dp" 
     android:layout_height="100dp" 
     android:layout_gravity="center" 
     android:background="#0f0" /> 

    </FrameLayout> 
</RelativeLayout> 

second.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 

    <FrameLayout 
    android:layout_width="200dp" 
    android:layout_height="200dp" 
    android:background="#f00"> 

    <View 
     android:id="@+id/myview" 
     android:layout_width="100dp" 
     android:layout_height="100dp" 
     android:layout_gravity="center" 
     android:background="#0f0" /> 

    </FrameLayout> 

    <FrameLayout 
    android:layout_width="200dp" 
    android:layout_height="200dp" 
    android:layout_alignParentBottom="true" 
    android:layout_alignParentRight="true" 
    android:background="#00f" /> 

</RelativeLayout> 

但是,我不能得到这个好好工作。默认转换只是淡化了所有内容,ChangeBounds转换完全没有效果,ChangeTransform也不正确。

我正在使用的代码:

protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    final ViewGroup root = (ViewGroup) findViewById(android.R.id.content); 

    setContentView(R.layout.first); 
    View myView1 = findViewById(R.id.myview); 
    myView1.setTransitionName("MYVIEW"); 

    new Handler().postDelayed(new Runnable() { 
     @Override 
     public void run() { 
     View second = LayoutInflater.from(MainActivity.this).inflate(R.layout.second, root, false); 
     View myView2 = second.findViewById(R.id.myview); 
     myView2.setTransitionName("MYVIEW"); 

     Scene scene = new Scene(root, second); 
     Transition transition = new ChangeTransform(); 
     transition.addTarget("MYVIEW"); 
     transition.setDuration(3000); 

     TransitionManager.go(scene, transition); 
     } 
    }, 2000); 
    } 

现在我可以手动通过使用ViewOverlay创建动画自己做到这一点。不过,我正在使用Transitions API寻找解决方案。这甚至有可能吗?

此外,我不希望'扁平'的层次结构。我故意嵌套视图来解决更复杂的用例。

回答

0

ChangeBounds类有一个不赞成的setReparent(Boolean)方法,它指的是ChangeTransform“处理不同父母之间的转换”。这个ChangeTransform类没有给出所期望的效果根本就没有

从我们can see,如果reparent设置为true(加上一些其他条件),则sceneroot的覆盖来增加覆盖的ChangeBounds source。不过由于某些原因,这不起作用:可能是因为它已被弃用。

我已经能够通过扩展ChangeBounds使用覆盖时,我想(在科特林)来解决此问题:

class OverlayChangeBounds : ChangeBounds() { 

    override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? { 
     if (startValues == null || endValues == null) return super.createAnimator(sceneRoot, startValues, endValues) 

     val startView = startValues.view 
     val endView = endValues.view 

     if (endView.id in targetIds || targetNames?.contains(endView.transitionName) == true) { 
      startView.visibility = View.INVISIBLE 
      endView.visibility = View.INVISIBLE 

      endValues.view = startValues.view.toImageView() 
      sceneRoot.overlay.add(endValues.view) 

      return super.createAnimator(sceneRoot, startValues, endValues)?.apply { 
       addListener(object : AnimatorListenerAdapter() { 
        override fun onAnimationEnd(animation: Animator) { 
         endView.visibility = View.VISIBLE 
         sceneRoot.overlay.remove(endValues.view) 
        } 
       }) 
      } 
     } 

     return super.createAnimator(sceneRoot, startValues, endValues) 
    } 
} 

private fun View.toImageView(): ImageView { 
    val v = this 
    val drawable = toBitmapDrawable() 
    return ImageView(context).apply { 
     setImageDrawable(drawable) 
     scaleType = ImageView.ScaleType.CENTER_CROP 
     layout(v.left, v.top, v.right, v.bottom) 
    } 
} 

private fun View.toBitmapDrawable(): BitmapDrawable { 
    val b = Bitmap.createBitmap(layoutParams.width, layoutParams.height, Bitmap.Config.ARGB_8888); 
    draw(Canvas(b)); 
    return BitmapDrawable(resources, b) 
} 
+0

虽然这可能是这对你的作品的解决方案,我建议考虑进去一看我的回答并考虑去的Android转换API的用途。 – keyboardsurfer

1

是,重排根可能与Android过渡API。 重建视图时需要考虑一个主要限制:

通常,每个子视图只允许绘制其父界限。这会导致视图在碰到其初始父项的边界时消失,并在一段时间后重新出现在新父项中。

通过在所有相关父母上设置android:clipChildren="false"来关闭相关场景的视图层次结构的子剪辑。

在更复杂的层次结构(如适配器支持的视图)中,通过TransitionListener动态切换子剪辑的性能更高。

您还必须使用ChangeTransform才能恢复该视图,而不是ChangeBounds或将其添加到TransitionSet

在这个转换级别中,不需要转换名称或目标,因为框架会自行解决这个问题。如果事情变得更加复杂,你会希望有任何

  • 匹配的资源ID或
  • 匹配过渡名称

参与过渡意见。

+0

啊谢谢,在这种情况下'clipChildren'就是诀窍。为什么不使用叠加?从'ChangeTransform.setReparentWithOverlay'文档:_Sets是否更改为父应使用覆盖或不。当父变更未使用叠加层时,会影响子项的变换。 *默认值为true * ._ – nhaarman

+0

在进行过渡之前和之后,请查看Android设备监视器。转换后层次结构中应该有一个'ViewOverlay'。 – keyboardsurfer

0

这里是我的这种情况下的解决方法(它只是在单独的类ChangeBounds的一部分)

import android.animation.Animator; 
import android.animation.AnimatorListenerAdapter; 
import android.animation.ObjectAnimator; 
import android.animation.PropertyValuesHolder; 
import android.annotation.TargetApi; 
import android.graphics.Bitmap; 
import android.graphics.Canvas; 
import android.graphics.Path; 
import android.graphics.PointF; 
import android.graphics.Rect; 
import android.graphics.drawable.BitmapDrawable; 
import android.graphics.drawable.Drawable; 
import android.os.Build; 
import android.transition.Transition; 
import android.transition.TransitionValues; 
import android.util.Property; 
import android.view.View; 
import android.view.ViewGroup; 

@TargetApi(Build.VERSION_CODES.LOLLIPOP) 
public class AbsoluteChangeBounds extends Transition { 

    private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX"; 
    private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY"; 

    private int[] tempLocation = new int[2]; 

    private static final Property<Drawable, PointF> DRAWABLE_ORIGIN_PROPERTY = 
      new Property<Drawable, PointF>(PointF.class, "boundsOrigin") { 
       private Rect mBounds = new Rect(); 

       @Override 
       public void set(Drawable object, PointF value) { 
        object.copyBounds(mBounds); 
        mBounds.offsetTo(Math.round(value.x), Math.round(value.y)); 
        object.setBounds(mBounds); 
       } 

       @Override 
       public PointF get(Drawable object) { 
        object.copyBounds(mBounds); 
        return new PointF(mBounds.left, mBounds.top); 
       } 
      }; 

    @Override 
    public void captureStartValues(TransitionValues transitionValues) { 
     captureValues(transitionValues); 
    } 

    @Override 
    public void captureEndValues(TransitionValues transitionValues) { 
     captureValues(transitionValues); 
    } 

    private void captureValues(TransitionValues values) { 
     View view = values.view; 
     if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) { 
      values.view.getLocationInWindow(tempLocation); 
      values.values.put(PROPNAME_WINDOW_X, tempLocation[0]); 
      values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]); 
     } 
    } 

    @Override 
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { 
     if (startValues == null || endValues == null) { 
      return null; 
     } 

     final View view = endValues.view; 
     sceneRoot.getLocationInWindow(tempLocation); 
     int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0]; 
     int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1]; 
     int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0]; 
     int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1]; 
     // TODO: also handle size changes: check bounds and animate size changes 
     if (startX != endX || startY != endY) { 
      final int width = view.getWidth(); 
      final int height = view.getHeight(); 
      Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 
      Canvas canvas = new Canvas(bitmap); 
      view.draw(canvas); 
      final BitmapDrawable drawable = new BitmapDrawable(bitmap); 
      view.setAlpha(0); 
      drawable.setBounds(startX, startY, startX + width, startY + height); 
      sceneRoot.getOverlay().add(drawable); 
      Path topLeftPath = getPathMotion().getPath(startX, startY, endX, endY); 
      PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
        DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath); 
      ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin); 
      anim.addListener(new AnimatorListenerAdapter() { 
       @Override 
       public void onAnimationEnd(Animator animation) { 
        sceneRoot.getOverlay().remove(drawable); 
        view.setAlpha(1); 
       } 
      }); 
      return anim; 
     } 
     return null; 
    } 

}