├── .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 | |![horizontal](/intro_img/demo.png "horizontal")|![vertical](/intro_img/demo8.png "vertical")| 9 | |---|---| 10 | |![Material 1](/intro_img/demo1.gif "Material 1")|![Material 2](/intro_img/demo2.gif "Material 2")| 11 | |![Material 3](/intro_img/demo3.gif "Material 3")|![Material 4](/intro_img/demo4.gif "Material 4")| 12 | 13 | ### 自定义扩展例子 14 | 15 | |仿京东重复刷新动画|普通效果| 16 | |---|---| 17 | |![PagerBottomTabStrip](/intro_img/demo9.gif "PagerBottomTabStrip")|![PagerBottomTabStrip](/intro_img/demo5.gif "PagerBottomTabStrip")| 18 | 19 | |Demo中的例子|| 20 | |---|---| 21 | |![PagerBottomTabStrip](/intro_img/demo7.png "PagerBottomTabStrip")|![PagerBottomTabStrip](/intro_img/demo6.png "PagerBottomTabStrip")| 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 |