FloatingActionButton 展开成一个新的活动

Posted

技术标签:

【中文标题】FloatingActionButton 展开成一个新的活动【英文标题】:FloatingActionButton expand into a new activity 【发布时间】:2015-10-15 23:26:16 【问题描述】:

android 材料设计原则页面上,其中一个示例显示 FAB 扩展到新的全屏。 (在“全屏”下)

http://www.google.com/design/spec/components/buttons-floating-action-button.html#buttons-floating-action-button-transitions

我尝试在我的应用中实现相同的效果,但收效甚微。 我设法使用以下代码作为参考创建了一个扩展为视图的 FAB:https://gist.github.com/chris95x8/882b5c5d0aa2096236ba。

它有效,但我想知道是否可以将相同的效果应用于活动转换。我试过自己查找并玩它,但找不到任何可能有用的东西。

我知道我可以将 FAB 扩展为一个片段,而不是一个全新的活动,但我不确定这是否是正在做的事情,以及这是否是最佳的。

所以我的问题是,有没有办法将 fab-expanding 显示效果实现为活动转换,还是应该只是显示一个新片段?

【问题讨论】:

您在材料设计指南中链接的内容绝对是Activity 过渡。使用Fragment 完全没有意义。 我从 Github 下载了你的代码。我没有看到任何与浮动操作按钮相关的代码。它在哪里。我认为您应该发布相关代码而不是让人们下载它们(这很麻烦)。并且很少/有一些解释。 回答您的问题,据我所知,浮动操作按钮与特定片段或活动无关。您想要上面的示例项目吗? @TheOriginalAndroid 感谢您的评论!正如我所说,这不是我的代码,而是我用作参考的代码。我正在寻找的是使用与我发布的 github 链接中的视图相同的动画,仅作为活动转换。我找不到这样的例子,我对android的了解还不足以达到那个效果。 很抱歉,代码参考对读者没有帮助,反而会误导读者。谷歌的网页和动画就足够了。但是我不知道您现有的代码是什么样的,如果有的话。无论如何,我发布了一个答案开始。由于时间限制,希望我们能在这方面迅速取得进展。 【参考方案1】:

我正在开发一个将FloatingActionButton 扩展为新Activity 的应用程序。我不确定你是否喜欢我的实现,但请先看图片:

所以第一张图片显示MainActivity,最后一张显示SecondActivity,这是从FAB“扩展”而来的。

现在,我想提一下 我实际上并没有扩展一个 FAB 到一个新的 Activity,但我可以让用户感觉到新页面是从那个 FAB 扩展的,我认为这对开发者和用户来说都足够了。

这里是实现:

准备工作:

    当然是FloatingActionButton, 访问https://github.com/kyze8439690/RevealLayout 并将此库导入您的项目。它用于播放显示动画。它有一个自定义的BakedBezierInterpolator 来控制显示动画并使其具有材质样式。

步骤:

    像这样创建activity_main.xml:

    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_
        android:layout_>
    
        <!--Your main content here-->
    
        <RevealLayout
            android:id="@+id/reveal_layout"
            android:layout_
            android:layout_
            android:visibility="invisible">
    
            <View
                android:id="@+id/reveal_view"
                android:layout_
                android:layout_
                android:visibility="invisible"/>
    
        </RevealLayout>
    
    </FrameLayout>
    

    查找视图:

    mRevealLayout = (RevealLayout) findViewById(R.id.reveal_layout);
    mRevealView = findViewById(R.id.reveal_view);
    

    当用户点击 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.
        
    );
    

    这里是 res/anim 中的 hold.xml:

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

就是这样。

改进:

    RevealLayout 对于 API 17(Android 4.2) 下的设备有一个错误(播放矩形而不是圆形显示动画),你可以在它的构造函数中添加这些行:

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) 
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    
    

    如果您的SecondActivity 包含复杂的内容,那么在layout.xml 中用作reveal_view 的简单View 是不够的/完美的。您可以在RevealLayoutreveal_layout 中包含第二个布局。如果第二个布局每次都不相同,这似乎很浪费且难以控制。但对我来说,会的。因此,如果需要,您可以进行其他改进。

    如果要实现与 Material Design Guide 中显示的完全相同的动画,可以将 RevealLayout 的 layout_height 设置为特定数字,而不是 match_parent。展开动画结束后(或动画播放后一段时间,应该使动画的整个过程流畅),然后你可以动画translationY。重要的一点是,通过控制动画时长在视觉上欺骗用户。

最后,这是我自己的经验/尝试,我是开发 Android 应用程序的初学者。 如果有任何错误/进一步改进,请留下 cmets/编辑我的答案。谢谢。

【讨论】:

这是一个有趣的库,您在详细的帖子上做得很好。然而,实现这一点似乎需要做很多工作,尤其是与使用 FragmentTransactions 和动画方法相比。 @The Original Android 是的,我同意。但是,当您选择使用片段展开时,很难让您将动画展开全屏。您只能在状态栏下方和导航栏上方为更改设置动画。或许你可以将style设置为windowTranslucentStatus/Navigation,这样每一个动画都可以真正的全屏显示,但是会导致“adjustResize不起作用”等其他问题,可能无法解决。另一方面,问题是“FAB 扩展为 Activity”----作者确实说过他想扩展为 Activity 而不是 Fragment,这是他的要求。 @ywwynm 感谢您的帖子,我考虑过以与您相同的方式实现效果,但正如您所说,这是一种肮脏的方式,我觉得很令人失望。我一直在寻找可能已将效果实现为活动转换但找不到任何应用程序,并希望有一种干净的 SDK-ish 方式来做到这一点。不幸的是,我想要么做你所做的,要么使用带有片段的单个活动是要走的路。感谢您抽出宝贵时间回答! 很抱歉 RevealLayout 有硬件加速错误,因为我在 Genymotion 上测试了较低的 api 版本,它似乎有效。我会尽快修复它。 @yugy 感谢您的简单而华丽的图书馆。当我回答这个问题时,我并不太关心开源世界,所以我忘了向你提交一个问题。很抱歉。【参考方案2】:

我根据这个问题 Circular reveal transition for new activity 制作了一个自定义活动,它在活动完成时处理 CircularRevealAnimation 和他的反向效果:

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();


您可以使用自己的活动来扩展它,并像这样调用 onCreate 方法“showRevealEffect”:

@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);


您还必须使用像这样的透明主题:

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

最后,要启动此活动,您应该通过额外的坐标传递动画应该开始的位置:

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

【讨论】:

【参考方案3】:

有人从Plaid调查了activity之间的transition实现。她的示例是通过https://github.com/hujiaweibujidao/FabDialogMorph 发布的。

简而言之,她通过以下方式过境两项活动:

    FAB 作为共享元素。 目标 Activity 中的布局与 FAB 具有相同的android:transitionName。 为了平滑动画,实现并应用了MorphDrawable(扩展自Drawable)和MorphTransition(扩展自ChangeBounds)。

【讨论】:

【参考方案4】:

你可以使用这个库 [https://github.com/sergiocasero/RevealFAB][1]

[1]: https://github.com/sergiocasero/RevealFAB 3rd 方,简单易用

添加到您的布局

<RelativeLayout...>

    <android.support.design.widget.CoordinatorLayout...>
        <!-- YOUR CONTENT -->
    </android.support.design.widget.CoordinatorLayout>

    <com.sergiocasero.revealfab.RevealFAB
        android:id="@+id/reveal_fab"
        android:layout_
        android:layout_
        app:fab_color="@color/colorAccent"
        app:fab_icon="@drawable/ic_add_white_24dp"
        app:reveal_color="@color/colorAccent" />
</RelativeLayout>

重要提示:此组件高于您的内容。如果需要,您可以使用 Coordinator、LinearLayout... 或其他相对布局:)

如您所见,您有 3 个自定义属性用于自定义颜色和图标

设置意图信息:

revealFAB = (RevealFAB) findViewById(R.id.reveal_fab);
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
revealFAB.setIntent(intent);

revealFAB.setOnClickListener(new RevealFAB.OnClickListener() 
    @Override
    public void onClick(RevealFAB button, View v) 
        button.startActivityWithAnimation();
    
);

不要忘记调用 onResume() 方法!

@Override
protected void onResume() 
    super.onResume();
    revealFAB.onResume();

【讨论】:

以上是关于FloatingActionButton 展开成一个新的活动的主要内容,如果未能解决你的问题,请参考以下文章

关于FloatingActionButton

FloatingActionButton的使用

FloatingActionButton的使用

FloatingActionButton的使用

带有支持库的 FloatingActionButton 示例

悬浮按钮FloatingActionButton