├── .gitignore
├── LibBottomBar
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── ms
│ │ └── bottombar
│ │ ├── BottomLayoutController.java
│ │ ├── ItemController.java
│ │ ├── MaterialMode.java
│ │ ├── NavigationController.java
│ │ ├── PageNavigationView.java
│ │ ├── internal
│ │ ├── BaselineLayout.java
│ │ ├── CustomItemLayout.java
│ │ ├── CustomItemVerticalLayout.java
│ │ ├── MaterialItemLayout.java
│ │ ├── MaterialItemVerticalLayout.java
│ │ ├── RoundMessageView.java
│ │ └── Utils.java
│ │ ├── item
│ │ ├── BaseTabItem.java
│ │ ├── MaterialItemView.java
│ │ └── OnlyIconMaterialItemView.java
│ │ ├── listener
│ │ └── OnTabItemSelectedListener.java
│ │ └── view
│ │ └── RefreshView.java
│ └── res
│ ├── drawable
│ ├── material_item_background.xml
│ └── round.xml
│ ├── layout
│ ├── item_material.xml
│ ├── item_material_only_icon.xml
│ └── round_message_view.xml
│ └── values
│ ├── attrs.xml
│ └── dimens.xml
├── README.md
├── build.gradle
├── demo
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── test
│ │ ├── BehaviorActivity.java
│ │ ├── Custom2Activity.java
│ │ ├── CustomActivity.java
│ │ ├── HideActivity.java
│ │ ├── MainActivity.java
│ │ ├── MaterialDesignActivity.java
│ │ ├── SpecialActivity.java
│ │ ├── VerticalActivity.java
│ │ ├── VerticalCustomActivity.java
│ │ ├── behavior
│ │ └── BottomViewBehavior.java
│ │ ├── custom
│ │ ├── OnlyIconItemView.java
│ │ ├── OnlyTextTab.java
│ │ ├── SpecialTab.java
│ │ ├── SpecialTabRound.java
│ │ └── TestRepeatTab.java
│ │ ├── item
│ │ └── NormalItemView.java
│ │ └── other
│ │ ├── AFragment.java
│ │ ├── MyViewPagerAdapter.java
│ │ └── NoTouchViewPager.java
│ └── res
│ ├── drawable
│ ├── ic_audiotrack_black_24dp.xml
│ ├── ic_book_black_24dp.xml
│ ├── ic_favorite_gray_24dp.xml
│ ├── ic_favorite_teal_24dp.xml
│ ├── ic_nearby_gray_24dp.xml
│ ├── ic_nearby_teal_24dp.xml
│ ├── ic_news_black_24dp.xml
│ ├── ic_ondemand_video_black_24dp.xml
│ ├── ic_restore_gray_24dp.xml
│ ├── ic_restore_teal_24dp.xml
│ ├── special_tab_bg.xml
│ ├── special_tab_round_bg.xml
│ ├── tab_background.xml
│ └── tab_vertical_background.xml
│ ├── layout
│ ├── activity_behavior.xml
│ ├── activity_hide.xml
│ ├── activity_main.xml
│ ├── activity_special.xml
│ ├── activity_vertical_custom.xml
│ ├── item_normal.xml
│ ├── item_only_icon.xml
│ ├── item_only_text.xml
│ ├── layout_horizontal.xml
│ ├── layout_vertical.xml
│ ├── recyclerview.xml
│ ├── special_tab.xml
│ └── special_tab_round.xml
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── intro_img
├── demo.png
├── demo1.gif
├── demo2.gif
├── demo3.gif
├── demo4.gif
├── demo5.gif
├── demo6.png
├── demo7.png
├── demo8.png
└── demo9.gif
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | #built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Windows thumbnail db
19 | Thumbs.db
20 |
21 | # OSX files
22 | .DS_Store
23 |
24 | # Eclipse project files
25 | .classpath
26 | .project
27 |
28 | # Android Studio
29 | *.iml
30 | .idea
31 | .gradle
32 | build/
33 | .idea/workspace.xml
34 | .idea/libraries
35 |
36 |
--------------------------------------------------------------------------------
/LibBottomBar/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 | defaultConfig {
8 | minSdkVersion rootProject.ext.minSdkVersion
9 | targetSdkVersion rootProject.ext.targetSdkVersion
10 | }
11 | }
12 |
13 | dependencies {
14 | implementation fileTree(dir: 'libs', include: ['*.jar'])
15 | implementation "androidx.appcompat:appcompat:$rootProject.ext.appcompat"
16 | }
--------------------------------------------------------------------------------
/LibBottomBar/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/BottomLayoutController.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar;
2 |
3 | import androidx.viewpager.widget.ViewPager;
4 |
5 | interface BottomLayoutController {
6 |
7 | /**
8 | * 方便适配ViewPager页面切换
9 | * 注意:ViewPager页面数量必须等于导航栏的Item数量
10 | *
11 | * @param viewPager {@link ViewPager}
12 | */
13 | void setupWithViewPager(ViewPager viewPager);
14 |
15 | /**
16 | * 向下移动隐藏导航栏
17 | */
18 | void hideBottomLayout();
19 |
20 | /**
21 | * 向上移动显示导航栏
22 | */
23 | void showBottomLayout();
24 | }
25 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/ItemController.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar;
2 |
3 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
4 |
5 | public interface ItemController {
6 | /**
7 | * 设置选中项
8 | *
9 | * @param index 顺序索引
10 | */
11 | void setSelect(int index);
12 |
13 | /**
14 | * 设置导航按钮上显示的圆形消息数字,通过顺序索引。
15 | *
16 | * @param index 顺序索引
17 | * @param number 消息数字
18 | */
19 | void setMessageNumber(int index, int number);
20 |
21 | /**
22 | * 设置显示无数字的消息小原点
23 | *
24 | * @param index 顺序索引
25 | * @param hasMessage true显示
26 | */
27 | void setHasMessage(int index, boolean hasMessage);
28 |
29 | /**
30 | * 导航栏按钮点击监听
31 | *
32 | * @param listener {@link OnTabItemSelectedListener}
33 | */
34 | void addTabItemSelectedListener(OnTabItemSelectedListener listener);
35 |
36 | /**
37 | * 获取当前选中项索引
38 | *
39 | * @return 索引
40 | */
41 | int getSelected();
42 |
43 | /**
44 | * 获取导航按钮总数
45 | *
46 | * @return 总数
47 | */
48 | int getItemCount();
49 |
50 | /**
51 | * 获取导航按钮文字
52 | *
53 | * @param index 顺序索引
54 | * @return 文字
55 | */
56 | String getItemTitle(int index);
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/MaterialMode.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar;
2 |
3 | /**
4 | *
模式选择。
5 | * 采用组合的形式,比如要两种效果可以这样做:
6 | * MaterialMode.HIDE_TEXT | MaterialMode.CHANGE_BACKGROUND_COLOR
7 | */
8 | public final class MaterialMode {
9 | /**
10 | * 隐藏文字内容,只在选中时显示文字
11 | */
12 | public static final int HIDE_TEXT = 0X1;
13 |
14 | /**
15 | * 开启导航栏背景变换。点击不同项切换不同颜色
16 | */
17 | public static final int CHANGE_BACKGROUND_COLOR = 0X2;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/NavigationController.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar;
2 |
3 | import androidx.viewpager.widget.ViewPager;
4 |
5 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
6 |
7 | public class NavigationController implements ItemController, BottomLayoutController {
8 |
9 | private BottomLayoutController mBottomLayoutController;
10 | private ItemController mItemController;
11 |
12 | public NavigationController(BottomLayoutController bottomLayoutController, ItemController itemController) {
13 | mBottomLayoutController = bottomLayoutController;
14 | mItemController = itemController;
15 | }
16 |
17 | @Override
18 | public void setSelect(int index) {
19 | mItemController.setSelect(index);
20 | }
21 |
22 | @Override
23 | public void setMessageNumber(int index, int number) {
24 | mItemController.setMessageNumber(index, number);
25 | }
26 |
27 | @Override
28 | public void setHasMessage(int index, boolean hasMessage) {
29 | mItemController.setHasMessage(index, hasMessage);
30 | }
31 |
32 | @Override
33 | public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
34 | mItemController.addTabItemSelectedListener(listener);
35 | }
36 |
37 | @Override
38 | public int getSelected() {
39 | return mItemController.getSelected();
40 | }
41 |
42 | @Override
43 | public int getItemCount() {
44 | return mItemController.getItemCount();
45 | }
46 |
47 | @Override
48 | public String getItemTitle(int index) {
49 | return mItemController.getItemTitle(index);
50 | }
51 |
52 | @Override
53 | public void setupWithViewPager(ViewPager viewPager) {
54 | mBottomLayoutController.setupWithViewPager(viewPager);
55 | }
56 |
57 | @Override
58 | public void hideBottomLayout() {
59 | mBottomLayoutController.hideBottomLayout();
60 | }
61 |
62 | @Override
63 | public void showBottomLayout() {
64 | mBottomLayoutController.showBottomLayout();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/PageNavigationView.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar;
2 |
3 | import android.animation.ObjectAnimator;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Color;
7 | import android.graphics.drawable.Drawable;
8 | import android.os.Bundle;
9 | import android.os.Parcelable;
10 | import android.util.AttributeSet;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.view.animation.AccelerateDecelerateInterpolator;
14 |
15 | import androidx.annotation.ColorInt;
16 | import androidx.annotation.DrawableRes;
17 | import androidx.core.content.ContextCompat;
18 | import androidx.viewpager.widget.ViewPager;
19 |
20 | import com.ms.bottombar.internal.CustomItemLayout;
21 | import com.ms.bottombar.internal.CustomItemVerticalLayout;
22 | import com.ms.bottombar.internal.MaterialItemLayout;
23 | import com.ms.bottombar.internal.MaterialItemVerticalLayout;
24 | import com.ms.bottombar.internal.Utils;
25 | import com.ms.bottombar.item.BaseTabItem;
26 | import com.ms.bottombar.item.MaterialItemView;
27 | import com.ms.bottombar.item.OnlyIconMaterialItemView;
28 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
29 |
30 | import java.util.ArrayList;
31 | import java.util.List;
32 |
33 | /**
34 | * 导航视图
35 | */
36 | public class PageNavigationView extends ViewGroup {
37 | private int mTabPaddingTop;
38 | private int mTabPaddingBottom;
39 |
40 | private NavigationController mNavigationController;
41 |
42 | private ViewPagerPageChangeListener mPageChangeListener;
43 | private ViewPager mViewPager;
44 |
45 | private boolean mEnableVerticalLayout;
46 |
47 | private OnTabItemSelectedListener mTabItemListener = new OnTabItemSelectedListener() {
48 | @Override
49 | public void onSelected(int index, int old) {
50 | if (mViewPager != null) {
51 | mViewPager.setCurrentItem(index, false);
52 | }
53 | }
54 |
55 | @Override
56 | public void onRepeat(int index) {
57 | }
58 | };
59 |
60 | public PageNavigationView(Context context) {
61 | this(context, null);
62 | }
63 |
64 | public PageNavigationView(Context context, AttributeSet attrs) {
65 | this(context, attrs, 0);
66 | }
67 |
68 | public PageNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
69 | super(context, attrs, defStyleAttr);
70 | setPadding(0, 0, 0, 0);
71 |
72 | final TypedArray a = context.obtainStyledAttributes(
73 | attrs, R.styleable.PageNavigationView);
74 | if (a.hasValue(R.styleable.PageNavigationView_NavigationPaddingTop)) {
75 | mTabPaddingTop = a.getDimensionPixelSize(R.styleable.PageNavigationView_NavigationPaddingTop, 0);
76 | }
77 | if (a.hasValue(R.styleable.PageNavigationView_NavigationPaddingBottom)) {
78 | mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.PageNavigationView_NavigationPaddingBottom, 0);
79 | }
80 | a.recycle();
81 | }
82 |
83 | @Override
84 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
85 |
86 | final int count = getChildCount();
87 |
88 | int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
89 | int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
90 |
91 | for (int i = 0; i < count; i++) {
92 | final View child = getChildAt(i);
93 | if (child.getVisibility() == GONE) {
94 | continue;
95 | }
96 | measureChild(child, widthMeasureSpec, heightMeasureSpec);
97 |
98 | maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
99 | maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
100 | }
101 |
102 | setMeasuredDimension(maxWidth, maxHeight);
103 | }
104 |
105 | @Override
106 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
107 | final int count = getChildCount();
108 | final int width = r - l;
109 | final int height = b - t;
110 |
111 | for (int i = 0; i < count; i++) {
112 | final View child = getChildAt(i);
113 | if (child.getVisibility() == GONE) {
114 | continue;
115 | }
116 | child.layout(0, 0, width, height);
117 | }
118 | }
119 |
120 | /**
121 | * 构建 Material Desgin 风格的导航栏
122 | */
123 | public MaterialBuilder material() {
124 | return new MaterialBuilder();
125 | }
126 |
127 | /**
128 | * 构建自定义导航栏
129 | */
130 | public CustomBuilder custom() {
131 | return new CustomBuilder();
132 | }
133 |
134 | /**
135 | * 构建 自定义 的导航栏
136 | */
137 | public class CustomBuilder {
138 | List items;
139 |
140 | boolean enableVerticalLayout;
141 |
142 | CustomBuilder() {
143 | items = new ArrayList<>();
144 | }
145 |
146 | /**
147 | * 完成构建
148 | *
149 | * @return {@link NavigationController},通过它进行后续操作
150 | */
151 | public NavigationController build() {
152 |
153 | mEnableVerticalLayout = enableVerticalLayout;
154 |
155 | //未添加任何按钮
156 | if (items.size() == 0) {
157 | return null;
158 | }
159 |
160 | ItemController itemController;
161 |
162 | if (enableVerticalLayout) {//垂直布局
163 | CustomItemVerticalLayout verticalItemLayout = new CustomItemVerticalLayout(getContext());
164 | verticalItemLayout.initialize(items);
165 | verticalItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
166 |
167 | PageNavigationView.this.removeAllViews();
168 | PageNavigationView.this.addView(verticalItemLayout);
169 | itemController = verticalItemLayout;
170 | } else {//水平布局
171 | CustomItemLayout customItemLayout = new CustomItemLayout(getContext());
172 | customItemLayout.initialize(items);
173 | customItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
174 |
175 | PageNavigationView.this.removeAllViews();
176 | PageNavigationView.this.addView(customItemLayout);
177 | itemController = customItemLayout;
178 | }
179 |
180 | mNavigationController = new NavigationController(new Controller(), itemController);
181 | mNavigationController.addTabItemSelectedListener(mTabItemListener);
182 |
183 | return mNavigationController;
184 | }
185 |
186 | /**
187 | * 添加一个导航按钮
188 | *
189 | * @param baseTabItem {@link BaseTabItem},所有自定义Item都必须继承它
190 | * @return {@link CustomBuilder}
191 | */
192 | public CustomBuilder addItem(BaseTabItem baseTabItem) {
193 | items.add(baseTabItem);
194 | return CustomBuilder.this;
195 | }
196 |
197 | /**
198 | * 获取对应位置的 item
199 | *
200 | * @param position 索引
201 | * @return {@link CustomBuilder}
202 | */
203 | public BaseTabItem getItem(int position) {
204 | if (position >= items.size())
205 | throw new IllegalArgumentException("position not exist");
206 | return items.get(position);
207 | }
208 |
209 | /**
210 | * 使用垂直布局
211 | */
212 | public CustomBuilder enableVerticalLayout() {
213 | enableVerticalLayout = true;
214 | return CustomBuilder.this;
215 | }
216 | }
217 |
218 | /**
219 | * 构建 Material Desgin 风格的导航栏
220 | */
221 | public class MaterialBuilder {
222 | List itemDatas;
223 | int defaultColor;
224 | int mode;
225 | int messageBackgroundColor;
226 | int messageNumberColor;
227 | boolean enableVerticalLayout;
228 |
229 | MaterialBuilder() {
230 | itemDatas = new ArrayList<>();
231 | }
232 |
233 | /**
234 | * 完成构建
235 | *
236 | * @return {@link NavigationController},通过它进行后续操作
237 | */
238 | public NavigationController build() {
239 | mEnableVerticalLayout = enableVerticalLayout;
240 |
241 | // 未添加任何按钮
242 | if (itemDatas.size() == 0) {
243 | return null;
244 | }
245 |
246 | // 设置默认颜色
247 | if (defaultColor == 0) {
248 | defaultColor = 0x56000000;
249 | }
250 |
251 | ItemController itemController;
252 |
253 | if (enableVerticalLayout) {//垂直布局
254 |
255 | List items = new ArrayList<>();
256 |
257 | for (ViewData data : itemDatas) {
258 |
259 | OnlyIconMaterialItemView materialItemView = new OnlyIconMaterialItemView(getContext());
260 | materialItemView.initialization(data.title, data.drawable, data.checkedDrawable, defaultColor, data.chekedColor);
261 |
262 | //检查是否设置了消息圆点的颜色
263 | if (messageBackgroundColor != 0) {
264 | materialItemView.setMessageBackgroundColor(messageBackgroundColor);
265 | }
266 |
267 | //检查是否设置了消息数字的颜色
268 | if (messageNumberColor != 0) {
269 | materialItemView.setMessageNumberColor(messageNumberColor);
270 | }
271 |
272 | items.add(materialItemView);
273 | }
274 |
275 | MaterialItemVerticalLayout materialItemVerticalLayout = new MaterialItemVerticalLayout(getContext());
276 | materialItemVerticalLayout.initialize(items);
277 | materialItemVerticalLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
278 |
279 | PageNavigationView.this.removeAllViews();
280 | PageNavigationView.this.addView(materialItemVerticalLayout);
281 |
282 | itemController = materialItemVerticalLayout;
283 |
284 | } else {//水平布局
285 |
286 | boolean changeBackground = (mode & MaterialMode.CHANGE_BACKGROUND_COLOR) > 0;
287 |
288 | List items = new ArrayList<>();
289 | List checkedColors = new ArrayList<>();
290 |
291 | for (ViewData data : itemDatas) {
292 | // 记录设置的选中颜色
293 | checkedColors.add(data.chekedColor);
294 |
295 | MaterialItemView materialItemView = new MaterialItemView(getContext());
296 | // 需要切换背景颜色就默认将选中颜色改成白色
297 | if (changeBackground) {
298 | materialItemView.initialization(data.title, data.drawable, data.checkedDrawable, defaultColor, Color.WHITE);
299 | } else {
300 | materialItemView.initialization(data.title, data.drawable, data.checkedDrawable, defaultColor, data.chekedColor);
301 | }
302 |
303 | //检查是否设置了消息圆点的颜色
304 | if (messageBackgroundColor != 0) {
305 | materialItemView.setMessageBackgroundColor(messageBackgroundColor);
306 | }
307 |
308 | //检查是否设置了消息数字的颜色
309 | if (messageNumberColor != 0) {
310 | materialItemView.setMessageNumberColor(messageNumberColor);
311 | }
312 |
313 | items.add(materialItemView);
314 | }
315 |
316 | MaterialItemLayout materialItemLayout = new MaterialItemLayout(getContext());
317 | materialItemLayout.initialize(items, checkedColors, mode);
318 | materialItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
319 |
320 | PageNavigationView.this.removeAllViews();
321 | PageNavigationView.this.addView(materialItemLayout);
322 |
323 | itemController = materialItemLayout;
324 | }
325 |
326 | mNavigationController = new NavigationController(new Controller(), itemController);
327 | mNavigationController.addTabItemSelectedListener(mTabItemListener);
328 |
329 | return mNavigationController;
330 | }
331 |
332 | /**
333 | * 添加一个导航按钮
334 | *
335 | * @param drawableRes 图标资源
336 | * @param title 显示文字内容.尽量简短
337 | * @return {@link MaterialBuilder}
338 | */
339 | public MaterialBuilder addItem(@DrawableRes int drawableRes, String title) {
340 | addItem(drawableRes, drawableRes, title, Utils.getColorPrimary(getContext()));
341 | return MaterialBuilder.this;
342 | }
343 |
344 | /**
345 | * 添加一个导航按钮
346 | *
347 | * @param drawableRes 图标资源
348 | * @param checkedDrawableRes 选中时的图标资源
349 | * @param title 显示文字内容.尽量简短
350 | * @return {@link MaterialBuilder}
351 | */
352 | public MaterialBuilder addItem(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, String title) {
353 | addItem(drawableRes, checkedDrawableRes, title, Utils.getColorPrimary(getContext()));
354 | return MaterialBuilder.this;
355 | }
356 |
357 | /**
358 | * 添加一个导航按钮
359 | *
360 | * @param drawableRes 图标资源
361 | * @param title 显示文字内容.尽量简短
362 | * @param chekedColor 选中的颜色
363 | * @return {@link MaterialBuilder}
364 | */
365 | public MaterialBuilder addItem(@DrawableRes int drawableRes, String title, @ColorInt int chekedColor) {
366 | addItem(drawableRes, drawableRes, title, chekedColor);
367 | return MaterialBuilder.this;
368 | }
369 |
370 | /**
371 | * 添加一个导航按钮
372 | *
373 | * @param drawableRes 图标资源
374 | * @param checkedDrawableRes 选中时的图标资源
375 | * @param title 显示文字内容.尽量简短
376 | * @param chekedColor 选中的颜色
377 | * @return {@link MaterialBuilder}
378 | */
379 | public MaterialBuilder addItem(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, String title, @ColorInt int chekedColor) {
380 | addItem(ContextCompat.getDrawable(getContext(), drawableRes), ContextCompat.getDrawable(getContext(), checkedDrawableRes), title, chekedColor);
381 | return MaterialBuilder.this;
382 | }
383 |
384 | /**
385 | * 添加一个导航按钮
386 | *
387 | * @param drawable 图标资源
388 | * @param title 显示文字内容.尽量简短
389 | * @return {@link MaterialBuilder}
390 | */
391 | public MaterialBuilder addItem(Drawable drawable, String title) {
392 | addItem(drawable, Utils.newDrawable(drawable), title, Utils.getColorPrimary(getContext()));
393 | return MaterialBuilder.this;
394 | }
395 |
396 | /**
397 | * 添加一个导航按钮
398 | *
399 | * @param drawable 图标资源
400 | * @param checkedDrawable 选中时的图标资源
401 | * @param title 显示文字内容.尽量简短
402 | * @return {@link MaterialBuilder}
403 | */
404 | public MaterialBuilder addItem(Drawable drawable, Drawable checkedDrawable, String title) {
405 | addItem(drawable, checkedDrawable, title, Utils.getColorPrimary(getContext()));
406 | return MaterialBuilder.this;
407 | }
408 |
409 | /**
410 | * 添加一个导航按钮
411 | *
412 | * @param drawable 图标资源
413 | * @param title 显示文字内容.尽量简短
414 | * @param chekedColor 选中的颜色
415 | * @return {@link MaterialBuilder}
416 | */
417 | public MaterialBuilder addItem(Drawable drawable, String title, @ColorInt int chekedColor) {
418 | addItem(drawable, Utils.newDrawable(drawable), title, chekedColor);
419 | return MaterialBuilder.this;
420 | }
421 |
422 | /**
423 | * 添加一个导航按钮
424 | *
425 | * @param drawable 图标资源
426 | * @param checkedDrawable 选中时的图标资源
427 | * @param title 显示文字内容.尽量简短
428 | * @param chekedColor 选中的颜色
429 | * @return {@link MaterialBuilder}
430 | */
431 | public MaterialBuilder addItem(Drawable drawable, Drawable checkedDrawable, String title, @ColorInt int chekedColor) {
432 | ViewData data = new ViewData();
433 | data.drawable = drawable;
434 | data.checkedDrawable = checkedDrawable;
435 | data.title = title;
436 | data.chekedColor = chekedColor;
437 | itemDatas.add(data);
438 | return MaterialBuilder.this;
439 | }
440 |
441 | /**
442 | * 设置导航按钮的默认(未选中状态)颜色
443 | *
444 | * @param color 16进制整形表示的颜色,例如红色:0xFFFF0000
445 | * @return {@link MaterialBuilder}
446 | */
447 | public MaterialBuilder setDefaultColor(@ColorInt int color) {
448 | defaultColor = color;
449 | return MaterialBuilder.this;
450 | }
451 |
452 | /**
453 | * 设置消息圆点的颜色
454 | *
455 | * @param color 16进制整形表示的颜色,例如红色:0xFFFF0000
456 | * @return {@link MaterialBuilder}
457 | */
458 | public MaterialBuilder setMessageBackgroundColor(@ColorInt int color) {
459 | messageBackgroundColor = color;
460 | return MaterialBuilder.this;
461 | }
462 |
463 | /**
464 | * 设置消息数字的颜色
465 | *
466 | * @param color 16进制整形表示的颜色,例如红色:0xFFFF0000
467 | * @return {@link MaterialBuilder}
468 | */
469 | public MaterialBuilder setMessageNumberColor(@ColorInt int color) {
470 | messageNumberColor = color;
471 | return MaterialBuilder.this;
472 | }
473 |
474 | /**
475 | * 设置模式(在垂直布局中无效)。默认文字一直显示,且背景色不变。
476 | * 可以通过{@link MaterialMode}选择模式。
477 | *
478 | *
例如:
479 | * {@code MaterialMode.HIDE_TEXT}
480 | *
481 | *
或者多选:
482 | * {@code MaterialMode.HIDE_TEXT | MaterialMode.CHANGE_BACKGROUND_COLOR}
483 | *
484 | * @param mode {@link MaterialMode}
485 | * @return {@link MaterialBuilder}
486 | */
487 | public MaterialBuilder setMode(int mode) {
488 | MaterialBuilder.this.mode = mode;
489 | return MaterialBuilder.this;
490 | }
491 |
492 | /**
493 | * 使用垂直布局
494 | */
495 | public MaterialBuilder enableVerticalLayout() {
496 | enableVerticalLayout = true;
497 | return MaterialBuilder.this;
498 | }
499 |
500 | private class ViewData {
501 | Drawable drawable;
502 | Drawable checkedDrawable;
503 | String title;
504 | @ColorInt
505 | int chekedColor;
506 | }
507 | }
508 |
509 | /**
510 | * 实现控制接口
511 | */
512 | private class Controller implements BottomLayoutController {
513 |
514 | private ObjectAnimator animator;
515 | private boolean hide = false;
516 |
517 | @Override
518 | public void setupWithViewPager(ViewPager viewPager) {
519 | if (viewPager == null) {
520 | return;
521 | }
522 |
523 | mViewPager = viewPager;
524 |
525 | if (mPageChangeListener != null) {
526 | mViewPager.removeOnPageChangeListener(mPageChangeListener);
527 | } else {
528 | mPageChangeListener = new ViewPagerPageChangeListener();
529 | }
530 |
531 | if (mNavigationController != null) {
532 | int n = mViewPager.getCurrentItem();
533 | if (mNavigationController.getSelected() != n) {
534 | mNavigationController.setSelect(n);
535 | }
536 | mViewPager.addOnPageChangeListener(mPageChangeListener);
537 | }
538 | }
539 |
540 | @Override
541 | public void hideBottomLayout() {
542 | if (!hide) {
543 | hide = true;
544 | getAnimator().start();
545 | }
546 | }
547 |
548 | @Override
549 | public void showBottomLayout() {
550 | if (hide) {
551 | hide = false;
552 | getAnimator().reverse();
553 | }
554 | }
555 |
556 | private ObjectAnimator getAnimator() {
557 |
558 | if (animator == null) {
559 |
560 | if (mEnableVerticalLayout) {//垂直布局向左隐藏
561 | animator = ObjectAnimator.ofFloat(
562 | PageNavigationView.this, "translationX", 0, -PageNavigationView.this.getWidth());
563 | } else {//水平布局向下隐藏
564 | animator = ObjectAnimator.ofFloat(
565 | PageNavigationView.this, "translationY", 0, PageNavigationView.this.getHeight());
566 | }
567 |
568 | animator.setDuration(300);
569 | animator.setInterpolator(new AccelerateDecelerateInterpolator());
570 | }
571 | return animator;
572 | }
573 | }
574 |
575 | private class ViewPagerPageChangeListener implements ViewPager.OnPageChangeListener {
576 |
577 | @Override
578 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
579 |
580 | }
581 |
582 | @Override
583 | public void onPageSelected(int position) {
584 | if (mNavigationController != null && mNavigationController.getSelected() != position) {
585 | mNavigationController.setSelect(position);
586 | }
587 | }
588 |
589 | @Override
590 | public void onPageScrollStateChanged(int state) {
591 |
592 | }
593 | }
594 |
595 | private static final String INSTANCE_STATUS = "INSTANCE_STATUS";
596 | private final String STATUS_SELECTED = "STATUS_SELECTED";
597 |
598 | @Override
599 | protected Parcelable onSaveInstanceState() {
600 | if (mNavigationController == null) {
601 | return super.onSaveInstanceState();
602 | }
603 | Bundle bundle = new Bundle();
604 | bundle.putParcelable(INSTANCE_STATUS, super.onSaveInstanceState());
605 | bundle.putInt(STATUS_SELECTED, mNavigationController.getSelected());
606 | return bundle;
607 | }
608 |
609 | @Override
610 | protected void onRestoreInstanceState(Parcelable state) {
611 | if (state instanceof Bundle) {
612 | Bundle bundle = (Bundle) state;
613 | int selected = bundle.getInt(STATUS_SELECTED, 0);
614 | super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATUS));
615 |
616 | if (selected != 0 && mNavigationController != null) {
617 | mNavigationController.setSelect(selected);
618 | }
619 |
620 | return;
621 | }
622 | super.onRestoreInstanceState(state);
623 | }
624 | }
625 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/internal/BaselineLayout.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.internal;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import androidx.core.view.ViewCompat;
9 |
10 | /**
11 | * A simple ViewGroup that aligns all the views inside on a baseline. Note: bottom padding for this
12 | * view will be measured starting from the baseline.
13 | */
14 | public class BaselineLayout extends ViewGroup {
15 | private int mBaseline = -1;
16 |
17 | public BaselineLayout(Context context) {
18 | super(context, null, 0);
19 | }
20 |
21 | public BaselineLayout(Context context, AttributeSet attrs) {
22 | super(context, attrs, 0);
23 | }
24 |
25 | public BaselineLayout(Context context, AttributeSet attrs, int defStyleAttr) {
26 | super(context, attrs, defStyleAttr);
27 | }
28 |
29 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
30 | final int count = getChildCount();
31 | int maxWidth = 0;
32 | int maxHeight = 0;
33 | int maxChildBaseline = -1;
34 | int maxChildDescent = -1;
35 | int childState = 0;
36 |
37 | for (int i = 0; i < count; i++) {
38 | final View child = getChildAt(i);
39 | if (child.getVisibility() == GONE) {
40 | continue;
41 | }
42 |
43 | measureChild(child, widthMeasureSpec, heightMeasureSpec);
44 | final int baseline = child.getBaseline();
45 | if (baseline != -1) {
46 | maxChildBaseline = Math.max(maxChildBaseline, baseline);
47 | maxChildDescent = Math.max(maxChildDescent, child.getMeasuredHeight() - baseline);
48 | }
49 | maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
50 | maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
51 | childState = childState | ViewCompat.getMeasuredState(child);
52 | }
53 | if (maxChildBaseline != -1) {
54 | maxChildDescent = Math.max(maxChildDescent, getPaddingBottom());
55 | maxHeight = Math.max(maxHeight, maxChildBaseline + maxChildDescent);
56 | mBaseline = maxChildBaseline;
57 | }
58 | maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
59 | maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
60 | setMeasuredDimension(
61 | ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
62 | ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec,
63 | childState << MEASURED_HEIGHT_STATE_SHIFT));
64 | }
65 |
66 | @Override
67 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
68 | final int count = getChildCount();
69 | final int parentLeft = getPaddingLeft();
70 | final int parentRight = right - left - getPaddingRight();
71 | final int parentContentWidth = parentRight - parentLeft;
72 | final int parentTop = getPaddingTop();
73 |
74 | for (int i = 0; i < count; i++) {
75 | final View child = getChildAt(i);
76 | if (child.getVisibility() == GONE) {
77 | continue;
78 | }
79 |
80 | final int width = child.getMeasuredWidth();
81 | final int height = child.getMeasuredHeight();
82 |
83 | final int childLeft = parentLeft + (parentContentWidth - width) / 2;
84 | final int childTop;
85 | if (mBaseline != -1 && child.getBaseline() != -1) {
86 | childTop = parentTop + mBaseline - child.getBaseline();
87 | } else {
88 | childTop = parentTop;
89 | }
90 |
91 | child.layout(childLeft, childTop, childLeft + width, childTop + height);
92 | }
93 | }
94 |
95 | @Override
96 | public int getBaseline() {
97 | return mBaseline;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/internal/CustomItemLayout.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.internal;
2 |
3 | import android.animation.LayoutTransition;
4 | import android.content.Context;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 |
10 | import androidx.core.view.ViewCompat;
11 |
12 | import com.ms.bottombar.ItemController;
13 | import com.ms.bottombar.item.BaseTabItem;
14 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | /**
20 | * 存放自定义项的布局
21 | */
22 | public class CustomItemLayout extends ViewGroup implements ItemController {
23 |
24 | private List mItems;
25 | private List mListeners = new ArrayList<>();
26 |
27 | private int mSelected = -1;
28 |
29 | public CustomItemLayout(Context context) {
30 | this(context, null);
31 | }
32 |
33 | public CustomItemLayout(Context context, AttributeSet attrs) {
34 | this(context, attrs, 0);
35 | }
36 |
37 | public CustomItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {
38 | super(context, attrs, defStyleAttr);
39 | setLayoutTransition(new LayoutTransition());
40 | }
41 |
42 | public void initialize(List items) {
43 | mItems = items;
44 |
45 | //添加按钮到布局,并注册点击事件
46 | int n = mItems.size();
47 | for (int i = 0; i < n; i++) {
48 | BaseTabItem v = mItems.get(i);
49 | v.setChecked(false);
50 | this.addView(v);
51 |
52 | final int finali = i;
53 | v.setOnClickListener(new OnClickListener() {
54 | @Override
55 | public void onClick(View v) {
56 | setSelect(finali);
57 | }
58 | });
59 | }
60 |
61 | //默认选中第一项
62 | mSelected = 0;
63 | mItems.get(0).setChecked(true);
64 | }
65 |
66 | @Override
67 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
68 |
69 | final int n = getChildCount();
70 | int visableChildCount = 0;
71 | for (int i = 0; i < n; i++) {
72 | if (getChildAt(i).getVisibility() != GONE) {
73 | visableChildCount++;
74 | }
75 | }
76 |
77 | if (visableChildCount == 0) {
78 | return;
79 | }
80 |
81 | final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec) / visableChildCount, MeasureSpec.EXACTLY);
82 | final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(0, MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop()), MeasureSpec.EXACTLY);
83 |
84 | for (int i = 0; i < n; i++) {
85 | final View child = getChildAt(i);
86 | if (child.getVisibility() == GONE) {
87 | continue;
88 | }
89 | child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
90 | }
91 |
92 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
93 | }
94 |
95 | @Override
96 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
97 | final int count = getChildCount();
98 | final int width = right - left;
99 | final int height = bottom - top;
100 | //只支持top、bottom的padding
101 | final int padding_top = getPaddingTop();
102 | final int padding_bottom = getPaddingBottom();
103 | int used = 0;
104 |
105 | for (int i = 0; i < count; i++) {
106 | final View child = getChildAt(i);
107 | if (child.getVisibility() == GONE) {
108 | continue;
109 | }
110 | if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
111 | child.layout(width - used - child.getMeasuredWidth(), padding_top, width - used, height - padding_bottom);
112 | } else {
113 | child.layout(used, padding_top, child.getMeasuredWidth() + used, height - padding_bottom);
114 | }
115 | used += child.getMeasuredWidth();
116 | }
117 | }
118 |
119 | @Override
120 | public void setSelect(int index) {
121 |
122 | //重复选择
123 | if (index == mSelected) {
124 | for (OnTabItemSelectedListener listener : mListeners) {
125 | mItems.get(mSelected).onRepeat();
126 | listener.onRepeat(mSelected);
127 | }
128 | return;
129 | }
130 |
131 | //记录前一个选中项和当前选中项
132 | int oldSelected = mSelected;
133 | mSelected = index;
134 |
135 | //前一个选中项必须不小于0才有效
136 | if (oldSelected >= 0) {
137 | mItems.get(oldSelected).setChecked(false);
138 | }
139 |
140 | mItems.get(mSelected).setChecked(true);
141 |
142 | //事件回调
143 | for (OnTabItemSelectedListener listener : mListeners) {
144 | listener.onSelected(mSelected, oldSelected);
145 | }
146 | }
147 |
148 | @Override
149 | public void setMessageNumber(int index, int number) {
150 | mItems.get(index).setMessageNumber(number);
151 | }
152 |
153 | @Override
154 | public void setHasMessage(int index, boolean hasMessage) {
155 | mItems.get(index).setHasMessage(hasMessage);
156 | }
157 |
158 | @Override
159 | public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
160 | mListeners.add(listener);
161 | }
162 |
163 | @Override
164 | public int getSelected() {
165 | return mSelected;
166 | }
167 |
168 | @Override
169 | public int getItemCount() {
170 | return mItems.size();
171 | }
172 |
173 | @Override
174 | public String getItemTitle(int index) {
175 | return mItems.get(index).getTitle();
176 | }
177 |
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/internal/CustomItemVerticalLayout.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.internal;
2 |
3 | import android.animation.LayoutTransition;
4 | import android.content.Context;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 |
10 | import com.ms.bottombar.ItemController;
11 | import com.ms.bottombar.item.BaseTabItem;
12 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * Created by mjj on 2017/9/27
19 | */
20 | public class CustomItemVerticalLayout extends ViewGroup implements ItemController {
21 |
22 | private List mItems;
23 | private List mListeners = new ArrayList<>();
24 |
25 | private int mSelected = -1;
26 |
27 | public CustomItemVerticalLayout(Context context) {
28 | this(context, null);
29 | }
30 |
31 | public CustomItemVerticalLayout(Context context, AttributeSet attrs) {
32 | this(context, attrs, 0);
33 | }
34 |
35 | public CustomItemVerticalLayout(Context context, AttributeSet attrs, int defStyleAttr) {
36 | super(context, attrs, defStyleAttr);
37 | setLayoutTransition(new LayoutTransition());
38 | }
39 |
40 | public void initialize(List items) {
41 | mItems = items;
42 |
43 | //添加按钮到布局,并注册点击事件
44 | int n = mItems.size();
45 | for (int i = 0; i < n; i++) {
46 | BaseTabItem v = mItems.get(i);
47 | v.setChecked(false);
48 | this.addView(v);
49 |
50 | final int finali = i;
51 | v.setOnClickListener(new OnClickListener() {
52 | @Override
53 | public void onClick(View v) {
54 | setSelect(finali);
55 | }
56 | });
57 | }
58 |
59 | //默认选中第一项
60 | mSelected = 0;
61 | mItems.get(0).setChecked(true);
62 | }
63 |
64 | @Override
65 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
66 | final int parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED);
67 | final int childwidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
68 |
69 | int totalHeight = getPaddingTop() + getPaddingBottom();
70 | final int n = getChildCount();
71 | for (int i = 0; i < n; i++) {
72 |
73 | final View child = getChildAt(i);
74 | if (child.getVisibility() == GONE) {
75 | continue;
76 | }
77 |
78 | final LayoutParams lp = child.getLayoutParams();
79 | final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
80 | getPaddingTop() + getPaddingBottom(), lp.height);
81 |
82 | child.measure(childwidthMeasureSpec, childHeightMeasureSpec);
83 |
84 | totalHeight += child.getMeasuredHeight();
85 | }
86 |
87 | setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), totalHeight);
88 | }
89 |
90 | @Override
91 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
92 |
93 | final int count = getChildCount();
94 | //只支持top的padding
95 | int used = getPaddingTop();
96 |
97 | for (int i = 0; i < count; i++) {
98 | final View child = getChildAt(i);
99 | if (child.getVisibility() == GONE) {
100 | continue;
101 | }
102 |
103 | child.layout(0, used, child.getMeasuredWidth(), used + child.getMeasuredHeight());
104 |
105 | used += child.getMeasuredHeight();
106 | }
107 | }
108 |
109 | @Override
110 | public void setSelect(int index) {
111 |
112 | //重复选择
113 | if (index == mSelected) {
114 | for (OnTabItemSelectedListener listener : mListeners) {
115 | listener.onRepeat(mSelected);
116 | }
117 | return;
118 | }
119 |
120 | //记录前一个选中项和当前选中项
121 | int oldSelected = mSelected;
122 | mSelected = index;
123 |
124 | //前一个选中项必须不小于0才有效
125 | if (oldSelected >= 0) {
126 | mItems.get(oldSelected).setChecked(false);
127 | }
128 |
129 | mItems.get(mSelected).setChecked(true);
130 |
131 | //事件回调
132 | for (OnTabItemSelectedListener listener : mListeners) {
133 | listener.onSelected(mSelected, oldSelected);
134 | }
135 | }
136 |
137 | @Override
138 | public void setMessageNumber(int index, int number) {
139 | mItems.get(index).setMessageNumber(number);
140 | }
141 |
142 | @Override
143 | public void setHasMessage(int index, boolean hasMessage) {
144 | mItems.get(index).setHasMessage(hasMessage);
145 | }
146 |
147 | @Override
148 | public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
149 | mListeners.add(listener);
150 | }
151 |
152 | @Override
153 | public int getSelected() {
154 | return mSelected;
155 | }
156 |
157 | @Override
158 | public int getItemCount() {
159 | return mItems.size();
160 | }
161 |
162 | @Override
163 | public String getItemTitle(int index) {
164 | return mItems.get(index).getTitle();
165 | }
166 | }
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/internal/MaterialItemLayout.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.internal;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.ValueAnimator;
6 | import android.content.Context;
7 | import android.content.res.ColorStateList;
8 | import android.content.res.Resources;
9 | import android.graphics.Canvas;
10 | import android.graphics.Paint;
11 | import android.graphics.RectF;
12 | import android.graphics.drawable.RippleDrawable;
13 | import android.os.Build;
14 | import android.util.AttributeSet;
15 | import android.view.MotionEvent;
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 | import android.view.animation.AccelerateDecelerateInterpolator;
19 | import android.view.animation.Interpolator;
20 |
21 | import androidx.core.view.ViewCompat;
22 |
23 | import com.ms.bottombar.ItemController;
24 | import com.ms.bottombar.MaterialMode;
25 | import com.ms.bottombar.R;
26 | import com.ms.bottombar.item.MaterialItemView;
27 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
28 |
29 | import java.util.ArrayList;
30 | import java.util.Iterator;
31 | import java.util.List;
32 |
33 | /**
34 | * 存放 Material Design 风格按钮的水平布局
35 | */
36 | public class MaterialItemLayout extends ViewGroup implements ItemController {
37 |
38 | private final int DEFAULT_SELECTED = 0;
39 |
40 | private final int MATERIAL_BOTTOM_NAVIGATION_ACTIVE_ITEM_MAX_WIDTH;
41 | private final int MATERIAL_BOTTOM_NAVIGATION_ITEM_MAX_WIDTH;
42 | private final int MATERIAL_BOTTOM_NAVIGATION_ITEM_MIN_WIDTH;
43 | private final int MATERIAL_BOTTOM_NAVIGATION_ITEM_HEIGHT;
44 |
45 | private List mItems;
46 |
47 | private List mListeners = new ArrayList<>();
48 |
49 | private int[] mTempChildWidths;
50 | private int mItemTotalWidth;
51 |
52 | private int mSelected = -1;
53 | private int mOldSelected = -1;
54 |
55 | private boolean mHideTitle;
56 |
57 | //切换背景颜色时使用
58 | private final int ANIM_TIME = 300;
59 | private Interpolator mInterpolator;
60 | private boolean mChangeBackgroundMode;
61 | private List mColors;
62 | private List mOvals;
63 | private RectF mTempRectF;
64 | private Paint mPaint;
65 |
66 | //最后手指抬起的坐标
67 | private float mLastUpX;
68 | private float mLastUpY;
69 |
70 | public MaterialItemLayout(Context context) {
71 | this(context, null);
72 | }
73 |
74 | public MaterialItemLayout(Context context, AttributeSet attrs) {
75 | this(context, attrs, 0);
76 | }
77 |
78 | public MaterialItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {
79 | super(context, attrs, defStyleAttr);
80 |
81 | final Resources res = getResources();
82 |
83 | MATERIAL_BOTTOM_NAVIGATION_ACTIVE_ITEM_MAX_WIDTH = res.getDimensionPixelSize(R.dimen.material_bottom_navigation_active_item_max_width);
84 | MATERIAL_BOTTOM_NAVIGATION_ITEM_MAX_WIDTH = res.getDimensionPixelSize(R.dimen.material_bottom_navigation_item_max_width);
85 | MATERIAL_BOTTOM_NAVIGATION_ITEM_MIN_WIDTH = res.getDimensionPixelSize(R.dimen.material_bottom_navigation_item_min_width);
86 | MATERIAL_BOTTOM_NAVIGATION_ITEM_HEIGHT = res.getDimensionPixelSize(R.dimen.material_bottom_navigation_height);
87 |
88 | //材料设计规范限制最多只能有5个导航按钮
89 | mTempChildWidths = new int[5];
90 | }
91 |
92 | /**
93 | * 初始化方法
94 | *
95 | * @param items 按钮集合
96 | * @param mode {@link MaterialMode}
97 | */
98 | public void initialize(List items, List checkedColors, int mode) {
99 | mItems = items;
100 |
101 | //判断是否需要切换背景
102 | if ((mode & MaterialMode.CHANGE_BACKGROUND_COLOR) > 0) {
103 | //初始化一些成员变量
104 | mChangeBackgroundMode = true;
105 | mOvals = new ArrayList<>();
106 | mColors = checkedColors;
107 | mInterpolator = new AccelerateDecelerateInterpolator();
108 | mTempRectF = new RectF();
109 | mPaint = new Paint();
110 |
111 | //设置默认的背景
112 | setBackgroundColor(mColors.get(DEFAULT_SELECTED));
113 |
114 | } else {
115 | //设置按钮点击效果
116 | for (int i = 0; i < mItems.size(); i++) {
117 | MaterialItemView v = mItems.get(i);
118 | if (Build.VERSION.SDK_INT >= 21) {
119 | v.setBackground(new RippleDrawable(new ColorStateList(new int[][]{{}}, new int[]{0xFFFFFF & checkedColors.get(i) | 0x56000000}), null, null));
120 | } else {
121 | v.setBackgroundResource(R.drawable.material_item_background);
122 | }
123 | }
124 | }
125 |
126 | //判断是否隐藏文字
127 | if ((mode & MaterialMode.HIDE_TEXT) > 0) {
128 | mHideTitle = true;
129 | for (MaterialItemView v : mItems) {
130 | v.setHideTitle(true);
131 | }
132 | }
133 |
134 | //添加按钮到布局,并注册点击事件
135 | int n = mItems.size();
136 | for (int i = 0; i < n; i++) {
137 | MaterialItemView v = mItems.get(i);
138 | v.setChecked(false);
139 | this.addView(v);
140 |
141 | final int finali = i;
142 | v.setOnClickListener(new OnClickListener() {
143 | @Override
144 | public void onClick(View v) {
145 | setSelect(finali, mLastUpX, mLastUpY);
146 | }
147 | });
148 | }
149 |
150 | //默认选中第一项
151 | mSelected = DEFAULT_SELECTED;
152 | mItems.get(DEFAULT_SELECTED).setChecked(true);
153 | }
154 |
155 | @Override
156 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
157 | //排除空状态
158 | if (mItems == null || mItems.size() <= 0) {
159 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
160 | return;
161 | }
162 |
163 | final int width = MeasureSpec.getSize(widthMeasureSpec);
164 | final int count = getChildCount();
165 |
166 | final int heightSpec = MeasureSpec.makeMeasureSpec(MATERIAL_BOTTOM_NAVIGATION_ITEM_HEIGHT, MeasureSpec.EXACTLY);
167 |
168 | if (mHideTitle) {
169 | final int inactiveCount = count - 1;
170 | final int activeMaxAvailable = width - inactiveCount * MATERIAL_BOTTOM_NAVIGATION_ITEM_MIN_WIDTH;
171 | final int activeWidth = Math.min(activeMaxAvailable, MATERIAL_BOTTOM_NAVIGATION_ACTIVE_ITEM_MAX_WIDTH);
172 | final int inactiveMaxAvailable = (width - activeWidth) / inactiveCount;
173 | final int inactiveWidth = Math.min(inactiveMaxAvailable, MATERIAL_BOTTOM_NAVIGATION_ITEM_MAX_WIDTH);
174 | for (int i = 0; i < count; i++) {
175 | if (i == mSelected) {
176 | mTempChildWidths[i] = (int) ((activeWidth - inactiveWidth) * mItems.get(mSelected).getAnimValue() + inactiveWidth);
177 | } else if (i == mOldSelected) {
178 | mTempChildWidths[i] = (int) (activeWidth - (activeWidth - inactiveWidth) * mItems.get(mSelected).getAnimValue());
179 | } else {
180 | mTempChildWidths[i] = inactiveWidth;
181 | }
182 | }
183 | } else {
184 | final int maxAvailable = width / (count == 0 ? 1 : count);
185 | final int childWidth = Math.min(maxAvailable, MATERIAL_BOTTOM_NAVIGATION_ACTIVE_ITEM_MAX_WIDTH);
186 | for (int i = 0; i < count; i++) {
187 | mTempChildWidths[i] = childWidth;
188 | }
189 | }
190 |
191 | mItemTotalWidth = 0;
192 | for (int i = 0; i < count; i++) {
193 | final View child = getChildAt(i);
194 | if (child.getVisibility() == GONE) {
195 | continue;
196 | }
197 | child.measure(MeasureSpec.makeMeasureSpec(mTempChildWidths[i], MeasureSpec.EXACTLY),
198 | heightSpec);
199 | LayoutParams params = child.getLayoutParams();
200 | params.width = child.getMeasuredWidth();
201 | mItemTotalWidth += child.getMeasuredWidth();
202 | }
203 |
204 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
205 | }
206 |
207 | @Override
208 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
209 | final int count = getChildCount();
210 | final int width = right - left;
211 | final int height = bottom - top;
212 | //只支持top、bottom的padding
213 | final int padding_top = getPaddingTop();
214 | final int padding_bottom = getPaddingBottom();
215 | int used = 0;
216 |
217 | if (mItemTotalWidth > 0 && mItemTotalWidth < width) {
218 | used = (width - mItemTotalWidth) / 2;
219 | }
220 |
221 | for (int i = 0; i < count; i++) {
222 | final View child = getChildAt(i);
223 | if (child.getVisibility() == GONE) {
224 | continue;
225 | }
226 | if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
227 | child.layout(width - used - child.getMeasuredWidth(), padding_top, width - used, height - padding_bottom);
228 | } else {
229 | child.layout(used, padding_top, child.getMeasuredWidth() + used, height - padding_bottom);
230 | }
231 | used += child.getMeasuredWidth();
232 | }
233 | }
234 |
235 | @Override
236 | protected void onDraw(Canvas canvas) {
237 | super.onDraw(canvas);
238 |
239 | if (mChangeBackgroundMode) {
240 | int width = getWidth();
241 | int height = getHeight();
242 |
243 | Iterator iterator = mOvals.iterator();
244 | while (iterator.hasNext()) {
245 | Oval oval = iterator.next();
246 | mPaint.setColor(oval.color);
247 | if (oval.r < oval.maxR) {
248 | mTempRectF.set(oval.getLeft(), oval.getTop(), oval.getRight(), oval.getBottom());
249 | canvas.drawOval(mTempRectF, mPaint);
250 | } else {
251 | this.setBackgroundColor(oval.color);
252 | canvas.drawRect(0, 0, width, height, mPaint);
253 | iterator.remove();
254 | }
255 | invalidate();
256 | }
257 | }
258 | }
259 |
260 | @Override
261 | public boolean onInterceptTouchEvent(MotionEvent ev) {
262 |
263 | if (ev.getAction() == MotionEvent.ACTION_UP) {
264 | mLastUpX = ev.getX();
265 | mLastUpY = ev.getY();
266 | }
267 |
268 | return super.onInterceptTouchEvent(ev);
269 | }
270 |
271 | @Override
272 | public void setSelect(int index) {
273 | //不正常的选择项
274 | if (index >= mItems.size() || index < 0) {
275 | return;
276 | }
277 |
278 | View v = mItems.get(index);
279 | setSelect(index, v.getX() + v.getWidth() / 2f, v.getY() + v.getHeight() / 2f);
280 | }
281 |
282 | @Override
283 | public void setMessageNumber(int index, int number) {
284 | mItems.get(index).setMessageNumber(number);
285 | }
286 |
287 | @Override
288 | public void setHasMessage(int index, boolean hasMessage) {
289 | mItems.get(index).setHasMessage(hasMessage);
290 | }
291 |
292 | @Override
293 | public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
294 | mListeners.add(listener);
295 | }
296 |
297 | @Override
298 | public int getSelected() {
299 | return mSelected;
300 | }
301 |
302 | @Override
303 | public int getItemCount() {
304 | return mItems.size();
305 | }
306 |
307 | @Override
308 | public String getItemTitle(int index) {
309 | return mItems.get(index).getTitle();
310 | }
311 |
312 | private void setSelect(int index, float x, float y) {
313 |
314 | //重复选择
315 | if (index == mSelected) {
316 | for (OnTabItemSelectedListener listener : mListeners) {
317 | listener.onRepeat(mSelected);
318 | }
319 | return;
320 | }
321 |
322 | //记录前一个选中项和当前选中项
323 | mOldSelected = mSelected;
324 | mSelected = index;
325 |
326 | //切换背景颜色
327 | if (mChangeBackgroundMode) {
328 | addOvalColor(mColors.get(mSelected), x, y);
329 | }
330 |
331 | //前一个选中项必须不小于0才有效
332 | if (mOldSelected >= 0) {
333 | mItems.get(mOldSelected).setChecked(false);
334 | }
335 |
336 | mItems.get(mSelected).setChecked(true);
337 |
338 | //事件回调
339 | for (OnTabItemSelectedListener listener : mListeners) {
340 | listener.onSelected(mSelected, mOldSelected);
341 | }
342 | }
343 |
344 | /**
345 | * 添加一个圆形波纹动画
346 | *
347 | * @param color 颜色
348 | * @param x X座标
349 | * @param y y座标
350 | */
351 | private void addOvalColor(int color, float x, float y) {
352 | final Oval oval = new Oval(color, 2, x, y);
353 |
354 | oval.maxR = getR(x, y);
355 | mOvals.add(oval);
356 |
357 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(oval.r, oval.maxR);
358 | valueAnimator.setInterpolator(mInterpolator);
359 | valueAnimator.setDuration(ANIM_TIME);
360 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
361 | @Override
362 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
363 | oval.r = (float) valueAnimator.getAnimatedValue();
364 | }
365 | });
366 | valueAnimator.addListener(new AnimatorListenerAdapter() {
367 | @Override
368 | public void onAnimationStart(Animator animation) {
369 | super.onAnimationStart(animation);
370 | invalidate();
371 | }
372 | });
373 | valueAnimator.start();
374 | }
375 |
376 | /**
377 | * 以矩形内一点为圆心画圆,覆盖矩形,求这个圆的最小半径
378 | *
379 | * @param x 横坐标
380 | * @param y 纵坐标
381 | * @return 最小半径
382 | */
383 | private float getR(float x, float y) {
384 | int width = getWidth();
385 | int height = getHeight();
386 |
387 | double r1_square = x * x + y * y;
388 | double r2_square = (width - x) * (width - x) + y * y;
389 | double r3_square = (width - x) * (width - x) + (height - y) * (height - y);
390 | double r4_square = x * x + (height - y) * (height - y);
391 |
392 | return (float) Math.sqrt(Math.max(Math.max(r1_square, r2_square), Math.max(r3_square, r4_square)));
393 | }
394 |
395 | private class Oval {
396 | int color;
397 | float r;
398 | float x;
399 | float y;
400 | float maxR;
401 |
402 | Oval(int color, float r, float x, float y) {
403 | this.color = color;
404 | this.r = r;
405 | this.x = x;
406 | this.y = y;
407 | }
408 |
409 | float getLeft() {
410 | return x - r;
411 | }
412 |
413 | float getTop() {
414 | return y - r;
415 | }
416 |
417 | float getRight() {
418 | return x + r;
419 | }
420 |
421 | float getBottom() {
422 | return y + r;
423 | }
424 | }
425 | }
426 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/internal/MaterialItemVerticalLayout.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.internal;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import com.ms.bottombar.ItemController;
9 | import com.ms.bottombar.R;
10 | import com.ms.bottombar.item.BaseTabItem;
11 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | /**
17 | * 存放 Material Design 风格按钮的垂直布局
18 | */
19 | public class MaterialItemVerticalLayout extends ViewGroup implements ItemController {
20 |
21 | private final int NAVIGATION_ITEM_SIZE;
22 |
23 | private List mItems;
24 | private List mListeners = new ArrayList<>();
25 |
26 | private int mSelected = -1;
27 |
28 | public MaterialItemVerticalLayout(Context context) {
29 | this(context, null);
30 | }
31 |
32 | public MaterialItemVerticalLayout(Context context, AttributeSet attrs) {
33 | this(context, attrs, 0);
34 | }
35 |
36 | public MaterialItemVerticalLayout(Context context, AttributeSet attrs, int defStyleAttr) {
37 | super(context, attrs, defStyleAttr);
38 |
39 | NAVIGATION_ITEM_SIZE = getResources().getDimensionPixelSize(R.dimen.material_bottom_navigation_height);
40 | }
41 |
42 | public void initialize(List items) {
43 | mItems = items;
44 |
45 | //添加按钮到布局,并注册点击事件
46 | int n = mItems.size();
47 | for (int i = 0; i < n; i++) {
48 | BaseTabItem v = mItems.get(i);
49 | v.setChecked(false);
50 | this.addView(v);
51 |
52 | final int finali = i;
53 | v.setOnClickListener(new OnClickListener() {
54 | @Override
55 | public void onClick(View v) {
56 | setSelect(finali);
57 | }
58 | });
59 | }
60 |
61 | //默认选中第一项
62 | mSelected = 0;
63 | mItems.get(0).setChecked(true);
64 | }
65 |
66 | @Override
67 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
68 |
69 | final int n = getChildCount();
70 |
71 | final int heightSpec = MeasureSpec.makeMeasureSpec(NAVIGATION_ITEM_SIZE, MeasureSpec.EXACTLY);
72 | final int widthSpec = MeasureSpec.makeMeasureSpec(NAVIGATION_ITEM_SIZE, MeasureSpec.EXACTLY);
73 |
74 | for (int i = 0; i < n; i++) {
75 | final View child = getChildAt(i);
76 | if (child.getVisibility() == GONE) {
77 | continue;
78 | }
79 | child.measure(widthSpec, heightSpec);
80 | }
81 |
82 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
83 | }
84 |
85 | @Override
86 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
87 | final int count = getChildCount();
88 | //只支持top的padding
89 | int used = getPaddingTop();
90 |
91 | for (int i = 0; i < count; i++) {
92 | final View child = getChildAt(i);
93 | if (child.getVisibility() == GONE) {
94 | continue;
95 | }
96 |
97 | child.layout(0, used, child.getMeasuredWidth(), used + child.getMeasuredHeight());
98 |
99 | used += child.getMeasuredHeight();
100 | }
101 | }
102 |
103 | @Override
104 | public void setSelect(int index) {
105 |
106 | //重复选择
107 | if (index == mSelected) {
108 | for (OnTabItemSelectedListener listener : mListeners) {
109 | listener.onRepeat(mSelected);
110 | }
111 | return;
112 | }
113 |
114 | //记录前一个选中项和当前选中项
115 | int oldSelected = mSelected;
116 | mSelected = index;
117 |
118 | //前一个选中项必须不小于0才有效
119 | if (oldSelected >= 0) {
120 | mItems.get(oldSelected).setChecked(false);
121 | }
122 |
123 | mItems.get(mSelected).setChecked(true);
124 |
125 | //事件回调
126 | for (OnTabItemSelectedListener listener : mListeners) {
127 | listener.onSelected(mSelected, oldSelected);
128 | }
129 | }
130 |
131 | @Override
132 | public void setMessageNumber(int index, int number) {
133 | mItems.get(index).setMessageNumber(number);
134 | }
135 |
136 | @Override
137 | public void setHasMessage(int index, boolean hasMessage) {
138 | mItems.get(index).setHasMessage(hasMessage);
139 | }
140 |
141 | @Override
142 | public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
143 | mListeners.add(listener);
144 | }
145 |
146 | @Override
147 | public int getSelected() {
148 | return mSelected;
149 | }
150 |
151 | @Override
152 | public int getItemCount() {
153 | return mItems.size();
154 | }
155 |
156 | @Override
157 | public String getItemTitle(int index) {
158 | return mItems.get(index).getTitle();
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/internal/RoundMessageView.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.internal;
2 |
3 | import android.content.Context;
4 | import android.graphics.Typeface;
5 | import android.graphics.drawable.Drawable;
6 | import android.util.AttributeSet;
7 | import android.util.TypedValue;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.widget.FrameLayout;
11 | import android.widget.TextView;
12 |
13 | import androidx.annotation.ColorInt;
14 | import androidx.core.content.ContextCompat;
15 | import androidx.core.view.ViewCompat;
16 |
17 | import com.ms.bottombar.R;
18 |
19 | import java.util.Locale;
20 |
21 | public class RoundMessageView extends FrameLayout {
22 | private final View mOval;
23 | private final TextView mMessages;
24 |
25 | private int mMessageNumber;
26 | private boolean mHasMessage;
27 |
28 | public RoundMessageView(Context context) {
29 | this(context, null);
30 | }
31 |
32 | public RoundMessageView(Context context, AttributeSet attrs) {
33 | this(context, attrs, 0);
34 | }
35 |
36 | public RoundMessageView(Context context, AttributeSet attrs, int defStyleAttr) {
37 | super(context, attrs, defStyleAttr);
38 | LayoutInflater.from(context).inflate(R.layout.round_message_view, this, true);
39 |
40 | mOval = findViewById(R.id.oval);
41 | mMessages = (TextView) findViewById(R.id.msg);
42 | mMessages.setTypeface(Typeface.DEFAULT_BOLD);
43 | mMessages.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10);
44 | }
45 |
46 | public void setMessageNumber(int number) {
47 | mMessageNumber = number;
48 |
49 | if (mMessageNumber > 0) {
50 | mOval.setVisibility(View.INVISIBLE);
51 | mMessages.setVisibility(View.VISIBLE);
52 |
53 | if (mMessageNumber < 10) {
54 | mMessages.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
55 | } else {
56 | mMessages.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10);
57 | }
58 |
59 | if (mMessageNumber <= 99) {
60 | mMessages.setText(String.format(Locale.ENGLISH, "%d", mMessageNumber));
61 | } else {
62 | mMessages.setText(String.format(Locale.ENGLISH, "%d+", 99));
63 | }
64 | } else {
65 | mMessages.setVisibility(View.INVISIBLE);
66 | if (mHasMessage) {
67 | mOval.setVisibility(View.VISIBLE);
68 | }
69 | }
70 | }
71 |
72 | public void setHasMessage(boolean hasMessage) {
73 | mHasMessage = hasMessage;
74 |
75 | if (hasMessage) {
76 | mOval.setVisibility(mMessageNumber > 0 ? View.INVISIBLE : View.VISIBLE);
77 | } else {
78 | mOval.setVisibility(View.INVISIBLE);
79 | }
80 | }
81 |
82 | public void tintMessageBackground(@ColorInt int color) {
83 | Drawable drawable = Utils.tint(ContextCompat.getDrawable(getContext(), R.drawable.round), color);
84 | ViewCompat.setBackground(mOval, drawable);
85 | ViewCompat.setBackground(mMessages, drawable);
86 | }
87 |
88 | public void setMessageNumberColor(@ColorInt int color) {
89 | mMessages.setTextColor(color);
90 | }
91 |
92 | public int getMessageNumber() {
93 | return mMessageNumber;
94 | }
95 |
96 | public boolean hasMessage() {
97 | return mHasMessage;
98 | }
99 |
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/internal/Utils.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.internal;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.graphics.drawable.Drawable;
6 | import android.util.TypedValue;
7 |
8 | import androidx.core.content.ContextCompat;
9 | import androidx.core.graphics.drawable.DrawableCompat;
10 |
11 | public class Utils {
12 |
13 | public static Drawable tint(Drawable drawable, int color) {
14 | final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
15 | wrappedDrawable.mutate();
16 | DrawableCompat.setTint(wrappedDrawable, color);
17 | return wrappedDrawable;
18 | }
19 |
20 | public static Drawable newDrawable(Drawable drawable) {
21 | Drawable.ConstantState constantState = drawable.getConstantState();
22 | return constantState != null ? constantState.newDrawable() : drawable;
23 | }
24 |
25 | /**
26 | * 获取colorPrimary的颜色,需要V7包的支持
27 | *
28 | * @param context 上下文
29 | * @return 0xAARRGGBB
30 | */
31 | public static int getColorPrimary(Context context) {
32 | Resources res = context.getResources();
33 | int attrRes = res.getIdentifier("colorPrimary", "attr", context.getPackageName());
34 | if (attrRes == 0) {
35 | return 0xFF009688;
36 | }
37 | return ContextCompat.getColor(context, getResourceId(context, attrRes));
38 | }
39 |
40 | /**
41 | * 获取自定义属性的资源ID
42 | *
43 | * @param context 上下文
44 | * @param attrRes 自定义属性
45 | * @return resourceId
46 | */
47 | private static int getResourceId(Context context, int attrRes) {
48 | TypedValue typedValue = new TypedValue();
49 | context.getTheme().resolveAttribute(attrRes, typedValue, true);
50 | return typedValue.resourceId;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/item/BaseTabItem.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.item;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.FrameLayout;
6 |
7 | import androidx.annotation.AttrRes;
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 |
11 | /**
12 | * 所有自定义Item都必须继承此类
13 | */
14 | public abstract class BaseTabItem extends FrameLayout {
15 | public BaseTabItem(@NonNull Context context) {
16 | super(context);
17 | }
18 |
19 | public BaseTabItem(@NonNull Context context, @Nullable AttributeSet attrs) {
20 | super(context, attrs);
21 | }
22 |
23 | public BaseTabItem(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
24 | super(context, attrs, defStyleAttr);
25 | }
26 |
27 | /**
28 | * 设置选中状态
29 | */
30 | abstract public void setChecked(boolean checked);
31 |
32 | /**
33 | * 设置消息数字。注意:数字需要大于0才会显示
34 | */
35 | abstract public void setMessageNumber(int number);
36 |
37 | /**
38 | * 设置是否显示无数字的小圆点。注意:如果消息数字不为0,优先显示带数字的圆
39 | */
40 | abstract public void setHasMessage(boolean hasMessage);
41 |
42 | /**
43 | * 获取标题文字
44 | */
45 | abstract public String getTitle();
46 |
47 | /**
48 | * 已选中的状态下再次点击时触发
49 | */
50 | public void onRepeat() {
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/item/MaterialItemView.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.item;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.graphics.drawable.Drawable;
6 | import android.util.AttributeSet;
7 | import android.util.TypedValue;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.animation.AccelerateDecelerateInterpolator;
11 | import android.widget.FrameLayout;
12 | import android.widget.ImageView;
13 | import android.widget.TextView;
14 |
15 | import androidx.annotation.AttrRes;
16 | import androidx.annotation.ColorInt;
17 | import androidx.annotation.NonNull;
18 | import androidx.annotation.Nullable;
19 |
20 | import com.ms.bottombar.R;
21 | import com.ms.bottombar.internal.RoundMessageView;
22 | import com.ms.bottombar.internal.Utils;
23 |
24 | /**
25 | * 材料设计风格项
26 | */
27 | public class MaterialItemView extends BaseTabItem {
28 |
29 | private final RoundMessageView mMessages;
30 | private final TextView mLabel;
31 | private final ImageView mIcon;
32 |
33 | private Drawable mDefaultDrawable;
34 | private Drawable mCheckedDrawable;
35 |
36 | private int mDefaultColor;
37 | private int mCheckedColor;
38 |
39 | private final float mTranslation;
40 | private final float mTranslationHideTitle;
41 |
42 | private final int mTopMargin;
43 | private final int mTopMarginHideTitle;
44 |
45 | private boolean mHideTitle;
46 | private boolean mChecked;
47 |
48 | private ValueAnimator mAnimator;
49 | private float mAnimatorValue = 1f;
50 |
51 | private boolean mIsMeasured = false;
52 |
53 | public MaterialItemView(@NonNull Context context) {
54 | this(context, null);
55 | }
56 |
57 | public MaterialItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
58 | this(context, attrs, 0);
59 | }
60 |
61 | public MaterialItemView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
62 | super(context, attrs, defStyleAttr);
63 |
64 | final float scale = context.getResources().getDisplayMetrics().density;
65 |
66 | mTranslation = scale * 2;
67 | mTranslationHideTitle = scale * 10;
68 | mTopMargin = (int) (scale * 8);
69 | mTopMarginHideTitle = (int) (scale * 16);
70 |
71 | LayoutInflater.from(context).inflate(R.layout.item_material, this, true);
72 |
73 | mIcon = (ImageView) findViewById(R.id.icon);
74 | mLabel = (TextView) findViewById(R.id.label);
75 | mMessages = (RoundMessageView) findViewById(R.id.messages);
76 | }
77 |
78 | public void initialization(String title, Drawable drawable, Drawable checkedDrawable, int color, int checkedColor) {
79 |
80 | mDefaultColor = color;
81 | mCheckedColor = checkedColor;
82 |
83 | mDefaultDrawable = Utils.tint(drawable, mDefaultColor);
84 | mCheckedDrawable = Utils.tint(checkedDrawable, mCheckedColor);
85 |
86 | mLabel.setText(title);
87 | mLabel.setTextColor(color);
88 |
89 | mIcon.setImageDrawable(mDefaultDrawable);
90 |
91 | mAnimator = ValueAnimator.ofFloat(1f);
92 | mAnimator.setDuration(115L);
93 | mAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
94 | mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
95 | @Override
96 | public void onAnimationUpdate(ValueAnimator animation) {
97 | mAnimatorValue = (float) animation.getAnimatedValue();
98 | if (mHideTitle) {
99 | mIcon.setTranslationY(-mTranslationHideTitle * mAnimatorValue);
100 | } else {
101 | mIcon.setTranslationY(-mTranslation * mAnimatorValue);
102 | }
103 | mLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f + mAnimatorValue * 2f);
104 | }
105 | });
106 | }
107 |
108 |
109 | @Override
110 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
111 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
112 | mIsMeasured = true;
113 | }
114 |
115 | @Override
116 | public void setChecked(boolean checked) {
117 | if (mChecked == checked) {
118 | return;
119 | }
120 |
121 | mChecked = checked;
122 |
123 | if (mHideTitle) {
124 | mLabel.setVisibility(mChecked ? View.VISIBLE : View.INVISIBLE);
125 | }
126 |
127 | if (mIsMeasured) {
128 | // 切换动画
129 | if (mChecked) {
130 | mAnimator.start();
131 | } else {
132 | mAnimator.reverse();
133 | }
134 | } else if (mChecked) { // 布局还未测量时选中,直接转换到选中的最终状态
135 | if (mHideTitle) {
136 | mIcon.setTranslationY(-mTranslationHideTitle);
137 | } else {
138 | mIcon.setTranslationY(-mTranslation);
139 | }
140 | mLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f);
141 | } else { // 布局还未测量并且未选中,保持未选中状态
142 | mIcon.setTranslationY(0);
143 | mLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f);
144 | }
145 |
146 | // 切换颜色
147 | if (mChecked) {
148 | mIcon.setImageDrawable(mCheckedDrawable);
149 | mLabel.setTextColor(mCheckedColor);
150 | } else {
151 | mIcon.setImageDrawable(mDefaultDrawable);
152 | mLabel.setTextColor(mDefaultColor);
153 | }
154 | }
155 |
156 | @Override
157 | public void setMessageNumber(int number) {
158 | mMessages.setVisibility(View.VISIBLE);
159 | mMessages.setMessageNumber(number);
160 | }
161 |
162 | @Override
163 | public void setHasMessage(boolean hasMessage) {
164 | mMessages.setVisibility(View.VISIBLE);
165 | mMessages.setHasMessage(hasMessage);
166 | }
167 |
168 | @Override
169 | public String getTitle() {
170 | return mLabel.getText().toString();
171 | }
172 |
173 | /**
174 | * 获取动画运行值[0,1]
175 | */
176 | public float getAnimValue() {
177 | return mAnimatorValue;
178 | }
179 |
180 | /**
181 | * 设置是否隐藏文字
182 | */
183 | public void setHideTitle(boolean hideTitle) {
184 | mHideTitle = hideTitle;
185 |
186 | FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) mIcon.getLayoutParams();
187 |
188 | if (mHideTitle) {
189 | iconParams.topMargin = mTopMarginHideTitle;
190 | } else {
191 | iconParams.topMargin = mTopMargin;
192 | }
193 |
194 | mLabel.setVisibility(mChecked ? View.VISIBLE : View.INVISIBLE);
195 |
196 | mIcon.setLayoutParams(iconParams);
197 | }
198 |
199 | /**
200 | * 设置消息圆形的颜色
201 | */
202 | public void setMessageBackgroundColor(@ColorInt int color) {
203 | mMessages.tintMessageBackground(color);
204 | }
205 |
206 | /**
207 | * 设置消息数据的颜色
208 | */
209 | public void setMessageNumberColor(@ColorInt int color) {
210 | mMessages.setMessageNumberColor(color);
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/item/OnlyIconMaterialItemView.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.item;
2 |
3 | import android.content.Context;
4 | import android.content.res.ColorStateList;
5 | import android.graphics.drawable.Drawable;
6 | import android.graphics.drawable.RippleDrawable;
7 | import android.os.Build;
8 | import android.util.AttributeSet;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.widget.ImageView;
12 |
13 | import androidx.annotation.AttrRes;
14 | import androidx.annotation.ColorInt;
15 | import androidx.annotation.NonNull;
16 | import androidx.annotation.Nullable;
17 |
18 | import com.ms.bottombar.R;
19 | import com.ms.bottombar.internal.RoundMessageView;
20 | import com.ms.bottombar.internal.Utils;
21 |
22 | /**
23 | * 只有图标的材料设计项(用于垂直布局)
24 | */
25 | public class OnlyIconMaterialItemView extends BaseTabItem {
26 |
27 | private final RoundMessageView mMessages;
28 | private final ImageView mIcon;
29 |
30 | private Drawable mDefaultDrawable;
31 | private Drawable mCheckedDrawable;
32 |
33 | private int mDefaultColor;
34 | private int mCheckedColor;
35 |
36 | private String mTitle;
37 |
38 | private boolean mChecked;
39 |
40 | public OnlyIconMaterialItemView(@NonNull Context context) {
41 | this(context, null);
42 | }
43 |
44 | public OnlyIconMaterialItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
45 | this(context, attrs, 0);
46 | }
47 |
48 | public OnlyIconMaterialItemView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
49 | super(context, attrs, defStyleAttr);
50 |
51 | LayoutInflater.from(context).inflate(R.layout.item_material_only_icon, this, true);
52 |
53 | mIcon = (ImageView) findViewById(R.id.icon);
54 | mMessages = (RoundMessageView) findViewById(R.id.messages);
55 | }
56 |
57 | public void initialization(String title, Drawable drawable, Drawable checkedDrawable, int color, int checkedColor) {
58 |
59 | mTitle = title;
60 |
61 | mDefaultColor = color;
62 | mCheckedColor = checkedColor;
63 |
64 | mDefaultDrawable = Utils.tint(drawable, mDefaultColor);
65 | mCheckedDrawable = Utils.tint(checkedDrawable, mCheckedColor);
66 |
67 | mIcon.setImageDrawable(mDefaultDrawable);
68 |
69 | if (Build.VERSION.SDK_INT >= 21) {
70 | setBackground(new RippleDrawable(new ColorStateList(new int[][]{{}}, new int[]{0xFFFFFF & checkedColor | 0x56000000}), null, null));
71 | } else {
72 | setBackgroundResource(R.drawable.material_item_background);
73 | }
74 | }
75 |
76 | @Override
77 | public void setChecked(boolean checked) {
78 | if (mChecked == checked) {
79 | return;
80 | }
81 |
82 | mChecked = checked;
83 |
84 | // 切换颜色
85 | if (mChecked) {
86 | mIcon.setImageDrawable(mCheckedDrawable);
87 | } else {
88 | mIcon.setImageDrawable(mDefaultDrawable);
89 | }
90 | }
91 |
92 | @Override
93 | public void setMessageNumber(int number) {
94 | mMessages.setVisibility(View.VISIBLE);
95 | mMessages.setMessageNumber(number);
96 | }
97 |
98 | @Override
99 | public void setHasMessage(boolean hasMessage) {
100 | mMessages.setVisibility(View.VISIBLE);
101 | mMessages.setHasMessage(hasMessage);
102 | }
103 |
104 | @Override
105 | public String getTitle() {
106 | return mTitle;
107 | }
108 |
109 | /**
110 | * 设置消息圆形的颜色
111 | */
112 | public void setMessageBackgroundColor(@ColorInt int color) {
113 | mMessages.tintMessageBackground(color);
114 | }
115 |
116 | /**
117 | * 设置消息数据的颜色
118 | */
119 | public void setMessageNumberColor(@ColorInt int color) {
120 | mMessages.setMessageNumberColor(color);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/listener/OnTabItemSelectedListener.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.listener;
2 |
3 | /**
4 | * 导航栏按钮选中事件监听
5 | */
6 | public interface OnTabItemSelectedListener {
7 | /**
8 | * 选中导航栏的某一项
9 | *
10 | * @param index 索引导航按钮,按添加顺序排序
11 | * @param old 前一个选中项,如果没有就等于-1
12 | */
13 | void onSelected(int index, int old);
14 |
15 | /**
16 | * 重复选中
17 | *
18 | * @param index 索引导航按钮,按添加顺序排序
19 | */
20 | void onRepeat(int index);
21 | }
22 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/java/com/ms/bottombar/view/RefreshView.java:
--------------------------------------------------------------------------------
1 | package com.ms.bottombar.view;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.Path;
10 | import android.graphics.PathMeasure;
11 | import android.graphics.RectF;
12 | import android.util.AttributeSet;
13 | import android.view.View;
14 |
15 | import com.ms.bottombar.R;
16 |
17 | public class RefreshView extends View {
18 |
19 | private float mWidth;
20 | private float mHeight;
21 |
22 | /*
23 | * 圆点
24 | */
25 | private int mCircleColor; // 颜色
26 | private Paint mCirclePaint;
27 |
28 | private Paint mPathPaint;
29 | private int mPathColor; // 颜色
30 | private int mPathStroke; // 大小
31 | private Path mPath;
32 |
33 | private Path mMiddlePath;
34 | private RectF mInnerRectF;
35 | private RectF mMiddleRectF;
36 | private RectF mOutPathRectF;
37 | private Paint mOutPathPaint;
38 |
39 | private int mStartAngle;
40 | private float mNoPaintArcAngle;
41 | private float mValue;
42 |
43 | private float[] mPos;
44 | private PathMeasure mPathMeasure;
45 |
46 | public RefreshView(Context context) {
47 | this(context, null);
48 | }
49 |
50 | public RefreshView(Context context, AttributeSet attrs) {
51 | this(context, attrs, 0);
52 | }
53 |
54 | public RefreshView(Context context, AttributeSet attrs, int defStyleAttr) {
55 | super(context, attrs, defStyleAttr);
56 |
57 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RefreshView);
58 | mCircleColor = array.getColor(R.styleable.RefreshView_rv_circle_color, Color.RED);
59 | mPathColor = array.getColor(R.styleable.RefreshView_rv_arc_color, Color.RED);
60 | mPathStroke = array.getDimensionPixelSize(R.styleable.RefreshView_rv_arc_stroke, 3);
61 | mStartAngle = array.getDimensionPixelSize(R.styleable.RefreshView_rv_start_angle, -180);
62 | mNoPaintArcAngle = array.getDimensionPixelSize(R.styleable.RefreshView_rv_no_paint_angle, 30);
63 | array.recycle();
64 |
65 | init();
66 | }
67 |
68 | private void init() {
69 | mInnerRectF = new RectF();
70 | mOutPathRectF = new RectF();
71 | mMiddleRectF = new RectF();
72 |
73 | mCirclePaint = new Paint();
74 | mCirclePaint.setColor(mCircleColor);
75 | mCirclePaint.setAntiAlias(true);
76 | mCirclePaint.setStyle(Paint.Style.FILL);
77 |
78 | mPathPaint = new Paint();
79 | mPathPaint.setColor(mPathColor);
80 | mPathPaint.setAntiAlias(true);
81 | mPathPaint.setStyle(Paint.Style.STROKE);
82 | mPathPaint.setStrokeWidth(mPathStroke);
83 | mPath = new Path();
84 |
85 | mOutPathPaint = new Paint();
86 | mOutPathPaint.setColor(mPathColor);
87 | mOutPathPaint.setAntiAlias(true);
88 | mOutPathPaint.setStyle(Paint.Style.STROKE);
89 | mOutPathPaint.setStrokeWidth(mPathStroke);
90 | mOutPathPaint.setAlpha(255 / 3);
91 |
92 | mPathMeasure = new PathMeasure();
93 | mMiddlePath = new Path();
94 | mPos = new float[2];
95 | }
96 |
97 | @Override
98 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
99 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
100 |
101 | mWidth = getMeasuredWidth();
102 | mHeight = getMeasuredHeight();
103 | mOutPathRectF.set(mPathStroke, mPathStroke, mWidth - mPathStroke, mHeight - mPathStroke);
104 | }
105 |
106 | @Override
107 | protected void onDraw(Canvas canvas) {
108 | super.onDraw(canvas);
109 |
110 | canvas.drawCircle(mWidth / 2, mHeight / 2, mValue * mWidth / 7, mCirclePaint);
111 |
112 | mOutPathPaint.setAlpha((int) (255 / 3 - 255 / 3 * mValue));
113 | canvas.drawArc(mOutPathRectF, 90 + mNoPaintArcAngle / 2, 360 - mNoPaintArcAngle, false, mOutPathPaint);
114 |
115 | mMiddleRectF.set(mWidth / 4 - mValue * mWidth / 4 + mPathStroke + mWidth / 9,
116 | mHeight / 4 - mValue * mHeight / 4 + mPathStroke + mWidth / 9,
117 | mWidth - mWidth / 4 + mValue * mWidth / 4 - mPathStroke - mWidth / 9,
118 | mHeight - mHeight / 4 + mValue * mHeight / 4 - mPathStroke - mWidth / 9);
119 | mMiddlePath.arcTo(mMiddleRectF,
120 | mStartAngle - mStartAngle * mValue + mValue * 30 - 10,
121 | 360 - mNoPaintArcAngle - 10);
122 |
123 | mPathMeasure.setPath(mMiddlePath, false);
124 | mPathMeasure.getPosTan(1 * mPathMeasure.getLength(), mPos, null);
125 | float x = mPos[0];
126 | float y = mPos[1];
127 |
128 | mInnerRectF.set(mWidth / 4 - mValue * mWidth / 4 + mPathStroke,
129 | mHeight / 4 - mValue * mHeight / 4 + mPathStroke,
130 | mWidth - mWidth / 4 + mValue * mWidth / 4 - mPathStroke,
131 | mHeight - mHeight / 4 + mValue * mHeight / 4 - mPathStroke);
132 | mPath.addArc(mInnerRectF,
133 | mStartAngle - mStartAngle * mValue + mValue * 30,
134 | 360 - mNoPaintArcAngle);
135 | mPath.lineTo(x, y);
136 | canvas.drawPath(mPath, mPathPaint);
137 |
138 | mPath.reset();
139 | mMiddlePath.reset();
140 | }
141 |
142 | public void startAnim() {
143 | ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
144 | anim.setDuration(350);
145 | anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
146 | @Override
147 | public void onAnimationUpdate(ValueAnimator animation) {
148 | mValue = (float) animation.getAnimatedValue();
149 | invalidate();
150 | }
151 | });
152 | anim.start();
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/res/drawable/material_item_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/res/drawable/round.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/res/layout/item_material.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
11 |
12 |
18 |
19 |
26 |
27 |
28 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/res/layout/item_material_only_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/res/layout/round_message_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
22 |
23 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/LibBottomBar/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 168dp
4 | 14sp
5 | 8dp
6 | 56dp
7 | 96dp
8 | 56dp
9 | 8dp
10 | 1dp
11 | 12sp
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 轻量级的底部导航栏
2 |
3 | 在原项目[PagerBottomTabStrip](https://github.com/tyzlmjj/PagerBottomTabStrip) 基础上
4 | 增加了 getItem 方法,能设置对应 position 的 tab 属性
5 |
6 | ### 实现效果图
7 |
8 | |||
9 | |---|---|
10 | |||
11 | |||
12 |
13 | ### 自定义扩展例子
14 |
15 | |仿京东重复刷新动画|普通效果|
16 | |---|---|
17 | |||
18 |
19 | |Demo中的例子||
20 | |---|---|
21 | |||
22 |
23 |
24 | ### 使用
25 |
26 | #### 布局文件中配置
27 |
28 | xml文件
29 |
30 |
36 |
37 | #### java文件中设置
38 |
39 | PageNavigationView bottomTabLayout = (PageNavigationView) findViewById(R.id.tab);
40 | PageNavigationView.CustomBuilder custom = bottomTabLayout.custom();
41 | NavigationController build = custom
42 | .addItem(newItem(android.R.drawable.ic_menu_camera, android.R.drawable.ic_menu_camera, "相机"))
43 | .addItem(newItem(android.R.drawable.ic_menu_compass, android.R.drawable.ic_menu_compass, "位置"))
44 | .addItem(newItem(android.R.drawable.ic_menu_search, android.R.drawable.ic_menu_search, "搜索"))
45 | .addItem(newItem(android.R.drawable.ic_menu_help, android.R.drawable.ic_menu_help, "帮助"))
46 | .build();
47 | build.setupWithViewPager(mVpContent);
48 |
49 | 这样就实现底部导航栏功能了
50 |
51 | #### 设置条目选中的监听
52 |
53 | navigationController.addTabItemSelectedListener(new OnTabItemSelectedListener() {
54 | @Override
55 | public void onSelected(int index, int old) {
56 | //选中时触发
57 | }
58 |
59 | @Override
60 | public void onRepeat(int index) {
61 | //重复选中时触发
62 | }
63 | });
64 |
65 | #### **导入方式**
66 |
67 | dependencies {
68 | implementation 'com.ms:bottombar:1.0.0'
69 | implementation 'com.github.wenchaosong:BottomBar:3.0.9'
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.4.2'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | flatDir {
15 | dirs 'libs'
16 | }
17 | jcenter()
18 | google()
19 | maven { url "https://jitpack.io" }
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
27 | ext {
28 | // 统一定义SDK版本
29 | compileSdkVersion = 30
30 | minSdkVersion = 21
31 | targetSdkVersion = 30
32 | buildToolsVersion = '29.0.2'
33 | appcompat = '1.3.0'
34 | recyclerview = '1.2.1'
35 | }
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 | defaultConfig {
8 | applicationId "com.test"
9 | minSdkVersion rootProject.ext.minSdkVersion
10 | targetSdkVersion rootProject.ext.targetSdkVersion
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 |
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | implementation fileTree(include: ['*.jar'], dir: 'libs')
25 | implementation "androidx.appcompat:appcompat:$rootProject.ext.appcompat"
26 | implementation 'com.google.android.material:material:1.0.0'
27 |
28 | implementation project(':LibBottomBar')
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in G:\SDK\AndroidStudioSDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/BehaviorActivity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.os.Bundle;
4 | import android.util.TypedValue;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import androidx.annotation.Nullable;
11 | import androidx.appcompat.app.AppCompatActivity;
12 | import androidx.appcompat.widget.Toolbar;
13 | import androidx.fragment.app.Fragment;
14 | import androidx.fragment.app.FragmentManager;
15 | import androidx.fragment.app.FragmentPagerAdapter;
16 | import androidx.recyclerview.widget.DividerItemDecoration;
17 | import androidx.recyclerview.widget.RecyclerView;
18 | import androidx.viewpager.widget.ViewPager;
19 |
20 | import com.ms.bottombar.NavigationController;
21 | import com.ms.bottombar.PageNavigationView;
22 |
23 | public class BehaviorActivity extends AppCompatActivity {
24 |
25 | NavigationController mNavigationController;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_behavior);
31 |
32 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
33 | setSupportActionBar(toolbar);
34 |
35 | PageNavigationView pageBottomTabLayout = (PageNavigationView) findViewById(R.id.tab);
36 |
37 | mNavigationController = pageBottomTabLayout.material()
38 | .addItem(R.drawable.ic_restore_teal_24dp, "Recents")
39 | .addItem(R.drawable.ic_favorite_teal_24dp, "Favorites")
40 | .addItem(R.drawable.ic_nearby_teal_24dp, "Nearby")
41 | .build();
42 |
43 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
44 | viewPager.setAdapter(new TestViewPagerAdapter(getSupportFragmentManager()));
45 |
46 | mNavigationController.setupWithViewPager(viewPager);
47 | }
48 |
49 | //下面几个类都是为了测试写的
50 | private class TestViewPagerAdapter extends FragmentPagerAdapter {
51 |
52 | public TestViewPagerAdapter(FragmentManager fm) {
53 | super(fm);
54 | }
55 |
56 | @Override
57 | public Fragment getItem(int position) {
58 | return new TestFragment();
59 | }
60 |
61 | @Override
62 | public int getCount() {
63 | return mNavigationController.getItemCount();
64 | }
65 | }
66 |
67 | public static class TestFragment extends Fragment {
68 |
69 | @Nullable
70 | @Override
71 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
72 | return inflater.inflate(R.layout.recyclerview, container, false);
73 | }
74 |
75 | @Override
76 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
77 | RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
78 | recyclerView.setAdapter(new TestAdapter());
79 | recyclerView.addItemDecoration(new DividerItemDecoration(view.getContext(), DividerItemDecoration.VERTICAL));
80 | }
81 | }
82 |
83 | private static class TestAdapter extends RecyclerView.Adapter {
84 |
85 | @Override
86 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
87 | int padding = (int) TypedValue.applyDimension(
88 | TypedValue.COMPLEX_UNIT_DIP, 16, parent.getResources().getDisplayMetrics());
89 | TextView textView = new TextView(parent.getContext());
90 | textView.setPadding(padding, padding, padding, padding);
91 | textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
92 |
93 | return new RecyclerView.ViewHolder(textView) {
94 | };
95 | }
96 |
97 | @Override
98 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
99 |
100 | if (holder.itemView instanceof TextView) {
101 | ((TextView) holder.itemView).setText(String.valueOf(position));
102 | }
103 | }
104 |
105 | @Override
106 | public int getItemCount() {
107 | return 100;
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/Custom2Activity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 | import androidx.viewpager.widget.ViewPager;
7 |
8 | import com.ms.bottombar.NavigationController;
9 | import com.ms.bottombar.PageNavigationView;
10 | import com.ms.bottombar.item.BaseTabItem;
11 | import com.test.custom.OnlyIconItemView;
12 | import com.test.custom.TestRepeatTab;
13 | import com.test.other.MyViewPagerAdapter;
14 |
15 | public class Custom2Activity extends AppCompatActivity {
16 |
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setContentView(R.layout.layout_horizontal);
21 |
22 | PageNavigationView tab = (PageNavigationView) findViewById(R.id.tab);
23 |
24 | NavigationController navigationController = tab.custom()
25 | .addItem(newItem_test(R.drawable.ic_restore_gray_24dp, R.drawable.ic_restore_teal_24dp))
26 | .addItem(newItem(R.drawable.ic_favorite_gray_24dp, R.drawable.ic_favorite_teal_24dp))
27 | .addItem(newItem(R.drawable.ic_nearby_gray_24dp, R.drawable.ic_nearby_teal_24dp))
28 | .build();
29 |
30 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
31 | viewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager(), navigationController.getItemCount()));
32 |
33 | //自动适配ViewPager页面切换
34 | navigationController.setupWithViewPager(viewPager);
35 | }
36 |
37 | //创建一个Item
38 | private BaseTabItem newItem(int drawable, int checkedDrawable) {
39 | OnlyIconItemView onlyIconItemView = new OnlyIconItemView(this);
40 | onlyIconItemView.initialize(drawable, checkedDrawable);
41 | return onlyIconItemView;
42 | }
43 |
44 | //创建一个Item(测试重复点击的方法)
45 | private BaseTabItem newItem_test(int drawable, int checkedDrawable) {
46 | TestRepeatTab testRepeatTab = new TestRepeatTab(this);
47 | testRepeatTab.initialize(drawable, checkedDrawable);
48 | return testRepeatTab;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/CustomActivity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.graphics.Color;
4 | import android.os.Bundle;
5 |
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import androidx.viewpager.widget.ViewPager;
8 |
9 | import com.ms.bottombar.NavigationController;
10 | import com.ms.bottombar.PageNavigationView;
11 | import com.ms.bottombar.item.BaseTabItem;
12 | import com.test.item.NormalItemView;
13 | import com.test.other.MyViewPagerAdapter;
14 |
15 | public class CustomActivity extends AppCompatActivity {
16 |
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setContentView(R.layout.layout_horizontal);
21 |
22 | PageNavigationView tab = (PageNavigationView) findViewById(R.id.tab);
23 |
24 | NavigationController navigationController = tab.custom()
25 | .addItem(newItem(R.drawable.ic_restore_gray_24dp, R.drawable.ic_restore_teal_24dp, "Recents", false))
26 | .addItem(newItem(R.drawable.ic_favorite_gray_24dp, R.drawable.ic_favorite_teal_24dp, "Favorites", true))
27 | .addItem(newItem(R.drawable.ic_nearby_gray_24dp, R.drawable.ic_nearby_teal_24dp, "Nearby", false))
28 | .build();
29 |
30 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
31 | viewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager(), navigationController.getItemCount()));
32 |
33 | //自动适配ViewPager页面切换
34 | navigationController.setupWithViewPager(viewPager);
35 |
36 | //设置消息数
37 | navigationController.setMessageNumber(1, 8);
38 |
39 | //设置显示小圆点
40 | navigationController.setHasMessage(0, true);
41 | }
42 |
43 | //创建一个Item
44 | private BaseTabItem newItem(int drawable, int checkedDrawable, String text, boolean showRefresh) {
45 | NormalItemView normalItemView = new NormalItemView(this);
46 | normalItemView.initialize(drawable, checkedDrawable, text, showRefresh);
47 | normalItemView.setTextDefaultColor(Color.GRAY);
48 | normalItemView.setTextCheckedColor(0xFF009688);
49 | return normalItemView;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/HideActivity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.os.Bundle;
4 | import android.util.TypedValue;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import androidx.annotation.Nullable;
11 | import androidx.appcompat.app.AppCompatActivity;
12 | import androidx.appcompat.widget.Toolbar;
13 | import androidx.fragment.app.Fragment;
14 | import androidx.fragment.app.FragmentManager;
15 | import androidx.fragment.app.FragmentPagerAdapter;
16 | import androidx.recyclerview.widget.DividerItemDecoration;
17 | import androidx.recyclerview.widget.RecyclerView;
18 | import androidx.viewpager.widget.ViewPager;
19 |
20 | import com.ms.bottombar.NavigationController;
21 | import com.ms.bottombar.PageNavigationView;
22 |
23 | public class HideActivity extends AppCompatActivity {
24 |
25 | static NavigationController mNavigationController;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_hide);
31 |
32 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
33 | setSupportActionBar(toolbar);
34 |
35 | PageNavigationView pageBottomTabLayout = (PageNavigationView) findViewById(R.id.tab);
36 |
37 | mNavigationController = pageBottomTabLayout.material()
38 | .addItem(R.drawable.ic_restore_teal_24dp, "Recents")
39 | .addItem(R.drawable.ic_favorite_teal_24dp, "Favorites")
40 | .addItem(R.drawable.ic_nearby_teal_24dp, "Nearby")
41 | .build();
42 |
43 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
44 | viewPager.setAdapter(new TestViewPagerAdapter(getSupportFragmentManager()));
45 |
46 | mNavigationController.setupWithViewPager(viewPager);
47 | }
48 |
49 | /**
50 | * 监听列表的滑动来控制底部导航栏的显示与隐藏
51 | */
52 | private static class ListScrollListener extends RecyclerView.OnScrollListener {
53 |
54 | @Override
55 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
56 | super.onScrolled(recyclerView, dx, dy);
57 | if (dy > 8) {//列表向上滑动
58 | mNavigationController.hideBottomLayout();
59 | } else if (dy < -8) {//列表向下滑动
60 | mNavigationController.showBottomLayout();
61 | }
62 | }
63 | }
64 |
65 | //下面几个类都是为了测试写的
66 |
67 | private class TestViewPagerAdapter extends FragmentPagerAdapter {
68 |
69 | TestViewPagerAdapter(FragmentManager fm) {
70 | super(fm);
71 | }
72 |
73 | @Override
74 | public Fragment getItem(int position) {
75 | return new TestFragment();
76 | }
77 |
78 | @Override
79 | public int getCount() {
80 | return mNavigationController.getItemCount();
81 | }
82 | }
83 |
84 | public static class TestFragment extends Fragment {
85 |
86 | @Nullable
87 | @Override
88 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
89 | return inflater.inflate(R.layout.recyclerview, container, false);
90 | }
91 |
92 | @Override
93 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
94 | RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
95 | recyclerView.setAdapter(new TestAdapter());
96 | recyclerView.addItemDecoration(new DividerItemDecoration(view.getContext(), DividerItemDecoration.VERTICAL));
97 | recyclerView.addOnScrollListener(new ListScrollListener());
98 | }
99 | }
100 |
101 | private static class TestAdapter extends RecyclerView.Adapter {
102 |
103 | @Override
104 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
105 | int padding = (int) TypedValue.applyDimension(
106 | TypedValue.COMPLEX_UNIT_DIP, 16, parent.getResources().getDisplayMetrics());
107 | TextView textView = new TextView(parent.getContext());
108 | textView.setPadding(padding, padding, padding, padding);
109 | textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
110 |
111 | return new RecyclerView.ViewHolder(textView) {
112 | };
113 | }
114 |
115 | @Override
116 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
117 |
118 | if (holder.itemView instanceof TextView) {
119 | ((TextView) holder.itemView).setText(String.valueOf(position));
120 | }
121 | }
122 |
123 | @Override
124 | public int getItemCount() {
125 | return 100;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | import androidx.annotation.Nullable;
8 | import androidx.appcompat.app.AppCompatActivity;
9 |
10 | public class MainActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(@Nullable Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_main);
16 | }
17 |
18 | public void toMaterialdesign(View view) {
19 | startActivity(new Intent(this, MaterialDesignActivity.class));
20 | }
21 |
22 | public void toCustom(View view) {
23 | startActivity(new Intent(this, CustomActivity.class));
24 | }
25 |
26 | public void toCustom2(View view) {
27 | startActivity(new Intent(this, Custom2Activity.class));
28 | }
29 |
30 | public void toBehavior(View view) {
31 | startActivity(new Intent(this, BehaviorActivity.class));
32 | }
33 |
34 | public void toHide(View view) {
35 | startActivity(new Intent(this, HideActivity.class));
36 | }
37 |
38 | public void toSpecial(View view) {
39 | startActivity(new Intent(this, SpecialActivity.class));
40 | }
41 |
42 | public void toVertical(View view) {
43 | startActivity(new Intent(this, VerticalActivity.class));
44 | }
45 |
46 | public void toCsutomVertical(View view) {
47 | startActivity(new Intent(this, VerticalCustomActivity.class));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/MaterialDesignActivity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 |
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import androidx.viewpager.widget.ViewPager;
8 |
9 | import com.ms.bottombar.MaterialMode;
10 | import com.ms.bottombar.NavigationController;
11 | import com.ms.bottombar.PageNavigationView;
12 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
13 | import com.test.other.MyViewPagerAdapter;
14 |
15 | public class MaterialDesignActivity extends AppCompatActivity {
16 |
17 | int[] testColors = {0xFF455A64, 0xFF00796B, 0xFF795548, 0xFF5B4947, 0xFFF57C00};
18 | // int[] testColors = {0xFF009688, 0xFF009688, 0xFF009688, 0xFF009688, 0xFF009688};
19 |
20 | NavigationController mNavigationController;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.layout_horizontal);
26 |
27 | PageNavigationView pageBottomTabLayout = (PageNavigationView) findViewById(R.id.tab);
28 |
29 | mNavigationController = pageBottomTabLayout.material()
30 | .addItem(R.drawable.ic_ondemand_video_black_24dp, "Movies & TV", testColors[0])
31 | .addItem(R.drawable.ic_audiotrack_black_24dp, "Music", testColors[1])
32 | .addItem(R.drawable.ic_book_black_24dp, "Books", testColors[2])
33 | .addItem(R.drawable.ic_news_black_24dp, "Newsstand", testColors[3])
34 | .setDefaultColor(0x89FFFFFF)//未选中状态的颜色
35 | .setMode(MaterialMode.CHANGE_BACKGROUND_COLOR | MaterialMode.HIDE_TEXT)//这里可以设置样式模式,总共可以组合出4种效果
36 | .build();
37 |
38 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
39 | viewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager(), mNavigationController.getItemCount()));
40 |
41 | //自动适配ViewPager页面切换
42 | mNavigationController.setupWithViewPager(viewPager);
43 |
44 | //也可以设置Item选中事件的监听
45 | mNavigationController.addTabItemSelectedListener(new OnTabItemSelectedListener() {
46 | @Override
47 | public void onSelected(int index, int old) {
48 | Log.i("asd", "selected: " + index + " old: " + old);
49 | }
50 |
51 | @Override
52 | public void onRepeat(int index) {
53 | Log.i("asd", "onRepeat selected: " + index);
54 | }
55 | });
56 |
57 | //设置消息圆点
58 | // mNavigationController.setMessageNumber(0,12);
59 | // mNavigationController.setHasMessage(3,true);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/SpecialActivity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.annotation.Nullable;
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import androidx.viewpager.widget.ViewPager;
8 |
9 | import com.ms.bottombar.NavigationController;
10 | import com.ms.bottombar.PageNavigationView;
11 | import com.ms.bottombar.item.BaseTabItem;
12 | import com.test.custom.SpecialTab;
13 | import com.test.custom.SpecialTabRound;
14 | import com.test.other.MyViewPagerAdapter;
15 |
16 | /**
17 | * Created by mjj on 2017/6/25
18 | */
19 | public class SpecialActivity extends AppCompatActivity {
20 |
21 | @Override
22 | protected void onCreate(@Nullable Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_special);
25 |
26 | PageNavigationView tab = (PageNavigationView) findViewById(R.id.tab);
27 |
28 | NavigationController navigationController = tab.custom()
29 | .addItem(newItem(R.drawable.ic_restore_gray_24dp, R.drawable.ic_restore_teal_24dp, "Recents"))
30 | .addItem(newItem(R.drawable.ic_favorite_gray_24dp, R.drawable.ic_favorite_teal_24dp, "Favorites"))
31 | .addItem(newRoundItem(R.drawable.ic_nearby_gray_24dp, R.drawable.ic_nearby_teal_24dp, "Nearby"))
32 | .addItem(newItem(R.drawable.ic_favorite_gray_24dp, R.drawable.ic_favorite_teal_24dp, "Favorites"))
33 | .addItem(newItem(R.drawable.ic_restore_gray_24dp, R.drawable.ic_restore_teal_24dp, "Recents"))
34 | .build();
35 |
36 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
37 | viewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager(), navigationController.getItemCount()));
38 |
39 | //自动适配ViewPager页面切换
40 | navigationController.setupWithViewPager(viewPager);
41 | }
42 |
43 | /**
44 | * 正常tab
45 | */
46 | private BaseTabItem newItem(int drawable, int checkedDrawable, String text) {
47 | SpecialTab mainTab = new SpecialTab(this);
48 | mainTab.initialize(drawable, checkedDrawable, text);
49 | mainTab.setTextDefaultColor(0xFF888888);
50 | mainTab.setTextCheckedColor(0xFF009688);
51 | return mainTab;
52 | }
53 |
54 | /**
55 | * 圆形tab
56 | */
57 | private BaseTabItem newRoundItem(int drawable, int checkedDrawable, String text) {
58 | SpecialTabRound mainTab = new SpecialTabRound(this);
59 | mainTab.initialize(drawable, checkedDrawable, text);
60 | mainTab.setTextDefaultColor(0xFF888888);
61 | mainTab.setTextCheckedColor(0xFF009688);
62 | return mainTab;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/VerticalActivity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 |
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import androidx.viewpager.widget.ViewPager;
8 |
9 | import com.ms.bottombar.NavigationController;
10 | import com.ms.bottombar.PageNavigationView;
11 | import com.ms.bottombar.listener.OnTabItemSelectedListener;
12 | import com.test.other.MyViewPagerAdapter;
13 |
14 | /**
15 | * Created by mjj on 2017/8/3
16 | */
17 | public class VerticalActivity extends AppCompatActivity {
18 |
19 | int[] testColors = {0xFF455A64, 0xFF00796B, 0xFF795548, 0xFF5B4947, 0xFFF57C00};
20 |
21 | NavigationController mNavigationController;
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.layout_vertical);
27 |
28 | PageNavigationView pageBottomTabLayout = (PageNavigationView) findViewById(R.id.tab);
29 |
30 | mNavigationController = pageBottomTabLayout.material()
31 | .addItem(R.drawable.ic_ondemand_video_black_24dp, "Movies & TV", testColors[0])
32 | .addItem(R.drawable.ic_audiotrack_black_24dp, "Music", testColors[1])
33 | .addItem(R.drawable.ic_book_black_24dp, "Books", testColors[2])
34 | .addItem(R.drawable.ic_news_black_24dp, "Newsstand", testColors[3])
35 | .enableVerticalLayout()//使用垂直布局
36 | .build();
37 |
38 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
39 | viewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager(), mNavigationController.getItemCount()));
40 |
41 | //自动适配ViewPager页面切换
42 | mNavigationController.setupWithViewPager(viewPager);
43 |
44 | //也可以设置Item选中事件的监听
45 | mNavigationController.addTabItemSelectedListener(new OnTabItemSelectedListener() {
46 | @Override
47 | public void onSelected(int index, int old) {
48 | Log.i("asd", "selected: " + index + " old: " + old);
49 | }
50 |
51 | @Override
52 | public void onRepeat(int index) {
53 | Log.i("asd", "onRepeat selected: " + index);
54 | }
55 | });
56 |
57 | //设置消息圆点
58 | mNavigationController.setMessageNumber(0, 8);
59 | mNavigationController.setHasMessage(3, true);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/VerticalCustomActivity.java:
--------------------------------------------------------------------------------
1 | package com.test;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.annotation.Nullable;
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import androidx.viewpager.widget.ViewPager;
8 |
9 | import com.ms.bottombar.NavigationController;
10 | import com.ms.bottombar.PageNavigationView;
11 | import com.test.custom.OnlyTextTab;
12 | import com.test.other.MyViewPagerAdapter;
13 |
14 | /**
15 | * Created by mjj on 2017/9/26
16 | */
17 | public class VerticalCustomActivity extends AppCompatActivity {
18 |
19 | private NavigationController mNavigationController;
20 |
21 | @Override
22 | protected void onCreate(@Nullable Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_vertical_custom);
25 |
26 | PageNavigationView pageNavigationView = (PageNavigationView) findViewById(R.id.tab);
27 | mNavigationController = pageNavigationView.custom()
28 | .addItem(new OnlyTextTab(this, "A"))
29 | .addItem(new OnlyTextTab(this, "B"))
30 | .addItem(new OnlyTextTab(this, "C"))
31 | .addItem(new OnlyTextTab(this, "D"))
32 | .addItem(new OnlyTextTab(this, "E"))
33 | .addItem(new OnlyTextTab(this, "F"))
34 | .addItem(new OnlyTextTab(this, "G"))
35 | .addItem(new OnlyTextTab(this, "H"))
36 | .addItem(new OnlyTextTab(this, "I"))
37 | .addItem(new OnlyTextTab(this, "J"))
38 | .addItem(new OnlyTextTab(this, "K"))
39 | .addItem(new OnlyTextTab(this, "L"))
40 | .addItem(new OnlyTextTab(this, "M"))
41 | .addItem(new OnlyTextTab(this, "N"))
42 | .addItem(new OnlyTextTab(this, "O"))
43 | .enableVerticalLayout()
44 | .build();
45 |
46 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
47 | viewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager(), mNavigationController.getItemCount()));
48 |
49 | //自动适配ViewPager页面切换
50 | mNavigationController.setupWithViewPager(viewPager);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/behavior/BottomViewBehavior.java:
--------------------------------------------------------------------------------
1 | package com.test.behavior;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 |
7 | import androidx.coordinatorlayout.widget.CoordinatorLayout;
8 | import androidx.core.view.ViewCompat;
9 |
10 | import com.google.android.material.appbar.AppBarLayout;
11 |
12 | public class BottomViewBehavior extends CoordinatorLayout.Behavior {
13 |
14 | public BottomViewBehavior() {
15 | }
16 |
17 | public BottomViewBehavior(Context context, AttributeSet attrs) {
18 | super(context, attrs);
19 | }
20 |
21 | @Override
22 | public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
23 |
24 | ((CoordinatorLayout.LayoutParams) child.getLayoutParams()).topMargin = parent.getMeasuredHeight() - child.getMeasuredHeight();
25 | return super.onLayoutChild(parent, child, layoutDirection);
26 | }
27 |
28 | @Override
29 | public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
30 | return dependency instanceof AppBarLayout;
31 | }
32 |
33 | @Override
34 | public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
35 |
36 | int top = ((AppBarLayout.Behavior) ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior()).getTopAndBottomOffset();
37 | ViewCompat.setTranslationY(child, -top);
38 | return false;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/custom/OnlyIconItemView.java:
--------------------------------------------------------------------------------
1 | package com.test.custom;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.LayoutInflater;
6 | import android.widget.ImageView;
7 |
8 | import androidx.annotation.DrawableRes;
9 |
10 | import com.ms.bottombar.item.BaseTabItem;
11 | import com.test.R;
12 |
13 | /**
14 | * 自定义一个只有图标的Item
15 | */
16 | public class OnlyIconItemView extends BaseTabItem {
17 |
18 | private ImageView mIcon;
19 |
20 | private int mDefaultDrawable;
21 | private int mCheckedDrawable;
22 |
23 | public OnlyIconItemView(Context context) {
24 | this(context, null);
25 | }
26 |
27 | public OnlyIconItemView(Context context, AttributeSet attrs) {
28 | this(context, attrs, 0);
29 | }
30 |
31 | public OnlyIconItemView(Context context, AttributeSet attrs, int defStyleAttr) {
32 | super(context, attrs, defStyleAttr);
33 |
34 | LayoutInflater.from(context).inflate(R.layout.item_only_icon, this, true);
35 |
36 | mIcon = (ImageView) findViewById(R.id.icon);
37 | }
38 |
39 | public void initialize(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes) {
40 | mDefaultDrawable = drawableRes;
41 | mCheckedDrawable = checkedDrawableRes;
42 | }
43 |
44 | @Override
45 | public void setChecked(boolean checked) {
46 | mIcon.setImageResource(checked ? mCheckedDrawable : mDefaultDrawable);
47 | }
48 |
49 | @Override
50 | public void setMessageNumber(int number) {
51 | //不需要就不用管
52 | }
53 |
54 | @Override
55 | public void setHasMessage(boolean hasMessage) {
56 | //不需要就不用管
57 | }
58 |
59 | @Override
60 | public String getTitle() {
61 | return "no title";
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/custom/OnlyTextTab.java:
--------------------------------------------------------------------------------
1 | package com.test.custom;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.widget.TextView;
6 |
7 | import com.ms.bottombar.item.BaseTabItem;
8 | import com.test.R;
9 |
10 | /**
11 | * Created by mjj on 2017/9/26
12 | */
13 | public class OnlyTextTab extends BaseTabItem {
14 |
15 | private final TextView mTitle;
16 |
17 | public OnlyTextTab(Context context, String title) {
18 | super(context);
19 | LayoutInflater.from(context).inflate(R.layout.item_only_text, this, true);
20 | mTitle = (TextView) findViewById(R.id.title);
21 | mTitle.setText(title);
22 | }
23 |
24 | @Override
25 | public void setChecked(boolean checked) {
26 | mTitle.setTextColor(checked ? 0xFFF4B400 : 0x56000000);
27 | }
28 |
29 | @Override
30 | public void setMessageNumber(int number) {
31 | }
32 |
33 | @Override
34 | public void setHasMessage(boolean hasMessage) {
35 | }
36 |
37 | @Override
38 | public String getTitle() {
39 | return mTitle.getText().toString();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/custom/SpecialTab.java:
--------------------------------------------------------------------------------
1 | package com.test.custom;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | import androidx.annotation.ColorInt;
11 | import androidx.annotation.DrawableRes;
12 | import androidx.annotation.Nullable;
13 |
14 | import com.ms.bottombar.internal.RoundMessageView;
15 | import com.ms.bottombar.item.BaseTabItem;
16 | import com.test.R;
17 |
18 | /**
19 | * Created by mjj on 2017/6/3
20 | */
21 | public class SpecialTab extends BaseTabItem {
22 |
23 | private ImageView mIcon;
24 | private final TextView mTitle;
25 | private final RoundMessageView mMessages;
26 |
27 | private int mDefaultDrawable;
28 | private int mCheckedDrawable;
29 |
30 | private int mDefaultTextColor = 0x56000000;
31 | private int mCheckedTextColor = 0x56000000;
32 |
33 | public SpecialTab(Context context) {
34 | this(context, null);
35 | }
36 |
37 | public SpecialTab(Context context, AttributeSet attrs) {
38 | this(context, attrs, 0);
39 | }
40 |
41 | public SpecialTab(Context context, AttributeSet attrs, int defStyleAttr) {
42 | super(context, attrs, defStyleAttr);
43 |
44 | LayoutInflater.from(context).inflate(R.layout.special_tab, this, true);
45 |
46 | mIcon = (ImageView) findViewById(R.id.icon);
47 | mTitle = (TextView) findViewById(R.id.title);
48 | mMessages = (RoundMessageView) findViewById(R.id.messages);
49 | }
50 |
51 | @Override
52 | public void setOnClickListener(@Nullable OnClickListener l) {
53 | View view = getChildAt(0);
54 | if (view != null) {
55 | view.setOnClickListener(l);
56 | }
57 | }
58 |
59 | /**
60 | * 方便初始化的方法
61 | *
62 | * @param drawableRes 默认状态的图标
63 | * @param checkedDrawableRes 选中状态的图标
64 | * @param title 标题
65 | */
66 | public void initialize(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, String title) {
67 | mDefaultDrawable = drawableRes;
68 | mCheckedDrawable = checkedDrawableRes;
69 | mTitle.setText(title);
70 | }
71 |
72 | @Override
73 | public void setChecked(boolean checked) {
74 | if (checked) {
75 | mIcon.setImageResource(mCheckedDrawable);
76 | mTitle.setTextColor(mCheckedTextColor);
77 | } else {
78 | mIcon.setImageResource(mDefaultDrawable);
79 | mTitle.setTextColor(mDefaultTextColor);
80 | }
81 | }
82 |
83 | @Override
84 | public void setMessageNumber(int number) {
85 | mMessages.setMessageNumber(number);
86 | }
87 |
88 | @Override
89 | public void setHasMessage(boolean hasMessage) {
90 | mMessages.setHasMessage(hasMessage);
91 | }
92 |
93 | @Override
94 | public String getTitle() {
95 | return mTitle.getText().toString();
96 | }
97 |
98 | public void setTextDefaultColor(@ColorInt int color) {
99 | mDefaultTextColor = color;
100 | }
101 |
102 | public void setTextCheckedColor(@ColorInt int color) {
103 | mCheckedTextColor = color;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/custom/SpecialTabRound.java:
--------------------------------------------------------------------------------
1 | package com.test.custom;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.LayoutInflater;
6 | import android.widget.ImageView;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.ColorInt;
10 | import androidx.annotation.DrawableRes;
11 |
12 | import com.ms.bottombar.internal.RoundMessageView;
13 | import com.ms.bottombar.item.BaseTabItem;
14 | import com.test.R;
15 |
16 | /**
17 | * Created by mjj on 2017/6/3
18 | */
19 | public class SpecialTabRound extends BaseTabItem {
20 |
21 | private ImageView mIcon;
22 | private final TextView mTitle;
23 | private final RoundMessageView mMessages;
24 |
25 | private int mDefaultDrawable;
26 | private int mCheckedDrawable;
27 |
28 | private int mDefaultTextColor = 0x56000000;
29 | private int mCheckedTextColor = 0x56000000;
30 |
31 | public SpecialTabRound(Context context) {
32 | this(context, null);
33 | }
34 |
35 | public SpecialTabRound(Context context, AttributeSet attrs) {
36 | this(context, attrs, 0);
37 | }
38 |
39 | public SpecialTabRound(Context context, AttributeSet attrs, int defStyleAttr) {
40 | super(context, attrs, defStyleAttr);
41 |
42 | LayoutInflater.from(context).inflate(R.layout.special_tab_round, this, true);
43 |
44 | mIcon = (ImageView) findViewById(R.id.icon);
45 | mTitle = (TextView) findViewById(R.id.title);
46 | mMessages = (RoundMessageView) findViewById(R.id.messages);
47 | }
48 |
49 | /**
50 | * 方便初始化的方法
51 | *
52 | * @param drawableRes 默认状态的图标
53 | * @param checkedDrawableRes 选中状态的图标
54 | * @param title 标题
55 | */
56 | public void initialize(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, String title) {
57 | mDefaultDrawable = drawableRes;
58 | mCheckedDrawable = checkedDrawableRes;
59 | mTitle.setText(title);
60 | }
61 |
62 | @Override
63 | public void setChecked(boolean checked) {
64 | if (checked) {
65 | mIcon.setImageResource(mCheckedDrawable);
66 | mTitle.setTextColor(mCheckedTextColor);
67 | } else {
68 | mIcon.setImageResource(mDefaultDrawable);
69 | mTitle.setTextColor(mDefaultTextColor);
70 | }
71 | }
72 |
73 | @Override
74 | public void setMessageNumber(int number) {
75 | mMessages.setMessageNumber(number);
76 | }
77 |
78 | @Override
79 | public void setHasMessage(boolean hasMessage) {
80 | mMessages.setHasMessage(hasMessage);
81 | }
82 |
83 | @Override
84 | public String getTitle() {
85 | return mTitle.getText().toString();
86 | }
87 |
88 | public void setTextDefaultColor(@ColorInt int color) {
89 | mDefaultTextColor = color;
90 | }
91 |
92 | public void setTextCheckedColor(@ColorInt int color) {
93 | mCheckedTextColor = color;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/custom/TestRepeatTab.java:
--------------------------------------------------------------------------------
1 | package com.test.custom;
2 |
3 | import android.animation.ObjectAnimator;
4 | import android.content.Context;
5 | import android.util.AttributeSet;
6 |
7 | import com.test.R;
8 |
9 | /**
10 | * Created by mjj on 2017/10/19
11 | *
12 | * 测试重复点击的触发
13 | *
14 | */
15 | public class TestRepeatTab extends OnlyIconItemView {
16 |
17 | public TestRepeatTab(Context context) {
18 | super(context);
19 | }
20 |
21 | public TestRepeatTab(Context context, AttributeSet attrs) {
22 | super(context, attrs);
23 | }
24 |
25 | public TestRepeatTab(Context context, AttributeSet attrs, int defStyleAttr) {
26 | super(context, attrs, defStyleAttr);
27 | }
28 |
29 | ObjectAnimator mAnimator;
30 |
31 | @Override
32 | public void onRepeat() {
33 | super.onRepeat();
34 |
35 | if (mAnimator == null) {
36 | ObjectAnimator animator = ObjectAnimator.ofFloat(findViewById(R.id.icon), "Rotation", 0f, -360f);
37 | animator.setDuration(375);
38 | mAnimator = animator;
39 | }
40 |
41 | if (!mAnimator.isStarted()) {
42 | mAnimator.start();
43 | }
44 |
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/item/NormalItemView.java:
--------------------------------------------------------------------------------
1 | package com.test.item;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.LayoutInflater;
6 | import android.widget.ImageView;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.ColorInt;
10 | import androidx.annotation.DrawableRes;
11 |
12 | import com.test.R;
13 | import com.ms.bottombar.internal.RoundMessageView;
14 | import com.ms.bottombar.item.BaseTabItem;
15 | import com.ms.bottombar.view.RefreshView;
16 |
17 | public class NormalItemView extends BaseTabItem {
18 |
19 | private ImageView mIcon;
20 | private final TextView mTitle;
21 | private final RoundMessageView mMessages;
22 | private RefreshView mRefreshView;
23 |
24 | private int mDefaultDrawable;
25 | private int mCheckedDrawable;
26 | private boolean mShowRefresh;
27 | private int mDefaultTextColor = 0x56000000;
28 | private int mCheckedTextColor = 0x56000000;
29 |
30 | public NormalItemView(Context context) {
31 | this(context, null);
32 | }
33 |
34 | public NormalItemView(Context context, AttributeSet attrs) {
35 | this(context, attrs, 0);
36 | }
37 |
38 | public NormalItemView(Context context, AttributeSet attrs, int defStyleAttr) {
39 | super(context, attrs, defStyleAttr);
40 |
41 | LayoutInflater.from(context).inflate(R.layout.item_normal, this, true);
42 |
43 | mIcon = findViewById(R.id.icon);
44 | mRefreshView = findViewById(R.id.refresh);
45 | mTitle = findViewById(R.id.title);
46 | mMessages = findViewById(R.id.messages);
47 | }
48 |
49 | /**
50 | * 方便初始化的方法
51 | *
52 | * @param drawableRes 默认状态的图标
53 | * @param checkedDrawableRes 选中状态的图标
54 | * @param title 标题
55 | */
56 | public void initialize(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, String title, boolean showRefresh) {
57 | mDefaultDrawable = drawableRes;
58 | mCheckedDrawable = checkedDrawableRes;
59 | mShowRefresh = showRefresh;
60 | mTitle.setText(title);
61 | }
62 |
63 | @Override
64 | public void setChecked(boolean checked) {
65 | if (checked) {
66 | if (mShowRefresh) {
67 | mRefreshView.startAnim();
68 | mRefreshView.setVisibility(VISIBLE);
69 | mIcon.setVisibility(GONE);
70 | } else {
71 | mIcon.setImageResource(mCheckedDrawable);
72 | mIcon.setVisibility(VISIBLE);
73 | }
74 | mTitle.setTextColor(mCheckedTextColor);
75 | } else {
76 | mRefreshView.setVisibility(GONE);
77 | mIcon.setImageResource(mDefaultDrawable);
78 | mIcon.setVisibility(VISIBLE);
79 | mTitle.setTextColor(mDefaultTextColor);
80 | }
81 | }
82 |
83 | @Override
84 | public void onRepeat() {
85 | if (mShowRefresh) {
86 | if (mRefreshView != null)
87 | mRefreshView.startAnim();
88 | }
89 | }
90 |
91 | @Override
92 | public void setMessageNumber(int number) {
93 | mMessages.setMessageNumber(number);
94 | }
95 |
96 | @Override
97 | public void setHasMessage(boolean hasMessage) {
98 | mMessages.setHasMessage(hasMessage);
99 | }
100 |
101 | @Override
102 | public String getTitle() {
103 | return mTitle.getText().toString();
104 | }
105 |
106 | public void setTextDefaultColor(@ColorInt int color) {
107 | mDefaultTextColor = color;
108 | }
109 |
110 | public void setTextCheckedColor(@ColorInt int color) {
111 | mCheckedTextColor = color;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/other/AFragment.java:
--------------------------------------------------------------------------------
1 | package com.test.other;
2 |
3 | import android.os.Bundle;
4 | import android.view.Gravity;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import androidx.annotation.Nullable;
11 | import androidx.fragment.app.Fragment;
12 |
13 | public class AFragment extends Fragment {
14 | private static final String ARG_C = "content";
15 |
16 | public static AFragment newInstance(String content) {
17 | Bundle args = new Bundle();
18 | args.putString(ARG_C, content);
19 | AFragment fragment = new AFragment();
20 | fragment.setArguments(args);
21 | return fragment;
22 | }
23 |
24 | @Nullable
25 | @Override
26 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
27 | String content = getArguments().getString(ARG_C);
28 | TextView textView = new TextView(getContext());
29 | textView.setTextSize(30);
30 | textView.setGravity(Gravity.CENTER);
31 | textView.setText("Test\n\n" + content);
32 | textView.setBackgroundColor(0xFFececec);
33 | return textView;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/other/MyViewPagerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.test.other;
2 |
3 | import androidx.fragment.app.Fragment;
4 | import androidx.fragment.app.FragmentManager;
5 | import androidx.fragment.app.FragmentPagerAdapter;
6 |
7 | public class MyViewPagerAdapter extends FragmentPagerAdapter {
8 |
9 | private int size;
10 |
11 | public MyViewPagerAdapter(FragmentManager fm, int size) {
12 | super(fm);
13 | this.size = size;
14 | }
15 |
16 | @Override
17 | public Fragment getItem(int position) {
18 | return AFragment.newInstance(position + "");
19 | }
20 |
21 | @Override
22 | public int getCount() {
23 | return size;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/test/other/NoTouchViewPager.java:
--------------------------------------------------------------------------------
1 | package com.test.other;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.MotionEvent;
6 |
7 | import androidx.viewpager.widget.ViewPager;
8 |
9 | /**
10 | * 使ViewPager不能滑动
11 | */
12 | public class NoTouchViewPager extends ViewPager {
13 |
14 | public NoTouchViewPager(Context context) {
15 | super(context);
16 | }
17 |
18 | public NoTouchViewPager(Context context, AttributeSet attrs) {
19 | super(context, attrs);
20 | }
21 |
22 | @Override
23 | public boolean onTouchEvent(MotionEvent event) {
24 | return false;
25 | }
26 |
27 | @Override
28 | public boolean onInterceptTouchEvent(MotionEvent event) {
29 | return false;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_audiotrack_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_book_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_favorite_gray_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_favorite_teal_24dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_nearby_gray_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_nearby_teal_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_news_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_ondemand_video_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_restore_gray_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_restore_teal_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/special_tab_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/special_tab_round_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
6 |
7 |
10 |
11 |
12 |
13 | -
14 |
15 |
18 |
21 |
22 |
23 |
24 |
25 | -
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/tab_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/tab_vertical_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_behavior.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
17 |
18 |
19 |
23 |
24 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_hide.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
17 |
18 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
17 |
18 |
23 |
24 |
29 |
30 |
35 |
36 |
41 |
42 |
47 |
48 |
53 |
54 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_special.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_vertical_custom.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/item_normal.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
11 |
18 |
19 |
25 |
26 |
33 |
34 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/item_only_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/item_only_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/layout_horizontal.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/layout_vertical.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/recyclerview.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/special_tab.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/special_tab_round.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
21 |
22 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #607D8B
4 | #455A64
5 | #F4B400
6 |
7 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | BottomBarLayout
3 |
4 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536m
2 | android.enableJetifier=true
3 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/intro_img/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo.png
--------------------------------------------------------------------------------
/intro_img/demo1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo1.gif
--------------------------------------------------------------------------------
/intro_img/demo2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo2.gif
--------------------------------------------------------------------------------
/intro_img/demo3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo3.gif
--------------------------------------------------------------------------------
/intro_img/demo4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo4.gif
--------------------------------------------------------------------------------
/intro_img/demo5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo5.gif
--------------------------------------------------------------------------------
/intro_img/demo6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo6.png
--------------------------------------------------------------------------------
/intro_img/demo7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo7.png
--------------------------------------------------------------------------------
/intro_img/demo8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo8.png
--------------------------------------------------------------------------------
/intro_img/demo9.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomsh/BottomBar/163ab552e38241d4e1773afa4cf2e5b58e2b98c0/intro_img/demo9.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':demo', ':LibBottomBar'
2 |
--------------------------------------------------------------------------------