package it.sephiroth.android.library.bottomnavigation; import android.annotation.SuppressLint; import android.content.Context; import android.support.annotation.NonNull; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.Toast; import it.sephiroth.android.library.bottonnavigation.R; import proguard.annotation.Keep; import static android.util.Log.INFO; import static it.sephiroth.android.library.bottomnavigation.MiscUtils.log; /** * Created by crugnola on 4/4/16. * MaterialBottomNavigation */ public class ShiftingLayout extends ViewGroup implements ItemsLayoutContainer { private static final String TAG = ShiftingLayout.class.getSimpleName(); public static final double ROUND_DECIMALS = 10d; public static final float RATIO_MIN_INCREASE = 0.05f; private final int maxActiveItemWidth; private final int minActiveItemWidth; private final int maxInactiveItemWidth; private final int minInactiveItemWidth; private int totalChildrenSize; private int minSize, maxSize; private int selectedIndex; private boolean hasFrame; OnItemClickListener listener; private MenuParser.Menu menu; public ShiftingLayout(final Context context) { super(context); totalChildrenSize = 0; maxActiveItemWidth = getResources().getDimensionPixelSize(R.dimen.bbn_shifting_maxActiveItemWidth); minActiveItemWidth = getResources().getDimensionPixelSize(R.dimen.bbn_shifting_minActiveItemWidth); maxInactiveItemWidth = getResources().getDimensionPixelSize(R.dimen.bbn_shifting_maxInactiveItemWidth); minInactiveItemWidth = getResources().getDimensionPixelSize(R.dimen.bbn_shifting_minInactiveItemWidth); } @Override public void removeAll() { removeAllViews(); totalChildrenSize = 0; selectedIndex = 0; menu = null; } @Override protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) { if (!hasFrame || getChildCount() == 0) { return; } log(TAG, INFO, "onLayout(change:%b, selectedIndex:%d)", changed, selectedIndex); if (totalChildrenSize == 0) { if (selectedIndex < 0) { totalChildrenSize = minSize * getChildCount(); } else { totalChildrenSize = minSize * (getChildCount() - 1) + maxSize; } } int width = (r - l); int left = (width - totalChildrenSize) / 2; for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); final LayoutParams params = child.getLayoutParams(); setChildFrame(child, left, 0, params.width, params.height); left += child.getWidth(); } } @Override protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { log(TAG, INFO, "onSizeChanged(" + w + ", " + h + ")"); super.onSizeChanged(w, h, oldw, oldh); hasFrame = true; if (null != menu) { populateInternal(menu); menu = null; } } @Override public void setOnItemClickListener(OnItemClickListener listener) { this.listener = listener; } private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); } public void setTotalSize(final int minSize, final int maxSize) { this.minSize = minSize; this.maxSize = maxSize; } @Override public void setSelectedIndex(final int index, final boolean animate) { log(TAG, INFO, "setSelectedIndex: " + index); if (selectedIndex == index) { return; } int oldSelectedIndex = this.selectedIndex; this.selectedIndex = index; log(TAG, Log.DEBUG, "change selection: %d --> %d", oldSelectedIndex, selectedIndex); if (!hasFrame || getChildCount() == 0) { return; } final BottomNavigationItemViewAbstract current = (BottomNavigationItemViewAbstract) getChildAt(oldSelectedIndex); final BottomNavigationItemViewAbstract child = (BottomNavigationItemViewAbstract) getChildAt(index); final boolean willAnimate = null != current && null != child; if (!willAnimate) { totalChildrenSize = 0; requestLayout(); } if (null != current) { current.setExpanded(false, minSize, willAnimate); } if (null != child) { child.setExpanded(true, maxSize, willAnimate); } } @Override public void setItemEnabled(final int index, final boolean enabled) { log(TAG, INFO, "setItemEnabled(%d, %b)", index, enabled); final BottomNavigationItemViewAbstract child = (BottomNavigationItemViewAbstract) getChildAt(index); if (null != child) { child.setEnabled(enabled); child.postInvalidate(); requestLayout(); } } @Override @Keep @SuppressWarnings ("unused") public int getSelectedIndex() { return selectedIndex; } @Override public void populate(@NonNull final MenuParser.Menu menu) { log(TAG, INFO, "populate: " + menu); if (hasFrame) { populateInternal(menu); } else { this.menu = menu; } } private void populateInternal(@NonNull final MenuParser.Menu menu) { log(TAG, Log.DEBUG, "populateInternal"); final BottomNavigation parent = (BottomNavigation) getParent(); final float density = getResources().getDisplayMetrics().density; final int screenWidth = parent.getWidth(); log(TAG, Log.VERBOSE, "density: " + density); log(TAG, Log.VERBOSE, "screenWidth(dp): " + (screenWidth / density)); int itemWidthMin; int itemWidthMax; final int totalWidth = maxInactiveItemWidth * (menu.getItemsCount() - 1) + maxActiveItemWidth; log(TAG, Log.VERBOSE, "totalWidth(dp): " + totalWidth / density); if (totalWidth > screenWidth) { float ratio = (float) screenWidth / totalWidth; ratio = (float) ((double) Math.round(ratio * ROUND_DECIMALS) / ROUND_DECIMALS) + RATIO_MIN_INCREASE; log(TAG, Log.VERBOSE, "ratio: " + ratio); itemWidthMin = (int) Math.max(maxInactiveItemWidth * ratio, minInactiveItemWidth); itemWidthMax = (int) (maxActiveItemWidth * ratio); if (BottomNavigation.DEBUG) { log(TAG, Log.DEBUG, "computing sizes..."); log(TAG, Log.VERBOSE, "itemWidthMin(dp): " + itemWidthMin / density); log(TAG, Log.VERBOSE, "itemWidthMax(dp): " + itemWidthMax / density); log(TAG, Log.VERBOSE, "total items size(dp): " + (itemWidthMin * (menu.getItemsCount() - 1) + itemWidthMax) / density); } if (itemWidthMin * (menu.getItemsCount() - 1) + itemWidthMax > screenWidth) { itemWidthMax = screenWidth - (itemWidthMin * (menu.getItemsCount() - 1)); // minActiveItemWidth? if (itemWidthMax == itemWidthMin) { itemWidthMin = minInactiveItemWidth; itemWidthMax = screenWidth - (itemWidthMin * (menu.getItemsCount() - 1)); } } } else { itemWidthMax = maxActiveItemWidth; itemWidthMin = maxInactiveItemWidth; } if (BottomNavigation.DEBUG) { log(TAG, Log.VERBOSE, "active size (dp): " + maxActiveItemWidth / density + ", " + minActiveItemWidth / density); log(TAG, Log.VERBOSE, "inactive size (dp): " + maxInactiveItemWidth / density + ", " + minInactiveItemWidth / density); log(TAG, Log.VERBOSE, "itemWidth(dp): " + (itemWidthMin / density) + ", " + (itemWidthMax / density)); } setTotalSize(itemWidthMin, itemWidthMax); for (int i = 0; i < menu.getItemsCount(); i++) { final BottomNavigationItem item = menu.getItemAt(i); log(TAG, Log.DEBUG, "item: " + item); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(itemWidthMin, getHeight()); if (i == selectedIndex) { params.width = itemWidthMax; } BottomNavigationItemViewAbstract view = new BottomNavigationShiftingItemView(parent, i == selectedIndex, menu); view.setItem(item); view.setLayoutParams(params); view.setClickable(true); view.setTypeface(parent.typeface); final int finalI = i; view.setOnTouchListener(new OnTouchListener() { @Override @SuppressLint ("ClickableViewAccessibility") public boolean onTouch(final View v, final MotionEvent event) { final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { if (null != listener) { listener.onItemPressed(ShiftingLayout.this, v, true); } } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { if (null != listener) { listener.onItemPressed(ShiftingLayout.this, v, false); } } return false; } }); view.setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { if (null != listener) { listener.onItemClick(ShiftingLayout.this, v, finalI, true); } } }); view.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(final View v) { Toast.makeText(getContext(), item.getTitle(), Toast.LENGTH_SHORT).show(); return true; } }); addView(view); } } }