Menu expansível do Botão de ação flutuante (FAB) da Biblioteca de suporte de design do Android

172

Agora que a Android Design Support Library está pronta, alguém sabe como implementar o menu Fab expandido, como o Fab no Inbox App?

Deve ficar assim:

insira a descrição da imagem aqui

EkKoZ
fonte
Eu já verifique toda a documentação, mas aparentemente não há nenhum sinal do menu FAB :(
EkKoZ
8
Você pode dar uma olhada nesta biblioteca FloatingActionButton .
Markus Rubey
3
@ MarkusRubey obrigado, na verdade esse é o que eu estou usando no momento, é só que eu queria fazer com o nativo, mas aparentemente ainda não é possível.
EkKoZ
Existem muitas bibliotecas de código aberto, que podem fazer o trabalho. Verifique este: github.com/futuresimple/android-floating-action-button
capt.swag

Respostas:

143

Atualmente, não há nenhum widget fornecido na Biblioteca de Design. A única maneira de fazer isso de maneira rápida e fácil é usando bibliotecas de terceiros.

Obviamente, você também pode fazer isso usando a Biblioteca de projetos, mas será um trabalho enorme e exigirá muito tempo. Mencionei algumas bibliotecas úteis, que podem ajudá-lo a conseguir isso.

  1. Botão flutuante rápido (wangjiegulu)
  2. Botão de ação flutuante (clãs)
  3. Botão de ação flutuante (makovkastar)
  4. Botão de ação flutuante do Android (futuro)

Eu estou usando o quarto.

Aritra Roy
fonte
31
bem, a ideia era fazê-lo com a biblioteca de design nativa, eu sei que existem muitas bibliotecas que já fazem esse recurso. Obrigado pela sua resposta.
EkKoZ
Você encontrou algum problema com o quarto? O meu não está aparecendo em dispositivos pirulito +. Também a cor sobreposição de fundo eo texto não está funcionando
Ajith Memana
@AjithMemana Nope. Meu aplicativo está em produção para mais de 100 mil usuários e nunca tive nenhum problema, como você mencionou.
Aritra Roy
Você poderia me fornecer um link para seu aplicativo? Preciso de algo semelhante ao mostrado acima, além de um fundo translúcido que cubra a tela quando o botão é clicado.
Ajith Memana
2
Como uma nota, Clãs e makovkastar não são mais suportados. Eu suspeito que isso se deva a melhorias na biblioteca de design da FAB
12560 Honest Objections
162

Tem uma abordagem melhor para implementar o menu FAB de animação sem usar nenhuma biblioteca ou para escrever um enorme código xml para animações. espero que isso ajude no futuro para alguém que precise de uma maneira simples de implementar isso.

Apenas usando a animate().translationY()função, você pode animar qualquer visualização para cima ou para baixo, exatamente como fiz no meu código abaixo, verifique o código completo no github . Caso esteja procurando o mesmo código no kotlin, você pode fazer o checkout do menu de animação do FAB do repositório de códigos kotlin .

primeiro defina todas as suas FAB no mesmo local para que elas se sobreponham; lembre-se de que a FAB deve ser o que você deseja clicar e mostrar a outra. por exemplo:

    <android.support.design.widget.FloatingActionButton
    android:id="@+id/fab3"
    android:layout_width="@dimen/standard_45"
    android:layout_height="@dimen/standard_45"
    android:layout_gravity="bottom|end"
    android:layout_margin="@dimen/standard_21"
    app:srcCompat="@android:drawable/ic_btn_speak_now" />

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab2"
    android:layout_width="@dimen/standard_45"
    android:layout_height="@dimen/standard_45"
    android:layout_gravity="bottom|end"
    android:layout_margin="@dimen/standard_21"
    app:srcCompat="@android:drawable/ic_menu_camera" />

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab1"
    android:layout_width="@dimen/standard_45"
    android:layout_height="@dimen/standard_45"
    android:layout_gravity="bottom|end"
    android:layout_margin="@dimen/standard_21"
    app:srcCompat="@android:drawable/ic_dialog_map" />

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="@dimen/fab_margin"
    app:srcCompat="@android:drawable/ic_dialog_email" />

Agora, na sua classe java, basta definir todo o seu FAB e executar o clique como mostrado abaixo:

 FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab1 = (FloatingActionButton) findViewById(R.id.fab1);
    fab2 = (FloatingActionButton) findViewById(R.id.fab2);
    fab3 = (FloatingActionButton) findViewById(R.id.fab3);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if(!isFABOpen){
                showFABMenu();
            }else{
                closeFABMenu();
            }
        }
    });

Use o animation().translationY()para animar o seu FAB, prefiro que você use o atributo desse método no DP, pois apenas o uso de um int afetará a compatibilidade de exibição com resolução mais alta ou mais baixa. como mostrado abaixo:

 private void showFABMenu(){
    isFABOpen=true;
    fab1.animate().translationY(-getResources().getDimension(R.dimen.standard_55));
    fab2.animate().translationY(-getResources().getDimension(R.dimen.standard_105));
    fab3.animate().translationY(-getResources().getDimension(R.dimen.standard_155));
}

private void closeFABMenu(){
    isFABOpen=false;
    fab1.animate().translationY(0);
    fab2.animate().translationY(0);
    fab3.animate().translationY(0);
}

Agora defina a dimensão acima mencionada em res-> values-> dimens.xml, como mostrado abaixo:

    <dimen name="standard_55">55dp</dimen>
<dimen name="standard_105">105dp</dimen>
<dimen name="standard_155">155dp</dimen>

Esperamos que esta solução ajude as pessoas no futuro, que estão procurando uma solução simples.

EDITADO

Se você quiser adicionar um rótulo ao FAB, basta pegar um LinearLayout horizontal e colocar o FAB com textview como rótulo e animar os layouts se encontrar algum problema, verifique o meu código de exemplo no github, eu lidei com toda a compatibilidade com versões anteriores problemas nesse código de exemplo. verifique meu código de exemplo para o FABMenu no Github

para fechar o FAB no Backpress, substitua onBackPress () conforme mostrado abaixo:

    @Override
public void onBackPressed() {
    if(!isFABOpen){
        this.super.onBackPressed();
    }else{
        closeFABMenu();
    }
}

A captura de tela tem o título também com a FAB, porque eu a retiro do meu exemplo de aplicativo presente ingithub

insira a descrição da imagem aqui

HAXM
fonte
8
até mesmo adicionar rótulos ao menu também é simples, basta adicionar o FAB dentro de um Linearlayout com algum texto como rótulo e, depois disso, animar todo o layout linear, não se esqueça de ocultar e mostrar o layout linear na função abrir e fechar
HAXM
1
mas o prashant criou um XML separado para animação, mas minha solução não precisa de nenhum XML extra para animação, portanto, acredite que minha resposta é melhor, pois não precisa de uma linha de código extra para animar a exibição.
HAXM 24/11
2
Essa é a melhor resposta. Você pode usar o FAB real da biblioteca de design e isso não é tão complicado.
Stephane Mathis
3
o que eu também gostei dessa abordagem é que, como estamos usando o Android FAB, muito do trabalho de base já foi feito. por exemplo, em vez de escrever o comportamento de exibição personalizado a partir do zero (desde bibliotecas não se estendia FAB), você pode simplesmente estender o comportamento nativa fab, que é um enorme monte de código já está disponível
RJFares
1
@droidHeaven dar uma FrameLayout e coloque todo o seu interior FAB que
HAXM
31
  • Primeiro, crie os layouts de menu no arquivo xml do layout de atividade. Por exemplo, um layout linear com orientação horizontal e inclua um TextView para etiqueta e um Botão de ação flutuante ao lado do TextView.

  • Crie os layouts de menu conforme sua necessidade e número.

  • Crie um botão de ação flutuante base e, ao clicar, altere a visibilidade dos layouts de menu.

Por favor, verifique o código abaixo para referência e para mais informações confira meu projeto no github

Projeto de checkout do Github

<android.support.constraint.ConstraintLayout
            android:id="@+id/activity_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context="com.app.fabmenu.MainActivity">

            <android.support.design.widget.FloatingActionButton
                android:id="@+id/baseFloatingActionButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="16dp"
                android:layout_marginEnd="16dp"
                android:layout_marginRight="16dp"
                android:clickable="true"
                android:onClick="@{FabHandler::onBaseFabClick}"
                android:tint="@android:color/white"
                app:fabSize="normal"
                app:layout_constraintBottom_toBottomOf="@+id/activity_main"
                app:layout_constraintRight_toRightOf="@+id/activity_main"
                app:srcCompat="@drawable/ic_add_black_24dp" />

            <LinearLayout
                android:id="@+id/shareLayout"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="12dp"
                android:layout_marginEnd="24dp"
                android:layout_marginRight="24dp"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:visibility="invisible"
                app:layout_constraintBottom_toTopOf="@+id/createLayout"
                app:layout_constraintLeft_toLeftOf="@+id/createLayout"
                app:layout_constraintRight_toRightOf="@+id/activity_main">

                <TextView
                    android:id="@+id/shareLabelTextView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="8dp"
                    android:layout_marginRight="8dp"
                    android:background="@drawable/shape_fab_label"
                    android:elevation="2dp"
                    android:fontFamily="sans-serif"
                    android:padding="5dip"
                    android:text="Share"
                    android:textColor="@android:color/white"
                    android:typeface="normal" />


                <android.support.design.widget.FloatingActionButton
                    android:id="@+id/shareFab"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:clickable="true"
                    android:onClick="@{FabHandler::onShareFabClick}"
                    android:tint="@android:color/white"
                    app:fabSize="mini"
                    app:srcCompat="@drawable/ic_share_black_24dp" />

            </LinearLayout>

            <LinearLayout
                android:id="@+id/createLayout"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="24dp"
                android:layout_marginEnd="24dp"
                android:layout_marginRight="24dp"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:visibility="invisible"
                app:layout_constraintBottom_toTopOf="@+id/baseFloatingActionButton"
                app:layout_constraintRight_toRightOf="@+id/activity_main">

                <TextView
                    android:id="@+id/createLabelTextView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="8dp"
                    android:layout_marginRight="8dp"
                    android:background="@drawable/shape_fab_label"
                    android:elevation="2dp"
                    android:fontFamily="sans-serif"
                    android:padding="5dip"
                    android:text="Create"
                    android:textColor="@android:color/white"
                    android:typeface="normal" />

                <android.support.design.widget.FloatingActionButton
                    android:id="@+id/createFab"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:clickable="true"
                    android:onClick="@{FabHandler::onCreateFabClick}"
                    android:tint="@android:color/white"
                    app:fabSize="mini"
                    app:srcCompat="@drawable/ic_create_black_24dp" />

            </LinearLayout>

        </android.support.constraint.ConstraintLayout>

Essas são as animações

Animação de abertura do menu FAB:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
    android:duration="300"
    android:fromXScale="0"
    android:fromYScale="0"
    android:interpolator="@android:anim/linear_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1"
    android:toYScale="1" />
<alpha
    android:duration="300"
    android:fromAlpha="0.0"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:toAlpha="1.0" />

</set>

Animação de encerramento do menu FAB:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
    android:duration="300"
    android:fromXScale="1"
    android:fromYScale="1"
    android:interpolator="@android:anim/linear_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="0.0"
    android:toYScale="0.0" />
<alpha
    android:duration="300"
    android:fromAlpha="1.0"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:toAlpha="0.0" />
</set>

Então, na minha atividade, simplesmente usei as animações acima para mostrar e ocultar o menu FAB:

Mostrar menu Fab:

  private void expandFabMenu() {

    ViewCompat.animate(binding.baseFloatingActionButton).rotation(45.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start();
    binding.createLayout.startAnimation(fabOpenAnimation);
    binding.shareLayout.startAnimation(fabOpenAnimation);
    binding.createFab.setClickable(true);
    binding.shareFab.setClickable(true);
    isFabMenuOpen = true;

}

Fechar Menu Fab:

private void collapseFabMenu() {

    ViewCompat.animate(binding.baseFloatingActionButton).rotation(0.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start();
    binding.createLayout.startAnimation(fabCloseAnimation);
    binding.shareLayout.startAnimation(fabCloseAnimation);
    binding.createFab.setClickable(false);
    binding.shareFab.setClickable(false);
    isFabMenuOpen = false;

}

Aqui está a classe Activity -

package com.app.fabmenu;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.OvershootInterpolator;

import com.app.fabmenu.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

private ActivityMainBinding binding;
private Animation fabOpenAnimation;
private Animation fabCloseAnimation;
private boolean isFabMenuOpen = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    binding = DataBindingUtil.setContentView(this,    R.layout.activity_main);
    binding.setFabHandler(new FabHandler());

    getAnimations();


}

private void getAnimations() {

    fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open);

    fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close);

}

private void expandFabMenu() {

    ViewCompat.animate(binding.baseFloatingActionButton).rotation(45.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start();
    binding.createLayout.startAnimation(fabOpenAnimation);
    binding.shareLayout.startAnimation(fabOpenAnimation);
    binding.createFab.setClickable(true);
    binding.shareFab.setClickable(true);
    isFabMenuOpen = true;


}

private void collapseFabMenu() {

    ViewCompat.animate(binding.baseFloatingActionButton).rotation(0.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start();
    binding.createLayout.startAnimation(fabCloseAnimation);
    binding.shareLayout.startAnimation(fabCloseAnimation);
    binding.createFab.setClickable(false);
    binding.shareFab.setClickable(false);
    isFabMenuOpen = false;

}


public class FabHandler {

    public void onBaseFabClick(View view) {

        if (isFabMenuOpen)
            collapseFabMenu();
        else
            expandFabMenu();


    }

    public void onCreateFabClick(View view) {

        Snackbar.make(binding.coordinatorLayout, "Create FAB tapped", Snackbar.LENGTH_SHORT).show();

    }

    public void onShareFabClick(View view) {

        Snackbar.make(binding.coordinatorLayout, "Share FAB tapped", Snackbar.LENGTH_SHORT).show();

    }


}

@Override
public void onBackPressed() {

    if (isFabMenuOpen)
        collapseFabMenu();
    else
        super.onBackPressed();
}
}

Aqui estão as capturas de tela

Menu de ação flutuante com rótulo Textview na nova família de fontes cursivas do Android

Menu de ação flutuante com rótulo Textview na nova família de fontes Roboto do Android

Prashant
fonte
7

Quando tentei criar algo semelhante ao botão de ação flutuante da caixa de entrada, pensei em criar o próprio componente personalizado.

Seria um layout de quadro simples com altura fixa (para conter o menu expandido) contendo o botão FAB e mais 3 colocados sob o FAB. Quando você clica no FAB, basta animar outros botões para traduzir sob o FAB.

Existem algumas bibliotecas que fazem isso (por exemplo, https://github.com/futuresimple/android-floating-action-button ), mas é sempre mais divertido se você criar você mesmo :)

rwojcik
fonte
Excelente explicação! Estou explorando essa biblioteca, mas estou tendo problemas para usá-la para alinhar o FAB entre dois modos de exibição. Meio que é perguntado nesta pergunta stackoverflow.com/questions/24459352/… . Alguma idéia de como fazer isso? layout_anchore layout_anchorGravitynão está trabalhando para mim
acrespo
Biblioteca de Futuresimple não permite um ícone exclusivo na vantagem que vem padrão para o seu elemento FloatingActionMenu
Odaym
7

Você pode usar a biblioteca FloatingActionMenu ou clicar aqui para obter um tutorial passo a passo. Saída do tutorial:

insira a descrição da imagem aqui

Pacific P. Regmi
fonte
Simples e funciona bem. No entanto, sugiro que você adicione os detalhes aqui em vez de vincular o tutorial, caso o link ocorra no futuro. Outra coisa, acabei de aprender que a biblioteca não está mais em desenvolvimento, infelizmente.
Ahmed salah
0

Outra opção para o mesmo resultado com a animação ConstraintSet:

exemplo de animação fab

1) Coloque todas as visualizações animadas em um ConstraintLayout

2) Anime-o a partir de código como este (se você quiser mais alguns efeitos, é com você .. este é apenas um exemplo)

menuItem1 e menuItem2 é o primeiro e o segundo FABs no menu, descriptionItem1 e descriptionItem2 é a descrição à esquerda do menu, parentConstraintLayout é a raiz ConstraintLayout que contém todas as visualizações animadas, isMenuOpened é alguma função para alterar o sinalizador aberto / fechado no estado

Eu coloquei o código de animação no arquivo de extensão, mas não é necessário.

fun FloatingActionButton.expandMenu(
    menuItem1: View,
    menuItem2: View,
    descriptionItem1: TextView,
    descriptionItem2: TextView,
    parentConstraintLayout: ConstraintLayout,
    isMenuOpened: (Boolean)-> Unit
) {
    val constraintSet = ConstraintSet()
    constraintSet.clone(parentConstraintLayout)

    constraintSet.setVisibility(descriptionItem1.id, View.VISIBLE)
    constraintSet.clear(menuItem1.id, ConstraintSet.TOP)
    constraintSet.connect(menuItem1.id, ConstraintSet.BOTTOM, this.id, ConstraintSet.TOP, 0)
    constraintSet.connect(menuItem1.id, ConstraintSet.START, this.id, ConstraintSet.START, 0)
    constraintSet.connect(menuItem1.id, ConstraintSet.END, this.id, ConstraintSet.END, 0)

    constraintSet.setVisibility(descriptionItem2.id, View.VISIBLE)
    constraintSet.clear(menuItem2.id, ConstraintSet.TOP)
    constraintSet.connect(menuItem2.id, ConstraintSet.BOTTOM, menuItem1.id, ConstraintSet.TOP, 0)
    constraintSet.connect(menuItem2.id, ConstraintSet.START, this.id, ConstraintSet.START, 0)
    constraintSet.connect(menuItem2.id, ConstraintSet.END, this.id, ConstraintSet.END, 0)

    val transition = AutoTransition()
    transition.duration = 150
    transition.interpolator = AccelerateInterpolator()

    transition.addListener(object: Transition.TransitionListener {
        override fun onTransitionEnd(p0: Transition) {
            isMenuOpened(true)
        }
        override fun onTransitionResume(p0: Transition) {}
        override fun onTransitionPause(p0: Transition) {}
        override fun onTransitionCancel(p0: Transition) {}
        override fun onTransitionStart(p0: Transition) {}
    })

    TransitionManager.beginDelayedTransition(parentConstraintLayout, transition)
    constraintSet.applyTo(parentConstraintLayout)
}
Konstantin Kuznetsov
fonte