Repository: roughike/BottomBar
Branch: master
Commit: 885d70e62359
Files: 105
Total size: 576.4 KB
Directory structure:
gitextract_7tzdrylq/
├── .gitignore
├── .idea/
│ ├── .name
│ ├── compiler.xml
│ ├── copyright/
│ │ └── profiles_settings.xml
│ ├── encodings.xml
│ ├── gradle.xml
│ ├── kotlinc.xml
│ ├── markdown-exported-files.xml
│ ├── markdown-navigator/
│ │ └── profiles_settings.xml
│ ├── markdown-navigator.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── runConfigurations/
│ │ └── All_Tests.xml
│ ├── runConfigurations.xml
│ └── vcs.xml
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── NOTICE
├── README.md
├── app/
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ └── fonts/
│ │ ├── GREAT-VIBES-LICENSE
│ │ └── GreatVibes-Regular.otf
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── bottombar/
│ │ └── sample/
│ │ ├── BadgeActivity.java
│ │ ├── CustomColorAndFontActivity.java
│ │ ├── FiveColorChangingTabsActivity.java
│ │ ├── IconsOnlyActivity.java
│ │ ├── MainActivity.java
│ │ ├── SampleFragment.java
│ │ ├── TabMessage.java
│ │ ├── ThreeTabsActivity.java
│ │ └── ThreeTabsQRActivity.java
│ └── res/
│ ├── layout/
│ │ ├── activity_color_changing_tabs.xml
│ │ ├── activity_custom_color_and_font.xml
│ │ ├── activity_icons_only.xml
│ │ ├── activity_main.xml
│ │ ├── activity_three_tabs.xml
│ │ └── activity_three_tabs_quick_return.xml
│ ├── layout-sw600dp/
│ │ ├── activity_color_changing_tabs.xml
│ │ └── activity_three_tabs.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-land-v21/
│ │ └── styles.xml
│ ├── values-sw600dp/
│ │ └── styles.xml
│ ├── values-v21/
│ │ └── styles.xml
│ ├── values-w820dp/
│ │ └── dimens.xml
│ └── xml/
│ ├── bottombar_tabs_color_changing.xml
│ ├── bottombar_tabs_five.xml
│ └── bottombar_tabs_three.xml
├── bottom-bar/
│ ├── build.gradle
│ └── src/
│ ├── androidTest/
│ │ ├── assets/
│ │ │ └── fonts/
│ │ │ ├── GREAT-VIBES-LICENSE
│ │ │ └── GreatVibes-Regular.otf
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── roughike/
│ │ │ └── bottombar/
│ │ │ ├── BadgeTest.java
│ │ │ ├── BottomBarTabTest.java
│ │ │ ├── BottomBarTest.java
│ │ │ └── TabParserTest.java
│ │ └── res/
│ │ ├── values/
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── xml/
│ │ ├── dummy_tabs_five.xml
│ │ └── dummy_tabs_three.xml
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── roughike/
│ │ │ └── bottombar/
│ │ │ ├── BadgeCircle.java
│ │ │ ├── BadgeContainer.java
│ │ │ ├── BatchTabPropertyApplier.java
│ │ │ ├── BottomBar.java
│ │ │ ├── BottomBarBadge.java
│ │ │ ├── BottomBarTab.java
│ │ │ ├── BottomNavigationBehavior.java
│ │ │ ├── MiscUtils.java
│ │ │ ├── NavbarUtils.java
│ │ │ ├── OnTabReselectListener.java
│ │ │ ├── OnTabSelectListener.java
│ │ │ ├── ShySettings.java
│ │ │ ├── TabParser.java
│ │ │ ├── TabSelectionInterceptor.java
│ │ │ └── VerticalScrollingBehavior.java
│ │ └── res/
│ │ ├── drawable/
│ │ │ └── bb_bottom_bar_top_shadow.xml
│ │ ├── layout/
│ │ │ ├── bb_bottom_bar_item_container.xml
│ │ │ ├── bb_bottom_bar_item_container_tablet.xml
│ │ │ ├── bb_bottom_bar_item_fixed.xml
│ │ │ ├── bb_bottom_bar_item_fixed_tablet.xml
│ │ │ ├── bb_bottom_bar_item_shifting.xml
│ │ │ └── bb_bottom_bar_item_titleless.xml
│ │ ├── layout-v21/
│ │ │ └── bb_bottom_bar_item_container.xml
│ │ ├── values/
│ │ │ ├── attrs.xml
│ │ │ ├── bools.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimensions.xml
│ │ │ ├── ids.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── values-land/
│ │ └── bools.xml
│ └── test/
│ └── java/
│ └── com/
│ └── roughike/
│ └── bottombar/
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── projectFilesBackup/
│ └── .idea/
│ └── workspace.xml
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# Intellij
*.iml
.idea/workspace.xml
.idea/libraries
# Keystore files
*.jks
================================================
FILE: .idea/.name
================================================
BottomBar
================================================
FILE: .idea/compiler.xml
================================================
## Version 2.0 released!
[The latest version before that can be found in the v1 branch](https://github.com/roughike/BottomBar/tree/v1)
* Cleaner code and better APIs
* No more unnecessary stuff or spaghetti mess
* Now the look, feel and behavior is defined in XML, as it should be
* No more nasty regressions, thanks to the automated tests
* **Everything is a little different compared to earlier, but it's for the greater good!**
[How to contribute](https://github.com/roughike/BottomBar/blob/master/README.md#contributions)
[Changelog](https://github.com/roughike/BottomBar/blob/master/CHANGELOG.md)
## What?
A custom view component that mimics the new [Material Design Bottom Navigation pattern](https://www.google.com/design/spec/components/bottom-navigation.html).
## Does it work on my Grandpa Gary's HTC Dream?
Nope. The minSDK version is **API level 11 (Honeycomb).**
## Gimme that Gradle sweetness, pls?
```groovy
compile 'com.roughike:bottom-bar:2.3.1'
```
**Maven:**
```xml
values/xml/shifting: the selected tab is wider than the rest. shy: put the BottomBar inside a CoordinatorLayout and it'll automatically hide on scroll! underNavbar: draw the BottomBar under the navBar!fonts/MySuperDuperFont.ttf. In that case your font path would look like src/main/assets/fonts/MySuperDuperFont.ttf, but you only need to provide fonts/MySuperDuperFont.ttf, as the asset folder will be auto-filled for you.* Note: Will be immediately called for the currently selected tab * once when set. * * @param listener a listener for monitoring changes in tab selection. */ public void setOnTabSelectListener(@NonNull OnTabSelectListener listener) { setOnTabSelectListener(listener, true); } /** * Set a listener that gets fired when the selected {@link BottomBarTab} changes. *
* If {@code shouldFireInitially} is set to false, this listener isn't fired straight away
* it's set, but you'll get all events normally for consecutive tab selection changes.
*
* @param listener a listener for monitoring changes in tab selection.
* @param shouldFireInitially whether the listener should be fired the first time it's set.
*/
public void setOnTabSelectListener(@NonNull OnTabSelectListener listener, boolean shouldFireInitially) {
onTabSelectListener = listener;
if (shouldFireInitially && getTabCount() > 0) {
listener.onTabSelected(getCurrentTabId());
}
}
/**
* Removes the current {@link OnTabSelectListener} listener
*/
public void removeOnTabSelectListener() {
onTabSelectListener = null;
}
/**
* Set a listener that gets fired when a currently selected {@link BottomBarTab} is clicked.
*
* @param listener a listener for handling tab reselections.
*/
public void setOnTabReselectListener(@NonNull OnTabReselectListener listener) {
onTabReselectListener = listener;
}
/**
* Removes the current {@link OnTabReselectListener} listener
*/
public void removeOnTabReselectListener() {
onTabReselectListener = null;
}
/**
* Set the default selected to be the tab with the corresponding tab id.
* By default, the first tab in the container is the default tab.
*/
public void setDefaultTab(@IdRes int defaultTabId) {
int defaultTabPosition = findPositionForTabWithId(defaultTabId);
setDefaultTabPosition(defaultTabPosition);
}
/**
* Sets the default tab for this BottomBar that is shown until the user changes
* the selection.
*
* @param defaultTabPosition the default tab position.
*/
public void setDefaultTabPosition(int defaultTabPosition) {
if (isComingFromRestoredState) return;
selectTabAtPosition(defaultTabPosition);
}
/**
* Select the tab with the corresponding id.
*/
public void selectTabWithId(@IdRes int tabResId) {
int tabPosition = findPositionForTabWithId(tabResId);
selectTabAtPosition(tabPosition);
}
/**
* Select a tab at the specified position.
*
* @param position the position to select.
*/
public void selectTabAtPosition(int position) {
selectTabAtPosition(position, false);
}
/**
* Select a tab at the specified position.
*
* @param position the position to select.
* @param animate should the tab change be animated or not.
*/
public void selectTabAtPosition(int position, boolean animate) {
if (position > getTabCount() - 1 || position < 0) {
throw new IndexOutOfBoundsException("Can't select tab at position " +
position + ". This BottomBar has no items at that position.");
}
BottomBarTab oldTab = getCurrentTab();
BottomBarTab newTab = getTabAtPosition(position);
oldTab.deselect(animate);
newTab.select(animate);
updateSelectedTab(position);
shiftingMagic(oldTab, newTab, animate);
handleBackgroundColorChange(newTab, animate);
}
public int getTabCount() {
return tabContainer.getChildCount();
}
/**
* Get the currently selected tab.
*/
public BottomBarTab getCurrentTab() {
return getTabAtPosition(getCurrentTabPosition());
}
/**
* Get the tab at the specified position.
*/
public BottomBarTab getTabAtPosition(int position) {
View child = tabContainer.getChildAt(position);
if (child instanceof BadgeContainer) {
return findTabInLayout((BadgeContainer) child);
}
return (BottomBarTab) child;
}
/**
* Get the resource id for the currently selected tab.
*/
@IdRes
public int getCurrentTabId() {
return getCurrentTab().getId();
}
/**
* Get the currently selected tab position.
*/
public int getCurrentTabPosition() {
return currentTabPosition;
}
/**
* Find the tabs' position in the container by id.
*/
public int findPositionForTabWithId(@IdRes int tabId) {
return getTabWithId(tabId).getIndexInTabContainer();
}
/**
* Find a BottomBarTab with the corresponding id.
*/
public BottomBarTab getTabWithId(@IdRes int tabId) {
return (BottomBarTab) tabContainer.findViewById(tabId);
}
/**
* Controls whether the long pressed tab title should be displayed in
* a helpful Toast if the title is not currently visible.
*
* @param enabled true if toasts should be shown to indicate the title
* of a long pressed tab, false otherwise.
*/
public void setLongPressHintsEnabled(boolean enabled) {
longPressHintsEnabled = enabled;
}
/**
* Set alpha value used for inactive BottomBarTabs.
*/
public void setInActiveTabAlpha(float alpha) {
inActiveTabAlpha = alpha;
batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() {
@Override
public void update(BottomBarTab tab) {
tab.setInActiveAlpha(inActiveTabAlpha);
}
});
}
/**
* Set alpha value used for active BottomBarTabs.
*/
public void setActiveTabAlpha(float alpha) {
activeTabAlpha = alpha;
batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() {
@Override
public void update(BottomBarTab tab) {
tab.setActiveAlpha(activeTabAlpha);
}
});
}
public void setInActiveTabColor(@ColorInt int color) {
inActiveTabColor = color;
batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() {
@Override
public void update(BottomBarTab tab) {
tab.setInActiveColor(inActiveTabColor);
}
});
}
/**
* Set active color used for selected BottomBarTabs.
*/
public void setActiveTabColor(@ColorInt int color) {
activeTabColor = color;
batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() {
@Override
public void update(BottomBarTab tab) {
tab.setActiveColor(activeTabColor);
}
});
}
/**
* Set background color for the badge.
*/
public void setBadgeBackgroundColor(@ColorInt int color) {
badgeBackgroundColor = color;
batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() {
@Override
public void update(BottomBarTab tab) {
tab.setBadgeBackgroundColor(badgeBackgroundColor);
}
});
}
/**
* Controls whether the badge (if any) for active tabs
* should be hidden or not.
*/
public void setBadgesHideWhenActive(final boolean hideWhenSelected) {
hideBadgeWhenActive = hideWhenSelected;
batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() {
@Override
public void update(BottomBarTab tab) {
tab.setBadgeHidesWhenActive(hideWhenSelected);
}
});
}
/**
* Set custom text apperance for all BottomBarTabs.
*/
public void setTabTitleTextAppearance(int textAppearance) {
titleTextAppearance = textAppearance;
batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() {
@Override
public void update(BottomBarTab tab) {
tab.setTitleTextAppearance(titleTextAppearance);
}
});
}
/**
* Set a custom typeface for all tab's titles.
*
* @param fontPath path for your custom font file, such as fonts/MySuperDuperFont.ttf.
* In that case your font path would look like src/main/assets/fonts/MySuperDuperFont.ttf,
* but you only need to provide fonts/MySuperDuperFont.ttf, as the asset folder
* will be auto-filled for you.
*/
public void setTabTitleTypeface(String fontPath) {
Typeface actualTypeface = getTypeFaceFromAsset(fontPath);
setTabTitleTypeface(actualTypeface);
}
/**
* Set a custom typeface for all tab's titles.
*/
public void setTabTitleTypeface(Typeface typeface) {
titleTypeFace = typeface;
batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() {
@Override
public void update(BottomBarTab tab) {
tab.setTitleTypeface(titleTypeFace);
}
});
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
if (!isTabletMode) {
resizeTabsToCorrectSizes(currentTabs);
}
updateTitleBottomPadding();
if (isShy()) {
initializeShyBehavior();
}
if (drawUnderNav()) {
resizeForDrawingUnderNavbar();
}
}
}
private void updateTitleBottomPadding() {
if (isIconsOnlyMode()) {
return;
}
int tabCount = getTabCount();
if (tabContainer == null || tabCount == 0 || !isShiftingMode()) {
return;
}
for (int i = 0; i < tabCount; i++) {
BottomBarTab tab = getTabAtPosition(i);
TextView title = tab.getTitleView();
if (title == null) {
continue;
}
int baseline = title.getBaseline();
int height = title.getHeight();
int paddingInsideTitle = height - baseline;
int missingPadding = tenDp - paddingInsideTitle;
if (missingPadding > 0) {
title.setPadding(title.getPaddingLeft(), title.getPaddingTop(),
title.getPaddingRight(), missingPadding + title.getPaddingBottom());
}
}
}
private void initializeShyBehavior() {
ViewParent parent = getParent();
boolean hasAbusiveParent = parent != null
&& parent instanceof CoordinatorLayout;
if (!hasAbusiveParent) {
throw new RuntimeException("In order to have shy behavior, the " +
"BottomBar must be a direct child of a CoordinatorLayout.");
}
if (!shyHeightAlreadyCalculated) {
int height = getHeight();
if (height != 0) {
updateShyHeight(height);
getShySettings().shyHeightCalculated();
shyHeightAlreadyCalculated = true;
}
}
}
private void updateShyHeight(int height) {
((CoordinatorLayout.LayoutParams) getLayoutParams())
.setBehavior(new BottomNavigationBehavior(height, 0, false));
}
private void resizeForDrawingUnderNavbar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
int currentHeight = getHeight();
if (currentHeight != 0 && !navBarAccountedHeightCalculated) {
navBarAccountedHeightCalculated = true;
tabContainer.getLayoutParams().height = currentHeight;
int navbarHeight = NavbarUtils.getNavbarHeight(getContext());
int finalHeight = currentHeight + navbarHeight;
getLayoutParams().height = finalHeight;
if (isShy()) {
updateShyHeight(finalHeight);
}
}
}
}
@Override
public Parcelable onSaveInstanceState() {
Bundle bundle = saveState();
bundle.putParcelable("superstate", super.onSaveInstanceState());
return bundle;
}
@VisibleForTesting
Bundle saveState() {
Bundle outState = new Bundle();
outState.putInt(STATE_CURRENT_SELECTED_TAB, currentTabPosition);
return outState;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
restoreState(bundle);
state = bundle.getParcelable("superstate");
}
super.onRestoreInstanceState(state);
}
@VisibleForTesting
void restoreState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
isComingFromRestoredState = true;
ignoreTabReselectionListener = true;
int restoredPosition = savedInstanceState.getInt(STATE_CURRENT_SELECTED_TAB, currentTabPosition);
selectTabAtPosition(restoredPosition, false);
}
}
@Override
public void onClick(View target) {
if (!(target instanceof BottomBarTab)) return;
handleClick((BottomBarTab) target);
}
@Override
public boolean onLongClick(View target) {
return !(target instanceof BottomBarTab) || handleLongClick((BottomBarTab) target);
}
private BottomBarTab findTabInLayout(ViewGroup child) {
for (int i = 0; i < child.getChildCount(); i++) {
View candidate = child.getChildAt(i);
if (candidate instanceof BottomBarTab) {
return (BottomBarTab) candidate;
}
}
return null;
}
private void handleClick(BottomBarTab newTab) {
BottomBarTab oldTab = getCurrentTab();
if (tabSelectionInterceptor != null
&& tabSelectionInterceptor.shouldInterceptTabSelection(oldTab.getId(), newTab.getId())) {
return;
}
oldTab.deselect(true);
newTab.select(true);
shiftingMagic(oldTab, newTab, true);
handleBackgroundColorChange(newTab, true);
updateSelectedTab(newTab.getIndexInTabContainer());
}
private boolean handleLongClick(BottomBarTab longClickedTab) {
boolean areInactiveTitlesHidden = isShiftingMode() || isTabletMode;
boolean isClickedTitleHidden = !longClickedTab.isActive();
boolean shouldShowHint = areInactiveTitlesHidden
&& isClickedTitleHidden
&& longPressHintsEnabled;
if (shouldShowHint) {
Toast.makeText(getContext(), longClickedTab.getTitle(), Toast.LENGTH_SHORT)
.show();
}
return true;
}
private void updateSelectedTab(int newPosition) {
int newTabId = getTabAtPosition(newPosition).getId();
if (newPosition != currentTabPosition) {
if (onTabSelectListener != null) {
onTabSelectListener.onTabSelected(newTabId);
}
} else if (onTabReselectListener != null && !ignoreTabReselectionListener) {
onTabReselectListener.onTabReSelected(newTabId);
}
currentTabPosition = newPosition;
if (ignoreTabReselectionListener) {
ignoreTabReselectionListener = false;
}
}
private void shiftingMagic(BottomBarTab oldTab, BottomBarTab newTab, boolean animate) {
if (isShiftingMode()) {
oldTab.updateWidth(inActiveShiftingItemWidth, animate);
newTab.updateWidth(activeShiftingItemWidth, animate);
}
}
private void handleBackgroundColorChange(BottomBarTab tab, boolean animate) {
int newColor = tab.getBarColorWhenSelected();
if (currentBackgroundColor == newColor) {
return;
}
if (!animate) {
outerContainer.setBackgroundColor(newColor);
return;
}
View clickedView = tab;
if (tab.hasActiveBadge()) {
clickedView = tab.getOuterView();
}
animateBGColorChange(clickedView, newColor);
currentBackgroundColor = newColor;
}
private void animateBGColorChange(View clickedView, final int newColor) {
prepareForBackgroundColorAnimation(newColor);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (!outerContainer.isAttachedToWindow()) {
return;
}
backgroundCircularRevealAnimation(clickedView, newColor);
} else {
backgroundCrossfadeAnimation(newColor);
}
}
private void prepareForBackgroundColorAnimation(int newColor) {
outerContainer.clearAnimation();
backgroundOverlay.clearAnimation();
backgroundOverlay.setBackgroundColor(newColor);
backgroundOverlay.setVisibility(View.VISIBLE);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void backgroundCircularRevealAnimation(View clickedView, final int newColor) {
int centerX = (int) (ViewCompat.getX(clickedView) + (clickedView.getMeasuredWidth() / 2));
int yOffset = isTabletMode ? (int) ViewCompat.getY(clickedView) : 0;
int centerY = yOffset + clickedView.getMeasuredHeight() / 2;
int startRadius = 0;
int finalRadius = isTabletMode ? outerContainer.getHeight() : outerContainer.getWidth();
Animator animator = ViewAnimationUtils.createCircularReveal(
backgroundOverlay,
centerX,
centerY,
startRadius,
finalRadius
);
if (isTabletMode) {
animator.setDuration(500);
}
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
onEnd();
}
@Override
public void onAnimationCancel(Animator animation) {
onEnd();
}
private void onEnd() {
outerContainer.setBackgroundColor(newColor);
backgroundOverlay.setVisibility(View.INVISIBLE);
ViewCompat.setAlpha(backgroundOverlay, 1);
}
});
animator.start();
}
private void backgroundCrossfadeAnimation(final int newColor) {
ViewCompat.setAlpha(backgroundOverlay, 0);
ViewCompat.animate(backgroundOverlay)
.alpha(1)
.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
onEnd();
}
@Override
public void onAnimationCancel(View view) {
onEnd();
}
private void onEnd() {
outerContainer.setBackgroundColor(newColor);
backgroundOverlay.setVisibility(View.INVISIBLE);
ViewCompat.setAlpha(backgroundOverlay, 1);
}
})
.start();
}
}
================================================
FILE: bottom-bar/src/main/java/com/roughike/bottombar/BottomBarBadge.java
================================================
package com.roughike.bottombar;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.AppCompatImageView;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TextView;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class BottomBarBadge extends TextView {
private int count;
private boolean isVisible = false;
BottomBarBadge(Context context) {
super(context);
}
/**
* Set the unread / new item / whatever count for this Badge.
*
* @param count the value this Badge should show.
*/
void setCount(int count) {
this.count = count;
setText(String.valueOf(count));
}
/**
* Get the currently showing count for this Badge.
*
* @return current count for the Badge.
*/
int getCount() {
return count;
}
/**
* Shows the badge with a neat little scale animation.
*/
void show() {
isVisible = true;
ViewCompat.animate(this)
.setDuration(150)
.alpha(1)
.scaleX(1)
.scaleY(1)
.start();
}
/**
* Hides the badge with a neat little scale animation.
*/
void hide() {
isVisible = false;
ViewCompat.animate(this)
.setDuration(150)
.alpha(0)
.scaleX(0)
.scaleY(0)
.start();
}
/**
* Is this badge currently visible?
*
* @return true is this badge is visible, otherwise false.
*/
boolean isVisible() {
return isVisible;
}
void attachToTab(BottomBarTab tab, int backgroundColor) {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
setLayoutParams(params);
setGravity(Gravity.CENTER);
MiscUtils.setTextAppearance(this, R.style.BB_BottomBarBadge_Text);
setColoredCircleBackground(backgroundColor);
wrapTabAndBadgeInSameContainer(tab);
}
void setColoredCircleBackground(int circleColor) {
int innerPadding = MiscUtils.dpToPixel(getContext(), 1);
ShapeDrawable backgroundCircle = BadgeCircle.make(innerPadding * 3, circleColor);
setPadding(innerPadding, innerPadding, innerPadding, innerPadding);
setBackgroundCompat(backgroundCircle);
}
private void wrapTabAndBadgeInSameContainer(final BottomBarTab tab) {
ViewGroup tabContainer = (ViewGroup) tab.getParent();
tabContainer.removeView(tab);
final BadgeContainer badgeContainer = new BadgeContainer(getContext());
badgeContainer.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
badgeContainer.addView(tab);
badgeContainer.addView(this);
tabContainer.addView(badgeContainer, tab.getIndexInTabContainer());
badgeContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
badgeContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
adjustPositionAndSize(tab);
}
});
}
void removeFromTab(BottomBarTab tab) {
BadgeContainer badgeAndTabContainer = (BadgeContainer) getParent();
ViewGroup originalTabContainer = (ViewGroup) badgeAndTabContainer.getParent();
badgeAndTabContainer.removeView(tab);
originalTabContainer.removeView(badgeAndTabContainer);
originalTabContainer.addView(tab, tab.getIndexInTabContainer());
}
void adjustPositionAndSize(BottomBarTab tab) {
AppCompatImageView iconView = tab.getIconView();
ViewGroup.LayoutParams params = getLayoutParams();
int size = Math.max(getWidth(), getHeight());
float xOffset = (float) (iconView.getWidth() / 1.25);
setX(iconView.getX() + xOffset);
setTranslationY(10);
if (params.width != size || params.height != size) {
params.width = size;
params.height = size;
setLayoutParams(params);
}
}
@SuppressWarnings("deprecation")
private void setBackgroundCompat(Drawable background) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
setBackground(background);
} else {
setBackgroundDrawable(background);
}
}
}
================================================
FILE: bottom-bar/src/main/java/com/roughike/bottombar/BottomBarTab.java
================================================
package com.roughike.bottombar;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v7.widget.AppCompatImageView;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class BottomBarTab extends LinearLayout {
@VisibleForTesting
static final String STATE_BADGE_COUNT = "STATE_BADGE_COUNT_FOR_TAB_";
private static final long ANIMATION_DURATION = 150;
private static final float ACTIVE_TITLE_SCALE = 1;
private static final float INACTIVE_FIXED_TITLE_SCALE = 0.86f;
private static final float ACTIVE_SHIFTING_TITLELESS_ICON_SCALE = 1.24f;
private static final float INACTIVE_SHIFTING_TITLELESS_ICON_SCALE = 1f;
private final int sixDps;
private final int eightDps;
private final int sixteenDps;
@VisibleForTesting
BottomBarBadge badge;
private Type type = Type.FIXED;
private boolean isTitleless;
private int iconResId;
private String title;
private float inActiveAlpha;
private float activeAlpha;
private int inActiveColor;
private int activeColor;
private int barColorWhenSelected;
private int badgeBackgroundColor;
private boolean badgeHidesWhenActive;
private AppCompatImageView iconView;
private TextView titleView;
private boolean isActive;
private int indexInContainer;
private int titleTextAppearanceResId;
private Typeface titleTypeFace;
BottomBarTab(Context context) {
super(context);
sixDps = MiscUtils.dpToPixel(context, 6);
eightDps = MiscUtils.dpToPixel(context, 8);
sixteenDps = MiscUtils.dpToPixel(context, 16);
}
void setConfig(@NonNull Config config) {
setInActiveAlpha(config.inActiveTabAlpha);
setActiveAlpha(config.activeTabAlpha);
setInActiveColor(config.inActiveTabColor);
setActiveColor(config.activeTabColor);
setBarColorWhenSelected(config.barColorWhenSelected);
setBadgeBackgroundColor(config.badgeBackgroundColor);
setBadgeHidesWhenActive(config.badgeHidesWhenSelected);
setTitleTextAppearance(config.titleTextAppearance);
setTitleTypeface(config.titleTypeFace);
}
void prepareLayout() {
inflate(getContext(), getLayoutResource(), this);
setOrientation(VERTICAL);
setGravity(isTitleless? Gravity.CENTER : Gravity.CENTER_HORIZONTAL);
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
setBackgroundResource(MiscUtils.getDrawableRes(getContext(), R.attr.selectableItemBackgroundBorderless));
iconView = (AppCompatImageView) findViewById(R.id.bb_bottom_bar_icon);
iconView.setImageResource(iconResId);
if (type != Type.TABLET && !isTitleless) {
titleView = (TextView) findViewById(R.id.bb_bottom_bar_title);
titleView.setVisibility(VISIBLE);
if (type == Type.SHIFTING) {
findViewById(R.id.spacer).setVisibility(VISIBLE);
}
updateTitle();
}
updateCustomTextAppearance();
updateCustomTypeface();
}
@VisibleForTesting
int getLayoutResource() {
int layoutResource;
switch (type) {
case FIXED:
layoutResource = R.layout.bb_bottom_bar_item_fixed;
break;
case SHIFTING:
layoutResource = R.layout.bb_bottom_bar_item_shifting;
break;
case TABLET:
layoutResource = R.layout.bb_bottom_bar_item_fixed_tablet;
break;
default:
// should never happen
throw new RuntimeException("Unknown BottomBarTab type.");
}
return layoutResource;
}
private void updateTitle() {
if (titleView != null) {
titleView.setText(title);
}
}
@SuppressWarnings("deprecation")
private void updateCustomTextAppearance() {
if (titleView == null || titleTextAppearanceResId == 0) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
titleView.setTextAppearance(titleTextAppearanceResId);
} else {
titleView.setTextAppearance(getContext(), titleTextAppearanceResId);
}
titleView.setTag(R.id.bb_bottom_bar_appearance_id, titleTextAppearanceResId);
}
private void updateCustomTypeface() {
if (titleTypeFace != null && titleView != null) {
titleView.setTypeface(titleTypeFace);
}
}
Type getType() {
return type;
}
void setType(Type type) {
this.type = type;
}
boolean isTitleless() {
return isTitleless;
}
void setIsTitleless(boolean isTitleless) {
if (isTitleless && getIconResId() == 0) {
throw new IllegalStateException("This tab is supposed to be " +
"icon only, yet it has no icon specified. Index in " +
"container: " + getIndexInTabContainer());
}
this.isTitleless = isTitleless;
}
public ViewGroup getOuterView() {
return (ViewGroup) getParent();
}
AppCompatImageView getIconView() {
return iconView;
}
int getIconResId() {
return iconResId;
}
void setIconResId(int iconResId) {
this.iconResId = iconResId;
}
TextView getTitleView() {
return titleView;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
updateTitle();
}
public float getInActiveAlpha() {
return inActiveAlpha;
}
public void setInActiveAlpha(float inActiveAlpha) {
this.inActiveAlpha = inActiveAlpha;
if (!isActive) {
setAlphas(inActiveAlpha);
}
}
public float getActiveAlpha() {
return activeAlpha;
}
public void setActiveAlpha(float activeAlpha) {
this.activeAlpha = activeAlpha;
if (isActive) {
setAlphas(activeAlpha);
}
}
public int getInActiveColor() {
return inActiveColor;
}
public void setInActiveColor(int inActiveColor) {
this.inActiveColor = inActiveColor;
if (!isActive) {
setColors(inActiveColor);
}
}
public int getActiveColor() {
return activeColor;
}
public void setActiveColor(int activeIconColor) {
this.activeColor = activeIconColor;
if (isActive) {
setColors(activeColor);
}
}
public int getBarColorWhenSelected() {
return barColorWhenSelected;
}
public void setBarColorWhenSelected(int barColorWhenSelected) {
this.barColorWhenSelected = barColorWhenSelected;
}
public int getBadgeBackgroundColor() {
return badgeBackgroundColor;
}
public void setBadgeBackgroundColor(int badgeBackgroundColor) {
this.badgeBackgroundColor = badgeBackgroundColor;
if (badge != null) {
badge.setColoredCircleBackground(badgeBackgroundColor);
}
}
public boolean getBadgeHidesWhenActive() {
return badgeHidesWhenActive;
}
public void setBadgeHidesWhenActive(boolean hideWhenActive) {
this.badgeHidesWhenActive = hideWhenActive;
}
int getCurrentDisplayedIconColor() {
Object tag = iconView.getTag(R.id.bb_bottom_bar_color_id);
if (tag instanceof Integer) {
return (int) tag;
}
return 0;
}
int getCurrentDisplayedTitleColor() {
if (titleView != null) {
return titleView.getCurrentTextColor();
}
return 0;
}
int getCurrentDisplayedTextAppearance() {
Object tag = titleView.getTag(R.id.bb_bottom_bar_appearance_id);
if (titleView != null && tag instanceof Integer) {
return (int) tag;
}
return 0;
}
public void setBadgeCount(int count) {
if (count <= 0) {
if (badge != null) {
badge.removeFromTab(this);
badge = null;
}
return;
}
if (badge == null) {
badge = new BottomBarBadge(getContext());
badge.attachToTab(this, badgeBackgroundColor);
}
badge.setCount(count);
if (isActive && badgeHidesWhenActive) {
badge.hide();
}
}
public void removeBadge() {
setBadgeCount(0);
}
boolean isActive() {
return isActive;
}
boolean hasActiveBadge() {
return badge != null;
}
int getIndexInTabContainer() {
return indexInContainer;
}
void setIndexInContainer(int indexInContainer) {
this.indexInContainer = indexInContainer;
}
void setIconTint(int tint) {
iconView.setColorFilter(tint);
}
public int getTitleTextAppearance() {
return titleTextAppearanceResId;
}
@SuppressWarnings("deprecation")
void setTitleTextAppearance(int resId) {
this.titleTextAppearanceResId = resId;
updateCustomTextAppearance();
}
public void setTitleTypeface(Typeface typeface) {
this.titleTypeFace = typeface;
updateCustomTypeface();
}
public Typeface getTitleTypeFace() {
return titleTypeFace;
}
void select(boolean animate) {
isActive = true;
if (animate) {
animateIcon(activeAlpha, ACTIVE_SHIFTING_TITLELESS_ICON_SCALE);
animateTitle(sixDps, ACTIVE_TITLE_SCALE, activeAlpha);
animateColors(inActiveColor, activeColor);
} else {
setTitleScale(ACTIVE_TITLE_SCALE);
setTopPadding(sixDps);
setIconScale(ACTIVE_SHIFTING_TITLELESS_ICON_SCALE);
setColors(activeColor);
setAlphas(activeAlpha);
}
setSelected(true);
if (badge != null && badgeHidesWhenActive) {
badge.hide();
}
}
void deselect(boolean animate) {
isActive = false;
boolean isShifting = type == Type.SHIFTING;
float titleScale = isShifting ? 0 : INACTIVE_FIXED_TITLE_SCALE;
int iconPaddingTop = isShifting ? sixteenDps : eightDps;
if (animate) {
animateTitle(iconPaddingTop, titleScale, inActiveAlpha);
animateIcon(inActiveAlpha, INACTIVE_SHIFTING_TITLELESS_ICON_SCALE);
animateColors(activeColor, inActiveColor);
} else {
setTitleScale(titleScale);
setTopPadding(iconPaddingTop);
setIconScale(INACTIVE_SHIFTING_TITLELESS_ICON_SCALE);
setColors(inActiveColor);
setAlphas(inActiveAlpha);
}
setSelected(false);
if (!isShifting && badge != null && !badge.isVisible()) {
badge.show();
}
}
private void animateColors(int previousColor, int color) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(previousColor, color);
anim.setEvaluator(new ArgbEvaluator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
setColors((Integer) valueAnimator.getAnimatedValue());
}
});
anim.setDuration(150);
anim.start();
}
private void setColors(int color) {
if (iconView != null) {
iconView.setColorFilter(color);
iconView.setTag(R.id.bb_bottom_bar_color_id, color);
}
if (titleView != null) {
titleView.setTextColor(color);
}
}
private void setAlphas(float alpha) {
if (iconView != null) {
ViewCompat.setAlpha(iconView, alpha);
}
if (titleView != null) {
ViewCompat.setAlpha(titleView, alpha);
}
}
void updateWidth(float endWidth, boolean animated) {
if (!animated) {
getLayoutParams().width = (int) endWidth;
if (!isActive && badge != null) {
badge.adjustPositionAndSize(this);
badge.show();
}
return;
}
float start = getWidth();
ValueAnimator animator = ValueAnimator.ofFloat(start, endWidth);
animator.setDuration(150);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
ViewGroup.LayoutParams params = getLayoutParams();
if (params == null) return;
params.width = Math.round((float) animator.getAnimatedValue());
setLayoutParams(params);
}
});
// Workaround to avoid using faulty onAnimationEnd() listener
postDelayed(new Runnable() {
@Override
public void run() {
if (!isActive && badge != null) {
clearAnimation();
badge.adjustPositionAndSize(BottomBarTab.this);
badge.show();
}
}
}, animator.getDuration());
animator.start();
}
private void updateBadgePosition() {
if (badge != null) {
badge.adjustPositionAndSize(this);
}
}
private void setTopPaddingAnimated(int start, int end) {
if (type == Type.TABLET || isTitleless) {
return;
}
ValueAnimator paddingAnimator = ValueAnimator.ofInt(start, end);
paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
iconView.setPadding(
iconView.getPaddingLeft(),
(Integer) animation.getAnimatedValue(),
iconView.getPaddingRight(),
iconView.getPaddingBottom()
);
}
});
paddingAnimator.setDuration(ANIMATION_DURATION);
paddingAnimator.start();
}
private void animateTitle(int padding, float scale, float alpha) {
if (type == Type.TABLET && isTitleless) {
return;
}
setTopPaddingAnimated(iconView.getPaddingTop(), padding);
ViewPropertyAnimatorCompat titleAnimator = ViewCompat.animate(titleView)
.setDuration(ANIMATION_DURATION)
.scaleX(scale)
.scaleY(scale);
titleAnimator.alpha(alpha);
titleAnimator.start();
}
private void animateIconScale(float scale) {
ViewCompat.animate(iconView)
.setDuration(ANIMATION_DURATION)
.scaleX(scale)
.scaleY(scale)
.start();
}
private void animateIcon(float alpha, float scale) {
ViewCompat.animate(iconView)
.setDuration(ANIMATION_DURATION)
.alpha(alpha)
.start();
if (isTitleless && type == Type.SHIFTING) {
animateIconScale(scale);
}
}
private void setTopPadding(int topPadding) {
if (type == Type.TABLET || isTitleless) {
return;
}
iconView.setPadding(
iconView.getPaddingLeft(),
topPadding,
iconView.getPaddingRight(),
iconView.getPaddingBottom()
);
}
private void setTitleScale(float scale) {
if (type == Type.TABLET || isTitleless) {
return;
}
ViewCompat.setScaleX(titleView, scale);
ViewCompat.setScaleY(titleView, scale);
}
private void setIconScale(float scale) {
if (isTitleless && type == Type.SHIFTING) {
ViewCompat.setScaleX(iconView, scale);
ViewCompat.setScaleY(iconView, scale);
}
}
@Override
public Parcelable onSaveInstanceState() {
if (badge != null) {
Bundle bundle = saveState();
bundle.putParcelable("superstate", super.onSaveInstanceState());
return bundle;
}
return super.onSaveInstanceState();
}
@VisibleForTesting
Bundle saveState() {
Bundle outState = new Bundle();
outState.putInt(STATE_BADGE_COUNT + getIndexInTabContainer(), badge.getCount());
return outState;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
restoreState(bundle);
state = bundle.getParcelable("superstate");
}
super.onRestoreInstanceState(state);
}
@VisibleForTesting
void restoreState(Bundle savedInstanceState) {
int previousBadgeCount = savedInstanceState.getInt(STATE_BADGE_COUNT + getIndexInTabContainer());
setBadgeCount(previousBadgeCount);
}
enum Type {
FIXED, SHIFTING, TABLET
}
public static class Config {
private final float inActiveTabAlpha;
private final float activeTabAlpha;
private final int inActiveTabColor;
private final int activeTabColor;
private final int barColorWhenSelected;
private final int badgeBackgroundColor;
private final int titleTextAppearance;
private final Typeface titleTypeFace;
private boolean badgeHidesWhenSelected = true;
private Config(Builder builder) {
this.inActiveTabAlpha = builder.inActiveTabAlpha;
this.activeTabAlpha = builder.activeTabAlpha;
this.inActiveTabColor = builder.inActiveTabColor;
this.activeTabColor = builder.activeTabColor;
this.barColorWhenSelected = builder.barColorWhenSelected;
this.badgeBackgroundColor = builder.badgeBackgroundColor;
this.badgeHidesWhenSelected = builder.hidesBadgeWhenSelected;
this.titleTextAppearance = builder.titleTextAppearance;
this.titleTypeFace = builder.titleTypeFace;
}
public static class Builder {
private float inActiveTabAlpha;
private float activeTabAlpha;
private int inActiveTabColor;
private int activeTabColor;
private int barColorWhenSelected;
private int badgeBackgroundColor;
private boolean hidesBadgeWhenSelected = true;
private int titleTextAppearance;
private Typeface titleTypeFace;
public Builder inActiveTabAlpha(float alpha) {
this.inActiveTabAlpha = alpha;
return this;
}
public Builder activeTabAlpha(float alpha) {
this.activeTabAlpha = alpha;
return this;
}
public Builder inActiveTabColor(@ColorInt int color) {
this.inActiveTabColor = color;
return this;
}
public Builder activeTabColor(@ColorInt int color) {
this.activeTabColor = color;
return this;
}
public Builder barColorWhenSelected(@ColorInt int color) {
this.barColorWhenSelected = color;
return this;
}
public Builder badgeBackgroundColor(@ColorInt int color) {
this.badgeBackgroundColor = color;
return this;
}
public Builder hideBadgeWhenSelected(boolean hide) {
this.hidesBadgeWhenSelected = hide;
return this;
}
public Builder titleTextAppearance(int titleTextAppearance) {
this.titleTextAppearance = titleTextAppearance;
return this;
}
public Builder titleTypeFace(Typeface titleTypeFace) {
this.titleTypeFace = titleTypeFace;
return this;
}
public Config build() {
return new Config(this);
}
}
}
}
================================================
FILE: bottom-bar/src/main/java/com/roughike/bottombar/BottomNavigationBehavior.java
================================================
package com.roughike.bottombar;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
/**
* Created by Nikola D. on 3/15/2016.
*
* Credit goes to Nikola Despotoski:
* https://github.com/NikolaDespotoski
*/
class BottomNavigationBehavior
* This listener is fired when the current {@link BottomBar} is about to change. This gives
* an opportunity to interrupt the {@link BottomBarTab} change.
*
* @param oldTabId the currently visible {@link BottomBarTab}
* @param newTabId the {@link BottomBarTab} that will be switched to
* @return true if you want to override/stop the tab change, false to continue as normal
*/
boolean shouldInterceptTabSelection(@IdRes int oldTabId, @IdRes int newTabId);
}
================================================
FILE: bottom-bar/src/main/java/com/roughike/bottombar/VerticalScrollingBehavior.java
================================================
package com.roughike.bottombar;
import android.content.Context;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.WindowInsetsCompat;
import android.util.AttributeSet;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by Nikola D. on 11/22/2015.
*
* Credit goes to Nikola Despotoski:
* https://github.com/NikolaDespotoski
*/
abstract class VerticalScrollingBehavior