2016-05-29 18 views
6

私は、Transitions APIを使用して2つのViewGroup間で共有要素をアニメーション化しようとしています。目標は、緑色のビューが新しい位置に向かって '親の境界から外れて'移動することです。ネストされた共有要素を使用したシーン遷移

enter image description hereenter image description hereenter image description here

私は、次のようなレイアウトがあります。しかし、私はこれを取得することはできません

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

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を正しく機能します。デフォルトのトランジションではすべてがフェードアウトしますが、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クラスは、 "異なる親の間の遷移を処理する" ChangeTransformを参照する非推奨のsetReparent(Boolean)メソッドを持っています。このChangeTransformクラスは、所望の効果を与えない。。我々はreparenttrue(プラスいくつかの他の条件)に設定されている場合、scenerootのオーバーレイがオーバーレイを追加するために使用される、ということcan seeChangeBounds sourceから

。しかし何らかの理由で、これはうまくいきません。恐らくそれが非難されているという事実のためです。

私は私がしたいとき(Kotlinに)オーバーレイを使用する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で可能です。 ビューを再作成するときに考慮する重要な制限が1つあります。

通常、すべての子ビューはその親の境界内にのみ描画できます。これにより、最初の親の境界線に当たったときに表示が消え、しばらくして新しい親に再び表示されます。

関連するすべての親にandroid:clipChildren="false"を設定して、関連するシーンのビュー階層を一番上にして子クリッピングをオフにします。

アダプタバックビューなどのより複雑な階層では、子クリッピングをTransitionListenerを介して動的に切り替える方がパフォーマンスが向上します。

ChangeBoundsの代わりにChangeTransformを使用するか、TransitionSetに追加する必要があります。

この移行レベルでは、フレームワークがそれ自体を理解するので、遷移名やターゲットは必要ありません。物事をより複雑になります場合は、

  • 一致するリソースIDまたは
  • マッチング遷移名の移行に参加するビューの

のいずれかを持っているしたいと思います。ここ

+0

ああ、ありがとう、 'clipChildren'はこの場合のトリックです。なぜそれはオーバーレイを使用しないのですか? 'ChangeTransform.setReparentWithOverlay' doc:から親に変更がオーバーレイを使用するかどうかを設定します。親の変更がオーバーレイを使用しない場合、それは子の変換に影響します。 *デフォルト値は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; 
    } 

} 
関連する問題