diff --git a/README.md b/README.md index e4ecae74..514e0582 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![](https://jitpack.io/v/wix-playground/ahbottomnavigation.svg)](https://jitpack.io/#wix-playground/ahbottomnavigation) # AHBottomNavigation Library to implement the Bottom Navigation component from Material Design guidelines (minSdkVersion=14). diff --git a/ahbottomnavigation/build.gradle b/ahbottomnavigation/build.gradle index b41b2f28..b8ad0052 100644 --- a/ahbottomnavigation/build.gradle +++ b/ahbottomnavigation/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'maven-publish' ext { bintrayRepo = 'maven' @@ -25,12 +26,12 @@ ext { } android { - compileSdkVersion 26 - buildToolsVersion '26.0.2' + namespace 'com.aurelhubert.ahbottomnavigation' defaultConfig { - minSdkVersion 14 - targetSdkVersion 26 + minSdkVersion 21 + targetSdkVersion 34 + compileSdkVersion 34 versionCode 40 versionName "2.1.0" } @@ -40,14 +41,23 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } dependencies { - compile 'com.android.support:design:26.1.0' + implementation 'com.google.android.material:material:1.12.0' } -// Place it at the end of the file -if (project.rootProject.file('local.properties').exists()) { - apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' - apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' +tasks.configureEach { task -> + if (task.name == 'lintVitalAnalyzeRelease') { + task.enabled = false + logger.lifecycle("Disabling lintVitalAnalyzeRelease task for ${project.name}.") + } } diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigation.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigation.java index 91e362e6..76f45166 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigation.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigation.java @@ -1,25 +1,21 @@ package com.aurelhubert.ahbottomnavigation; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static com.aurelhubert.ahbottomnavigation.AHHelper.fill; +import static com.aurelhubert.ahbottomnavigation.AHHelper.map; + import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.Bundle; import android.os.Parcelable; -import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.animation.LinearOutSlowInInterpolator; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -36,10 +32,21 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; import com.aurelhubert.ahbottomnavigation.notification.AHNotification; import com.aurelhubert.ahbottomnavigation.notification.AHNotificationHelper; +import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; @@ -65,7 +72,7 @@ public enum TitleState { // Static private static String TAG = "AHBottomNavigation"; - private static final String EXCEPTION_INDEX_OUT_OF_BOUNDS = "The position (%d) is out of bounds of the items (%d elements)"; + private static final String EXCEPTION_INDEX_OUT_OF_BOUNDS = "The position (%d) is out of bounds of the items (%d elements)"; private static final int MIN_ITEMS = 3; private static final int MAX_ITEMS = 5; @@ -79,7 +86,7 @@ public enum TitleState { private ArrayList items = new ArrayList<>(); private ArrayList views = new ArrayList<>(); private AHBottomNavigationBehavior bottomNavigationBehavior; - private LinearLayout linearLayoutContainer; + private ViewGroup linearLayoutContainer; private View backgroundColorView; private Animator circleRevealAnim; private boolean colored = false; @@ -96,21 +103,36 @@ public enum TitleState { private boolean soundEffectsEnabled = true; // Variables (Styles) - private Typeface titleTypeface; + private ArrayList titleTypeface = new ArrayList<>(); private int defaultBackgroundColor = Color.WHITE; private int defaultBackgroundResource = 0; - private @ColorInt int itemActiveColor; - private @ColorInt int itemInactiveColor; - private @ColorInt int titleColorActive; - private @ColorInt int itemDisableColor; - private @ColorInt int titleColorInactive; - private @ColorInt int coloredTitleColorActive; - private @ColorInt int coloredTitleColorInactive; - private float titleActiveTextSize, titleInactiveTextSize; + private int activePaddingTop; + private ArrayList iconActiveColor = new ArrayList<>(MAX_ITEMS); + private ArrayList iconInactiveColor = new ArrayList<>(MAX_ITEMS); + + private ArrayList iconHeight = new ArrayList<>(MAX_ITEMS); + private ArrayList iconWidth = new ArrayList<>(MAX_ITEMS); + + + private ArrayList titleActiveColor = new ArrayList<>(MAX_ITEMS); + private ArrayList titleInactiveColor = new ArrayList<>(MAX_ITEMS); + + private ArrayList iconDisableColor = new ArrayList<>(MAX_ITEMS); + private ArrayList titleDisableColor = new ArrayList<>(MAX_ITEMS); + + private ArrayList coloredTitleColorActive = new ArrayList<>(MAX_ITEMS); + private ArrayList coloredTitleColorInactive = new ArrayList<>(MAX_ITEMS); + private ArrayList titleActiveTextSize = new ArrayList<>(MAX_ITEMS); + private ArrayList titleInactiveTextSize = new ArrayList<>(MAX_ITEMS); private int bottomNavigationHeight, navigationBarHeight = 0; private float selectedItemWidth, notSelectedItemWidth; private boolean forceTint = false; + private boolean preferLargeIcons = false; private TitleState titleState = TitleState.SHOW_WHEN_ACTIVE; + private int activeMarginTop; + private float widthDifference; + private int defaultIconHeight; + private int defaultIconWidth; // Notifications private @ColorInt int notificationTextColor; @@ -120,6 +142,8 @@ public enum TitleState { private int notificationActiveMarginLeft, notificationInactiveMarginLeft; private int notificationActiveMarginTop, notificationInactiveMarginTop; private long notificationAnimationDuration; + private int defaultNotificationElevation; + private boolean animateTabSelection = true; /** * Constructors @@ -166,7 +190,7 @@ protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable("superState", super.onSaveInstanceState()); bundle.putInt("current_item", currentItem); - bundle.putParcelableArrayList("notifications", new ArrayList<> (notifications)); + bundle.putParcelableArrayList("notifications", new ArrayList<> (notifications)); return bundle; } @@ -175,7 +199,7 @@ protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; currentItem = bundle.getInt("current_item"); - notifications = bundle.getParcelableArrayList("notifications"); + notifications = bundle.getParcelableArrayList("notifications"); state = bundle.getParcelable("superState"); } super.onRestoreInstanceState(state); @@ -187,42 +211,52 @@ protected void onRestoreInstanceState(Parcelable state) { /** * Init - * - * @param context */ private void init(Context context, AttributeSet attrs) { this.context = context; resources = this.context.getResources(); - - // Item colors - titleColorActive = ContextCompat.getColor(context, R.color.colorBottomNavigationAccent); - titleColorInactive = ContextCompat.getColor(context, R.color.colorBottomNavigationInactive); - itemDisableColor = ContextCompat.getColor(context, R.color.colorBottomNavigationDisable); - + defaultNotificationElevation = resources.getDimensionPixelSize(R.dimen.bottom_navigation_notification_elevation); + activePaddingTop = (int) resources.getDimension(R.dimen.bottom_navigation_margin_top_active); + activeMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_small_margin_top_active); + widthDifference = resources.getDimension(R.dimen.bottom_navigation_small_selected_width_difference); + defaultIconHeight = resources.getDimensionPixelSize(R.dimen.bottom_navigation_icon); + defaultIconWidth = resources.getDimensionPixelSize(R.dimen.bottom_navigation_icon); + + // Icon colors + fill(iconActiveColor, MAX_ITEMS, null); + fill(iconInactiveColor, MAX_ITEMS, null); + fill(iconDisableColor, MAX_ITEMS, null); + // Title colors + fill(titleActiveColor, MAX_ITEMS, null); + fill(titleInactiveColor, MAX_ITEMS, null); + fill(titleDisableColor, MAX_ITEMS, null); + + fill(iconWidth, MAX_ITEMS, null); + fill(iconHeight, MAX_ITEMS, null); + + fill(titleTypeface, MAX_ITEMS, null); + fill(titleActiveTextSize, MAX_ITEMS, null); + fill(titleInactiveTextSize, MAX_ITEMS, null); + // Colors for colored bottom navigation - coloredTitleColorActive = ContextCompat.getColor(context, R.color.colorBottomNavigationActiveColored); - coloredTitleColorInactive = ContextCompat.getColor(context, R.color.colorBottomNavigationInactiveColored); - + fill(coloredTitleColorActive, MAX_ITEMS, ContextCompat.getColor(context, R.color.colorBottomNavigationActiveColored)); + fill(coloredTitleColorInactive, MAX_ITEMS, ContextCompat.getColor(context, R.color.colorBottomNavigationInactiveColored)); + if (attrs != null) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.AHBottomNavigationBehavior_Params, 0, 0); try { selectedBackgroundVisible = ta.getBoolean(R.styleable.AHBottomNavigationBehavior_Params_selectedBackgroundVisible, false); translucentNavigationEnabled = ta.getBoolean(R.styleable.AHBottomNavigationBehavior_Params_translucentNavigationEnabled, false); - - titleColorActive = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_accentColor, - ContextCompat.getColor(context, R.color.colorBottomNavigationAccent)); - titleColorInactive = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_inactiveColor, - ContextCompat.getColor(context, R.color.colorBottomNavigationInactive)); - itemDisableColor = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_disableColor, - ContextCompat.getColor(context, R.color.colorBottomNavigationDisable)); - - coloredTitleColorActive = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_coloredActive, - ContextCompat.getColor(context, R.color.colorBottomNavigationActiveColored)); - coloredTitleColorInactive = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_coloredInactive, - ContextCompat.getColor(context, R.color.colorBottomNavigationInactiveColored)); - + + map(titleActiveColor, ignored -> ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_accentColor, ContextCompat.getColor(context, R.color.colorBottomNavigationAccent))); + map(titleInactiveColor, ignored -> ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_inactiveColor, ContextCompat.getColor(context, R.color.colorBottomNavigationInactive))); + map(iconDisableColor, ignored -> ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_disableColor, ContextCompat.getColor(context, R.color.colorBottomNavigationDisable))); + + map(coloredTitleColorActive, ignored -> ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_coloredActive, ContextCompat.getColor(context, R.color.colorBottomNavigationActiveColored))); + map(coloredTitleColorInactive, ignored -> ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_coloredInactive, ContextCompat.getColor(context, R.color.colorBottomNavigationInactiveColored))); + colored = ta.getBoolean(R.styleable.AHBottomNavigationBehavior_Params_colored, false); - + } finally { ta.recycle(); } @@ -230,9 +264,6 @@ private void init(Context context, AttributeSet attrs) { notificationTextColor = ContextCompat.getColor(context, android.R.color.white); bottomNavigationHeight = (int) resources.getDimension(R.dimen.bottom_navigation_height); - - itemActiveColor = titleColorActive; - itemInactiveColor = titleColorInactive; // Notifications notificationActiveMarginLeft = (int) resources.getDimension(R.dimen.bottom_navigation_notification_margin_left_active); @@ -244,15 +275,14 @@ private void init(Context context, AttributeSet attrs) { ViewCompat.setElevation(this, resources.getDimension(R.dimen.bottom_navigation_elevation)); setClipToPadding(false); - ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, bottomNavigationHeight); + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(MATCH_PARENT, bottomNavigationHeight); setLayoutParams(params); } /** * Create the items in the bottom navigation */ - private void createItems() { + protected void createItems() { if (items.size() < MIN_ITEMS) { Log.w(TAG, "The items list should have at least 3 items"); } else if (items.size() > MAX_ITEMS) { @@ -263,68 +293,90 @@ private void createItems() { removeAllViews(); views.clear(); - backgroundColorView = new View(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - LayoutParams backgroundLayoutParams = new LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, calculateHeight(layoutHeight)); - addView(backgroundColorView, backgroundLayoutParams); - bottomNavigationHeight = layoutHeight; - } - linearLayoutContainer = new LinearLayout(context); - linearLayoutContainer.setOrientation(LinearLayout.HORIZONTAL); - linearLayoutContainer.setGravity(Gravity.CENTER); + linearLayoutContainer = createContainerWithItems(); + backgroundColorView = createItemsContainerBkgView(linearLayoutContainer, layoutHeight); + bottomNavigationHeight = layoutHeight; - LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, layoutHeight); - addView(linearLayoutContainer, layoutParams); + LayoutParams backgroundLayoutParams = new LayoutParams(MATCH_PARENT, calculateHeight(layoutHeight)); + addView(backgroundColorView, backgroundLayoutParams); - if (titleState != TitleState.ALWAYS_HIDE && - titleState != TitleState.SHOW_WHEN_ACTIVE_FORCE && - (items.size() == MIN_ITEMS || titleState == TitleState.ALWAYS_SHOW)) { - createClassicItems(linearLayoutContainer); + // Force a request layout after all the items have been created + post(this::requestLayout); + } + + @NonNull + private ViewGroup createContainerWithItems() { + LinearLayout container = new LinearLayout(context); + container.setOrientation(LinearLayout.HORIZONTAL); + container.setGravity(Gravity.CENTER); + + if (isClassic()) { + createClassicItems(container); + } else { + createSmallItems(container); + } + return container; + } + + @NonNull + private View createItemsContainerBkgView(ViewGroup itemsContainer, int layoutHeight) { + int width = (getLayoutParams() == null ? MATCH_PARENT : getLayoutParams().width); + + // Brain-f* moment explained: + // In order to really adhere to MATCH_PARENT, we must add an actual *view* (not view-group) + // which requires real-estate -- the background-view in this case; This is mandatory since + // the tab items' width calc has to be based on the total width, which would otherwise + // be measured and remeasured as 0. + // In WRAP_CONTENT, however, things work exactly the other way around. We need the + // background-view to act as a mere "placeholder" in the form of a frame-layout with the + // actual tab-items' content stating its desired width. + View backgroundView; + if (width == MATCH_PARENT) { + backgroundView = new View(context); + LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, layoutHeight); + this.addView(itemsContainer, layoutParams); + } else if (width == WRAP_CONTENT) { + backgroundView = new FrameLayout(context); + ((ViewGroup) backgroundView).addView(itemsContainer); } else { - createSmallItems(linearLayoutContainer); + throw new IllegalStateException("Specific width specs (" + width + "px) are not supported"); } + return backgroundView; + } - // Force a request layout after all the items have been created - post(new Runnable() { - @Override - public void run() { - requestLayout(); - } - }); + /** + * Check if items must be classic + * + * @return true if classic (icon + title) + */ + private boolean isClassic() { + if (preferLargeIcons && items.size() == MIN_ITEMS) return true; + return titleState != TitleState.ALWAYS_HIDE && + titleState != TitleState.SHOW_WHEN_ACTIVE_FORCE && + (items.size() == MIN_ITEMS || titleState == TitleState.ALWAYS_SHOW); } - @SuppressLint("NewApi") - @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @SuppressLint({"NewApi", "ResourceType"}) private int calculateHeight(int layoutHeight) { if(!translucentNavigationEnabled) return layoutHeight; int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android"); - if (resourceId > 0) { - navigationBarHeight = resources.getDimensionPixelSize(resourceId); - } + if (resourceId > 0) navigationBarHeight = resources.getDimensionPixelSize(resourceId); int[] attrs = {android.R.attr.fitsSystemWindows, android.R.attr.windowTranslucentNavigation}; TypedArray typedValue = getContext().getTheme().obtainStyledAttributes(attrs); - @SuppressWarnings("ResourceType") - boolean fitWindow = typedValue.getBoolean(0, false); - @SuppressWarnings("ResourceType") boolean translucentNavigation = typedValue.getBoolean(1, true); - if(hasImmersive() /*&& !fitWindow*/ && translucentNavigation) { - layoutHeight += navigationBarHeight; - } + if(hasImmersive() && translucentNavigation) layoutHeight += navigationBarHeight; typedValue.recycle(); - return layoutHeight; } @SuppressLint("NewApi") - @TargetApi(Build.VERSION_CODES.LOLLIPOP) public boolean hasImmersive() { Display d = ((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); @@ -343,77 +395,54 @@ public boolean hasImmersive() { return (realWidth > displayWidth) || (realHeight > displayHeight); } - // updated - - /** - * Check if items must be classic - * - * @return true if classic (icon + title) - */ - private boolean isClassic() { - return titleState == TitleState.ALWAYS_SHOW || items.size() <= MIN_ITEMS && titleState != TitleState.ALWAYS_SHOW; - } - /** * Create classic items (only 3 items in the bottom navigation) * * @param linearLayout The layout where the items are added */ private void createClassicItems(LinearLayout linearLayout) { - LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - float height = resources.getDimension(R.dimen.bottom_navigation_height); - float minWidth = resources.getDimension(R.dimen.bottom_navigation_min_width); - float maxWidth = resources.getDimension(R.dimen.bottom_navigation_max_width); - - if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { - minWidth = resources.getDimension(R.dimen.bottom_navigation_small_inactive_min_width); - maxWidth = resources.getDimension(R.dimen.bottom_navigation_small_inactive_max_width); - } - - int layoutWidth = getWidth(); - if (layoutWidth == 0 || items.size() == 0) { + if (items.isEmpty()) { return; } - float itemWidth = layoutWidth / items.size(); - if (itemWidth < minWidth) { - itemWidth = minWidth; - } else if (itemWidth > maxWidth) { - itemWidth = maxWidth; + Integer itemWidth = calcItemLayoutWidth(); + if (itemWidth == null) { + return; } - float activeSize = resources.getDimension(R.dimen.bottom_navigation_text_size_active); - float inactiveSize = resources.getDimension(R.dimen.bottom_navigation_text_size_inactive); - int activePaddingTop = (int) resources.getDimension(R.dimen.bottom_navigation_margin_top_active); - - if (titleActiveTextSize != 0 && titleInactiveTextSize != 0) { - activeSize = titleActiveTextSize; - inactiveSize = titleInactiveTextSize; - } else if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { - activeSize = resources.getDimension(R.dimen.bottom_navigation_text_size_forced_active); - inactiveSize = resources.getDimension(R.dimen.bottom_navigation_text_size_forced_inactive); - } + float height = resources.getDimension(R.dimen.bottom_navigation_height); for (int i = 0; i < items.size(); i++) { - final boolean current = currentItem == i; + final boolean current = (currentItem == i); final int itemIndex = i; + AHBottomNavigationItem item = items.get(itemIndex); View view = inflater.inflate(R.layout.bottom_navigation_item, this, false); - FrameLayout container = (FrameLayout) view.findViewById(R.id.bottom_navigation_container); - ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon); - TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title); - TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); + FrameLayout container = view.findViewById(R.id.bottom_navigation_container); + ImageView icon = view.findViewById(R.id.bottom_navigation_item_icon); + AHTextView title = view.findViewById(R.id.bottom_navigation_item_title); + AHTextView notification = view.findViewById(R.id.bottom_navigation_notification); + icon.getLayoutParams().width = getIconWidth(itemIndex); + icon.getLayoutParams().height = getIconHeight(itemIndex); icon.setImageDrawable(item.getDrawable(context)); - title.setText(item.getTitle(context)); - if (titleTypeface != null) { - title.setTypeface(titleTypeface); + if (titleState == TitleState.ALWAYS_HIDE || item.getTitle(context).isEmpty()) { + title.setVisibility(View.GONE); + if (!animateTabSelection) { + AHHelper.updateMargin(icon, 0, 0, 0, 0); + } + ((LayoutParams) icon.getLayoutParams()).gravity = Gravity.CENTER; + ((MarginLayoutParams) notification.getLayoutParams()).topMargin = (bottomNavigationHeight - getIconHeight(itemIndex)) / 2 - dpToPx(4); + } else { + title.setText(item.getTitle(context)); } + title.setTypeface(titleTypeface.get(i)); + if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { container.setPadding(0, container.getPaddingTop(), 0, container.getPaddingBottom()); } @@ -424,23 +453,19 @@ private void createClassicItems(LinearLayout linearLayout) { } icon.setSelected(true); // Update margins (icon & notification) - if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams && animateTabSelection) { ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) icon.getLayoutParams(); p.setMargins(p.leftMargin, activePaddingTop, p.rightMargin, p.bottomMargin); - ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) - notification.getLayoutParams(); - paramsNotification.setMargins(notificationActiveMarginLeft, paramsNotification.topMargin, - paramsNotification.rightMargin, paramsNotification.bottomMargin); + ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) notification.getLayoutParams(); + paramsNotification.setMargins(notificationActiveMarginLeft, paramsNotification.topMargin, paramsNotification.rightMargin, paramsNotification.bottomMargin); view.requestLayout(); } } else { icon.setSelected(false); - ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) - notification.getLayoutParams(); - paramsNotification.setMargins(notificationInactiveMarginLeft, paramsNotification.topMargin, - paramsNotification.rightMargin, paramsNotification.bottomMargin); + ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) notification.getLayoutParams(); + paramsNotification.setMargins(notificationInactiveMarginLeft, paramsNotification.topMargin, paramsNotification.rightMargin, paramsNotification.bottomMargin); } if (colored) { @@ -455,41 +480,97 @@ private void createClassicItems(LinearLayout linearLayout) { setBackgroundColor(defaultBackgroundColor); } } - - title.setTextSize(TypedValue.COMPLEX_UNIT_PX, current ? activeSize : inactiveSize); - + + title.setTextSize(TypedValue.COMPLEX_UNIT_PX, current ? getActiveTextSize(i) : getInactiveTextSize(i)); + if (itemsEnabledStates[i]) { - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - updateItems(itemIndex, true); - } - }); - icon.setImageDrawable(AHHelper.getTintDrawable(items.get(i).getDrawable(context), - current ? itemActiveColor : itemInactiveColor, forceTint)); - title.setTextColor(current ? itemActiveColor : itemInactiveColor); + view.setOnClickListener(v -> updateItems(itemIndex, true)); + icon.setImageDrawable(AHHelper.getTintDrawable(items.get(i).getDrawable(context), current ? iconActiveColor.get(i) : iconInactiveColor.get(i), forceTint)); + title.setTextColor(current ? titleActiveColor.get(i) : titleInactiveColor.get(i)); view.setSoundEffectsEnabled(soundEffectsEnabled); } else { - icon.setImageDrawable(AHHelper.getTintDrawable(items.get(i).getDrawable(context), - itemDisableColor, forceTint)); - title.setTextColor(itemDisableColor); + icon.setImageDrawable(AHHelper.getTintDrawable(items.get(i).getDrawable(context), iconDisableColor.get(i), forceTint)); + title.setTextColor(titleDisableColor.get(i)); } - LayoutParams params = new LayoutParams((int) itemWidth, (int) height); + if (item.getTag() != null) view.setTag(item.getTag()); + + LayoutParams params = new LayoutParams(itemWidth, (int) height); linearLayout.addView(view, params); views.add(view); + setTabAccessibilityLabel(itemIndex, currentItem); } updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); } + private int getIconHeight(int index) { + return this.iconHeight.get(index) == null ? + defaultIconHeight : + dpToPx(this.iconHeight.get(index)); + } + + private int getIconWidth(int index) { + return iconWidth.get(index) == null ? + defaultIconWidth : + dpToPx(iconWidth.get(index)); + } + + private float getInactiveTextSize(int index) { + if (titleInactiveTextSize.get(index) != null) return titleInactiveTextSize.get(index); + if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { + return resources.getDimension(R.dimen.bottom_navigation_text_size_forced_inactive); + } else { + return resources.getDimension(R.dimen.bottom_navigation_text_size_inactive); + } + } + + private float getActiveTextSize(int index) { + if (titleActiveTextSize.get(index) != null) return titleActiveTextSize.get(index).floatValue(); + if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { + return resources.getDimension(R.dimen.bottom_navigation_text_size_forced_active); + } else { + return resources.getDimension(R.dimen.bottom_navigation_text_size_active); + } + } + + private Integer calcItemLayoutWidth() { + ViewGroup.LayoutParams lp = getLayoutParams(); + int width = (lp == null ? MATCH_PARENT : lp.width); + + if (width == WRAP_CONTENT) { + return WRAP_CONTENT; + } + + if (width == MATCH_PARENT) { + float layoutWidth = getWidth(); + if (layoutWidth == 0f) { + return null; + } + + float minWidth = resources.getDimension(R.dimen.bottom_navigation_min_width); + float maxWidth = resources.getDimension(R.dimen.bottom_navigation_max_width); + + if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { + minWidth = resources.getDimension(R.dimen.bottom_navigation_small_inactive_min_width); + maxWidth = resources.getDimension(R.dimen.bottom_navigation_small_inactive_max_width); + } + + float itemWidth = layoutWidth / items.size(); + itemWidth = Math.min(itemWidth, maxWidth); + itemWidth = Math.max(itemWidth, minWidth); + return (int) itemWidth; + } + + throw new IllegalStateException("Specific width specs ("+width+"px) are not supported"); + } + /** * Create small items (more than 3 items in the bottom navigation) * * @param linearLayout The layout where the items are added */ private void createSmallItems(LinearLayout linearLayout) { - LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); float height = resources.getDimension(R.dimen.bottom_navigation_height); @@ -509,45 +590,53 @@ private void createSmallItems(LinearLayout linearLayout) { itemWidth = maxWidth; } - int activeMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_small_margin_top_active); - float difference = resources.getDimension(R.dimen.bottom_navigation_small_selected_width_difference); - - selectedItemWidth = itemWidth + items.size() * difference; - itemWidth -= difference; + selectedItemWidth = itemWidth + items.size() * widthDifference; + itemWidth -= widthDifference; notSelectedItemWidth = itemWidth; for (int i = 0; i < items.size(); i++) { - final int itemIndex = i; + final boolean current = currentItem == i; AHBottomNavigationItem item = items.get(itemIndex); View view = inflater.inflate(R.layout.bottom_navigation_small_item, this, false); - ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_small_item_icon); - TextView title = (TextView) view.findViewById(R.id.bottom_navigation_small_item_title); - TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); + ImageView icon = view.findViewById(R.id.bottom_navigation_small_item_icon); + AHTextView title = view.findViewById(R.id.bottom_navigation_small_item_title); + AHTextView notification = view.findViewById(R.id.bottom_navigation_notification); icon.setImageDrawable(item.getDrawable(context)); + icon.getLayoutParams().width = getIconWidth(itemIndex); + icon.getLayoutParams().height = getIconHeight(itemIndex); + if (titleState != TitleState.ALWAYS_HIDE) { title.setText(item.getTitle(context)); } - - if (titleActiveTextSize != 0) { - title.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleActiveTextSize); + if ( + (titleState == TitleState.ALWAYS_HIDE || item.getTitle(context).isEmpty()) && + titleState != TitleState.SHOW_WHEN_ACTIVE && + titleState != TitleState.SHOW_WHEN_ACTIVE_FORCE + ) { + title.setVisibility(View.GONE); + ((LayoutParams) icon.getLayoutParams()).gravity = Gravity.CENTER; + AHHelper.updateMargin(icon, 0, 0, 0, 0); } - if (titleTypeface != null) { - title.setTypeface(titleTypeface); + float activeTextSize = getActiveTextSize(i); + if (activeTextSize != 0) { + title.setTextSize(TypedValue.COMPLEX_UNIT_PX, activeTextSize); } - if (i == currentItem) { + title.setTypeface(titleTypeface.get(i)); + + if (current) { if (selectedBackgroundVisible) { view.setSelected(true); } icon.setSelected(true); // Update margins (icon & notification) - if (titleState != TitleState.ALWAYS_HIDE) { + if (titleState != TitleState.ALWAYS_HIDE && animateTabSelection) { if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) icon.getLayoutParams(); p.setMargins(p.leftMargin, activeMarginTop, p.rightMargin, p.bottomMargin); @@ -562,10 +651,15 @@ private void createSmallItems(LinearLayout linearLayout) { } } else { icon.setSelected(false); - ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) - notification.getLayoutParams(); - paramsNotification.setMargins(notificationInactiveMarginLeft, notificationInactiveMarginTop, - paramsNotification.rightMargin, paramsNotification.bottomMargin); + if (animateTabSelection) { + ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) notification.getLayoutParams(); + paramsNotification.setMargins( + notificationInactiveMarginLeft, + notificationInactiveMarginTop, + paramsNotification.rightMargin, + paramsNotification.bottomMargin + ); + } } if (colored) { @@ -582,24 +676,17 @@ private void createSmallItems(LinearLayout linearLayout) { } if (itemsEnabledStates[i]) { - icon.setImageDrawable(AHHelper.getTintDrawable(items.get(i).getDrawable(context), - currentItem == i ? itemActiveColor : itemInactiveColor, forceTint)); - title.setTextColor(currentItem == i ? itemActiveColor : itemInactiveColor); + icon.setImageDrawable(AHHelper.getTintDrawable(items.get(i).getDrawable(context), currentItem == i ? iconActiveColor.get(i) : iconInactiveColor.get(i), forceTint)); + title.setTextColor(currentItem == i ? titleActiveColor.get(i) : titleInactiveColor.get(i)); title.setAlpha(currentItem == i ? 1 : 0); - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - updateSmallItems(itemIndex, true); - } - }); + view.setOnClickListener(v -> updateSmallItems(itemIndex, true)); view.setSoundEffectsEnabled(soundEffectsEnabled); } else { - icon.setImageDrawable(AHHelper.getTintDrawable(items.get(i).getDrawable(context), - itemDisableColor, forceTint)); - title.setTextColor(itemDisableColor); + icon.setImageDrawable(AHHelper.getTintDrawable(items.get(i).getDrawable(context), iconDisableColor.get(i), forceTint)); + title.setTextColor(titleDisableColor.get(i)); title.setAlpha(0); } - + int width = i == currentItem ? (int) selectedItemWidth : (int) itemWidth; @@ -607,14 +694,28 @@ public void onClick(View v) { width = (int) (itemWidth * 1.16); } + if (item.getTag() != null) view.setTag(item.getTag()); + LayoutParams params = new LayoutParams(width, (int) height); linearLayout.addView(view, params); views.add(view); + setTabAccessibilityLabel(itemIndex, currentItem); } updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); } + private void setTabAccessibilityLabel(int itemIndex, int currentItem) { + AHBottomNavigationItem item = items.get(itemIndex); + String contentDescription = currentItem == itemIndex ? "selected, " : ""; + if (item.getTitle(context) != null) contentDescription += (item.getTitle(context) + ", "); + if (AHHelper.isInteger(notifications.get(itemIndex).getReadableText())) { + int num = Integer.parseInt(notifications.get(itemIndex).getReadableText()); + contentDescription += (num + " new item" + (num == 1 ? "" : "s") + ", "); + } + contentDescription += "tab, " + (itemIndex + 1) + " out of " + getItemsCount(); + views.get(itemIndex).setContentDescription(contentDescription); + } /** * Update Items UI @@ -623,7 +724,9 @@ public void onClick(View v) { * @param useCallback boolean: Use or not the callback */ private void updateItems(final int itemIndex, boolean useCallback) { - + for (int i = 0; i < views.size(); i++) { + setTabAccessibilityLabel(i, itemIndex); + } if (currentItem == itemIndex) { if (tabSelectedListener != null && useCallback) { tabSelectedListener.onTabSelected(itemIndex, true); @@ -638,16 +741,6 @@ private void updateItems(final int itemIndex, boolean useCallback) { int activeMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_margin_top_active); int inactiveMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_margin_top_inactive); - float activeSize = resources.getDimension(R.dimen.bottom_navigation_text_size_active); - float inactiveSize = resources.getDimension(R.dimen.bottom_navigation_text_size_inactive); - - if (titleActiveTextSize != 0 && titleInactiveTextSize != 0) { - activeSize = titleActiveTextSize; - inactiveSize = titleInactiveTextSize; - } else if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { - activeSize = resources.getDimension(R.dimen.bottom_navigation_text_size_forced_active); - inactiveSize = resources.getDimension(R.dimen.bottom_navigation_text_size_forced_inactive); - } for (int i = 0; i < views.size(); i++) { @@ -657,21 +750,20 @@ private void updateItems(final int itemIndex, boolean useCallback) { } if (i == itemIndex) { - - final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title); - final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon); - final TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); + final AHTextView title = view.findViewById(R.id.bottom_navigation_item_title); + final ImageView icon = view.findViewById(R.id.bottom_navigation_item_icon); + final AHTextView notification = view.findViewById(R.id.bottom_navigation_notification); icon.setSelected(true); - AHHelper.updateTopMargin(icon, inactiveMarginTop, activeMarginTop); - AHHelper.updateLeftMargin(notification, notificationInactiveMarginLeft, notificationActiveMarginLeft); - AHHelper.updateTextColor(title, itemInactiveColor, itemActiveColor); - AHHelper.updateTextSize(title, inactiveSize, activeSize); - AHHelper.updateDrawableColor(context, items.get(itemIndex).getDrawable(context), icon, - itemInactiveColor, itemActiveColor, forceTint); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && colored) { + if (animateTabSelection) { + AHHelper.updateTopMargin(icon, inactiveMarginTop, activeMarginTop); + AHHelper.updateLeftMargin(notification, notificationInactiveMarginLeft, notificationActiveMarginLeft); + AHHelper.updateTextSize(title, getInactiveTextSize(i), getActiveTextSize(i)); + } + AHHelper.updateTextColor(title, titleInactiveColor.get(i), titleActiveColor.get(i)); + AHHelper.updateDrawableColor(items.get(itemIndex).getDrawable(context), icon, iconInactiveColor.get(i), iconActiveColor.get(i), forceTint); + if (colored) { int finalRadius = Math.max(getWidth(), getHeight()); int cx = (int) view.getX() + view.getWidth() / 2; int cy = view.getHeight() / 2; @@ -684,30 +776,19 @@ private void updateItems(final int itemIndex, boolean useCallback) { circleRevealAnim = ViewAnimationUtils.createCircularReveal(backgroundColorView, cx, cy, 0, finalRadius); circleRevealAnim.setStartDelay(5); - circleRevealAnim.addListener(new Animator.AnimatorListener() { + circleRevealAnim.addListener(new StubAnimationListener() { @Override - public void onAnimationStart(Animator animation) { + public void onAnimationStart(@NonNull Animator animation) { backgroundColorView.setBackgroundColor(items.get(itemIndex).getColor(context)); } @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(@NonNull Animator animation) { setBackgroundColor(items.get(itemIndex).getColor(context)); backgroundColorView.setBackgroundColor(Color.TRANSPARENT); } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } }); circleRevealAnim.start(); - } else if (colored) { - AHHelper.updateViewBackgroundColor(this, currentColor, - items.get(itemIndex).getColor(context)); } else { if (defaultBackgroundResource != 0) { setBackgroundResource(defaultBackgroundResource); @@ -718,18 +799,18 @@ public void onAnimationRepeat(Animator animation) { } } else if (i == currentItem) { - - final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title); - final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon); - final TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); + final AHTextView title = view.findViewById(R.id.bottom_navigation_item_title); + final ImageView icon = view.findViewById(R.id.bottom_navigation_item_icon); + final AHTextView notification = view.findViewById(R.id.bottom_navigation_notification); icon.setSelected(false); - AHHelper.updateTopMargin(icon, activeMarginTop, inactiveMarginTop); - AHHelper.updateLeftMargin(notification, notificationActiveMarginLeft, notificationInactiveMarginLeft); - AHHelper.updateTextColor(title, itemActiveColor, itemInactiveColor); - AHHelper.updateTextSize(title, activeSize, inactiveSize); - AHHelper.updateDrawableColor(context, items.get(currentItem).getDrawable(context), icon, - itemActiveColor, itemInactiveColor, forceTint); + if (animateTabSelection) { + AHHelper.updateTopMargin(icon, activeMarginTop, inactiveMarginTop); + AHHelper.updateLeftMargin(notification, notificationActiveMarginLeft, notificationInactiveMarginLeft); + AHHelper.updateTextSize(title, getActiveTextSize(i), getInactiveTextSize(i)); + } + AHHelper.updateTextColor(title, titleActiveColor.get(i), titleInactiveColor.get(i)); + AHHelper.updateDrawableColor(items.get(currentItem).getDrawable(context), icon, iconActiveColor.get(i), iconInactiveColor.get(i), forceTint); } } @@ -778,10 +859,10 @@ private void updateSmallItems(final int itemIndex, boolean useCallback) { if (i == itemIndex) { - final FrameLayout container = (FrameLayout) view.findViewById(R.id.bottom_navigation_small_container); - final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_small_item_title); - final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_small_item_icon); - final TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); + final FrameLayout container = view.findViewById(R.id.bottom_navigation_small_container); + final AHTextView title = view.findViewById(R.id.bottom_navigation_small_item_title); + final ImageView icon = view.findViewById(R.id.bottom_navigation_small_item_icon); + final AHTextView notification = view.findViewById(R.id.bottom_navigation_notification); icon.setSelected(true); @@ -789,15 +870,14 @@ private void updateSmallItems(final int itemIndex, boolean useCallback) { AHHelper.updateTopMargin(icon, inactiveMargin, activeMarginTop); AHHelper.updateLeftMargin(notification, notificationInactiveMarginLeft, notificationActiveMarginLeft); AHHelper.updateTopMargin(notification, notificationInactiveMarginTop, notificationActiveMarginTop); - AHHelper.updateTextColor(title, itemInactiveColor, itemActiveColor); + AHHelper.updateTextColor(title, iconInactiveColor.get(i), iconActiveColor.get(i)); AHHelper.updateWidth(container, notSelectedItemWidth, selectedItemWidth); } AHHelper.updateAlpha(title, 0, 1); - AHHelper.updateDrawableColor(context, items.get(itemIndex).getDrawable(context), icon, - itemInactiveColor, itemActiveColor, forceTint); + AHHelper.updateDrawableColor(items.get(itemIndex).getDrawable(context), icon, iconInactiveColor.get(i), iconActiveColor.get(i), forceTint); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && colored) { + if (colored) { int finalRadius = Math.max(getWidth(), getHeight()); int cx = (int) views.get(itemIndex).getX() + views.get(itemIndex).getWidth() / 2; int cy = views.get(itemIndex).getHeight() / 2; @@ -831,9 +911,6 @@ public void onAnimationRepeat(Animator animation) { } }); circleRevealAnim.start(); - } else if (colored) { - AHHelper.updateViewBackgroundColor(this, currentColor, - items.get(itemIndex).getColor(context)); } else { if (defaultBackgroundResource != 0) { setBackgroundResource(defaultBackgroundResource); @@ -844,11 +921,10 @@ public void onAnimationRepeat(Animator animation) { } } else if (i == currentItem) { - final View container = view.findViewById(R.id.bottom_navigation_small_container); - final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_small_item_title); - final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_small_item_icon); - final TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); + final AHTextView title = view.findViewById(R.id.bottom_navigation_small_item_title); + final ImageView icon = view.findViewById(R.id.bottom_navigation_small_item_icon); + final AHTextView notification = view.findViewById(R.id.bottom_navigation_notification); icon.setSelected(false); @@ -856,13 +932,12 @@ public void onAnimationRepeat(Animator animation) { AHHelper.updateTopMargin(icon, activeMarginTop, inactiveMargin); AHHelper.updateLeftMargin(notification, notificationActiveMarginLeft, notificationInactiveMarginLeft); AHHelper.updateTopMargin(notification, notificationActiveMarginTop, notificationInactiveMarginTop); - AHHelper.updateTextColor(title, itemActiveColor, itemInactiveColor); + AHHelper.updateTextColor(title, iconActiveColor.get(i), iconInactiveColor.get(i)); AHHelper.updateWidth(container, selectedItemWidth, notSelectedItemWidth); } AHHelper.updateAlpha(title, 1, 0); - AHHelper.updateDrawableColor(context, items.get(currentItem).getDrawable(context), icon, - itemActiveColor, itemInactiveColor, forceTint); + AHHelper.updateDrawableColor(items.get(currentItem).getDrawable(context), icon, iconActiveColor.get(i), iconInactiveColor.get(i), forceTint); } } @@ -883,13 +958,11 @@ public void onAnimationRepeat(Animator animation) { * Update notifications */ private void updateNotifications(boolean updateStyle, int itemPosition) { - for (int i = 0; i < views.size(); i++) { - if (i >= notifications.size()) { break; } - + if (itemPosition != UPDATE_ALL_NOTIFICATIONS && itemPosition != i) { continue; } @@ -898,12 +971,10 @@ private void updateNotifications(boolean updateStyle, int itemPosition) { final int currentTextColor = AHNotificationHelper.getTextColor(notificationItem, notificationTextColor); final int currentBackgroundColor = AHNotificationHelper.getBackgroundColor(notificationItem, notificationBackgroundColor); - TextView notification = (TextView) views.get(i).findViewById(R.id.bottom_navigation_notification); - - String currentValue = notification.getText().toString(); - boolean animate = !currentValue.equals(String.valueOf(notificationItem.getText())); + AHTextView notification = views.get(i).findViewById(R.id.bottom_navigation_notification); if (updateStyle) { + notification.setElevation(notificationItem.isDot() ? 0 : defaultNotificationElevation); notification.setTextColor(currentTextColor); if (notificationTypeface != null) { notification.setTypeface(notificationTypeface); @@ -912,53 +983,92 @@ private void updateNotifications(boolean updateStyle, int itemPosition) { } if (notificationBackgroundDrawable != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - Drawable drawable = notificationBackgroundDrawable.getConstantState().newDrawable(); - notification.setBackground(drawable); - } else { - notification.setBackgroundDrawable(notificationBackgroundDrawable); - } + Drawable drawable = notificationBackgroundDrawable.getConstantState().newDrawable(); + notification.setBackground(drawable); } else if (currentBackgroundColor != 0) { - Drawable defautlDrawable = ContextCompat.getDrawable(context, R.drawable.notification_background); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - notification.setBackground(AHHelper.getTintDrawable(defautlDrawable, - currentBackgroundColor, forceTint)); - } else { - notification.setBackgroundDrawable(AHHelper.getTintDrawable(defautlDrawable, - currentBackgroundColor, forceTint)); - } + Drawable defaultDrawable = ContextCompat.getDrawable(context, R.drawable.notification_background); + notification.setBackground(AHHelper.getTintDrawable(defaultDrawable, currentBackgroundColor, forceTint)); } } - if (notificationItem.isEmpty() && notification.getText().length() > 0) { - notification.setText(""); - if (animate) { - notification.animate() - .scaleX(0) - .scaleY(0) - .alpha(0) - .setInterpolator(new AccelerateInterpolator()) - .setDuration(notificationAnimationDuration) - .start(); - } - } else if (!notificationItem.isEmpty()) { - notification.setText(String.valueOf(notificationItem.getText())); - if (animate) { - notification.setScaleX(0); - notification.setScaleY(0); - notification.animate() - .scaleX(1) - .scaleY(1) - .alpha(1) - .setInterpolator(new OvershootInterpolator()) - .setDuration(notificationAnimationDuration) - .start(); - } + if (notificationItem.isEmpty()) { + hideNotification(notificationItem, notification); + } else { + showNotification(notificationItem, notification); + } + } + } + + private void showNotification(AHNotification notification, AHTextView notificationView) { + notificationView.setText(notification.getReadableText()); + updateNotificationSize(notification, notificationView); + if (notificationView.getAlpha() != 1) { + if (shouldAnimateNotification(notification)) { + animateNotificationShow(notificationView); + notification.setAnimate(false); + } else { + notificationView.setScaleX(1); + notificationView.setScaleY(1); + notificationView.setAlpha(1); + } + } + } + + private void animateNotificationShow(AHTextView notification) { + notification.setScaleX(0); + notification.setScaleY(0); + notification.setAlpha(0); + notification.animate() + .scaleX(1) + .scaleY(1) + .alpha(1) + .setInterpolator(new OvershootInterpolator()) + .setDuration(notificationAnimationDuration) + .start(); + } + + private void hideNotification(AHNotification notification, AHTextView notificationView) { + if (notificationView.getAlpha() != 0) { + if (shouldAnimateNotification(notification)) { + animateHideNotification(notificationView); + notification.setAnimate(false); + } else { + notificationView.setScaleX(0); + notificationView.setScaleY(0); + notificationView.setAlpha(0); } } } + private void animateHideNotification(AHTextView notification) { + notification.animate() + .scaleX(0) + .scaleY(0) + .alpha(0) + .setInterpolator(new AccelerateInterpolator()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (notification.getAlpha() == 0) notification.setText(""); + } + + }) + .setDuration(notificationAnimationDuration) + .start(); + } + + private void updateNotificationSize(AHNotification notificationItem, AHTextView notification) { + ViewGroup.LayoutParams lp = notification.getLayoutParams(); + lp.width = notificationItem.getSize() >= 0 && !notificationItem.hasText() ? notificationItem.getSize() : ViewGroup.LayoutParams.WRAP_CONTENT; + lp.height = notificationItem.getSize() >= 0 ? notificationItem.getSize() : getResources().getDimensionPixelSize(R.dimen.bottom_navigation_notification_height); + notification.requestLayout(); + } + + private boolean shouldAnimateNotification(AHNotification notification) { + return notification.shouldAnimate() && animateTabSelection; + } + //////////// // PUBLIC // @@ -1032,8 +1142,8 @@ public boolean isColored() { */ public void setColored(boolean colored) { this.colored = colored; - this.itemActiveColor = colored ? coloredTitleColorActive : titleColorActive; - this.itemInactiveColor = colored ? coloredTitleColorInactive : titleColorInactive; + this.iconActiveColor = colored ? coloredTitleColorActive : titleActiveColor; + this.iconInactiveColor = colored ? coloredTitleColorInactive : titleInactiveColor; createItems(); } @@ -1066,33 +1176,69 @@ public void setDefaultBackgroundResource(@DrawableRes int defaultBackgroundResou createItems(); } + public void setAnimateTabSelection(boolean animateTabSelection) { + this.animateTabSelection = animateTabSelection; + } + /** * Get the accent color (used when the view contains 3 items) * * @return The default accent color */ - public int getAccentColor() { - return itemActiveColor; + public @Nullable Integer getIconActiveColor(int index) { + return iconActiveColor.get(index); } /** * Set the accent color (used when the view contains 3 items) * - * @param accentColor The new accent color + * @param activeColor The new accent color */ - public void setAccentColor(int accentColor) { - this.titleColorActive = accentColor; - this.itemActiveColor = accentColor; + public void setIconActiveColor(int index, @Nullable Integer activeColor) { + if (AHHelper.equals(iconActiveColor.get(index), activeColor)) return; + iconActiveColor.set(index, activeColor); + createItems(); + } + + public void setIconWidth(int index, Integer width) { + if (AHHelper.equals(iconWidth.get(index), width)) return; + iconWidth.set(index, width); + createItems(); + } + + public void setIconHeight(int index, Integer height) { + if (AHHelper.equals(iconHeight.get(index), height)) return; + iconHeight.set(index, height); createItems(); } + /** + * Set the accent color (used when the view contains 3 items) + * + * @param activeColor The new accent color + */ + public void setTitleActiveColor(int index, @Nullable Integer activeColor) { + if (AHHelper.equals(titleActiveColor.get(index), activeColor)) return; + titleActiveColor.set(index, activeColor); + createItems(); + } + + /** + * Get the inactive color (used when the view contains 3 items) + * + * @return The inactive color + */ + public @Nullable Integer getIconInactiveColor(int index) { + return iconInactiveColor.get(index); + } + /** * Get the inactive color (used when the view contains 3 items) * * @return The inactive color */ - public int getInactiveColor() { - return itemInactiveColor; + public @Nullable Integer getTitleInactiveColor(int index) { + return titleInactiveColor.get(index); } /** @@ -1100,9 +1246,20 @@ public int getInactiveColor() { * * @param inactiveColor The inactive color */ - public void setInactiveColor(int inactiveColor) { - this.titleColorInactive = inactiveColor; - this.itemInactiveColor = inactiveColor; + public void setIconInactiveColor(int index, @Nullable Integer inactiveColor) { + if (AHHelper.equals(iconInactiveColor.get(index), inactiveColor)) return; + iconInactiveColor.set(index, inactiveColor); + createItems(); + } + + /** + * Set the inactive color (used when the view contains 3 items) + * + * @param inactiveColor The inactive color + */ + public void setTitleInactiveColor(int index, @Nullable Integer inactiveColor) { + if (AHHelper.equals(titleInactiveColor.get(index), inactiveColor)) return; + titleInactiveColor.set(index, inactiveColor); createItems(); } @@ -1112,15 +1269,15 @@ public void setInactiveColor(int inactiveColor) { * @param colorActive The active color * @param colorInactive The inactive color */ - public void setColoredModeColors(@ColorInt int colorActive, @ColorInt int colorInactive) { - this.coloredTitleColorActive = colorActive; - this.coloredTitleColorInactive = colorInactive; + public void setColoredModeColors(int index, @ColorInt int colorActive, @ColorInt int colorInactive) { + coloredTitleColorActive.set(index, colorActive); + coloredTitleColorInactive.set(index, colorInactive); createItems(); } /** * Set selected background visibility - */ + */ public void setSelectedBackgroundVisible(boolean visible) { this.selectedBackgroundVisible = visible; createItems(); @@ -1131,35 +1288,56 @@ public void setSelectedBackgroundVisible(boolean visible) { * * @param typeface Typeface */ - public void setTitleTypeface(Typeface typeface) { - this.titleTypeface = typeface; + public void setTitleTypeface(int index, @Nullable Typeface typeface) { + if (titleTypeface.get(index) == typeface) return; + titleTypeface.set(index, typeface); + createItems(); + } + + /** + * Set title active text size in pixels + */ + public void setTitleActiveTextSize(int index, Float activeSize) { + if (AHHelper.equals(titleActiveTextSize.get(index), activeSize)) return; + titleActiveTextSize.set(index, activeSize); createItems(); } /** - * Set title text size in pixels + * Set title inactive text size in pixels + */ + public void setTitleInactiveTextSize(int index, Float inactiveSize) { + if (AHHelper.equals(titleInactiveTextSize.get(index), inactiveSize)) return; + titleInactiveTextSize.set(index, inactiveSize); + createItems(); + } + + /** + * Set title text size in SP * - * @param activeSize - * @param inactiveSize + * @param activeSize in sp */ - public void setTitleTextSize(float activeSize, float inactiveSize) { - this.titleActiveTextSize = activeSize; - this.titleInactiveTextSize = inactiveSize; + public void setTitleActiveTextSizeInSp(int index, Float activeSize) { + if (AHHelper.equals(titleActiveTextSize.get(index), activeSize)) return; + this.titleActiveTextSize.set(index, (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, activeSize, resources.getDisplayMetrics()))); createItems(); } /** * Set title text size in SP * - + * @param activeSize in sp - + * @param inactiveSize in sp + * @param inactiveSize in sp */ - public void setTitleTextSizeInSp(float activeSize, float inactiveSize) { - this.titleActiveTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, activeSize, resources.getDisplayMetrics()); - this.titleInactiveTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, inactiveSize, resources.getDisplayMetrics()); + public void setTitleInactiveTextSizeInSp(int index, Float inactiveSize) { + if (AHHelper.equals(titleInactiveTextSize.get(index), inactiveSize)) return; + this.titleInactiveTextSize.set(index, (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, inactiveSize, resources.getDisplayMetrics()))); createItems(); } + public void setTag(int index, String tag) { + if (index >= 0 && index < views.size()) views.get(index).setTag(tag); + } + /** * Get item at the given index * @@ -1204,8 +1382,7 @@ public void setCurrentItem(int position, boolean useCallback) { return; } - if (titleState != TitleState.ALWAYS_HIDE && - (items.size() == MIN_ITEMS || titleState == TitleState.ALWAYS_SHOW)) { + if (isClassic()) { updateItems(position, useCallback); } else { updateSmallItems(position, useCallback); @@ -1411,10 +1588,10 @@ public void removeOnNavigationPositionListener() { @Deprecated public void setNotification(int nbNotification, int itemPosition) { if (itemPosition < 0 || itemPosition > items.size() - 1) { - throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); + throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); } - final String title = nbNotification == 0 ? "" : String.valueOf(nbNotification); - notifications.set(itemPosition, AHNotification.justText(title)); + final String title = nbNotification == 0 ? "" : String.valueOf(nbNotification); + notifications.set(itemPosition, AHNotification.justText(title)); updateNotifications(false, itemPosition); } @@ -1425,37 +1602,44 @@ public void setNotification(int nbNotification, int itemPosition) { * @param itemPosition int */ public void setNotification(String title, int itemPosition) { - if (itemPosition < 0 || itemPosition > items.size() - 1) { - throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); - } - notifications.set(itemPosition, AHNotification.justText(title)); - updateNotifications(false, itemPosition); - } - - /** - * Set fully customized Notification - * - * @param notification AHNotification - * @param itemPosition int - */ + if (itemPosition < 0 || itemPosition > items.size() - 1) { + throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); + } + notifications.set(itemPosition, AHNotification.justText(title)); + updateNotifications(false, itemPosition); + } + + /** + * Set fully customized Notification + * + * @param notification AHNotification + * @param itemPosition int + */ public void setNotification(AHNotification notification, int itemPosition) { if (itemPosition < 0 || itemPosition > items.size() - 1) { - throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); + throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); + } + if (notification == null) { + notification = new AHNotification(); // instead of null, use empty notification } - if (notification == null) { - notification = new AHNotification(); // instead of null, use empty notification - } notifications.set(itemPosition, notification); updateNotifications(true, itemPosition); } + public void setNotificationSize(int index, @Px Integer size) { + if (AHHelper.equals(notifications.get(index).getSize(), size)) return; + notifications.get(index).setSize(size); + updateNotifications(true, index); + } + /** * Set notification text color * * @param textColor int */ public void setNotificationTextColor(@ColorInt int textColor) { - this.notificationTextColor = textColor; + if (notificationTextColor == textColor) return; + notificationTextColor = textColor; updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); } @@ -1485,7 +1669,8 @@ public void setNotificationBackground(Drawable drawable) { * @param color int */ public void setNotificationBackgroundColor(@ColorInt int color) { - this.notificationBackgroundColor = color; + if (notificationBackgroundColor == color) return; + notificationBackgroundColor = color; updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); } @@ -1509,16 +1694,13 @@ public void setNotificationTypeface(Typeface typeface) { updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); } - public void setNotificationAnimationDuration(long notificationAnimationDuration){ + public void setNotificationAnimationDuration(long notificationAnimationDuration) { this.notificationAnimationDuration = notificationAnimationDuration; updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); } /** * Set the notification margin left - * - * @param activeMargin - * @param inactiveMargin */ public void setNotificationMarginLeft(int activeMargin, int inactiveMargin) { this.notificationActiveMarginLeft = activeMargin; @@ -1532,8 +1714,7 @@ public void setNotificationMarginLeft(int activeMargin, int inactiveMargin) { * @param useElevation boolean */ public void setUseElevation(boolean useElevation) { - ViewCompat.setElevation(this, useElevation ? - resources.getDimension(R.dimen.bottom_navigation_elevation) : 0); + ViewCompat.setElevation(this, useElevation ? resources.getDimension(R.dimen.bottom_navigation_elevation) : 0); setClipToPadding(false); } @@ -1570,7 +1751,7 @@ public View getViewAtPosition(int position) { } return null; } - + /** * Enable the tab item at the given position * @param position int @@ -1583,7 +1764,7 @@ public void enableItemAtPosition(int position) { itemsEnabledStates[position] = true; createItems(); } - + /** * Disable the tab item at the given position * @param position int @@ -1596,15 +1777,15 @@ public void disableItemAtPosition(int position) { itemsEnabledStates[position] = false; createItems(); } - + /** * Set the item disable color - * @param itemDisableColor int + * @param iconDisableColor int */ - public void setItemDisableColor(@ColorInt int itemDisableColor) { - this.itemDisableColor = itemDisableColor; + public void setIconDisableColor(int index, @ColorInt int iconDisableColor) { + this.iconDisableColor.set(index, iconDisableColor); } - + //////////////// // INTERFACES // //////////////// @@ -1632,4 +1813,12 @@ public interface OnNavigationPositionListener { void onPositionChange(int y); } + public void setPreferLargeIcons(boolean preferLargeIcons) { + this.preferLargeIcons = preferLargeIcons; + } + + private int dpToPx(int dp) { + DisplayMetrics metrics = getResources().getDisplayMetrics(); + return dp * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); + } } diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationAdapter.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationAdapter.java index 5a885c17..cadf0e32 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationAdapter.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationAdapter.java @@ -1,9 +1,9 @@ package com.aurelhubert.ahbottomnavigation; import android.app.Activity; -import android.support.annotation.ColorInt; -import android.support.annotation.MenuRes; -import android.support.v7.widget.PopupMenu; +import androidx.annotation.ColorInt; +import androidx.annotation.MenuRes; +import androidx.appcompat.widget.PopupMenu; import android.view.Menu; import android.view.MenuItem; diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationBehavior.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationBehavior.java index 579e5372..c9c2d68a 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationBehavior.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationBehavior.java @@ -5,14 +5,14 @@ import android.content.Context; import android.content.res.TypedArray; import android.os.Build; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.design.widget.TabLayout; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPropertyAnimatorCompat; -import android.support.v4.view.ViewPropertyAnimatorUpdateListener; -import android.support.v4.view.animation.LinearOutSlowInInterpolator; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.tabs.TabLayout; +import androidx.core.view.ViewCompat; +import androidx.core.view.ViewPropertyAnimatorCompat; +import androidx.core.view.ViewPropertyAnimatorUpdateListener; +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationFABBehavior.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationFABBehavior.java index 711a4d22..d183667b 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationFABBehavior.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationFABBehavior.java @@ -1,8 +1,8 @@ package com.aurelhubert.ahbottomnavigation; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; import android.view.View; import android.view.ViewGroup; diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationItem.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationItem.java index 4b240bc1..027a0c44 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationItem.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationItem.java @@ -4,12 +4,14 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Drawable; -import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; -import android.support.annotation.StringRes; -import android.support.v4.content.ContextCompat; -import android.support.v7.content.res.AppCompatResources; +import android.graphics.drawable.StateListDrawable; + +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.StringRes; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.core.content.ContextCompat; /** * AHBottomNavigationItem @@ -18,39 +20,36 @@ public class AHBottomNavigationItem { private String title = ""; - private Drawable drawable; - private int color = Color.GRAY; - - private - @StringRes - int titleRes = 0; - private - @DrawableRes - int drawableRes = 0; - private - @ColorRes - int colorRes = 0; - + private Drawable icon; + private Drawable selectedIcon; + private String tag; + private int color = Color.GRAY; + + private @DrawableRes int iconRes = 0; + private @DrawableRes int selectedIconRes = 0; + private @StringRes int titleRes = 0; + private @ColorRes int colorRes = 0; + /** * Constructor * * @param title Title - * @param resource Drawable resource + * @param iconRes Drawable resource */ - public AHBottomNavigationItem(String title, @DrawableRes int resource) { + public AHBottomNavigationItem(String title, @DrawableRes int iconRes) { this.title = title; - this.drawableRes = resource; + this.iconRes = iconRes; } - + /** * @param title Title - * @param resource Drawable resource + * @param iconRes Drawable resource * @param color Background color */ @Deprecated - public AHBottomNavigationItem(String title, @DrawableRes int resource, @ColorRes int color) { + public AHBottomNavigationItem(String title, @DrawableRes int iconRes, @ColorRes int color) { this.title = title; - this.drawableRes = resource; + this.iconRes = iconRes; this.color = color; } @@ -58,16 +57,27 @@ public AHBottomNavigationItem(String title, @DrawableRes int resource, @ColorRes * Constructor * * @param titleRes String resource - * @param drawableRes Drawable resource + * @param iconRes Drawable resource * @param colorRes Color resource */ - public AHBottomNavigationItem(@StringRes int titleRes, @DrawableRes int drawableRes, @ColorRes int colorRes) { + public AHBottomNavigationItem(@StringRes int titleRes, @DrawableRes int iconRes, @ColorRes int colorRes) { this.titleRes = titleRes; - this.drawableRes = drawableRes; + this.iconRes = iconRes; this.colorRes = colorRes; } - - /** + + /** + * Constructor + * + * @param titleRes String resource + * @param iconRes Drawable resource + */ + public AHBottomNavigationItem(@StringRes int titleRes, @DrawableRes int iconRes) { + this.titleRes = titleRes; + this.iconRes = iconRes; + } + + /** * Constructor * * @param title String @@ -75,9 +85,23 @@ public AHBottomNavigationItem(@StringRes int titleRes, @DrawableRes int drawable */ public AHBottomNavigationItem(String title, Drawable drawable) { this.title = title; - this.drawable = drawable; + this.icon = drawable; } - + + /** + * Constructor + * + * @param title String + * @param icon Drawable + * @param tag String + */ + public AHBottomNavigationItem(String title, Drawable icon, Drawable selectedIcon, String tag) { + this.title = title; + this.icon = icon; + this.selectedIcon = selectedIcon; + this.tag = tag; + } + /** * Constructor * @@ -87,11 +111,11 @@ public AHBottomNavigationItem(String title, Drawable drawable) { */ public AHBottomNavigationItem(String title, Drawable drawable, @ColorInt int color) { this.title = title; - this.drawable = drawable; + this.icon = drawable; this.color = color; } - - public String getTitle(Context context) { + + public String getTitle(Context context) { if (titleRes != 0) { return context.getString(titleRes); } @@ -126,23 +150,52 @@ public void setColorRes(@ColorRes int colorRes) { } public Drawable getDrawable(Context context) { - if (drawableRes != 0) { - try { - return AppCompatResources.getDrawable(context, drawableRes); - } catch (Resources.NotFoundException e) { - return ContextCompat.getDrawable(context, drawableRes); - } - } - return drawable; + Drawable icon = this.icon == null ? getResourceDrawable(context, iconRes) : this.icon; + Drawable selectedIcon = this.selectedIcon == null ? getResourceDrawable(context, selectedIconRes) : this.selectedIcon; + + StateListDrawable stateDrawable = new StateListDrawable(); + if (selectedIcon != null) stateDrawable.addState(new int[] { android.R.attr.state_selected }, selectedIcon); + stateDrawable.addState(new int[] {}, icon); + return stateDrawable; } - - public void setDrawable(@DrawableRes int drawableRes) { - this.drawableRes = drawableRes; - this.drawable = null; + + public void setIcon(@DrawableRes int drawableRes) { + this.iconRes = drawableRes; + this.icon = null; } - public void setDrawable(Drawable drawable) { - this.drawable = drawable; - this.drawableRes = 0; + public void setIcon(Drawable drawable) { + this.icon = drawable; + this.iconRes = 0; } + + public void setSelectedIcon(Drawable selectedIcon) { + this.selectedIcon = selectedIcon; + } + + public void setSelectedIcon(@DrawableRes int selectedIconRes) { + this.selectedIconRes = selectedIconRes; + } + + public String getTag() { + return tag; + } + + private Drawable getResourceDrawable(Context context, @DrawableRes int drawableRes) { + if (drawableRes != 0) { + try { + return AppCompatResources.getDrawable(context, drawableRes); + } catch (Resources.NotFoundException e) { + return ContextCompat.getDrawable(context, drawableRes); + } + } + return null; + } + + public boolean hasIcon() { + return icon != null || + iconRes != 0 || + selectedIcon != null || + selectedIconRes != 0; + } } diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationViewPager.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationViewPager.java index e8daa291..0a52960d 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationViewPager.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationViewPager.java @@ -1,7 +1,7 @@ package com.aurelhubert.ahbottomnavigation; import android.content.Context; -import android.support.v4.view.ViewPager; +import androidx.viewpager.widget.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHHelper.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHHelper.java index 46c08a5c..fca1aeb7 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHHelper.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHHelper.java @@ -8,8 +8,6 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.v4.graphics.drawable.DrawableCompat; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; @@ -19,20 +17,29 @@ import android.widget.ImageView; import android.widget.TextView; +import java.util.ArrayList; + +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; +import androidx.core.graphics.drawable.DrawableCompat; + /** * */ +@SuppressWarnings({"WeakerAccess", "MagicNumber"}) public class AHHelper { /** * Return a tint drawable - * - * @param drawable - * @param color - * @param forceTint - * @return */ - public static Drawable getTintDrawable(Drawable drawable, @ColorInt int color, boolean forceTint) { + public static Drawable getTintDrawable(Drawable drawable, @Nullable Integer color, boolean forceTint) { + if (color == null) { + Drawable wrapDrawable = DrawableCompat.wrap(drawable).mutate(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + wrapDrawable.setTintList(null); + } + return wrapDrawable; + } if (forceTint) { drawable.clearColorFilter(); drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); @@ -50,17 +57,14 @@ public static Drawable getTintDrawable(Drawable drawable, @ColorInt int color, b public static void updateTopMargin(final View view, int fromMargin, int toMargin) { ValueAnimator animator = ValueAnimator.ofFloat(fromMargin, toMargin); animator.setDuration(150); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float animatedValue = (float) valueAnimator.getAnimatedValue(); - if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { - ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); - p.setMargins(p.leftMargin, (int) animatedValue, p.rightMargin, p.bottomMargin); - view.requestLayout(); - } - } - }); + animator.addUpdateListener(valueAnimator -> { + float animatedValue = (float) valueAnimator.getAnimatedValue(); + if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + p.setMargins(p.leftMargin, (int) animatedValue, p.rightMargin, p.bottomMargin); + view.requestLayout(); + } + }); animator.start(); } @@ -70,37 +74,41 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) { public static void updateBottomMargin(final View view, int fromMargin, int toMargin, int duration) { ValueAnimator animator = ValueAnimator.ofFloat(fromMargin, toMargin); animator.setDuration(duration); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float animatedValue = (float) valueAnimator.getAnimatedValue(); - if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { - ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); - p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) animatedValue); - view.requestLayout(); - } - } - }); + animator.addUpdateListener(valueAnimator -> { + float animatedValue = (float) valueAnimator.getAnimatedValue(); + if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) animatedValue); + view.requestLayout(); + } + }); animator.start(); } + public static void updateMargin(final View view, int left, int top, int right, int bottom) { + if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + lp.leftMargin = left; + lp.topMargin = top; + lp.rightMargin = right; + lp.bottomMargin = bottom; + } + } + /** * Update left margin with animation */ public static void updateLeftMargin(final View view, int fromMargin, int toMargin) { ValueAnimator animator = ValueAnimator.ofFloat(fromMargin, toMargin); animator.setDuration(150); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float animatedValue = (float) valueAnimator.getAnimatedValue(); - if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { - ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); - p.setMargins((int) animatedValue, p.topMargin, p.rightMargin, p.bottomMargin); - view.requestLayout(); - } - } - }); + animator.addUpdateListener(valueAnimator -> { + float animatedValue = (float) valueAnimator.getAnimatedValue(); + if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + p.setMargins((int) animatedValue, p.topMargin, p.rightMargin, p.bottomMargin); + view.requestLayout(); + } + }); animator.start(); } @@ -110,13 +118,10 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) { public static void updateTextSize(final TextView textView, float fromSize, float toSize) { ValueAnimator animator = ValueAnimator.ofFloat(fromSize, toSize); animator.setDuration(150); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float animatedValue = (float) valueAnimator.getAnimatedValue(); - textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, animatedValue); - } - }); + animator.addUpdateListener(valueAnimator -> { + float animatedValue = (float) valueAnimator.getAnimatedValue(); + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, animatedValue); + }); animator.start(); } @@ -126,65 +131,55 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) { public static void updateAlpha(final View view, float fromValue, float toValue) { ValueAnimator animator = ValueAnimator.ofFloat(fromValue, toValue); animator.setDuration(150); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float animatedValue = (float) valueAnimator.getAnimatedValue(); - view.setAlpha(animatedValue); - } - }); + animator.addUpdateListener(valueAnimator -> { + float animatedValue = (float) valueAnimator.getAnimatedValue(); + view.setAlpha(animatedValue); + }); animator.start(); } /** * Update text color with animation */ - public static void updateTextColor(final TextView textView, @ColorInt int fromColor, - @ColorInt int toColor) { - ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor); - colorAnimation.setDuration(150); - colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - textView.setTextColor((Integer) animator.getAnimatedValue()); - } - }); - colorAnimation.start(); + public static void updateTextColor(final AHTextView textView, @Nullable Integer fromColor, @Nullable Integer toColor) { + if (fromColor != null && toColor != null) { + ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor); + colorAnimation.setDuration(150); + colorAnimation.addUpdateListener(animator -> textView.setTextColor((Integer) animator.getAnimatedValue())); + colorAnimation.start(); + } else { + textView.setTextColor(toColor); + } } /** * Update text color with animation */ - public static void updateViewBackgroundColor(final View view, @ColorInt int fromColor, - @ColorInt int toColor) { + public static void updateViewBackgroundColor(final View view, @ColorInt int fromColor, @ColorInt int toColor) { ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor); colorAnimation.setDuration(150); - colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - view.setBackgroundColor((Integer) animator.getAnimatedValue()); - } - }); + colorAnimation.addUpdateListener(animator -> view.setBackgroundColor((Integer) animator.getAnimatedValue())); colorAnimation.start(); } /** * Update image view color with animation */ - public static void updateDrawableColor(final Context context, final Drawable drawable, - final ImageView imageView, @ColorInt int fromColor, - @ColorInt int toColor, final boolean forceTint) { - ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor); - colorAnimation.setDuration(150); - colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - imageView.setImageDrawable(AHHelper.getTintDrawable(drawable, - (Integer) animator.getAnimatedValue(), forceTint)); - imageView.requestLayout(); - } - }); - colorAnimation.start(); + public static void updateDrawableColor(final Drawable drawable, + final ImageView imageView, @Nullable Integer fromColor, + @Nullable Integer toColor, final boolean forceTint) { + if (fromColor != null && toColor != null) { + ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor); + colorAnimation.setDuration(150); + colorAnimation.addUpdateListener(animator -> { + imageView.setImageDrawable(AHHelper.getTintDrawable(drawable, (Integer) animator.getAnimatedValue(), forceTint)); + imageView.requestLayout(); + }); + colorAnimation.start(); + } else { + imageView.setImageDrawable(AHHelper.getTintDrawable(drawable, toColor, forceTint)); + imageView.requestLayout(); + } } /** @@ -193,14 +188,11 @@ public void onAnimationUpdate(ValueAnimator animator) { public static void updateWidth(final View view, float fromWidth, float toWidth) { ValueAnimator animator = ValueAnimator.ofFloat(fromWidth, toWidth); animator.setDuration(150); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - ViewGroup.LayoutParams params = view.getLayoutParams(); - params.width = Math.round((float) animator.getAnimatedValue()); - view.setLayoutParams(params); - } - }); + animator.addUpdateListener(animator1 -> { + ViewGroup.LayoutParams params = view.getLayoutParams(); + params.width = Math.round((float) animator1.getAnimatedValue()); + view.setLayoutParams(params); + }); animator.start(); } @@ -208,7 +200,6 @@ public void onAnimationUpdate(ValueAnimator animator) { * Check if the status bar is translucent * * @param context Context - * @return */ public static boolean isTranslucentStatusBar(Context context) { Window w = unwrap(context).getWindow(); @@ -225,7 +216,6 @@ public static boolean isTranslucentStatusBar(Context context) { * Get the height of the buttons bar * * @param context Context - * @return */ public static int getSoftButtonsBarSizePort(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { @@ -244,7 +234,7 @@ public static int getSoftButtonsBarSizePort(Context context) { } /** - * Unwrap wactivity + * Unwrap activity * * @param context Context * @return Activity @@ -256,4 +246,36 @@ public static Activity unwrap(Context context) { } return (Activity) context; } + + public interface Mapper { + T map(T value); + } + + public static void map(ArrayList al, Mapper mapper) { + if (al == null) return; + for (int i = 0; i < al.size(); i++) { + al.set(i, mapper.map(al.get(i))); + } + } + + public static void fill(ArrayList al, int count, T value) { + for (int i = 0; i < count; i++) { + al.add(value); + } + } + + public static boolean equals(@Nullable Object o1, @Nullable Object o2) { + return o1 == null && o2 == null || o1 != null && o1.equals(o2); + } + + public static boolean isInteger(String s) { + try { + Integer.parseInt(s); + } catch(NumberFormatException e) { + return false; + } catch(NullPointerException e) { + return false; + } + return true; + } } diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHTextView.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHTextView.java new file mode 100644 index 00000000..03528fe8 --- /dev/null +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHTextView.java @@ -0,0 +1,36 @@ +package com.aurelhubert.ahbottomnavigation; + +import android.content.Context; +import androidx.annotation.Nullable; +import android.util.AttributeSet; + +public class AHTextView extends androidx.appcompat.widget.AppCompatTextView { + private Integer originalTextColor; + + public AHTextView(Context context) { + super(context); + } + + public AHTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AHTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setTextColor(@Nullable Integer color) { + saveOriginalTextColor(); + if (color == null) { + super.setTextColor(originalTextColor); + } else { + super.setTextColor(color); + } + } + + private void saveOriginalTextColor() { + if (originalTextColor == null) { + originalTextColor = getCurrentTextColor(); + } + } +} diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/StubAnimationListener.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/StubAnimationListener.java new file mode 100644 index 00000000..1716b54a --- /dev/null +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/StubAnimationListener.java @@ -0,0 +1,19 @@ +package com.aurelhubert.ahbottomnavigation; + +import android.animation.Animator; + +import androidx.annotation.NonNull; + +public class StubAnimationListener implements Animator.AnimatorListener { + @Override + public void onAnimationStart(@NonNull Animator animation) {} + + @Override + public void onAnimationEnd(@NonNull Animator animation) {} + + @Override + public void onAnimationCancel(@NonNull Animator animation) {} + + @Override + public void onAnimationRepeat(@NonNull Animator animation) {} +} diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/VerticalScrollingBehavior.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/VerticalScrollingBehavior.java index 1471d39b..fdc21e17 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/VerticalScrollingBehavior.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/VerticalScrollingBehavior.java @@ -3,9 +3,9 @@ import android.content.Context; import android.os.Parcelable; -import android.support.annotation.IntDef; -import android.support.design.widget.CoordinatorLayout; -import android.support.v4.view.WindowInsetsCompat; +import androidx.annotation.IntDef; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.WindowInsetsCompat; import android.util.AttributeSet; import android.view.View; diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotification.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotification.java index 79844785..a6348898 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotification.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotification.java @@ -2,17 +2,21 @@ import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.ColorInt; -import android.support.annotation.Nullable; import android.text.TextUtils; import java.util.ArrayList; import java.util.List; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; + /** * @author repitch */ public class AHNotification implements Parcelable { + public static final int NOTIFICATION_SIZE_DEFAULT = -1; @Nullable private String text; // can be null, so notification will not be shown @@ -23,6 +27,11 @@ public class AHNotification implements Parcelable { @ColorInt private int backgroundColor; // if 0 then use default value + @Px + private int size = NOTIFICATION_SIZE_DEFAULT; + + private boolean animate = false; + public AHNotification() { // empty } @@ -34,11 +43,20 @@ private AHNotification(Parcel in) { } public boolean isEmpty() { - return TextUtils.isEmpty(text); + return TextUtils.isEmpty(text) && size <= 0; + } + + public boolean hasValue() { + return text != null || size != NOTIFICATION_SIZE_DEFAULT; + } + + public boolean isDot() { + return TextUtils.isEmpty(text) && size >= 0; } - public String getText() { - return text; + @NonNull + public String getReadableText() { + return text == null ? "" : text; } public int getTextColor() { @@ -49,6 +67,22 @@ public int getBackgroundColor() { return backgroundColor; } + public int getSize() { + return size; + } + + public void setSize(@Px int size) { + this.size = size; + } + + public void setAnimate(boolean animate) { + this.animate = animate; + } + + public boolean shouldAnimate() { + return animate; + } + public static AHNotification justText(String text) { return new Builder().setText(text).build(); } @@ -71,6 +105,11 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(text); dest.writeInt(textColor); dest.writeInt(backgroundColor); + dest.writeInt(size); + } + + public boolean hasText() { + return !TextUtils.isEmpty(text); } public static class Builder { @@ -80,6 +119,9 @@ public static class Builder { private int textColor; @ColorInt private int backgroundColor; + @Px + private int size = NOTIFICATION_SIZE_DEFAULT; + private boolean animate = false; public Builder setText(String text) { this.text = text; @@ -91,16 +133,29 @@ public Builder setTextColor(@ColorInt int textColor) { return this; } - public Builder setBackgroundColor(@ColorInt int backgroundColor) { + public Builder setBackgroundColor(@ColorInt Integer backgroundColor) { + if (backgroundColor == null) return this; this.backgroundColor = backgroundColor; return this; } + public Builder setSize(@Px int size) { + this.size = size; + return this; + } + + public Builder animate(boolean animate) { + this.animate = animate; + return this; + } + public AHNotification build() { AHNotification notification = new AHNotification(); notification.text = text; notification.textColor = textColor; notification.backgroundColor = backgroundColor; + notification.size = size; + notification.animate = animate; return notification; } } diff --git a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotificationHelper.java b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotificationHelper.java index 161aef58..8898995c 100644 --- a/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotificationHelper.java +++ b/ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotificationHelper.java @@ -1,7 +1,7 @@ package com.aurelhubert.ahbottomnavigation.notification; -import android.support.annotation.ColorInt; -import android.support.annotation.NonNull; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; /** * @author repitch diff --git a/ahbottomnavigation/src/main/res/layout-v21/bottom_navigation_item.xml b/ahbottomnavigation/src/main/res/layout-v21/bottom_navigation_item.xml index 743e78f5..5f92a4fe 100644 --- a/ahbottomnavigation/src/main/res/layout-v21/bottom_navigation_item.xml +++ b/ahbottomnavigation/src/main/res/layout-v21/bottom_navigation_item.xml @@ -19,7 +19,7 @@ android:layout_marginTop="@dimen/bottom_navigation_margin_top_inactive" android:gravity="center"/> - - - - - - - - #303F9F #FF4081 #747474 + #FF4081 + #747474 #3A000000 #FFFFFF #50FFFFFF diff --git a/build.gradle b/build.gradle index 6a772838..fabfa2d5 100644 --- a/build.gradle +++ b/build.gradle @@ -2,24 +2,17 @@ buildscript { repositories { - jcenter() + google() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath 'com.android.tools.build:gradle:8.8.2' } } allprojects { repositories { - jcenter() - maven { - url "https://maven.google.com" - } + google() + mavenCentral() } } - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/demo/build.gradle b/demo/build.gradle index 69143493..acdcd053 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 26 - buildToolsVersion '26.0.2' + compileSdkVersion 34 defaultConfig { + namespace 'com.aurelhubert.ahbottomnavigation.demo' applicationId "com.aurelhubert.ahbottomnavigation.demo" - minSdkVersion 16 - targetSdkVersion 26 + minSdkVersion 21 + targetSdkVersion 34 versionCode 1 versionName "1.0" vectorDrawables.useSupportLibrary = true @@ -19,13 +19,29 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } + } } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:26.1.0' - compile 'com.android.support:cardview-v7:26.1.0' - compile 'com.android.support:design:26.1.0' - compile project(':ahbottomnavigation') + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'com.google.android.material:material:1.12.0' + implementation project(':ahbottomnavigation') +} + +tasks.configureEach { task -> + if (task.name == 'lintVitalAnalyzeRelease') { + task.enabled = false + logger.lifecycle("Disabling lintVitalAnalyzeRelease task for ${project.name}.") + } } diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index a342598a..5ce7e1af 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> - + diff --git a/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoActivity.java b/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoActivity.java index 16cd4053..623bda8b 100644 --- a/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoActivity.java +++ b/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoActivity.java @@ -3,15 +3,18 @@ import android.animation.Animator; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.animation.LinearOutSlowInInterpolator; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.app.AppCompatDelegate; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; +import androidx.core.content.ContextCompat; +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; + +import android.util.DisplayMetrics; import android.view.View; import android.view.animation.OvershootInterpolator; @@ -41,8 +44,7 @@ public class DemoActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - boolean enabledTranslucentNavigation = getSharedPreferences("shared", Context.MODE_PRIVATE) - .getBoolean("translucentNavigation", false); + boolean enabledTranslucentNavigation = getSharedPreferences("shared", Context.MODE_PRIVATE).getBoolean("translucentNavigation", false); setTheme(enabledTranslucentNavigation ? R.style.AppTheme_TranslucentNavigation : R.style.AppTheme); setContentView(R.layout.activity_home); initUI(); @@ -72,9 +74,9 @@ private void initUI() { navigationAdapter = new AHBottomNavigationAdapter(this, R.menu.bottom_navigation_menu_3); navigationAdapter.setupWithBottomNavigation(bottomNavigation, tabColors); } else { - AHBottomNavigationItem item1 = new AHBottomNavigationItem(R.string.tab_1, R.drawable.ic_apps_black_24dp, R.color.color_tab_1); - AHBottomNavigationItem item2 = new AHBottomNavigationItem(R.string.tab_2, R.drawable.ic_maps_local_bar, R.color.color_tab_2); - AHBottomNavigationItem item3 = new AHBottomNavigationItem(R.string.tab_3, R.drawable.ic_maps_local_restaurant, R.color.color_tab_3); + AHBottomNavigationItem item1 = new AHBottomNavigationItem(R.string.tab_1, R.drawable.ic_apps_black_24dp); + AHBottomNavigationItem item2 = new AHBottomNavigationItem(R.string.tab_2, R.drawable.ic_maps_local_bar); + AHBottomNavigationItem item3 = new AHBottomNavigationItem(R.string.tab_3, R.drawable.ic_maps_local_restaurant); bottomNavigationItems.add(item1); bottomNavigationItems.add(item2); @@ -86,106 +88,103 @@ private void initUI() { bottomNavigation.manageFloatingActionButtonBehavior(floatingActionButton); bottomNavigation.setTranslucentNavigationEnabled(true); - bottomNavigation.setOnTabSelectedListener(new AHBottomNavigation.OnTabSelectedListener() { - @Override - public boolean onTabSelected(int position, boolean wasSelected) { - - if (currentFragment == null) { - currentFragment = adapter.getCurrentFragment(); - } - - if (wasSelected) { - currentFragment.refresh(); - return true; - } - - if (currentFragment != null) { - currentFragment.willBeHidden(); - } - - viewPager.setCurrentItem(position, false); - - if (currentFragment == null) { - return true; - } - - currentFragment = adapter.getCurrentFragment(); - currentFragment.willBeDisplayed(); - - if (position == 1) { - bottomNavigation.setNotification("", 1); - - floatingActionButton.setVisibility(View.VISIBLE); - floatingActionButton.setAlpha(0f); - floatingActionButton.setScaleX(0f); - floatingActionButton.setScaleY(0f); - floatingActionButton.animate() - .alpha(1) - .scaleX(1) - .scaleY(1) - .setDuration(300) - .setInterpolator(new OvershootInterpolator()) - .setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - - } - - @Override - public void onAnimationEnd(Animator animation) { - floatingActionButton.animate() - .setInterpolator(new LinearOutSlowInInterpolator()) - .start(); - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }) - .start(); - - } else { - if (floatingActionButton.getVisibility() == View.VISIBLE) { - floatingActionButton.animate() - .alpha(0) - .scaleX(0) - .scaleY(0) - .setDuration(300) - .setInterpolator(new LinearOutSlowInInterpolator()) - .setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - - } - - @Override - public void onAnimationEnd(Animator animation) { - floatingActionButton.setVisibility(View.GONE); - } - - @Override - public void onAnimationCancel(Animator animation) { - floatingActionButton.setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }) - .start(); - } - } - - return true; - } - }); + bottomNavigation.setOnTabSelectedListener((position, wasSelected) -> { + + if (currentFragment == null) { + currentFragment = adapter.getCurrentFragment(); + } + + if (wasSelected) { + currentFragment.refresh(); + return true; + } + + if (currentFragment != null) { + currentFragment.willBeHidden(); + } + + viewPager.setCurrentItem(position, false); + + if (currentFragment == null) { + return true; + } + + currentFragment = adapter.getCurrentFragment(); + currentFragment.willBeDisplayed(); + + if (position == 1) { + bottomNavigation.setNotification("", 1); + + floatingActionButton.setVisibility(View.VISIBLE); + floatingActionButton.setAlpha(0f); + floatingActionButton.setScaleX(0f); + floatingActionButton.setScaleY(0f); + floatingActionButton.animate() + .alpha(1) + .scaleX(1) + .scaleY(1) + .setDuration(300) + .setInterpolator(new OvershootInterpolator()) + .setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + floatingActionButton.animate() + .setInterpolator(new LinearOutSlowInInterpolator()) + .start(); + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }) + .start(); + + } else { + if (floatingActionButton.getVisibility() == View.VISIBLE) { + floatingActionButton.animate() + .alpha(0) + .scaleX(0) + .scaleY(0) + .setDuration(300) + .setInterpolator(new LinearOutSlowInInterpolator()) + .setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + floatingActionButton.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + floatingActionButton.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }) + .start(); + } + } + + return true; + }); /* bottomNavigation.setOnNavigationPositionListener(new AHBottomNavigation.OnNavigationPositionListener() { @@ -201,23 +200,21 @@ public void onAnimationRepeat(Animator animation) { currentFragment = adapter.getCurrentFragment(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - // Setting custom colors for notification - AHNotification notification = new AHNotification.Builder() - .setText(":)") - .setBackgroundColor(ContextCompat.getColor(DemoActivity.this, R.color.color_notification_back)) - .setTextColor(ContextCompat.getColor(DemoActivity.this, R.color.color_notification_text)) - .build(); - bottomNavigation.setNotification(notification, 1); - Snackbar.make(bottomNavigation, "Snackbar with bottom navigation", - Snackbar.LENGTH_SHORT).show(); - - } - }, 3000); + handler.postDelayed(() -> { + // Setting custom colors for notification + AHNotification notification = new AHNotification.Builder() + .setText(":)") + .setBackgroundColor(ContextCompat.getColor(DemoActivity.this, R.color.color_notification_back)) + .setTextColor(ContextCompat.getColor(DemoActivity.this, R.color.color_notification_text)) + .build(); + bottomNavigation.setNotification(notification, 1); + Snackbar.make(bottomNavigation, "Snackbar with bottom navigation", + Snackbar.LENGTH_SHORT).show(); + + }, 3000); //bottomNavigation.setDefaultBackgroundResource(R.drawable.bottom_navigation_background); + bottomNavigation.setDefaultBackgroundColor(Color.parseColor("#d7ffd9")); } /** @@ -290,6 +287,7 @@ public void updateSelectedBackgroundVisibility(boolean isVisible) { * Set title state for bottomNavigation */ public void setTitleState(AHBottomNavigation.TitleState titleState) { + if (titleState == AHBottomNavigation.TitleState.ALWAYS_HIDE) bottomNavigation.setPreferLargeIcons(true); bottomNavigation.setTitleState(titleState); } @@ -308,4 +306,74 @@ public int getBottomNavigationNbItems() { return bottomNavigation.getItemsCount(); } + public void tab1CustomColors(View view) { + bottomNavigation.setIconActiveColor(0, Color.GREEN); + bottomNavigation.setTitleActiveColor(0, Color.GREEN); + bottomNavigation.setIconInactiveColor(0, Color.BLUE); + bottomNavigation.setTitleInactiveColor(0, Color.BLUE); + } + + public void tab2DifferentColors(View view) { + bottomNavigation.setIconActiveColor(1, Color.MAGENTA); + bottomNavigation.setTitleActiveColor(1, Color.YELLOW); + bottomNavigation.setIconInactiveColor(1, Color.parseColor("#80e27e")); + bottomNavigation.setTitleInactiveColor(1, Color.parseColor("#4dd0e1")); + } + + public void tab3SelectedIconColor(View view) { + bottomNavigation.setIconActiveColor(2, Color.parseColor("#6a1b9a")); +// bottomNavigation.setIconInactiveColor(2, Color.parseColor("#6a1b9a")); +// bottomNavigation.setTitleActiveColor(2, Color.parseColor("#6a1b9a")); +// bottomNavigation.setTitleInactiveColor(2, Color.parseColor("#6a1b9a")); + } + + public void setTab3FontSize(View view) { + bottomNavigation.setTitleActiveTextSizeInSp(2, (float) 19); + bottomNavigation.setTitleInactiveTextSizeInSp(2, null); + } + + private boolean tab1DotVisible; + public void tab1Dot(View view) { + if (tab1DotVisible) { + tab1DotVisible = false; + bottomNavigation.setNotificationSize(0, AHNotification.NOTIFICATION_SIZE_DEFAULT); + } else { + tab1DotVisible = true; + bottomNavigation.setNotificationSize(0, dpToPx(8)); + } + } + + private boolean tab2DotVisible; + public void tab2Dot(View view) { + tab2BadgeVisible = false; + if (tab2DotVisible) { + tab2DotVisible = false; + bottomNavigation.setNotification("", 1); + } else { + tab2DotVisible = true; + bottomNavigation.setNotification(new AHNotification.Builder().setSize(dpToPx(12)).build(), 1); + } + } + + private boolean tab2BadgeVisible; + public void tab2Badge(View view) { + tab2DotVisible = false; + if (tab2BadgeVisible) { + tab2BadgeVisible = false; + bottomNavigation.setNotification(new AHNotification.Builder() + .setText("") + .build(), 1); + } else { + tab2BadgeVisible = true; + bottomNavigation.setNotification(new AHNotification.Builder() + .setText("99+") + .setBackgroundColor(Color.GREEN) + .build(), 1); + } + } + + private int dpToPx(int dp) { + DisplayMetrics metrics = getResources().getDisplayMetrics(); + return dp * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); + } } diff --git a/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoAdapter.java b/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoAdapter.java index b05a7066..7fbaa284 100644 --- a/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoAdapter.java +++ b/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoAdapter.java @@ -1,6 +1,6 @@ package com.aurelhubert.ahbottomnavigation.demo; -import android.support.v7.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoFragment.java b/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoFragment.java index 217a55ad..9150a940 100644 --- a/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoFragment.java +++ b/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoFragment.java @@ -3,11 +3,11 @@ import android.content.Context; import android.os.Build; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.SwitchCompat; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.SwitchCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,7 +15,6 @@ import android.view.animation.AnimationUtils; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.CompoundButton; import android.widget.FrameLayout; import android.widget.Spinner; @@ -31,9 +30,8 @@ public class DemoFragment extends Fragment { private FrameLayout fragmentContainer; private RecyclerView recyclerView; - private RecyclerView.LayoutManager layoutManager; - - /** + + /** * Create a new instance of the fragment */ public static DemoFragment newInstance(int index) { @@ -79,48 +77,26 @@ private void initDemoSettings(View view) { switchTranslucentNavigation.setVisibility( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? View.VISIBLE : View.GONE); - switchTranslucentNavigation.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - getActivity() - .getSharedPreferences("shared", Context.MODE_PRIVATE) - .edit() - .putBoolean("translucentNavigation", isChecked) - .apply(); - demoActivity.reload(); - } - }); - switchColored.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - demoActivity.updateBottomNavigationColor(isChecked); - } - }); - switchFiveItems.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - demoActivity.updateBottomNavigationItems(isChecked); - } - }); - showHideBottomNavigation.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - demoActivity.showOrHideBottomNavigation(isChecked); - } - }); - showSelectedBackground.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - demoActivity.updateSelectedBackgroundVisibility(isChecked); - } - }); + switchTranslucentNavigation.setOnCheckedChangeListener((buttonView, isChecked) -> { + getActivity() + .getSharedPreferences("shared", Context.MODE_PRIVATE) + .edit() + .putBoolean("translucentNavigation", isChecked) + .apply(); + demoActivity.reload(); + }); + switchColored.setOnCheckedChangeListener((buttonView, isChecked) -> demoActivity.updateBottomNavigationColor(isChecked)); + switchFiveItems.setOnCheckedChangeListener((buttonView, isChecked) -> demoActivity.updateBottomNavigationItems(isChecked)); + showHideBottomNavigation.setOnCheckedChangeListener((buttonView, isChecked) -> demoActivity.showOrHideBottomNavigation(isChecked)); + showSelectedBackground.setOnCheckedChangeListener((buttonView, isChecked) -> demoActivity.updateSelectedBackgroundVisibility(isChecked)); final List titleStates = new ArrayList<>(); for (AHBottomNavigation.TitleState titleState : AHBottomNavigation.TitleState.values()) { titleStates.add(titleState.toString()); } - ArrayAdapter spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, titleStates); - spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinnerTitleState.setAdapter(spinnerAdapter); + // TitleState spinner + ArrayAdapter titleStateSpinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, titleStates); + titleStateSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerTitleState.setAdapter(titleStateSpinnerAdapter); spinnerTitleState.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { @@ -143,7 +119,7 @@ private void initDemoList(View view) { fragmentContainer = view.findViewById(R.id.fragment_container); recyclerView = view.findViewById(R.id.fragment_demo_recycler_view); recyclerView.setHasFixedSize(true); - layoutManager = new LinearLayoutManager(getActivity()); + RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); ArrayList itemsData = new ArrayList<>(); diff --git a/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoViewPagerAdapter.java b/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoViewPagerAdapter.java index 15add109..5f85d42c 100644 --- a/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoViewPagerAdapter.java +++ b/demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoViewPagerAdapter.java @@ -1,7 +1,7 @@ package com.aurelhubert.ahbottomnavigation.demo; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; import android.view.ViewGroup; import java.util.ArrayList; diff --git a/demo/src/main/res/layout/activity_home.xml b/demo/src/main/res/layout/activity_home.xml index 9129c696..cde0fb4d 100644 --- a/demo/src/main/res/layout/activity_home.xml +++ b/demo/src/main/res/layout/activity_home.xml @@ -1,5 +1,5 @@ - - - + diff --git a/demo/src/main/res/layout/fragment_demo_list.xml b/demo/src/main/res/layout/fragment_demo_list.xml index 95437bf5..41430754 100644 --- a/demo/src/main/res/layout/fragment_demo_list.xml +++ b/demo/src/main/res/layout/fragment_demo_list.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/demo/src/main/res/layout/fragment_demo_settings.xml b/demo/src/main/res/layout/fragment_demo_settings.xml index ef1427a3..d3df297e 100644 --- a/demo/src/main/res/layout/fragment_demo_settings.xml +++ b/demo/src/main/res/layout/fragment_demo_settings.xml @@ -5,29 +5,29 @@ android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin"> - - - - - + +