How to create a bottom navigation bar with a snap-able indicator that always centers the selected option?
I have tried using TabLayout, had achieved the centering feature, but wasn't able to implement the snapping feature.
Design part:
<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());
}
}
I have roughly researched for other ways of doing it (using ViewPager + Button/ImageView and FrameLayout, bottom navigation bar, etc.) [not sure if they would fulfil all the requirements though], but does anyone know or could think of better way of implementing it?