CannyViewAnimator:状態を美しく切り替える

みなさんこんにちは! 私はアニメーションを扱うのが本当に好きです-私が作成している、または見ているすべてのAndroidアプリケーションで、私はカップルのための場所を見つけるでしょう。 それほど遠くない2016年4月、アニメーションクラスの種類に関する私の記録から、 Live Typing社のブログが公開され始め、その後、次のOmsk IT subbotnikでアニメーションに関するプレゼンテーションを行いました 。 この記事では、CannyViewAnimatorライブラリを紹介し、その開発プロセスに没頭します。 ビューの可視性を美しく切り替えるために必要です。 ライブラリ、またはその作成の歴史に興味がある場合、または少なくとも私が遭遇した興味深い問題とその解決策に興味がある場合は、記事へようこそ!







私たちは何について話しているのですか



しかし、最初に、Android開発でありふれた状況を想像してみましょう。 画面があり、その上にサーバーからのリストがあります。 美しいサーバーから美しいデータがロードされている間、ローダーを表示します。 データが到着するとすぐに、それらを確認します。空の場合はスタブを表示し、そうでない場合は実際にデータを表示します。

UIでこの状況を解決する方法は? 以前は、Live Typingで次のソリューションを使用していましたが、これはかつてU2020をスパイし、 U2020 MVPに転送しました。これはViewAnimatorから継承されたBetterViewAnimator Viewです。 BetterViewAnimatorとその祖先の唯一の重要な違いは、リソースIDを操作できることです。 しかし、彼は完璧ではありません。







ViewAnimatorとは何ですか?

ViewAnimatorは、FrameLayoutを継承し、特定の時点で子の1つだけが表示されるビューです。 表示される子を切り替えるには、一連のメソッドがあります。







BetterViewAnimatorの重要な欠点は、古いAnimationFrameworkでのみ機能することです。 この状況では、CannyViewAnimatorが助けになります。 AnimatorとAppCompat Transitionの使用をサポートしています。

Githubのプロジェクトへのリンク









それがすべて始まった方法



次の「リストローダースタブ」画面の開発中に、私たちはもちろんBetterViewAnimatorを使用することを考えましたが、何らかの理由でそれを使用しません。ほとんどの主要機能であるアニメーションです。 楽観的に、私はアニメーションを追加することに決め、忘れていたものにつまずきました。ViewAnimatorはアニメーションでのみ動作します。 残念ながら、Githubでの代替の検索は失敗しました-価値のあるものはなく、 Android View Controllerしかありませんでしたが、絶対に柔軟性がなく、8つのアニメーションのみが事前にサポートされています。 これはただ一つのことを意味しました:あなたはすべてを自分で書く必要があります。







何を手に入れたいですか



私が最初に決めたのは、最終的に何を得たいかを考えることでした。









私は自分の欲望を決定し、将来のプロジェクトの「アーキテクチャ」について考え始めました。 次の3つの部分が判明しました。









2つのパラメーターを持つインターフェイスを使用して、アニメーターとトランジションをセットアップすることにしました。子が表示され、子が表示されなくなります。 表示される子が変更されるたびに、必要なアニメーションがインターフェイス実装から取得されます。 3つのインターフェイスがあります。









トランジションは、表示および非表示のすべての子にすぐに重ねられるため、トランジションのみのインターフェースを作成することにしました。 コンセプトが考え出され、私は開発を始めました。







ViewAnimator



基本クラスではあまり考えず、SDKのViewAnimatorでカーボンコピーを作成することにしました。 それらの多くは私にとって冗長であるように思われたので、私はそれを使ってAnimationの仕事を捨て、その中のメソッドを最適化しました。 また、BetterViewAnimatorからメソッドを追加することも忘れませんでした。 それを操作するための重要なメソッドの最終リストは次のとおりです。









少し考えた後、現在表示されている子の位置をonSaveInstanceState()に追加して保存し、onRestoreInstanceState(Parcelable状態)に復元してすぐに表示することにしました。

結果のコードは次のとおりです。







ViewAnimator
public class ViewAnimator extends FrameLayout { private int lastWhichIndex = 0; public ViewAnimator(Context context) { super(context); } public ViewAnimator(Context context, AttributeSet attrs) { super(context, attrs); } public void setDisplayedChildIndex(int inChildIndex) { if (inChildIndex >= getChildCount()) { inChildIndex = 0; } else if (inChildIndex < 0) { inChildIndex = getChildCount() - 1; } boolean hasFocus = getFocusedChild() != null; int outChildIndex = lastWhichIndex; lastWhichIndex = inChildIndex; changeVisibility(getChildAt(inChildIndex), getChildAt(outChildIndex)); if (hasFocus) { requestFocus(FOCUS_FORWARD); } } public void setDisplayedChildId(@IdRes int id) { if (getDisplayedChildId() == id) { return; } for (int i = 0, count = getChildCount(); i < count; i++) { if (getChildAt(i).getId() == id) { setDisplayedChildIndex(i); return; } } throw new IllegalArgumentException("No view with ID " + id); } public void setDisplayedChild(View view) { setDisplayedChildId(view.getId()); } public int getDisplayedChildIndex() { return lastWhichIndex; } public View getDisplayedChild() { return getChildAt(lastWhichIndex); } public int getDisplayedChildId() { return getDisplayedChild().getId(); } protected void changeVisibility(View inChild, View outChild) { outChild.setVisibility(INVISIBLE); inChild.setVisibility(VISIBLE); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); if (getChildCount() == 1) { child.setVisibility(View.VISIBLE); } else { child.setVisibility(View.INVISIBLE); } if (index >= 0 && lastWhichIndex >= index) { setDisplayedChildIndex(lastWhichIndex + 1); } } @Override public void removeAllViews() { super.removeAllViews(); lastWhichIndex = 0; } @Override public void removeView(View view) { final int index = indexOfChild(view); if (index >= 0) { removeViewAt(index); } } @Override public void removeViewAt(int index) { super.removeViewAt(index); final int childCount = getChildCount(); if (childCount == 0) { lastWhichIndex = 0; } else if (lastWhichIndex >= childCount) { setDisplayedChildIndex(childCount - 1); } else if (lastWhichIndex == index) { setDisplayedChildIndex(lastWhichIndex); } } @Override public void removeViewInLayout(View view) { removeView(view); } @Override public void removeViews(int start, int count) { super.removeViews(start, count); if (getChildCount() == 0) { lastWhichIndex = 0; } else if (lastWhichIndex >= start && lastWhichIndex < start + count) { setDisplayedChildIndex(lastWhichIndex); } } @Override public void removeViewsInLayout(int start, int count) { removeViews(start, count); } @Override protected void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); lastWhichIndex = ss.lastWhichIndex; setDisplayedChildIndex(lastWhichIndex); } @Override protected Parcelable onSaveInstanceState() { SavedState savedState = new SavedState(super.onSaveInstanceState()); savedState.lastWhichIndex = lastWhichIndex; return savedState; } public static class SavedState extends View.BaseSavedState { int lastWhichIndex; SavedState(Parcelable superState) { super(superState); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(this.lastWhichIndex); } @Override public String toString() { return "ViewAnimator.SavedState{" + "lastWhichIndex=" + lastWhichIndex + '}'; } public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { @Override public SavedState createFromParcel(Parcel source) { return new SavedState(source); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; protected SavedState(Parcel in) { super(in); this.lastWhichIndex = in.readInt(); } } }
      
      





Githubリンク







TransitionViewAnimator



ViewAnimatorを使い終えた後、私はかなり単純ですが、それほど面白くないタスクに進みました。移行をサポートすることです。 作業の本質はこれです。オーバーライドされたメソッドchangeVisibility(View inChild、View outChild)を呼び出すと、アニメーションが準備されます。 遷移は、インターフェースを使用して指定されたCannyTransitionから取得され、クラスフィールドに記録されます。







CannyTransition
 public interface CannyTransition { Transition getTransition(View inChild, View outChild); }
      
      





次に、別の方法で、この移行が開始されます。 将来の基盤を備えた別のメソッドを起動することにしました-実際、TransitionはTransitionManager.beginDelayedTransitionメソッドを使用して起動され、これにはいくつかの制限があります。 結局、Transitionは、TransitionManager.beginDelayedTransitionを呼び出した後、一定期間プロパティを変更したビューに対してのみ実行されます。 将来的には比較的長時間続くアニメーターの導入を計画しているため、Visibilityを変更する直前にTransitionManager.beginDelayedTransitionを呼び出す必要があります。 それでは、 super.changeVisibility(inChild、outChild)を呼び出します。 、目的の子の可視性を変更します。







TransitionViewAnimator
 public class TransitionViewAnimator extends ViewAnimator { private CannyTransition cannyTransition; private Transition transition; public TransitionViewAnimator(Context context) { super(context); } public TransitionViewAnimator(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void changeVisibility(View inChild, View outChild) { prepareTransition(inChild, outChild); startTransition(); super.changeVisibility(inChild, outChild); } protected void prepareTransition(View inChild, View outChild) { if (cannyTransition != null) { transition = cannyTransition.getTransition(inChild, outChild); } } public void startTransition() { if (transition != null) { TransitionManager.beginDelayedTransition(this, transition); } } public void setCannyTransition(CannyTransition cannyTransition) { this.cannyTransition = cannyTransition; } }
      
      





Githubリンク







CannyViewAnimator



だから私はメイン層に着きました。 当初は、Animatorを制御するためにLayoutTransitionを使用したかったのですが、松葉杖を使用せずに並行してアニメーションを実行することは不可能であるという私の夢はクラッシュしました。 LayoutTransitionのその他の欠点は、AnimatorSetの継続時間を設定する必要性、手動による中断の不可能性など、追加の問題を引き起こしました。独自の作業ロジックを作成することにしました。 すべてが非常にシンプルに見えました。消える子に対してAnimatorを起動し、最後にVisibility.GONEを設定して 、表示されている子をすぐに表示し、Animatorを実行します。







それから最初の問題に出くわしました: 接続されていないビュー (まだonAttachを実行していないか、すでにonDetachで動作しているビューに対してAnimatorを起動できません 。 これにより、onAttachの前に動作するコンストラクターまたはその他のメソッドで子の可視性を変更できませんでした。 これが必要になる可能性のあるさまざまな状況を予測し、Githubの問題も少なくないことを予想して、私はそれを試して修正することにしました。 残念ながら、 isAttachedToWindow()メソッドの呼び出しという形での最も単純なソリューションは、バージョン19のAPIを呼び出すことが不可能であるため、14のAPIをサポートしたかったのです。







ただし、ViewにはOnAttachStateChangeListenerがあり、使用に失敗しませんでした。 void addViewメソッド(View child、int index、ViewGroup.LayoutParams params)を再定義し、追加された各ビューでこのリスナーをハングさせました。 次に、HashMapにビュー自体へのリンクと、その状態を示すブール変数を配置します。 onViewAttachedToWindow(View v)が機能する場合はtrueに設定し、 onViewDetachedFromWindow(View v)の場合はfalseに設定します。 これで、Animatorを起動する直前に、Viewのステータスを確認し、Animatorを起動するかどうかを決定できました。







最初の「バリケード」を克服した後、アニメーターを受信するための2つのインターフェイス、InAnimatorとOutAnimatorを作成しました。







アニメーター
 public interface InAnimator { Animator getInAnimator(View inChild, View outChild); }
      
      





アウトアニメーター
 public interface OutAnimator { Animator getOutAnimator(View inChild, View outChild); }
      
      





新しい問題に直面するまで、すべてがスムーズに進みました。Animatorの実行後、ビュー状態を復元する必要があります







StackOverflowに対する答えが見つかりませんでした。 30分間のブレーンストーミングの後、ValueAnimatorの逆メソッドを使用して、期間をゼロに設定することにしました。







  if (animator instanceof ValueAnimator) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { animation.removeListener(this); animation.setDuration(0); ((ValueAnimator) animation).reverse(); } }); }
      
      





それは助けになり、StackOverflowにまさに答えさえしました。







この問題を解決した直後に、別の問題が発生しました。 ビューがMeasureでまだ実行していない場合、CircularRevealAnimatorはアニメーションを実行しません。







ViewAnimatorの非表示の子にはVisibility.GONEがあるため、これは悪いニュースでした。 これは、別の種類の可視性(VISIBLEまたはINVISIBLE)にさらされるまで測定されないことを意味します。 アニメーションを開始する前にVisibilityをINVISIBLEに変更しても、問題は解決しません。 ビューの寸法はフレームのレンダリング中に測定され、フレームは非同期に描画されるため、Animatorが開始するまでにビューが測定される保証はありません。 遅延を設定したり、onPreDrawListenerを使用したくなかったので、デフォルトではVisibility.GONEではなくVisibility.INVISIBLEを使用することにしました。







ホラーシーンは、ビューが膨らんでどのように測定されるかに基づいて頭をスクロールしました(まったく必要ありませんが)。これには視覚的な遅れが伴います。 そのため、Visibility.INVISIBLEおよびVisibility.GONEを10ビューおよびネスト5にして、膨張時間を測定する小さなテストを実施することにしました。テストでは、差が1ミリ秒を超えないことが示されました。 携帯電話がより強力になったことに気づかなかったか、Androidが最適化されたかはわかりませんが、余分なVisibility.INVISIBLEがパフォーマンスに悪影響を与えたことを漠然と思い出します。 さて、問題は解決しました。







前の「戦い」から回復する時間がないので、私は次へと急ぎました。 FrameLayoutでは子が互いの上にあるため、InAnimatorとOutAnimatorが同時に実行されると、子のインデックスに応じてアニメーションの外観が異なる場合に状況が発生します。

アニメーターの実装で発生したすべての問題のために、それらを終了したかったのですが、「開始したばかり-終了した」という感覚が私を前進させました。 この問題は、現在表示されているビューの下にあるビューを表示しようとすると発生します。 このため、絶滅アニメーションは外観アニメーションと完全に重なり、その逆も同様です。 解決策を探して、私は他のViewGroupsを使用して、Zプロパティで遊んでみて、他のことを試しました。







最後に、単にコンテナから目的のビューを削除し、上部に追加し、アニメーションの最後にもう一度削除して元の場所に戻すというアイデアが、アニメーションの最初に現れました。 アイデアはうまくいきましたが、弱いデバイスではアニメーションがクラッシュしました。 ビューを削除または追加すると、 requestLayout()がそれ自体およびその親で呼び出さ再カウントおよび再描画されるため、中断が発生しました。 ViewGroupクラスの荒野に登らなければなりませんでした。 数分勉強した後、ViewGroup内のViewの順序は1つの配列のみに依存し、ViewGroupの相続人(FrameLayoutやLinearLayoutなど)が表示方法をすでに決定しているという結論に達しました。 残念なことに、配列とその操作方法はプライベートとマークされていました。 しかし、良いニュースがありました。Javaでは、Java Reflectionがあるため、これは問題ではありません。 Java Reflectionを使用して、配列を操作する方法を使用し、必要なビューの位置を直接制御できるようになりました。 結果はこのメソッドです:







 public void bringChildToPosition(View child, int position) { final int index = indexOfChild(child); if (index < 0 && position >= getChildCount()) return; try { Method removeFromArray = ViewGroup.class.getDeclaredMethod("removeFromArray", int.class); removeFromArray.setAccessible(true); removeFromArray.invoke(this, index); Method addInArray = ViewGroup.class.getDeclaredMethod("addInArray", View.class, int.class); addInArray.setAccessible(true); addInArray.invoke(this, child, position); Field mParent = View.class.getDeclaredField("mParent"); mParent.setAccessible(true); mParent.set(child, this); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } }
      
      





このメソッドは、ビューに必要な位置を設定します。 これらの操作の最後に再描画を呼び出す必要はありません-アニメーションがそれを行います。 これで、アニメーションを開始する前に、必要なビューを配置し、アニメーションの最後に戻すことができました。 これで、CannyViewAnimatorストーリーの主要部分は終了しました。







CannyViewAnimator
 public class CannyViewAnimator extends TransitionViewAnimator { public static final int SEQUENTIALLY = 1; public static final int TOGETHER = 2; private int animateType = SEQUENTIALLY; @Retention(RetentionPolicy.SOURCE) @IntDef({SEQUENTIALLY, TOGETHER}) @interface AnimateType { } public static final int FOR_POSITION = 1; public static final int IN_ALWAYS_TOP = 2; public static final int OUT_ALWAYS_TOP = 3; private int locationType = FOR_POSITION; @Retention(RetentionPolicy.SOURCE) @IntDef({FOR_POSITION, IN_ALWAYS_TOP, OUT_ALWAYS_TOP}) @interface LocationType { } private List<? extends InAnimator> inAnimator; private List<? extends OutAnimator> outAnimator; private final Map<View, Boolean> attachedList = new HashMap<>(getChildCount()); public CannyViewAnimator(Context context) { super(context); } public CannyViewAnimator(Context context, AttributeSet attrs) { super(context, attrs); } @SafeVarargs public final <T extends InAnimator> void setInAnimator(T... inAnimators) { setInAnimator(Arrays.asList(inAnimators)); } public void setInAnimator(List<? extends InAnimator> inAnimators) { this.inAnimator = inAnimators; } @SafeVarargs public final <T extends OutAnimator> void setOutAnimator(T... outAnimators) { setOutAnimator(Arrays.asList(outAnimators)); } public void setOutAnimator(List<? extends OutAnimator> outAnimators) { this.outAnimator = outAnimators; } @Override protected void changeVisibility(View inChild, View outChild) { if (attachedList.get(outChild) && attachedList.get(inChild)) { AnimatorSet animatorSet = new AnimatorSet(); Animator inAnimator = mergeInAnimators(inChild, outChild); Animator outAnimator = mergeOutAnimators(inChild, outChild); prepareTransition(inChild, outChild); switch (animateType) { case SEQUENTIALLY: animatorSet.playSequentially(outAnimator, inAnimator); break; case TOGETHER: animatorSet.playTogether(outAnimator, inAnimator); break; } switch (locationType) { case FOR_POSITION: addOnStartVisibleListener(inAnimator, inChild); addOnEndInvisibleListener(outAnimator, outChild); break; case IN_ALWAYS_TOP: addOnStartVisibleListener(inAnimator, inChild); addOnEndInvisibleListener(inAnimator, outChild); addOnStartToTopOnEndToInitPositionListener(inAnimator, inChild); break; case OUT_ALWAYS_TOP: addOnStartVisibleListener(outAnimator, inChild); addOnEndInvisibleListener(outAnimator, outChild); addOnStartToTopOnEndToInitPositionListener(outAnimator, outChild); break; } animatorSet.start(); } else { super.changeVisibility(inChild, outChild); } } private AnimatorSet mergeInAnimators(final View inChild, final View outChild) { AnimatorSet animatorSet = new AnimatorSet(); List<Animator> animators = new ArrayList<>(inAnimator.size()); for (InAnimator inAnimator : this.inAnimator) { if (inAnimator != null) { Animator animator = inAnimator.getInAnimator(inChild, outChild); if (animator != null) { animators.add(animator); } } } animatorSet.playTogether(animators); return animatorSet; } private AnimatorSet mergeOutAnimators(final View inChild, final View outChild) { AnimatorSet animatorSet = new AnimatorSet(); List<Animator> animators = new ArrayList<>(outAnimator.size()); for (OutAnimator outAnimator : this.outAnimator) { if (outAnimator != null) { Animator animator = outAnimator.getOutAnimator(inChild, outChild); if (animator != null) animators.add(animator); } } animatorSet.playTogether(animators); addRestoreInitValuesListener(animatorSet); return animatorSet; } private void addRestoreInitValuesListener(AnimatorSet animatorSet) { for (Animator animator : animatorSet.getChildAnimations()) { if (animator instanceof ValueAnimator) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { animation.removeListener(this); animation.setDuration(0); ((ValueAnimator) animation).reverse(); } }); } } } private void addOnStartVisibleListener(Animator animator, final View view) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { startTransition(); view.setVisibility(VISIBLE); } }); } private void addOnEndInvisibleListener(Animator animator, final View view) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { startTransition(); view.setVisibility(INVISIBLE); } }); } private void addOnStartToTopOnEndToInitPositionListener(Animator animator, final View view) { final int initLocation = indexOfChild(view); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { bringChildToPosition(view, getChildCount() - 1); } @Override public void onAnimationEnd(Animator animation) { bringChildToPosition(view, initLocation); } }); } public int getAnimateType() { return animateType; } public void setAnimateType(@AnimateType int animateType) { this.animateType = animateType; } public int getLocationType() { return locationType; } public void setLocationType(@LocationType int locationType) { this.locationType = locationType; } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { attachedList.put(child, false); child.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { attachedList.put(v, true); } @Override public void onViewDetachedFromWindow(View v) { attachedList.put(v, false); } }); super.addView(child, index, params); } @Override public void removeAllViews() { attachedList.clear(); super.removeAllViews(); } @Override public void removeView(View view) { attachedList.remove(view); super.removeView(view); } @Override public void removeViewAt(int index) { attachedList.remove(getChildAt(index)); super.removeViewAt(index); } @Override public void removeViews(int start, int count) { for (int i = start; i < start + count; i++) { attachedList.remove(getChildAt(i)); } super.removeViews(start, count); } }
      
      





Github







XMLサポートとヘルパークラスの追加



新しい課題:XMLを使用してカスタマイズを追加します。 私はXMLでAnimatorを作成するのは本当に好きではないので(判読が難しく、明らかではないようです)、フラグを使用して設定できる標準アニメーションのセットを作成することにしました。 さらに、このアプローチにより、Javaコードを使用してアニメーションを簡単に定義できます。 CircularRevalAnimatorの作成方法は標準のものとは異なるため、通常のプロパティ用とCircularReval用の2種類のヘルパークラスを作成する必要がありました。

結果は6つのクラスです:







プロパティキャニー
 class PropertyCanny { Animator propertyAnimator; public PropertyCanny(PropertyValuesHolder... holders) { this.propertyAnimator = ObjectAnimator.ofPropertyValuesHolder(holders); } public PropertyCanny(Property<?, Float> property, float start, float end) { this.propertyAnimator = ObjectAnimator.ofFloat(null, property, start, end); } public PropertyCanny(String propertyName, float start, float end) { this.propertyAnimator = ObjectAnimator.ofFloat(null, propertyName, start, end); } public Animator getPropertyAnimator(View child) { propertyAnimator.setTarget(child); return propertyAnimator.clone(); } }
      
      





PropertyIn
 public class PropertyIn extends PropertyCanny implements InAnimator { public PropertyIn(PropertyValuesHolder... holders) { super(holders); } public PropertyIn(Property<?, Float> property, float start, float end) { super(property, start, end); } public PropertyIn(String propertyName, float start, float end) { super(propertyName, start, end); } public PropertyIn setDuration(long millis) { propertyAnimator.setDuration(millis); return this; } @Override public Animator getInAnimator(View inChild, View outChild) { return getPropertyAnimator(inChild); } }
      
      





Propertyout
 public class PropertyOut extends PropertyCanny implements OutAnimator { public PropertyOut(PropertyValuesHolder... holders) { super(holders); } public PropertyOut(Property<?, Float> property, float start, float end) { super(property, start, end); } public PropertyOut(String propertyName, float start, float end) { super(propertyName, start, end); } public PropertyOut setDuration(long millis) { propertyAnimator.setDuration(millis); return this; } @Override public Animator getOutAnimator(View inChild, View outChild) { return getPropertyAnimator(outChild); } }
      
      





キャニーを明らかにする
 class RevealCanny { private final int gravity; public RevealCanny(int gravity) { this.gravity = gravity; } @SuppressLint("RtlHardcoded") protected int getCenterX(View view) { final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK; if (horizontalGravity == Gravity.LEFT) { return 0; } else if (horizontalGravity == Gravity.RIGHT) { return view.getWidth(); } else { // (Gravity.CENTER_HORIZONTAL) return view.getWidth() / 2; } } protected int getCenterY(View view) { final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; if (verticalGravity == Gravity.TOP) { return 0; } else if (verticalGravity == Gravity.BOTTOM) { return view.getHeight(); } else { // (Gravity.CENTER_VERTICAL) return view.getHeight() / 2; } } public int getGravity() { return gravity; } }
      
      





レベリン
 @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class RevealIn extends RevealCanny implements InAnimator { public RevealIn(int gravity) { super(gravity); } @Override public Animator getInAnimator(View inChild, View outChild) { float inRadius = (float) Math.hypot(inChild.getWidth(), inChild.getHeight()); return ViewAnimationUtils.createCircularReveal(inChild, getCenterX(inChild), getCenterY(inChild), 0, inRadius); } }
      
      





明らかにする
 @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class RevealOut extends RevealCanny implements OutAnimator { public RevealOut(int gravity) { super(gravity); } @Override public Animator getOutAnimator(View inChild, View outChild) { float outRadius = (float) Math.hypot(outChild.getWidth(), outChild.getHeight()); return ViewAnimationUtils.createCircularReveal(outChild, getCenterX(outChild), getCenterY(outChild), outRadius, 0); } }
      
      





彼らの助けにより、アニメーションの初期化がより簡単でエレガントになりました。 代わりに:







 animator.setInAnimator(new InAnimator() { @Override public Animator getInAnimator(View inChild, View outChild) { return ObjectAnimator.ofFloat(inChild, View.ALPHA, 0, 1); } }); animator.setOutAnimator(new OutAnimator() { @Override public Animator getOutAnimator(View inChild, View outChild) { return ObjectAnimator.ofFloat(outChild, View.ALPHA, 1, 0); } });
      
      





あなたは簡単に書くことができます:







  animator.setInAnimator(new PropertyIn(View.ALPHA, 0, 1)); animator.setOutAnimator(new PropertyOut(View.ALPHA, 1, 0));
      
      





ラムダ式を使用するよりもさらに同情的でした。 次に、これらのクラスを使用して、標準アニメーションの2つのリストを作成しました。1つはProperty- PropertyAnimators用、もう1つはCircularReaval- RevealAnimators用です。 次に、フラグの助けを借りて、これらのリストでXMLの位置を見つけて置換しました。 CircularRevealAnimatorはAndroid 5以降でのみ動作するため。 2つではなく4つのパラメーターを作成する必要がありました。









次に、XMLからパラメーターを解析するとき、システムのバージョンを判別します。 5.0より高い場合は、値をinおよびoutから取得します。 以下の場合、pre_lollipop_inおよびpre_lollipop_outから。 バージョンが5.0より低いが、pre_lollipop_inおよびpre_lollipop_outが設定されていない場合、値はinおよびoutから取得されます。







多くの問題にもかかわらず、私はまだCannyViewAnimatorを正常に完了しました。 一般的に、奇妙なことは、ウィッシュリストのいくつかを実現するたびに、Java Reflectionを使用して深くクロールする必要があることです。 これは、Android SDKに何か問題があるか、私がやりたいことを示唆しています。 アイデアや提案があれば、コメントしてください。

もう一度、以下のプロジェクトへのリンクを繰り返します。

Githubのプロジェクトへのリンク







みなさんさようなら!








All Articles