Barra de navegación inferior de animación de Android

efecto final:

Descripción del requisito:

Como se muestra en la figura anterior, la parte inferior es un número de pestañas sin fijar, y la disposición es que el espacio restante se divide uniformemente. Cuando haces clic para cambiar de pestaña, la pestaña seleccionada seguirá la animación y mostrará el contenido de la pestaña correspondiente.

División de la demanda:

1. En cuanto a la animación, el método de implementación puede ser cargar archivos gif, también puede usar lottie para cargar archivos json. La ventaja de lottie es que la carga de píxeles es lo suficientemente clara. Si lottie encuentra problemas de carga, se recomienda actualizar la biblioteca de lottie a la última versión.

2. Con respecto a la distribución uniforme, puede usar la propiedad chainStyle de ConstraintLayout para implementar la propagación, también puede usar LinearLayout para agregar un control de espacio vacío entre cada pestaña, el peso es 1 y el espacio restante se divide por igual.

3. Hay muchas formas de implementar el cambio de pestaña. Este artículo utiliza una vista personalizada, que es mejor para la personalización. Otros también pueden usar BottomNavigationView o TabLayout de Materail Design, pero los requisitos de personalización son más limitados, como algunos efectos de animación y estilos de fuente. Espere

4. Con respecto a la visualización del contenido encima de la pestaña, generalmente se logra cambiando diferentes fragmentos para lograr el cambio de pestaña para cargar contenido diferente.

Realización de la demanda:

Importe las bibliotecas necesarias:

implementation 'com.airbnb.android:lottie:3.4.0'
implementation 'com.github.bumptech.glide:glide:3.8.0'

1. Elementos de pestaña personalizados

Antes de personalizar la Vista, defina la estructura de datos necesaria para cargar la Vista. Los elementos necesarios para mostrar incluyen el identificador único de una pestaña, el recurso de imagen dinámica, el recurso de imagen estática y el nombre de la pestaña. Puede definir una interfaz para implementarlo.

public interface TabItem {

    String getName();

    @DrawableRes
    int getStaticRes();

    @DrawableRes
    int getAnimateRes();

    String getTabType();
}

Implementación de vista personalizada:

public class TabItemView extends FrameLayout {
    private Context mContext;
    private TextView mTabNameView;
    private LottieAnimationView mTabLottieView;
    private String mTabName;
    private int mTabStaticRes;
    private int mTabAnimateRes;
    private boolean isSelected;
    private int mSelectedTextColor;
    private int mUnSelectedTextColor;
    private int mTextSize;
    private int mIconSize;

    public TabItemView(@NonNull Context context) {
        this(context, null);
    }

    public TabItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TabItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public TabItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
        TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TabItemView);
        mTextSize = a.getDimensionPixelSize(R.styleable.TabItemView_textSize, 10);
        mIconSize = a.getDimensionPixelSize(R.styleable.TabItemView_iconSize, 40);
        mSelectedTextColor = a.getColor(R.styleable.TabItemView_selectedTextColor, getResources().getColor(R.color.tab_selected));
        mUnSelectedTextColor = a.getColor(R.styleable.TabItemView_unSelectedTextColor, getResources().getColor(R.color.tab_unselected));
        mTabStaticRes = a.getResourceId(R.styleable.TabItemView_mTabStaticRes, 0);
        mTabAnimateRes = a.getResourceId(R.styleable.TabItemView_mTabAnimateRes, 0);
        mTabName = a.getString(R.styleable.TabItemView_tabName);
        a.recycle();
        init();
    }

    private void init() {
        View view = LayoutInflater.from(mContext).inflate(R.layout.bottom_tab_view_item, this, false);
        mTabNameView = view.findViewById(R.id.tab_name);
        mTabLottieView = view.findViewById(R.id.tab_animation_view);
        addView(view);
    }

    private void setSelectedUI() {
        if (mTabAnimateRes == 0) {
            throw new NullPointerException("animation resource must be not empty");
        } else {
            mTabNameView.setTextColor(mSelectedTextColor);
            mTabLottieView.setAnimation(mTabAnimateRes);
            mTabLottieView.playAnimation();
        }
    }

    private void setUnselectedUI() {
        mTabNameView.setTextColor(mUnSelectedTextColor);
        mTabLottieView.clearAnimation();
        // 没有找到静态图,就选择加载gif图的第一帧,这里可以选择mipmap下的静图
        Glide.with(mContext).load(mTabStaticRes).asBitmap().into(mTabLottieView);
    }

    public void updateUI() {
        if (isSelected) {
            setSelectedUI();
        } else {
            setUnselectedUI();
        }
    }

    public TabItemView setIconSize(int iconSize) {
        if (iconSize > 0) {
            mIconSize = iconSize;
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mTabLottieView.getLayoutParams();
            lp.width = mIconSize;
            lp.height = mIconSize;
            mTabLottieView.setLayoutParams(lp);
        }
        return this;
    }

    public TabItemView setStaticIcon(int staticRes) {
        mTabStaticRes = staticRes;
        return this;
    }

    public TabItemView setAnimateIcon(int animateRes) {
        mTabAnimateRes = animateRes;
        return this;
    }

    public TabItemView setTabName(String tabName) {
        mTabName = tabName;
        mTabNameView.setText(mTabName);
        return this;
    }

    public TabItemView setTextColor(int selectColor, int unSelectColor) {
        this.mSelectedTextColor = selectColor;
        this.mUnSelectedTextColor = unSelectColor;
        return this;
    }

    public TabItemView setTabSelected(boolean isSelected) {
        this.isSelected = isSelected;
        updateUI();
        return this;
    }

    public TabItemView setTabTextSize(int textSize) {
        this.mTextSize = textSize;
        mTabNameView.setTextSize(mTextSize);
        return this;
    }
}

2. Encapsular la carga de artículos.

public class BottomTabNavigation extends LinearLayout implements View.OnClickListener {
    public static final String TAB_NONE = "none";
    public static final String TAB_HOME = "home";
    public static final String TAB_CLOUD = "cloud";
    public static final String TAB_EXCHANGE = "exchange";
    public static final String TAB_USER_CENTER = "user_center";
    private Context mContext;
    private List<TabItem> tabList = new ArrayList<>();
    private TabSelectedListener tabSelectedListener;
    private String selectedTabType = TAB_NONE;

    @StringDef({TAB_HOME, TAB_CLOUD, TAB_EXCHANGE, TAB_USER_CENTER, TAB_NONE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface BottomTabType {
    }

    public BottomTabNavigation(@NonNull Context context) {
        this(context, null);
    }

    public BottomTabNavigation(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BottomTabNavigation(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public BottomTabNavigation(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
        setOrientation(LinearLayout.HORIZONTAL);
    }

    public void setTabItems(List<TabItem> tabList) {
        removeAllViews();
        this.tabList = tabList;
        if (null == tabList || tabList.size() == 0)
            return;
        addView(getSpaceView());
        for (int i = 0; i < tabList.size(); i++) {
            TabItem tab = tabList.get(i);
            TabItemView tabItemView = new TabItemView(mContext)
                    .setTabName(tab.getName())
                    .setStaticIcon(tab.getStaticRes())
                    .setAnimateIcon(tab.getAnimateRes());
            tabItemView.setOnClickListener(this);
            tabItemView.setTag(tab.getTabType());
            addView(tabItemView);
            addView(getSpaceView());
        }

        if (selectedTabType.equals(TAB_NONE)) {
            getChildAt(1).performClick();
        }
    }

    private Space getSpaceView() {
        Space space = new Space(mContext);
        space.setLayoutParams(new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1));
        return space;
    }

    @Override
    public void onClick(View v) {
        if (null == v.getTag())
            return;
        String tabType = (String) v.getTag();
        if (tabType.equals(selectedTabType))
            return;
        if (null != tabSelectedListener) {
            this.selectedTabType = tabType;
            for (int i = 0; i < tabList.size(); i++) {
                TabItem tab = tabList.get(i);
                if (tabType.equals(tab.getTabType())) {
                    TabItemView tabView = (TabItemView) v;
                    tabView.setTabSelected(true);
                    tabSelectedListener.tabSelected(tabView);
                } else {
                    TabItemView tabView = (TabItemView) getChildAt(i * 2 + 1);
                    tabSelectedListener.tabUnselected(tabView);
                    tabView.setTabSelected(false);
                }
            }
        }
    }

    public void addOnTabSelectedListener(TabSelectedListener selectedListener){
        this.tabSelectedListener = selectedListener;
    }

    public interface TabSelectedListener {
        void tabSelected(TabItemView itemView);

        void tabUnselected(TabItemView itemView);
    }
}

3. Utilice

public class MainActivity extends AppCompatActivity {
    private HomeFragment homeFragment;
    private UserCenterFragment userCenterFragment;
    private ExchangeFragment exchangeFragment;
    private CloudFragment cloudFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFragment();
        BottomTabNavigation navigation = findViewById(R.id.bottom_navigation);
        navigation.addOnTabSelectedListener(new BottomTabNavigation.TabSelectedListener() {
            @Override
            public void tabSelected(TabItemView itemView) {
                displayFragment((String) itemView.getTag());
            }

            @Override
            public void tabUnselected(TabItemView itemView) {

            }
        });
        navigation.setTabItems(getTabList());
    }

    private void initFragment() {
        homeFragment = new HomeFragment();
        cloudFragment = new CloudFragment();
        exchangeFragment = new ExchangeFragment();
        userCenterFragment = new UserCenterFragment();
        FragmentManager manager = getSupportFragmentManager();
        manager.beginTransaction()
                .add(R.id.fl_container, homeFragment)
                .add(R.id.fl_container, exchangeFragment)
                .add(R.id.fl_container, cloudFragment)
                .add(R.id.fl_container, userCenterFragment)
                .commitAllowingStateLoss();
    }

    private List<TabItem> getTabList() {
        List<TabItem> tabs = new ArrayList<>();
        tabs.add(new BottomTab("首页", BottomTabNavigation.TAB_HOME));
        tabs.add(new BottomTab("云存储", BottomTabNavigation.TAB_CLOUD));
        tabs.add(new BottomTab("交易", BottomTabNavigation.TAB_EXCHANGE));
        tabs.add(new BottomTab("我的", BottomTabNavigation.TAB_USER_CENTER));
        return tabs;
    }

    private void displayFragment(String selectTabType) {
        switch (selectTabType) {
            case BottomTabNavigation.TAB_HOME:
            default:
                getSupportFragmentManager().beginTransaction()
                        .show(homeFragment)
                        .hide(exchangeFragment)
                        .hide(cloudFragment)
                        .hide(userCenterFragment)
                        .commitAllowingStateLoss();
                break;
            case BottomTabNavigation.TAB_CLOUD:
                getSupportFragmentManager().beginTransaction()
                        .show(cloudFragment)
                        .hide(exchangeFragment)
                        .hide(homeFragment)
                        .hide(userCenterFragment)
                        .commitAllowingStateLoss();
                break;
            case BottomTabNavigation.TAB_EXCHANGE:
                getSupportFragmentManager().beginTransaction()
                        .show(exchangeFragment)
                        .hide(homeFragment)
                        .hide(cloudFragment)
                        .hide(userCenterFragment)
                        .commitAllowingStateLoss();
                break;
            case BottomTabNavigation.TAB_USER_CENTER:
                getSupportFragmentManager().beginTransaction()
                        .show(userCenterFragment)
                        .hide(exchangeFragment)
                        .hide(cloudFragment)
                        .hide(homeFragment)
                        .commitAllowingStateLoss();
                break;
        }
    }
}

otros problemas:

1. Este artículo se basa en el efecto logrado al consultar https://blog.csdn.net/yiranhaiziqi/article/details/88965548 , gracias por esto.

2. Acerca de los recursos de animación utilizados en este artículo provienen de los recursos de animación proporcionados por lottie, la dirección específica es https://lottiefiles.com/user/475858 , también puede descargar otros recursos usted mismo , puede descargar archivos gif o json, gracias por la animación Recursos proporcionados por el autor; 

3. No se puede encontrar la imagen estática en el estado no seleccionado, y la demostración se implementa cargando solo el primer fotograma de la imagen gif

4. Se produjo un bloqueo causado por la animación de lottie [java.lang.IllegalStateException: faltan valores para el fotograma clave]

Actualice la biblioteca de animación lottie a más de 3.0, consulte https://www.freesion.com/article/1000623784/ para obtener más detalles

dirección de github:

https://github.com/Jane205/BottomNavigation

Supongo que te gusta

Origin blog.csdn.net/qingwangwang/article/details/108698037
Recomendado
Clasificación