Sur la page Android principes de conception des matériaux, l'un des exemples montre un FAB se développant dans un nouveau plein écran. (Sous "Plein écran")
J'ai essayé d'implémenter le même effet dans mon application, mais avec peu de succès.
J'ai réussi à créer un FAB qui se développe dans une vue en utilisant ce code comme référence: https://Gist.github.com/chris95x8/882b5c5d0aa2096236ba .
Cela a fonctionné, mais je me demandais si je pouvais appliquer le même effet à une transition d'activité. J'ai essayé de le rechercher et de jouer avec lui-même, mais je n'ai rien trouvé qui puisse fonctionner.
Je sais que je pourrais faire du FAB un fragment et non pas une toute nouvelle activité, mais je ne sais pas si c'est ce qui se fait, et si c'est optimal ou pas.
Et donc ma question est, existe-t-il un moyen de mettre en œuvre l'effet de révélation de l'expansion fab comme une transition d'activité, ou est-il censé révéler simplement un nouveau fragment?
Je développe une application qui développe un FloatingActionButton
en un nouveau Activity
. Je ne suis pas sûr que si vous aimez mon implémentation, mais veuillez d'abord voir les photos:
Ainsi, la première image montre MainActivity
et la dernière montre SecondActivity
, qui est "développée" à partir de FAB.
Maintenant, je veux mentionner que je ne suis pas réellement en expansion un FAB dans un nouveau Activity
mais je peux laisser l'utilisateur sentir que le nouvelle page est développée à partir de ce FAB , et je pense que cela suffit pour les développeurs et les utilisateurs.
Voici la mise en œuvre:
Préparation:
FloatingActionButton
bien sûr,BakedBezierInterpolator
personnalisé pour contrôler l'animation de révélation et lui donner un style matériel.Étapes:
créez activity_main.xml comme ceci:
<FrameLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<!--Your main content here-->
<RevealLayout
Android:id="@+id/reveal_layout"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:visibility="invisible">
<View
Android:id="@+id/reveal_view"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:visibility="invisible"/>
</RevealLayout>
</FrameLayout>
trouver des vues:
mRevealLayout = (RevealLayout) findViewById(R.id.reveal_layout);
mRevealView = findViewById(R.id.reveal_view);
développer lorsque l'utilisateur clique sur FAB:
mFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mFab.setClickable(false); // Avoid naughty guys clicking FAB again and again...
int[] location = new int[2];
mFab.getLocationOnScreen(location);
location[0] += mFab.getWidth() / 2;
location[1] += mFab.getHeight() / 2;
final Intent intent = new Intent(MainActivity.this, SecondActivity.class);
mRevealView.setVisibility(View.VISIBLE);
mRevealLayout.setVisibility(View.VISIBLE);
mRevealLayout.show(location[0], location[1]); // Expand from center of FAB. Actually, it just plays reveal animation.
mFab.postDelayed(new Runnable() {
@Override
public void run() {
startActivity(intent);
/**
* Without using R.anim.hold, the screen will flash because of transition
* of Activities.
*/
overridePendingTransition(0, R.anim.hold);
}
}, 600); // 600 is default duration of reveal animation in RevealLayout
mFab.postDelayed(new Runnable() {
@Override
public void run() {
mFab.setClickable(true);
mRevealLayout.setVisibility(View.INVISIBLE);
mViewToReveal.setVisibility(View.INVISIBLE);
}
}, 960); // Or some numbers larger than 600.
}
});
Et voici hold.xml dans res/anim:
<set
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:shareInterpolator="false">
<translate
Android:duration="960" <!-- Enough-large time is OK -->
Android:fromXDelta="0%"
Android:fromYDelta="0%"
Android:toXDelta="0%"
Android:toYDelta="0%"/>
</set>
C'est tout.
Améliorations:
RevealLayout
a un bug (joue l'animation de révélation rectangulaire au lieu de circulaire) pour les appareils sous API 17 (Android 4.2), vous pouvez ajouter ces lignes dans le constructeur de celui-ci:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
Si votre SecondActivity
contient un contenu compliqué, un simple View
utilisé comme révélation_view dans le layout.xml n'est pas suffisant/parfait. Vous pouvez inclure la deuxième disposition à l'intérieur de RevealLayout
Révéler_layout. Il semble inutile et difficile de contrôler si la deuxième mise en page n'apparaîtra pas à chaque fois. Mais pour moi, ça le sera. Vous pouvez donc apporter d'autres améliorations si vous le souhaitez.
Si vous souhaitez implémenter une animation totalement identique à celle indiquée dans le Material Design Guide, vous pouvez définir la hauteur de disposition de RevealLayout
en un nombre spécifique au lieu de match_parent. Une fois l'expansion de l'animation terminée (ou quelque temps après la lecture de l'animation, ce qui devrait faciliter le processus d'animation), vous pouvez alors animer translationY. Le point important est de tromper visuellement les utilisateurs en contrôlant la durée de l'animation.
Enfin, c'est ma propre expérience/tentative et je suis un débutant dans le développement d'applications Android. S'il y a des erreurs/améliorations, veuillez laisser des commentaires/modifier ma réponse Merci.
J'ai fait une activité personnalisée, basée sur cette question Transition de révélation circulaire pour une nouvelle activité , qui gère le CircularRevealAnimation et son effet inverse lorsque l'activité se termine:
public class RevealActivity extends AppCompatActivity {
private View revealView;
public static final String REVEAL_X="REVEAL_X";
public static final String REVEAL_Y="REVEAL_Y";
public void showRevealEffect(Bundle savedInstanceState, final View rootView) {
revealView=rootView;
if (savedInstanceState == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Lollipop) {
rootView.setVisibility(View.INVISIBLE);
ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();
if(viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
circularRevealActivity(rootView);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
}
}
}
@TargetApi(Build.VERSION_CODES.Lollipop)
private void circularRevealActivity(View rootView) {
int cx = getIntent().getIntExtra(REVEAL_X, 0);
int cy = getIntent().getIntExtra(REVEAL_Y, 0);
float finalRadius = Math.max(rootView.getWidth(), rootView.getHeight());
// create the animator for this view (the start radius is zero)
Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootView, cx, cy, 0, finalRadius);
circularReveal.setDuration(400);
// make the view visible and start the animation
rootView.setVisibility(View.VISIBLE);
circularReveal.start();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case Android.R.id.home: onBackPressed();break;
return super.onOptionsItemSelected(item);
}
}
@Override
public void onBackPressed() {
destroyActivity(revealView);
}
private void destroyActivity(View rootView) {
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.Lollipop)
destroyCircularRevealActivity(rootView);
else
finish();
}
@TargetApi(Build.VERSION_CODES.Lollipop)
private void destroyCircularRevealActivity(final View rootView) {
int cx = getIntent().getIntExtra(REVEAL_X, 0);
int cy = getIntent().getIntExtra(REVEAL_Y, 0);
float finalRadius = Math.max(rootView.getWidth(), rootView.getHeight());
// create the animator for this view (the start radius is zero)
Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootView, cx, cy, finalRadius, 0);
circularReveal.setDuration(400);
circularReveal.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
rootView.setVisibility(View.INVISIBLE);
finishAfterTransition();
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
// make the view visible and start the animation
rootView.setVisibility(View.VISIBLE);
circularReveal.start();
}
}
Vous pouvez étendre cela avec votre propre activité et appeler dans votre onCreate la méthode 'showRevealEffect' comme ceci:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_activity_layout);
//your code
View root= findViewById(R.id.your_root_id);
showRevealEffect(savedInstanceState, root);
}
Vous devez également utiliser un thème transparent comme celui-ci:
<style name="Theme.Transparent" parent="Theme.AppCompat.Light.NoActionBar">
<item name="Android:windowIsTranslucent">true</item>
<item name="Android:windowBackground">@Android:color/transparent</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorControlNormal">@Android:color/white</item>
</style>
En fin de compte, pour lancer cette activité, vous devez passer via les coordonnées supplémentaires où l'animation doit commencer:
int[] location = new int[2];
fab.getLocationOnScreen(location);
Intent intent = new Intent(this, YourRevealActivity.class);
intent.putExtra(SearchActivity.REVEAL_X, location[0]);
intent.putExtra(SearchActivity.REVEAL_Y, location[1]);
startActivity(intent);
Quelqu'un a étudié la mise en œuvre de la transition entre les activités de Plaid . Son exemple a été publié via https://github.com/hujiaweibujidao/FabDialogMorph .
En bref, elle transite deux activités avec:
Android:transitionName
comme FAB.MorphDrawable
(étendu de Drawable
) et MorphTransition
(étendu de ChangeBounds
) sont implémentés et appliqués.