【问题标题】:Android: How can I create a sliding bottom navigation bar like this?Android:如何创建这样的滑动底部导航栏?
【发布时间】:2021-08-27 18:02:15
【问题描述】:

如何创建一个底部导航栏,其底部导航栏始终位于所选选项的中心?

我尝试过使用 TabLayout,已经实现了居中功能,但无法实现对齐功能。

设计部分:

    <xxx.xxx.xxx.CenteringTabLayout
        android:id="@+id/modes"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:layout_alignParentBottom="true"
        app:tabRippleColor="@android:color/transparent"
        app:tabIndicatorFullWidth="false"
        app:tabMode="scrolling"
        app:tabIndicator="@drawable/mode_indicator"
        app:tabGravity="center"
        app:tabIndicatorHeight="30dp"
        android:background="@android:color/transparent"
        android:layout_marginVertical="12dp"
        android:layout_marginHorizontal="8dp"/>

CenteringTabLayout.java

package xxx.xxx.xxx;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import androidx.camera.extensions.ExtensionMode;
import androidx.core.view.ViewCompat;

import com.google.android.material.tabs.TabLayout;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

public class CenteringTabLayout extends TabLayout {

    private final ArrayList<Integer> snapPoints = new ArrayList<>();
    private int count = 0;

    public CenteringTabLayout(Context context) {
        super(context);
    }

    public CenteringTabLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CenteringTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private int sp = 0;

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        ViewGroup tabParent = (ViewGroup)getChildAt(0);

        View firstTab = tabParent.getChildAt(0);
        View lastTab = tabParent.getChildAt(tabParent.getChildCount()-1);
        sp = (getWidth()/2) - (firstTab.getWidth()/2);
        ViewCompat.setPaddingRelative(getChildAt(0), sp,0,(getWidth()/2) - (lastTab.getWidth()/2),0);

        View centerTab = tabParent.getChildAt(tabParent.getChildCount()/2);
        centerView(centerTab);

        count = getTabCount();

        snapPoints.clear();

        int widthC = 0;

        snapPoints.add(0);

        for (int i = 0; i < count; ++i) {
            View tabView = tabParent.getChildAt(i);
            widthC+=tabView.getWidth()/2;
            snapPoints.add(widthC + 19);
            widthC+=tabView.getWidth()/2;
        }

//        widthC += tabParent.getChildAt(count-1).getWidth()/2;
        snapPoints.add(widthC);

        --count;

        Log.i("TAG", Arrays.toString(snapPoints.toArray()));
    }

    private void centerView(View view){
        scrollTo(getRelativeLeft(view) - sp - view.getPaddingLeft() , 0);
    }

    @Override
    protected void onScrollChanged(int x, int t, int oldX, int oldT) {

        if(Math.abs(oldX-x)<=1) return;

        int i = 0;

        while(i<count){
            final int p = snapPoints.get(i);
            final int n = snapPoints.get(++i);

            Log.i("i:P,L,N", i+":"+p+","+x+","+n);

            if(x>=p && x<=n){
                --i;
                if(getSelectedTabPosition()==i) return;
                View tabView = Objects.requireNonNull(getTabAt(i)).view;
                Log.i("Selected", String.valueOf(Objects.requireNonNull(getTabAt(i)).getText()));
                tabView.performClick();
                centerView(tabView);
                return;
            }
            ExtensionMode;
        }

        super.onScrollChanged(x, t, oldX, oldT);
    }

    private int getRelativeLeft(View myView) {
        if (myView.getParent() == myView.getRootView())
            return myView.getLeft();
        else
            return myView.getLeft() + getRelativeLeft((View) myView.getParent());
    }
}

我已经粗略研究了其他方法(使用 ViewPager + Button/ImageView 和 FrameLayout、底部导航栏等)[虽然不确定它们是否能满足所有要求],但有人知道或能想到吗有更好的实现方式吗?

【问题讨论】:

  • 那不是底部导航栏。那是嵌入到水平 ScrollView 中的视图
  • 您能否分享任何参考资料或提供一些关于如何实现这一目标的指导?

标签: android


【解决方案1】:

您可以尝试使用这个library,它的功能与您需要的一些令人难以置信的动画类似,但如果您想实现这种确切的行为,我认为 ViewPager2 是您的最佳选择,因为它具有您需要的所有功能以及更多功能,而且您可以非常轻松地对其进行自定义。

更新:

对于您的特定用例,您可以分几步完成:

  1. 为 ViewPager2 创建一个适配器 - 初始化时,将您拥有的模式列表作为文本传递。我建议你创建一种字典,以每个文本值的索引为键,文本为值,因为要在点击时在页面之间移动,或者从中间开始,你需要调用: pager.setCurrentItem(itemIndex)
  2. 在点击监听器上设置每个页面。请参阅此link 了解如何操作。
  3. 实现onPageSelcted()回调以显示选择项目的手势并在相机模式之间切换。我的实现:
/**
 * An extension function that gives a callback for moving between pages in the ViewPager2
 *
 * @param action A function to execute every time a different page is selected
 */
fun ViewPager2.onPageSelected(action: (Int) -> Unit) {
    registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
        override fun onPageSelected(position: Int) {
            super.onPageSelected(position)
            action(position)
        }
    })
}

初始化适配器和视图分页器:

List<String> modesList = new ArrayList();
modeslist.add("camera");
// todo: add the modes in the order that you want
CustomAdapter adapter = new CustomAdapter(modesList);
pager = (ViewPager2) findViewById(R.id.awesomepager); // Referencing the ViewPager
pager.setAdapter(adapter); // Setting view pager adapter
pager.setCurrentItem(theStartPositionIndex); // Setting the start position
pager.onPageSelected(page -> {
    // todo: to what you want when a mode is selected
});

希望这能让您大致了解如何实施您的用例。

【讨论】:

  • 您能否大致了解一下如何完成(对于我的用例)?
  • A ViewPager2 需要占用父级的全部宽度...
  • 查看this answer
  • 这就是问题所在......它需要占据整个宽度,这使得 viewpager 不适合用例。顺便说一句,非常感谢您的回复。
猜你喜欢
  • 2019-12-03
  • 2021-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-26
  • 1970-01-01
  • 2021-11-29
  • 1970-01-01
相关资源
最近更新 更多