├── .gitignore ├── README-2.0.md ├── README-en-2.0.md ├── README-en.md ├── README.md ├── apk └── demo.apk ├── build.gradle ├── demo ├── .gitignore ├── build.gradle ├── demo.jks ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── chaychan │ │ └── bottombarlayout │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── cart.json │ │ ├── category.json │ │ ├── home.json │ │ └── mine.json │ ├── java │ │ └── com │ │ │ └── chaychan │ │ │ └── bottombarlayout │ │ │ ├── BaseViewPagerActivity.java │ │ │ ├── DemoBean.java │ │ │ ├── DynamicAddItemActivity.java │ │ │ ├── FragmentManagerActivity.java │ │ │ ├── LottieDemoActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── TabFragment.java │ │ │ ├── ViewPager2DemoActivity.java │ │ │ └── ViewPagerDemoActivity.java │ └── res │ │ ├── drawable │ │ └── selector_bg.xml │ │ ├── layout │ │ ├── activity_dynamic_add_item.xml │ │ ├── activity_fragment_manager.xml │ │ ├── activity_lottie_demo.xml │ │ ├── activity_main.xml │ │ ├── activity_view_pager2_demo.xml │ │ └── activity_view_pager_demo.xml │ │ ├── menu │ │ ├── menu_demo.xml │ │ └── menu_dynamic.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── icon_add.png │ │ ├── tab_home_normal.png │ │ ├── tab_home_selected.png │ │ ├── tab_loading.png │ │ ├── tab_me_normal.png │ │ ├── tab_me_selected.png │ │ ├── tab_micro_normal.png │ │ ├── tab_micro_selected.png │ │ ├── tab_video_normal.png │ │ └── tab_video_selected.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── chaychan │ └── bottombarlayout │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── intro_img ├── 4.png ├── display1.gif ├── display2.gif ├── display3.gif ├── download_qr.png ├── float.gif ├── lottie.gif └── transfer_code.jpg ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── chaychan │ │ └── library │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── chaychan │ │ │ └── library │ │ │ ├── BottomBarItem.java │ │ │ ├── BottomBarLayout.java │ │ │ ├── MyLottieAnimationView.java │ │ │ ├── TabData.java │ │ │ └── UIUtils.java │ └── res │ │ ├── drawable │ │ ├── shape_msg.xml │ │ ├── shape_notify_point.xml │ │ └── shape_unread.xml │ │ ├── layout │ │ └── item_bottom_bar.xml │ │ └── values │ │ ├── attr.xml │ │ ├── colors.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── chaychan │ └── library │ └── ExampleUnitTest.java ├── settings.gradle ├── update-note-en.md └── update-note.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | /.idea 11 | -------------------------------------------------------------------------------- /README-2.0.md: -------------------------------------------------------------------------------- 1 | [英文(English)](https://github.com/chaychan/BottomBarLayout/blob/master/README-en-2.0.md) 2 | 3 | ### 支持 4 | 5 | 如果觉得我的项目对你有所帮助的话,帮我点下**Star** 吧,让更多人的人可以看到,谢谢! 6 | 7 | ### 轻量级的底部导航栏 8 |   目前市场上的App,几乎都有底部页签导航栏,所以我们在开发的时候经常需要用到这个,虽然github上有不少已经封装好的底部导航栏的工具,例如bottombar,alphaIndicator(仿微信滑动渐变底部控件)等,但是这些控件由于功能太多,而且也没有给予详细的介绍文档,所以用起来不是特别容易,有时候我们仅仅只是想要一个简简单单的底部导航,但是又不想去自己在布局中搞一个个LinearLayout或者RadioGroup,然后切换页签的时候更换图标,让ViewPager跳转到对应的页面等一系列繁琐的操作,这时候,你可以使用BottomBarLayout,简简单单就可以实现以下效果: 9 | 10 | ### 我的博客 11 | 12 | [http://chaychan.tech](http://chaychan.tech) 13 | 14 | #### 下载体验 15 | 16 | [点击下载体验](https://raw.githubusercontent.com/chaychan/BottomBarLayout/master/apk/demo.apk) 17 | 18 | 扫码下载: 19 | 20 | ![](./intro_img/download_qr.png) 21 | 22 | 23 | #### **导入方式** 24 | 25 | 在项目根目录下的build.gradle中的allprojects{}中,添加jitpack仓库地址,如下: 26 | 27 | allprojects { 28 | repositories { 29 | jcenter() 30 | maven { url 'https://jitpack.io' }//添加jitpack仓库地址 31 | } 32 | } 33 | 34 | 打开app的module中的build.gradle,在dependencies{}中,添加依赖,如下: 35 | 36 | dependencies { 37 | implementation 'com.github.chaychan:BottomBarLayout:2.1.0' //建议使用最新版本 38 | } 39 | 40 | 41 | 最新发布的版本可以查看 42 | 43 | [https://github.com/chaychan/BottomBarLayout/releases](https://github.com/chaychan/BottomBarLayout/releases) 44 | 45 | ![](./intro_img/display1.gif) 46 | 47 | #### 显示未读数、提示小红点、提示消息 48 | 49 | ![](./intro_img/4.png) 50 | 51 | #### 支持lottie 52 | 53 | ![](./intro_img/lottie.gif) 54 | 55 | #### 历史版本更新说明 56 | 57 | [历史更新记录](https://github.com/chaychan/BottomBarLayout/blob/master/update-note.md) 58 | 59 | ### BottomBarLayout的使用 60 | 61 | #### BottomBarItem属性介绍 62 | ``` 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | ``` 113 | #### 布局文件中配置 114 | 115 | 在xml文件中,配置BottomBarLayout,包裹子条目BottomBarItem 116 | ``` 117 | 118 | 125 | 126 | 132 | 133 | 142 | 143 | 155 | 156 | 168 | 169 | 170 | 182 | 183 | 195 | 196 | 197 | 198 | 199 | ``` 200 | 201 | #### java文件中设置 202 | 203 | 找过对应的ViewPager和BottomBarLayout,为ViewPager设置Adapter,然后为BottomBarLayout设置ViewPager 204 | ``` 205 | mVpContent.setAdapter(new MyAdapter(getSupportFragmentManager())); 206 | mBottomBarLayout.setViewPager(mVpContent); 207 | ``` 208 | 这样就实现底部导航栏功能了 209 | 210 | #### 动态添加条目 211 | ``` 212 | for (int i = 0; i < mTitleIds.length; i++) { 213 | //创建item 214 | BottomBarItem item = createBottomBarItem(i); 215 | mBottomBarLayout.addItem(item); //添加条目 216 | 217 | TabFragment homeFragment = createFragment(mTitleIds[i]); 218 | mFragmentList.add(homeFragment); 219 | } 220 | 221 | private BottomBarItem createBottomBarItem(int i) { 222 | BottomBarItem item = new BottomBarItem.Builder(this) 223 | .titleTextSize(8) 224 | .titleNormalColor(R.color.tab_normal_color) 225 | .titleSelectedColor(R.color.tab_selected_color) 226 | // .openTouchBg(false) 227 | // .marginTop(5) 228 | // .itemPadding(5) 229 | // .unreadNumThreshold(99) 230 | // .unreadTextColor(R.color.white) 231 | 232 | //还有很多属性,详情请查看Builder里面的方法 233 | .create(mNormalIconIds[i], mSelectedIconIds[i], getString(mTitleIds[i])); 234 | return item; 235 | } 236 | ``` 237 | #### 移除条目 238 | ``` 239 | mBottomBarLayout.removeItem(0); 240 | ``` 241 | #### 开启滑动效果 242 | 243 | 页签之间的切换默认关闭了滑动效果,如果需要开启可以通过调用BottomBarLayout的setSmoothScroll()方法: 244 | ``` 245 | mBottomBarLayout.setSmoothScroll(true); 246 | ``` 247 | 也可以在布局文件中指定BottomBarLayout的smoothScroll属性为true 248 | 249 | 开启后效果如下: 250 | 251 | ![](./intro_img/display2.gif) 252 | 253 | #### 跳转前拦截 254 | ``` 255 | mBottomBarLayout.setOnPageChangeInterceptor(position -> { 256 | boolean isLogin = false; //模拟没有登录 257 | if (position == TAB_POSITION_ME && !isLogin){ 258 | //no login intercept to other tab or to LoginActivity 259 | Toast.makeText(ViewPager2DemoActivity.this, "Test intercept, Login first please", Toast.LENGTH_SHORT).show(); 260 | return true; 261 | } 262 | return false; 263 | }); 264 | ``` 265 | 266 | #### 设置条目选中的监听 267 | ``` 268 | mBottomBarLayout.setOnItemSelectedListener((bottomBarItem, previousPosition, currentPosition) -> { 269 | //do something 270 | }); 271 | ``` 272 | 273 | #### 设置同个tab重复点击是否回调setOnItemSelectedListener 274 | ``` 275 | app:sameTabClickCallBack="true" //默认为false 276 | ``` 277 | 278 | #### 显示未读数、提示小红点、提示消息 279 | ``` 280 | mBottomBarLayout.setUnread(0,20);//设置第一个页签的未读数为20 281 | mBottomBarLayout.setUnread(1,101);//设置第二个页签的未读数 282 | mBottomBarLayout.showNotify(2);//设置第三个页签显示提示的小红点 283 | mBottomBarLayout.setMsg(3,"NEW");//设置第四个页签显示NEW提示文字 284 | ``` 285 | 当设置的未读数小于或等于0时,消失未读数的小红点将会消失; 286 | 当未读数为1-99时,则显示对应的数字; 287 | 当未读数大于99时,显示99+; 288 | 289 | #### 设置未读数阈值 290 |    未读数的阈值可以指定BottomBarItem的unreadThreshold属性设置,默认该值为99,如设置 app:unreadThreshold="999" , 若未读数超过该值,则显示"999+"。 291 | 292 | #### 隐藏提示小红点、提示消息 293 | ``` 294 | mBottomBarLayout.hideNotify(2);//隐藏第三个页签显示提示的小红点 295 | mBottomBarLayout.hideMsg(3);//隐藏第四个页签显示的提示文字 296 | ``` 297 | 298 | #### 设置未读数字体颜色 299 | ``` 300 | app:unreadTextColor="@color/unreadTextColor" 301 | ``` 302 | #### 设置未读数背景 303 | ``` 304 | app:unreadTextBg="@drawable/shape_unread" 305 | ``` 306 | drawable的编写如下: 307 | ``` 308 | 309 | 310 | 311 | 312 | 313 | 314 | ``` 315 | #### 设置提示文字字体颜色、背景 316 | ``` 317 | app:msgTextColor="@color/msgTextColor" 318 | app:msgTextBg="@drawable/shape_msg" 319 | ``` 320 | #### 设置提示点背景 321 | ``` 322 | app:notifyPointBg="@drawable/shape_notify_point" 323 | ``` 324 | #### 设置lottie文件名 325 | ``` 326 | app:lottieJson="home.json" 327 | ``` 328 | home.json存放在assets目录中,如果要设置lottie的宽高还是使用iconWidth、iconHeight属性 329 | 330 | #### BottomBarItem的介绍 331 |   BottomBarItem继承于LinearLayout,其子View有显示图标的ImageView和展示文字的TextView,分别可以通过getImageView()和getTextView()方法获取到对应的子控件。github上不少底部导航栏的控件都没能获取到对应的子控件,所以在需要对子控件进行操作的时候极不方便,有一些的思路并不是用ImageView和TextView,而是用绘制的,所以也不能获取到对应的显示图标的控件或展示文字的控件,造成无法获取到该控件,无法进行一些业务上的操作,比如类似今日头条的底部的首页,点击首页的页签,会更换成加载中的图标,执行旋转动画,BottomBarLayout可以轻松地做到这个需求。 332 | 333 | 演示效果如下: 334 | 335 | ![](./intro_img/display3.gif) 336 | 337 | 338 | 只需为BottomBarLayout设置页签选中的监听,在回调中进行以下处理: 339 | ``` 340 | mBottomBarLayout.setOnItemSelectedListener(new BottomBarLayout.OnItemSelectedListener() { 341 | @Override 342 | public void onItemSelected(final BottomBarItem bottomBarItem, int previousPosition, final int currentPosition) { 343 | Log.i("MainActivity", "position: " + currentPosition); 344 | if (currentPosition == 0) { 345 | //如果是第一个,即首页 346 | if (previousPosition == currentPosition) { 347 | //如果是在原来位置上点击,更换首页图标并播放旋转动画 348 | if (mRotateAnimation != null && !mRotateAnimation.hasEnded()){ 349 | //如果当前动画正在执行 350 | return; 351 | } 352 | 353 | bottomBarItem.setSelectedIcon(R.mipmap.tab_loading);//更换成加载图标 354 | 355 | //播放旋转动画 356 | if (mRotateAnimation == null) { 357 | mRotateAnimation = new RotateAnimation(0, 360, 358 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 359 | 0.5f); 360 | mRotateAnimation.setDuration(800); 361 | mRotateAnimation.setRepeatCount(-1); 362 | } 363 | ImageView bottomImageView = bottomBarItem.getImageView(); 364 | bottomImageView.setAnimation(mRotateAnimation); 365 | bottomImageView.startAnimation(mRotateAnimation);//播放旋转动画 366 | 367 | //模拟数据刷新完毕 368 | mHandler.postDelayed(new Runnable() { 369 | @Override 370 | public void run() { 371 | boolean tabNotChanged = mBottomBarLayout.getCurrentItem() == currentPosition; //是否还停留在当前页签 372 | bottomBarItem.setSelectedIcon(R.mipmap.tab_home_selected);//更换成首页原来选中图标 373 | cancelTabLoading(bottomBarItem); 374 | } 375 | }, 3000); 376 | return; 377 | } 378 | } 379 | 380 | //如果点击了其他条目 381 | BottomBarItem bottomItem = mBottomBarLayout.getBottomItem(0); 382 | bottomItem.setSelectedIcon(R.mipmap.tab_home_selected);//更换为原来的图标 383 | 384 | cancelTabLoading(bottomItem);//停止旋转动画 385 | } 386 | }); 387 | 388 | 389 | /**停止首页页签的旋转动画*/ 390 | private void cancelTabLoading(BottomBarItem bottomItem) { 391 | Animation animation = bottomItem.getImageView().getAnimation(); 392 | if (animation != null){ 393 | animation.cancel(); 394 | } 395 | } 396 | ``` 397 | #### 实现思路: 398 | 399 | 1.当点击页签加载的时候,BottomBarItem通过调用setIconSelectedResourceId()设置成选中状态下的图标资源id为加载中图标的资源id,完成图标的更换操作; 400 | 401 | 2.通过BottomBarItem获取到对应页签的ImageView,对其设置旋转动画,执行旋转动画,当点击其他页签或者数据加载完成后,更换回原来的选中图标,停止旋转动画。 402 | 403 | 好了,到这里BottomBarLayout的介绍就到此为止了,之所以封装这个控件主要是为了方便开发,希望可以帮助到更多人,如果大家有什么想法或者意见不妨向我提出,我会不断完善BottomBarLayout的。 404 | 405 | 406 | #### 支持和鼓励 407 | 408 | 如果觉得我的项目对你有所帮助的话,不妨打赏一下吧!这样我会更加有动力去完善好这个项目: 409 | 410 | 微信赞赏: 411 | 412 | ![](./intro_img/transfer_code.jpg) 413 | 414 | 415 | -------------------------------------------------------------------------------- /README-en-2.0.md: -------------------------------------------------------------------------------- 1 | [中文(Chinese)](https://github.com/chaychan/BottomBarLayout) 2 | 3 | ### Support 4 | 5 | If you feel that my project is helpful to you, please help me to click on the **star** and let more people see it. Thank you! 6 | 7 | ### Introduction 8 |   Currently, App on the market almost has a navigation bar at the bottom, so we often need to use this during development. Although there are many tools on the github packaged bottom navigation bar, such as bottombar, alphaIndicator Swipe gradient bottom controls etc., but these controls are not particularly easy to use due to too many functions and no detailed documentation. Sometimes we just want a simple bottom navigation, but we don't want to go One by one in the layout of the LinearLayout or RadioGroup, and then change the tab icon, let ViewPager jump to the corresponding page and a series of tedious operations, this time, you can use BottomBarLayout, simply can achieve the following effect: 9 | 10 | #### Apk 11 | 12 | [click to download](https://raw.githubusercontent.com/chaychan/BottomBarLayout/master/apk/demo.apk) 13 | 14 | or scan the QR code 15 | 16 | ![](./intro_img/download_qr.png) 17 | 18 | 19 | #### **How to import** 20 | 21 | Add the jitpack repository address in allprojects{} in build.gradle in the project root directory, as follows: 22 | ``` 23 | allprojects { 24 | repositories { 25 | jcenter() 26 | maven { url 'https://jitpack.io' }//Add jitpack warehouse address 27 | } 28 | } 29 | ``` 30 | Open the build.gradle in the app's module, add dependencies in dependencies {} as follows: 31 | ``` 32 | dependencies { 33 | compile 'com.github.chaychan:BottomBarLayout:2.1.0' //It is recommended to use the latest version 34 | } 35 | ``` 36 | 37 | The latest version can be viewed 38 | 39 | [https://github.com/chaychan/BottomBarLayout/releases](https://github.com/chaychan/BottomBarLayout/releases) 40 | 41 | ### Demo 42 | 43 | ![](./intro_img/display1.gif) 44 | 45 | #### Display unread, show red dot, display message 46 | 47 | ![](./intro_img/4.png) 48 | 49 | #### Support to use lottie 50 | 51 | ![](./intro_img/lottie.gif) 52 | 53 | #### Historical version update notes 54 | 55 | [Historical version update notes](https://github.com/chaychan/BottomBarLayout/blob/master/update-note-en.md) 56 | 57 | ### Usage 58 | 59 | #### Attribute introduction 60 | ``` 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | ``` 117 | #### Configuration in the layout file 118 | 119 | In the xml file, configure BottomBarLayout to wrap the sub-item BottomBarItem 120 | ``` 121 | 122 | 129 | 130 | 136 | 137 | 146 | 147 | 159 | 160 | 172 | 173 | 174 | 186 | 187 | 199 | 200 | 201 | 202 | 203 | 204 | ``` 205 | #### Java file settings 206 | 207 | Find the corresponding ViewPager and BottomBarLayout, set Adapter for ViewPager, and then set the ViewPager for BottomBarLayout 208 | ``` 209 | mVpContent.setAdapter(new MyAdapter(getSupportFragmentManager())); 210 | mBottomBarLayout.setViewPager(mVpContent); 211 | ``` 212 | This realizes the bottom navigation bar function 213 | 214 | #### Add item dynamically 215 | ``` 216 | for (int i = 0; i < mTitleIds.length; i++) { 217 | //create item 218 | BottomBarItem item = createBottomBarItem(i); 219 | mBottomBarLayout.addItem(item); //addItem 220 | 221 | TabFragment homeFragment = createFragment(mTitleIds[i]); 222 | mFragmentList.add(homeFragment); 223 | } 224 | 225 | 226 | private BottomBarItem createBottomBarItem(int i) { 227 | BottomBarItem item = new BottomBarItem.Builder(this) 228 | .titleTextSize(8) 229 | .titleNormalColor(R.color.tab_normal_color) 230 | .titleSelectedColor(R.color.tab_selected_color) 231 | // .openTouchBg(false) 232 | // .marginTop(5) 233 | // .itemPadding(5) 234 | // .unreadNumThreshold(99) 235 | // .unreadTextColor(R.color.white) 236 | 237 | //There are still many properties, please see the methods in the Builder for details. 238 | .create(mNormalIconIds[i], mSelectedIconIds[i], getString(mTitleIds[i])); 239 | return item; 240 | } 241 | ``` 242 | #### Remove item 243 | ``` 244 | mBottomBarLayout.removeItem(0); 245 | ``` 246 | #### Turn on the slide effect 247 | 248 | Tab switch between the closure of the default sliding effect, if you need to open the setSmoothScroll () method can be called by calling BottomBarLayout: 249 | ``` 250 | mBottomBarLayout.setSmoothScroll(true); 251 | ``` 252 | You can also specify BottomBarLayout's smoothScroll property to be true in the layout file. 253 | 254 | The effect after opening is as follows: 255 | 256 | ![](./intro_img/display2.gif) 257 | 258 | #### Intercept before jump 259 | ``` 260 | mBottomBarLayout.setOnPageChangeInterceptor(position -> { 261 | boolean isLogin = false; //Simulate no login 262 | if (position == TAB_POSITION_ME && !isLogin){ 263 | //no login intercept to other tab or to LoginActivity 264 | Toast.makeText(ViewPager2DemoActivity.this, "Test intercept, Login first please", Toast.LENGTH_SHORT).show(); 265 | return true; 266 | } 267 | return false; 268 | }); 269 | ``` 270 | 271 | #### Set the item selected listener 272 | ``` 273 | mBottomBarLayout.setOnItemSelectedListener((bottomBarItem, previousPosition, currentPosition) -> { 274 | //do something 275 | }); 276 | ``` 277 | #### Display unread, show red dot, display message 278 | ``` 279 | mBottomBarLayout.setUnread(0,20);//Set the first tab's unread 20 280 | mBottomBarLayout.setUnread(1,101);//Set the first tab's unread 101 281 | mBottomBarLayout.showNotify(2);//The third page shows the tips of the little red dot 282 | mBottomBarLayout.setMsg(3,"NEW");//The fourth tab shows the NEW text 283 | ``` 284 | When the setting of unread less than or equal to 0, the disappearance of small red dot will disappear; 285 | When the unread count is 1-99, the corresponding number is displayed. 286 | When unread more than 99, it shows 99+; 287 | 288 | #### Set unaware reading threshold 289 |    The unread threshold may specify the BottomBarItem's unreadThreshold property setting, which defaults to 99. For example, if app:unreadThreshold="999" is set, if the reading does not exceed this value, "999+" is displayed。 290 | 291 | #### Hidden tips red dot, tips message 292 | ``` 293 | mBottomBarLayout.hideNotify(2);//Hide the third page shows the tips of the little red dot 294 | mBottomBarLayout.hideMsg(3);//Hide the text displayed on the fourth tab 295 | ``` 296 | #### Set unread font color 297 | ``` 298 | app:unreadTextColor="@color/unreadTextColor" 299 | ``` 300 | #### Set the unread background 301 | ``` 302 | app:unreadTextBg="@drawable/shape_unread" 303 | ``` 304 | Drawable is written as follows: 305 | ``` 306 | 307 | 308 | 309 | 310 | 311 | 312 | ``` 313 | #### Set prompt text font color, background 314 | ``` 315 | app:msgTextColor="@color/msgTextColor" 316 | app:msgTextBg="@drawable/shape_msg" 317 | ``` 318 | #### Set prompt point background 319 | ``` 320 | app:notifyPointBg="@drawable/shape_notify_point" 321 | ``` 322 | #### Set the name of lottie file 323 | ``` 324 | app:lottieJson="home.json" 325 | ``` 326 | "home.json" is stored in the assets directory. To set Lottie's width and height, use the iconWidth and iconHeight attributes 327 | 328 | 329 | Well, here's the introduction of BottomBarLayout stop here, the reason for the package this control is mainly for the convenience of development, hope to help more people, if you have any ideas or comments may wish to put forward to me, I will continue to improve BottomBarLayout of. 330 | 331 | 332 | #### Support and encouragement 333 | 334 | If you think my project is helpful to you, star! So I will be more motivated to improve this project. 335 | 336 | 337 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | [中文(Chinese)](https://github.com/chaychan/BottomBarLayout) 2 | 3 | ### Support 4 | 5 | If you feel that my project is helpful to you, please help me to click on the **star** and let more people see it. Thank you! 6 | 7 | ### Version 3.0 8 | 9 | The new version 3.0 has refactored the project, and the usage is very different. If you used version 2.0 before, please refer to [BottomBarLayout-2.1.0](https://github.com/chaychan/BottomBarLayout/blob/master/README-en-2.0.md) for detailed usage if you want to continue to update and maintain it. The old version will no longer be maintained. If necessary, please download the source code and modify it. The branch name is feature-2.1.0 10 | 11 | ### Introduction 12 | Currently, App on the market almost has a navigation bar at the bottom, so we often need to use this during development. Although there are many tools on the github packaged bottom navigation bar, such as bottombar, alphaIndicator Swipe gradient bottom controls etc., but these controls are not particularly easy to use due to too many functions and no detailed documentation. Sometimes we just want a simple bottom navigation, but we don't want to go One by one in the layout of the LinearLayout or RadioGroup, and then change the tab icon, let ViewPager jump to the corresponding page and a series of tedious operations, this time, you can use BottomBarLayout, simply can achieve the following effect: 13 | 14 | #### Apk 15 | 16 | [click to download](https://raw.githubusercontent.com/chaychan/BottomBarLayout/master/apk/demo.apk) 17 | 18 | or scan the QR code 19 | 20 | ![](./intro_img/download_qr.png) 21 | 22 | 23 | #### **How to import** 24 | 25 | Add the jitpack repository address in allprojects{} in build.gradle in the project root directory, as follows: 26 | ``` 27 | allprojects { 28 | repositories { 29 | jcenter() 30 | maven { url 'https://jitpack.io' }//Add jitpack 31 | } 32 | } 33 | ``` 34 | Open the build.gradle in the app's module, add dependencies in dependencies {} as follows: 35 | ``` 36 | dependencies { 37 | implementation 'com.github.chaychan:BottomBarLayout:3.0.0' //It is recommended to use the latest version 38 | } 39 | ``` 40 | 41 | The latest version can be viewed 42 | 43 | [https://github.com/chaychan/BottomBarLayout/releases](https://github.com/chaychan/BottomBarLayout/releases) 44 | 45 | ### Demo 46 | 47 | #### Supports raising the middle icon and intercepting before clicking 48 | ![](./intro_img/float.gif) 49 | 50 | #### Display unread, show red dot, display message 51 | 52 | ![](./intro_img/4.png) 53 | 54 | #### Support to use lottie 55 | 56 | ![](./intro_img/lottie.gif) 57 | 58 | #### Historical version update notes 59 | 60 | [Historical version update notes](https://github.com/chaychan/BottomBarLayout/blob/master/update-note-en.md) 61 | 62 | ### Usage 63 | 64 | #### Attribute introduction 65 | ``` 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | ``` 128 | 129 | #### Configuration in the layout file 130 | 131 | In the xml file, configure BottomBarLayout to wrap the sub-item BottomBarItem 132 | 133 | ``` 134 | 135 | 141 | 142 | 147 | 148 | 162 | 163 | 164 | ``` 165 | 166 | #### Java file settings 167 | 168 | Find the corresponding ViewPager2 and BottomBarLayout, set Adapter for ViewPager2, and then set the ViewPager2 for BottomBarLayout 169 | ``` 170 | protected List getTabData() { 171 | List tabData = new ArrayList<>(); 172 | tabData.add(new TabData("首页", R.mipmap.tab_home_normal, R.mipmap.tab_home_selected)); 173 | tabData.add(new TabData("视频", R.mipmap.tab_video_normal, R.mipmap.tab_video_selected)); 174 | tabData.add(new TabData("微头条", R.mipmap.tab_micro_normal, R.mipmap.tab_micro_selected)); 175 | tabData.add(new TabData("我的", R.mipmap.tab_me_normal, R.mipmap.tab_me_selected)); 176 | 177 | //If it is lottie. The lottie file storage location: /src/main/assets 178 | //tabData.add(new TabData("首页", "home.json")); 179 | //tabData.add(new TabData("分类", "category.json")); 180 | //tabData.add(new TabData("购物车", "cart.json")); 181 | //tabData.add(new TabData("我的", "mine.json")); 182 | 183 | return tabData; 184 | } 185 | 186 | ... 187 | mBottomBarLayout.setData(tabData); //Setting up the data source 188 | mVpContent.setAdapter(new MyAdapter(getSupportFragmentManager())); 189 | mBottomBarLayout.setViewPager2(mVpContent); 190 | ``` 191 | 192 | This realizes the bottom navigation bar function 193 | 194 | #### Set the middle icon to be raised 195 | ``` 196 | 197 | 202 | 203 | 208 | 209 | 236 | 237 | 238 | ``` 239 | 240 | 241 | #### Add item dynamically 242 | 243 | ``` 244 | for (int i = 0; i < mTitleIds.length; i++) { 245 | //create item 246 | BottomBarItem item = createBottomBarItem(i); 247 | mBottomBarLayout.addItem(item); //addItem 248 | 249 | TabFragment homeFragment = createFragment(mTitleIds[i]); 250 | mFragmentList.add(homeFragment); 251 | } 252 | 253 | 254 | private BottomBarItem createBottomBarItem(int i) { 255 | BottomBarItem item = new BottomBarItem.Builder(this) 256 | .titleTextSize(8) 257 | .titleNormalColor(R.color.tab_normal_color) 258 | .titleSelectedColor(R.color.tab_selected_color) 259 | // .marginTop(5) 260 | // .itemPadding(5) 261 | // .unreadNumThreshold(99) 262 | // .unreadTextColor(R.color.white) 263 | 264 | //There are still many properties, please see the methods in the Builder for details. 265 | .create(mNormalIconIds[i], mSelectedIconIds[i], getString(mTitleIds[i])); 266 | return item; 267 | } 268 | ``` 269 | 270 | #### Remove item 271 | ``` 272 | mBottomBarLayout.removeItem(0); 273 | ``` 274 | #### Turn on the slide effect 275 | 276 | Tab switch between the closure of the default sliding effect, if you need to open the setSmoothScroll () method can be called by calling BottomBarLayout: 277 | ``` 278 | mBottomBarLayout.setSmoothScroll(true); 279 | ``` 280 | You can also specify BottomBarLayout's smoothScroll property to be true in the layout file. 281 | 282 | The effect after opening is as follows: 283 | 284 | ![](./intro_img/display2.gif) 285 | 286 | #### Intercept before jump 287 | ``` 288 | mBottomBarLayout.setOnPageChangeInterceptor(position -> { 289 | boolean isLogin = false; //Simulate no login 290 | if (position == TAB_POSITION_ME && !isLogin){ 291 | //no login intercept to other tab or to LoginActivity 292 | Toast.makeText(ViewPager2DemoActivity.this, "Test intercept, Login first please", Toast.LENGTH_SHORT).show(); 293 | return true; 294 | } 295 | return false; 296 | }); 297 | ``` 298 | 299 | #### Set the item selected listener 300 | ``` 301 | mBottomBarLayout.setOnItemSelectedListener((bottomBarItem, previousPosition, currentPosition) -> { 302 | //do something 303 | }); 304 | ``` 305 | 306 | #### Set whether to call back setOnItemSelectedListener when clicking the same tab repeatedly 307 | ``` 308 | app:sameTabClickCallBack="true" //Defaults to false 309 | ``` 310 | 311 | #### Display unread, show red dot, display message 312 | ``` 313 | mBottomBarLayout.setUnread(0,20);//Set the first tab's unread 20 314 | mBottomBarLayout.setUnread(1,101);//Set the first tab's unread 101 315 | mBottomBarLayout.showNotify(2);//The third page shows the tips of the little red dot 316 | mBottomBarLayout.setMsg(3,"NEW");//The fourth tab shows the NEW text 317 | ``` 318 | When the setting of unread less than or equal to 0, the disappearance of small red dot will disappear; 319 | When the unread count is 1-99, the corresponding number is displayed. 320 | When unread more than 99, it shows 99+; 321 | 322 | #### Set unaware reading threshold 323 |    The unread threshold may specify the BottomBarItem's unreadThreshold property setting, which defaults to 99. For example, if app:unreadThreshold="999" is set, if the reading does not exceed this value, "999+" is displayed。 324 | 325 | #### Hidden tips red dot, tips message 326 | ``` 327 | mBottomBarLayout.hideNotify(2);//Hide the third page shows the tips of the little red dot 328 | mBottomBarLayout.hideMsg(3);//Hide the text displayed on the fourth tab 329 | ``` 330 | #### Set unread font color 331 | ``` 332 | app:unreadTextColor="@color/unreadTextColor" 333 | ``` 334 | #### Set the unread background 335 | ``` 336 | app:unreadTextBg="@drawable/shape_unread" 337 | ``` 338 | Drawable is written as follows: 339 | ``` 340 | 341 | 342 | 343 | 344 | 345 | 346 | ``` 347 | #### Set prompt text font color, background 348 | ``` 349 | app:msgTextColor="@color/msgTextColor" 350 | app:msgTextBg="@drawable/shape_msg" 351 | ``` 352 | #### Set prompt point background 353 | ``` 354 | app:notifyPointBg="@drawable/shape_notify_point" 355 | ``` 356 | 357 | 358 | Well, here's the introduction of BottomBarLayout stop here, the reason for the package this control is mainly for the convenience of development, hope to help more people, if you have any ideas or comments may wish to put forward to me, I will continue to improve BottomBarLayout of. 359 | 360 | 361 | #### Support and encouragement 362 | 363 | If you think my project is helpful to you, star! So I will be more motivated to improve this project. 364 | 365 | 366 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [英文(English)](https://github.com/chaychan/BottomBarLayout/blob/master/README-en.md) 2 | 3 | ### 支持 4 | 5 | 如果觉得我的项目对你有所帮助的话,帮我点下**Star** 吧,让更多人的人可以看到,谢谢! 6 | 7 | ### 旧版本 8 | 9 | 新版本3.0对项目进行了重构,用法有很大不同,如果你之前使用的是2.0版本,想继续更新维护详细用法请参考 10 | [BottomBarLayout-2.1.0](https://github.com/chaychan/BottomBarLayout/blob/master/README-2.0.md), 旧版本的实现后续将不再维护,若有需要请下载源码修改,分支名feature-2.1.0 11 | 12 | ### 2024-07-17 13 | 14 | 距离上次更新接近4年,看到依旧有不少小伙伴提issue,很抱歉没有及时回复,趁着这段时间空闲,针对issue进行了修复,如: 15 | 16 | - [#57](https://github.com/chaychan/BottomBarLayout/issues/57) —— 支持ViewPager2 17 | - [#56](https://github.com/chaychan/BottomBarLayout/issues/56) —— 针对某些场景添加跳转前拦截 18 | 19 | 以及一些小问题的修复,发布了2.1.0版本,如果项目之前集成了这个库,可以更新下这个版本 20 | 21 | 同时想着让大家可以更方便地使用这个库,对该库进行了重构,发布3.0版本,看有不少人提了中间凸起图标的需求, 22 | 特意支持中间图标凸起,详情请看文档介绍。 23 | 24 | ### 轻量级的底部导航栏 25 |   目前市场上的App,几乎都有底部页签导航栏,所以我们在开发的时候经常需要用到这个,虽然github上有不少已经封装好的底部导航栏的工具,例如bottombar,alphaIndicator(仿微信滑动渐变底部控件)等,但是这些控件由于功能太多,而且也没有给予详细的介绍文档,所以用起来不是特别容易,有时候我们仅仅只是想要一个简简单单的底部导航,但是又不想去自己在布局中搞一个个LinearLayout或者RadioGroup,然后切换页签的时候更换图标,让ViewPager跳转到对应的页面等一系列繁琐的操作,这时候,你可以使用BottomBarLayout,简简单单就可以实现以下效果: 26 | 27 | ### 我的博客 28 | 29 | [http://chaychan.tech](http://chaychan.tech) 30 | 31 | 32 | #### 下载体验 33 | 34 | [点击下载体验](https://raw.githubusercontent.com/chaychan/BottomBarLayout/master/apk/demo.apk) 35 | 36 | 扫码下载: 37 | 38 | ![](./intro_img/download_qr.png) 39 | 40 | 41 | #### **导入方式** 42 | 43 | 在项目根目录下的build.gradle中的allprojects{}中,添加jitpack仓库地址,如下: 44 | 45 | ``` 46 | allprojects { 47 | repositories { 48 | jcenter() 49 | maven { url 'https://jitpack.io' }//添加jitpack仓库地址 50 | } 51 | } 52 | 53 | ``` 54 | 55 | 打开app的module中的build.gradle,在dependencies{}中,添加依赖,如下: 56 | 57 | ``` 58 | dependencies { 59 | implementation 'com.github.chaychan:BottomBarLayout:3.0.0' //建议使用最新版本 60 | } 61 | 62 | ``` 63 | 64 | 最新发布的版本可以查看 65 | 66 | [https://github.com/chaychan/BottomBarLayout/releases](https://github.com/chaychan/BottomBarLayout/releases) 67 | 68 | #### 支持中间图标凸起,点击跳转前拦截 69 | ![](./intro_img/float.gif) 70 | 71 | #### 显示未读数、提示小红点、提示消息 72 | 73 | ![](./intro_img/4.png) 74 | 75 | #### 支持lottie 76 | 77 | ![](./intro_img/lottie.gif) 78 | 79 | #### 历史版本更新说明 80 | 81 | [历史更新记录](https://github.com/chaychan/BottomBarLayout/blob/master/update-note.md) 82 | 83 | ### BottomBarLayout的使用 84 | ``` 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | ``` 147 | #### 布局文件中配置 148 | 149 | 在xml文件中,配置BottomBarLayout 150 | 151 | ``` 152 | 153 | 159 | 160 | 165 | 166 | 180 | 181 | 182 | ``` 183 | 184 | 185 | #### java文件中设置 186 | 187 | 找过对应的ViewPager和BottomBarLayout,为ViewPager设置Adapter,然后为BottomBarLayout设置ViewPager 188 | 189 | ``` 190 | protected List getTabData() { 191 | List tabData = new ArrayList<>(); 192 | tabData.add(new TabData("首页", R.mipmap.tab_home_normal, R.mipmap.tab_home_selected)); 193 | tabData.add(new TabData("视频", R.mipmap.tab_video_normal, R.mipmap.tab_video_selected)); 194 | tabData.add(new TabData("微头条", R.mipmap.tab_micro_normal, R.mipmap.tab_micro_selected)); 195 | tabData.add(new TabData("我的", R.mipmap.tab_me_normal, R.mipmap.tab_me_selected)); 196 | 197 | //如果是lottie lottie文件存放位置 /src/main/assets 198 | //tabData.add(new TabData("首页", "home.json")); 199 | //tabData.add(new TabData("分类", "category.json")); 200 | //tabData.add(new TabData("购物车", "cart.json")); 201 | //tabData.add(new TabData("我的", "mine.json")); 202 | 203 | return tabData; 204 | } 205 | 206 | ... 207 | mBottomBarLayout.setData(tabData); //设置数据源 208 | //和ViewPager2联动 209 | mVpContent.setAdapter(new MyAdapter(getSupportFragmentManager())); 210 | mBottomBarLayout.setViewPager2(mVpContent); 211 | ``` 212 | 213 | 这样就实现底部导航栏功能了 214 | 215 | #### 设置中间图标凸起 216 | ``` 217 | 218 | 223 | 224 | 229 | 230 | 257 | 258 | 259 | ``` 260 | 261 | #### 动态添加条目 262 | 263 | ``` 264 | for (int i = 0; i < mTitleIds.length; i++) { 265 | //创建item 266 | BottomBarItem item = createBottomBarItem(i); 267 | mBottomBarLayout.addItem(item); //添加条目 268 | 269 | TabFragment homeFragment = createFragment(mTitleIds[i]); 270 | mFragmentList.add(homeFragment); 271 | } 272 | 273 | private BottomBarItem createBottomBarItem(int i) { 274 | BottomBarItem item = new BottomBarItem.Builder(this) 275 | .titleTextSize(8) 276 | .titleNormalColor(R.color.tab_normal_color) 277 | .titleSelectedColor(R.color.tab_selected_color) 278 | // .marginTop(5) 279 | // .itemPadding(5) 280 | // .unreadNumThreshold(99) 281 | // .unreadTextColor(R.color.white) 282 | 283 | //还有很多属性,详情请查看Builder里面的方法 284 | .create(mNormalIconIds[i], mSelectedIconIds[i], getString(mTitleIds[i])); 285 | return item; 286 | } 287 | ``` 288 | 289 | #### 移除条目 290 | 291 | ``` 292 | mBottomBarLayout.removeItem(0); 293 | ``` 294 | 295 | 296 | #### 开启滑动效果 297 | 298 | 页签之间的切换默认关闭了滑动效果,如果需要开启可以通过调用BottomBarLayout的setSmoothScroll()方法: 299 | ``` 300 | mBottomBarLayout.setSmoothScroll(true); 301 | ``` 302 | 303 | 304 | 也可以在布局文件中指定BottomBarLayout的smoothScroll属性为true 305 | 306 | 开启后效果如下: 307 | 308 | ![](./intro_img/display2.gif) 309 | 310 | #### 跳转前拦截 311 | ``` 312 | mBottomBarLayout.setOnPageChangeInterceptor(position -> { 313 | if(position == TAB_POSITION_ADD){ 314 | //中间凸起图标的位置 315 | Toast.makeText(ViewPager2DemoActivity.this, "可以跳转别的页面,比如发布页", Toast.LENGTH_SHORT).show(); 316 | return true; //是否拦截 true拦截不进行跳转 false不拦截 317 | } 318 | boolean isLogin = false; //模拟没有登录 319 | if (position == TAB_POSITION_ME && !isLogin){ 320 | //no login intercept to other tab or to LoginActivity 321 | Toast.makeText(ViewPager2DemoActivity.this, "Test intercept, Login first please", Toast.LENGTH_SHORT).show(); 322 | return true; 323 | } 324 | return false; 325 | }); 326 | ``` 327 | 328 | #### 设置条目选中的监听 329 | ``` 330 | mBottomBarLayout.setOnItemSelectedListener((bottomBarItem, previousPosition, currentPosition) -> { 331 | //do something 332 | }); 333 | ``` 334 | 335 | #### 设置同个tab重复点击是否回调setOnItemSelectedListener 336 | ``` 337 | app:sameTabClickCallBack="true" //默认为false 338 | ``` 339 | 340 | 341 | #### 显示未读数、提示小红点、提示消息 342 | 343 | ``` 344 | mBottomBarLayout.setUnread(0,20);//设置第一个页签的未读数为20 345 | mBottomBarLayout.setUnread(1,101);//设置第二个页签的未读数 346 | mBottomBarLayout.showNotify(2);//设置第三个页签显示提示的小红点 347 | mBottomBarLayout.setMsg(3,"NEW");//设置第四个页签显示NEW提示文字 348 | ``` 349 | 350 | 351 | 当设置的未读数小于或等于0时,消失未读数的小红点将会消失; 352 | 当未读数为1-99时,则显示对应的数字; 353 | 当未读数大于99时,显示99+; 354 | 355 | #### 设置未读数阈值 356 |    未读数的阈值可以指定BottomBarItem的unreadThreshold属性设置,默认该值为99,如设置 app:unreadThreshold="999" , 若未读数超过该值,则显示"999+"。 357 | 358 | #### 隐藏提示小红点、提示消息 359 | 360 | ``` 361 | mBottomBarLayout.hideNotify(2);//隐藏第三个页签显示提示的小红点 362 | mBottomBarLayout.hideMsg(3);//隐藏第四个页签显示的提示文字 363 | ``` 364 | 365 | 366 | #### 设置未读数字体颜色 367 | 368 | ``` 369 | app:unreadTextColor="@color/unreadTextColor" 370 | ``` 371 | 372 | 373 | #### 设置未读数背景 374 | 375 | ``` 376 | app:unreadTextBg="@drawable/shape_unread" 377 | ``` 378 | 379 | 380 | drawable的编写如下: 381 | 382 | ``` 383 | 384 | 385 | 386 | 387 | 388 | 389 | ``` 390 | 391 | 392 | 393 | #### 设置提示文字字体颜色、背景 394 | ``` 395 | app:msgTextColor="@color/msgTextColor" 396 | app:msgTextBg="@drawable/shape_msg" 397 | ``` 398 | 399 | #### 设置提示点背景 400 | ``` 401 | app:notifyPointBg="@drawable/shape_notify_point" 402 | ``` 403 | 404 | 405 | #### BottomBarItem的介绍 406 |   BottomBarItem继承于LinearLayout,其子View有显示图标的ImageView和展示文字的TextView,分别可以通过getImageView()和getTextView()方法获取到对应的子控件。github上不少底部导航栏的控件都没能获取到对应的子控件,所以在需要对子控件进行操作的时候极不方便,有一些的思路并不是用ImageView和TextView,而是用绘制的,所以也不能获取到对应的显示图标的控件或展示文字的控件,造成无法获取到该控件,无法进行一些业务上的操作,比如类似今日头条的底部的首页,点击首页的页签,会更换成加载中的图标,执行旋转动画,BottomBarLayout可以轻松地做到这个需求。 407 | 408 | 演示效果如下: 409 | 410 | ![](./intro_img/display3.gif) 411 | 412 | 413 | 只需为BottomBarLayout设置页签选中的监听,在回调中进行以下处理: 414 | 415 | mBottomBarLayout.setOnItemSelectedListener(new BottomBarLayout.OnItemSelectedListener() { 416 | @Override 417 | public void onItemSelected(final BottomBarItem bottomBarItem, int previousPosition, final int currentPosition) { 418 | Log.i("MainActivity", "position: " + currentPosition); 419 | if (currentPosition == 0) { 420 | //如果是第一个,即首页 421 | if (previousPosition == currentPosition) { 422 | //如果是在原来位置上点击,更换首页图标并播放旋转动画 423 | if (mRotateAnimation != null && !mRotateAnimation.hasEnded()){ 424 | //如果当前动画正在执行 425 | return; 426 | } 427 | 428 | bottomBarItem.setSelectedIcon(R.mipmap.tab_loading);//更换成加载图标 429 | 430 | //播放旋转动画 431 | if (mRotateAnimation == null) { 432 | mRotateAnimation = new RotateAnimation(0, 360, 433 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 434 | 0.5f); 435 | mRotateAnimation.setDuration(800); 436 | mRotateAnimation.setRepeatCount(-1); 437 | } 438 | ImageView bottomImageView = bottomBarItem.getImageView(); 439 | bottomImageView.setAnimation(mRotateAnimation); 440 | bottomImageView.startAnimation(mRotateAnimation);//播放旋转动画 441 | 442 | //模拟数据刷新完毕 443 | mHandler.postDelayed(new Runnable() { 444 | @Override 445 | public void run() { 446 | boolean tabNotChanged = mBottomBarLayout.getCurrentItem() == currentPosition; //是否还停留在当前页签 447 | bottomBarItem.setSelectedIcon(R.mipmap.tab_home_selected);//更换成首页原来选中图标 448 | cancelTabLoading(bottomBarItem); 449 | } 450 | }, 3000); 451 | return; 452 | } 453 | } 454 | 455 | //如果点击了其他条目 456 | BottomBarItem bottomItem = mBottomBarLayout.getBottomItem(0); 457 | bottomItem.setSelectedIcon(R.mipmap.tab_home_selected);//更换为原来的图标 458 | 459 | cancelTabLoading(bottomItem);//停止旋转动画 460 | } 461 | }); 462 | 463 | 464 | /**停止首页页签的旋转动画*/ 465 | private void cancelTabLoading(BottomBarItem bottomItem) { 466 | Animation animation = bottomItem.getImageView().getAnimation(); 467 | if (animation != null){ 468 | animation.cancel(); 469 | } 470 | } 471 | 472 | #### 实现思路: 473 | 474 | 1.当点击页签加载的时候,BottomBarItem通过调用setIconSelectedResourceId()设置成选中状态下的图标资源id为加载中图标的资源id,完成图标的更换操作; 475 | 476 | 2.通过BottomBarItem获取到对应页签的ImageView,对其设置旋转动画,执行旋转动画,当点击其他页签或者数据加载完成后,更换回原来的选中图标,停止旋转动画。 477 | 478 | 好了,到这里BottomBarLayout的介绍就到此为止了,之所以封装这个控件主要是为了方便开发,希望可以帮助到更多人,如果大家有什么想法或者意见不妨向我提出,我会不断完善BottomBarLayout的。 479 | 480 | 481 | #### 支持和鼓励 482 | 483 | 如果觉得我的项目对你有所帮助的话,不妨打赏一下吧!这样我会更加有动力去完善好这个项目: 484 | 485 | 微信赞赏: 486 | 487 | ![](./intro_img/transfer_code.jpg) 488 | 489 | 490 | -------------------------------------------------------------------------------- /apk/demo.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/apk/demo.apk -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.compile_sdk_version = 28 5 | ext.build_tools_version = "28.0.3" 6 | 7 | ext.min_sdk_version = 16 8 | ext.target_sdk_version = 28 9 | 10 | repositories { 11 | jcenter() 12 | google() 13 | } 14 | dependencies { 15 | classpath 'com.android.tools.build:gradle:4.2.2' 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | jcenter() 25 | google() 26 | maven { url 'https://jitpack.io' } 27 | } 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion compile_sdk_version 5 | buildToolsVersion build_tools_version 6 | defaultConfig { 7 | applicationId "com.chaychan.bottombarlayout" 8 | minSdkVersion min_sdk_version 9 | targetSdkVersion target_sdk_version 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | signingConfigs { 15 | appSign { 16 | keyAlias KEY_ALIAS 17 | keyPassword KEY_PASSWORD 18 | storeFile file(KEY_FILE_PATH) 19 | storePassword KEY_STORE_PASSWORD 20 | } 21 | } 22 | 23 | lintOptions { 24 | checkReleaseBuilds false 25 | abortOnError false 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 32 | signingConfig signingConfigs.appSign 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation fileTree(include: ['*.jar'], dir: 'libs') 39 | implementation project(':library') 40 | implementation "androidx.appcompat:appcompat:1.2.0" 41 | implementation "androidx.recyclerview:recyclerview:1.1.0" 42 | implementation "androidx.viewpager2:viewpager2:1.0.0" 43 | } 44 | -------------------------------------------------------------------------------- /demo/demo.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/demo.jks -------------------------------------------------------------------------------- /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/androidTest/java/com/chaychan/bottombarlayout/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.chaychan.bottombarlayout", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demo/src/main/assets/cart.json: -------------------------------------------------------------------------------- 1 | {"v":"5.1.16","fr":30,"ip":0,"op":38,"w":48,"h":48,"nm":"icon/Tabbar_静态_01/购物车_48_Sel","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Merged Shape Layer","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[23.11,26.25,0],"ix":2},"a":{"a":0,"k":[23.11,26.25,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[5,5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形备份","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34.5,40.722],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形备份","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[5,5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[12.5,40.722],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":1,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,-2.21],[0.1,-0.35],[0,0],[1.79,0],[0,0]],"o":[[0,0],[2.21,0],[0,0.37],[0,0],[-0.48,1.73],[0,0],[0,0]],"v":[[-14.86,-8],[10.86,-8],[14.86,-4],[14.71,-2.92],[12.46,5.08],[8.61,8],[-14.86,8]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[24.86,18.222],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":1,"cix":2,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,-1.06],[0,0],[-2.21,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0.75,0.75],[0,0],[0,2.21],[0,0],[0,0],[0,0]],"v":[[-10.25,-13.75],[-13.75,-13.75],[-11.42,-11.42],[-10.25,-8.59],[-10.25,9.75],[-6.25,13.75],[13.75,13.75],[13.75,13.75]],"c":false},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径 2","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[20.25,20.472],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径 2","np":1,"cix":2,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-24,-24],[24,-24],[24,24],[-24,24]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[24,24],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":1,"cix":2,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":4,"st":-32,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"路径 3备份","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.609,-1.375,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5.5,0],[5.5,0]],"c":false},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径 3备份","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":17,"s":[0],"e":[100]},{"t":21}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":38,"st":-32,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"路径 3","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.891,-7.375,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-9,0],[9,0]],"c":false},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径 3","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":13,"s":[0],"e":[100]},{"t":17}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":38,"st":-32,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"形状","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[9.251,1.75,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.38,0],[0,-1.38],[1.38,0],[0,1.38]],"o":[[1.38,0],[0,1.38],[-1.38,0],[0,-1.38]],"v":[[2.14,12],[4.64,14.5],[2.14,17],[-0.36,14.5]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,-0.55],[0.49,-0.05],[0,0],[0,0]],"o":[[0,0],[0.55,0],[0,0.51],[0,0],[0,0],[0,0]],"v":[[-8.36,7],[1.64,7],[2.64,8],[1.76,8.99],[1.64,9],[-8.36,9]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,-2.76],[0.08,-0.36],[0,0],[0,0],[2.15,-0.1],[0,0],[0,0]],"o":[[0,0],[2.76,0],[0,0.37],[0,0],[0,0],[-0.58,2.09],[0,0],[0,0],[0,0]],"v":[[-8.36,-17],[3.36,-17],[8.36,-12],[8.24,-10.91],[8.17,-10.65],[5.92,-2.65],[1.33,1],[1.11,1],[-8.36,1]],"c":true},"ix":2},"nm":"路径 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1,0,1,0.5,0.5,1,0],"ix":9}},"s":{"a":0,"k":[-13.352,-121.199],"ix":5},"e":{"a":0,"k":[2.423,17],"ix":6},"t":1,"nm":"gradient 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"形状","np":5,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":38,"st":-32,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"形状","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[23.107,43.125,0],"ix":2},"a":{"a":0,"k":[-0.003,18.75,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,-6.5]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p833_0p333_0","0p833_0p833_0p333_0","0p833_-6p5_0p333_0"],"t":3,"s":[80,80,100],"e":[100,100,100]},{"t":5}],"ix":6,"x":"var $bm_rt;\nvar nearestKeyIndex, nearestKeyIndex, currentTime, currentTime, calculatedVelocity, amplitude, frequency, decay;\n$bm_rt = nearestKeyIndex = 0;\nif (numKeys > 0) {\n $bm_rt = nearestKeyIndex = nearestKey(time).index;\n if (key(nearestKeyIndex).time > time) {\n nearestKeyIndex--;\n }\n}\nif (nearestKeyIndex == 0) {\n $bm_rt = currentTime = 0;\n} else {\n $bm_rt = currentTime = sub(time, key(nearestKeyIndex).time);\n}\nif (nearestKeyIndex > 0 && currentTime < 1) {\n calculatedVelocity = velocityAtTime(sub(key(nearestKeyIndex).time, div(thisComp.frameDuration, 10)));\n amplitude = 0.06;\n frequency = 2;\n decay = 6;\n $bm_rt = sum(value, div(mul(mul(calculatedVelocity, amplitude), Math.sin(mul(mul(mul(frequency, currentTime), 2), Math.PI))), Math.exp(mul(decay, currentTime))));\n} else {\n $bm_rt = value;\n}"}},"ao":0,"ef":[{"ty":25,"nm":"投影","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"阴影颜色","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[1,0.2666670084,0.313724994659,0.40000000596],"ix":1}},{"ty":0,"nm":"不透明度","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":102,"ix":2}},{"ty":0,"nm":"方向","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180,"ix":3}},{"ty":0,"nm":"距离","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":2,"ix":4}},{"ty":0,"nm":"柔和度","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":5,"ix":5}},{"ty":7,"nm":"仅阴影","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0,"ix":6}}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.38,0],[0,-1.38],[1.38,0],[0,1.38]],"o":[[1.38,0],[0,1.38],[-1.38,0],[0,-1.38]],"v":[[-10.609,13.75],[-8.109,16.25],[-10.609,18.75],[-13.109,16.25]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-1.38,0],[0,-1.38],[1.38,0],[0,1.38]],"o":[[1.38,0],[0,1.38],[-1.38,0],[0,-1.38]],"v":[[11.391,13.75],[13.891,16.25],[11.391,18.75],[8.891,16.25]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,-0.55],[0.5,-0.05],[0,0],[0,0],[0,0],[-0.2,-0.32],[0,0],[0,-2.76],[0.08,-0.36],[0,0],[0,0],[2.15,-0.1],[0,0],[0,0],[0,0],[-1.58,-0.09],[0,0],[0,0],[0,-0.55],[0.5,-0.05],[0,0],[0,0],[0.11,2.66],[0,0],[0,0],[0.48,0.55],[0,0],[0,0],[-0.82,0.08],[0,0]],"o":[[0.55,0],[0,0.51],[0,0],[0,0],[0,0],[0.27,0.27],[0,0],[2.76,0],[0,0.37],[0,0],[0,0],[-0.59,2.09],[0,0],[0,0],[0,0],[0,1.6],[0,0],[0,0],[0.55,0],[0,0.51],[0,0],[0,0],[-2.69,0],[0,0],[0,0],[0,-0.73],[0,0],[0,0],[-0.6,-0.61],[0,0],[0,0]],"v":[[-13.111,-18.75],[-12.111,-17.75],[-12.991,-16.76],[-13.111,-16.75],[-14.191,-16.75],[-13.571,-16.13],[-12.871,-15.25],[12.609,-15.25],[17.609,-10.25],[17.489,-9.16],[17.429,-8.9],[15.179,-0.9],[10.579,2.75],[10.359,2.75],[-12.111,2.75],[-12.111,5.75],[-9.281,8.74],[-9.111,8.75],[10.889,8.75],[11.889,9.75],[11.009,10.74],[10.889,10.75],[-9.111,10.75],[-14.101,5.97],[-14.111,5.75],[-14.111,-12.59],[-14.851,-14.57],[-14.991,-14.71],[-17.321,-17.04],[-16.721,-18.74],[-16.611,-18.75]],"c":true},"ix":2},"nm":"路径 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,0.447,0.474,0.5,1,0.356,0.393,1,1,0.265,0.312],"ix":9}},"s":{"a":0,"k":[0,-17.677],"ix":5},"e":{"a":0,"k":[0,18.75],"ix":6},"t":1,"nm":"gradient 2","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"形状","np":5,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":38,"st":-32,"bm":0}],"markers":[{"tm":3,"cm":"1","dr":0},{"tm":27,"cm":"2","dr":0},{"tm":37,"cm":"3","dr":0}]} -------------------------------------------------------------------------------- /demo/src/main/assets/category.json: -------------------------------------------------------------------------------- 1 | {"v":"5.1.16","fr":30,"ip":0,"op":33,"w":48,"h":48,"nm":"icon/Tabbar_静态_01/分类_48_Sel","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Merged Shape Layer","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[24,24,0],"ix":2},"a":{"a":0,"k":[24,24,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.87],[0,0],[0,0],[0,-3.87],[3.87,0]],"o":[[0,0],[0,0],[3.87,0],[0,3.87],[-3.87,0]],"v":[[-7,0],[-7,-7],[0,-7],[7,0],[0,7]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形备份","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,34],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形备份 4","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.87,0],[0,0],[0,0],[-3.87,0],[0,-3.87]],"o":[[0,0],[0,0],[0,-3.87],[3.87,0],[0,3.87]],"v":[[0,7],[-7,7],[-7,0],[0,-7],[7,0]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形备份","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,14],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形备份 3","np":1,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-3.31],[0,0],[0,0],[0,3.87],[-3.87,0],[0,0]],"o":[[0,0],[0,0],[-3.87,0],[0,-3.87],[0,0],[3.31,0]],"v":[[7,-1],[7,7],[0,7],[-7,0],[0,-7],[1,-7]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形备份","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[14,14],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形备份 2","np":1,"cix":2,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.87,0],[0,0],[0,0],[3.87,0],[0,3.87]],"o":[[0,0],[0,0],[0,3.87],[-3.87,0],[0,-3.87]],"v":[[0,-7],[7,-7],[7,0],[0,7],[-7,0]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形备份","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[14,34],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形备份","np":1,"cix":2,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[48,48],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002861,0.847000002861,0.847000002861,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[24,24],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3,"x":"var $bm_rt;\nvar nearestKeyIndex, nearestKeyIndex, currentTime, currentTime, calculatedVelocity, amplitude, frequency, decay;\n$bm_rt = nearestKeyIndex = 0;\nif (numKeys > 0) {\n $bm_rt = nearestKeyIndex = nearestKey(time).index;\n if (key(nearestKeyIndex).time > time) {\n nearestKeyIndex--;\n }\n}\nif (nearestKeyIndex == 0) {\n $bm_rt = currentTime = 0;\n} else {\n $bm_rt = currentTime = sub(time, key(nearestKeyIndex).time);\n}\nif (nearestKeyIndex > 0 && currentTime < 1) {\n calculatedVelocity = velocityAtTime(sub(key(nearestKeyIndex).time, div(thisComp.frameDuration, 10)));\n amplitude = 0.06;\n frequency = 2;\n decay = 6;\n $bm_rt = sum(value, div(mul(mul(calculatedVelocity, amplitude), Math.sin(mul(mul(mul(frequency, currentTime), 2), Math.PI))), Math.exp(mul(decay, currentTime))));\n} else {\n $bm_rt = value;\n}"},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":0,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":1,"cix":2,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":4,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"路径","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-2.21]],"o":[[2.21,0],[0,0]],"v":[[-2,-2],[2,2]],"c":false},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":13,"s":[0],"e":[100]},{"t":16}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":33,"st":3,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"形状 - 形状","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1.999,18,0],"ix":2},"a":{"a":0,"k":[-8,18,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-4.42],[4.42,0],[0.14,4.3],[0,0],[0,0],[-0.49,0.05],[0,0]],"o":[[4.42,0],[0,4.42],[-4.33,0],[0,0],[0,0],[0,-0.51],[0,0],[0,0]],"v":[[0,2],[8,10],[0,18],[-8,10.25],[-8,10],[-8,3],[-7.12,2.01],[-7,2]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-4.42,0],[0,-4.42],[4.3,-0.14],[0,0],[0,0],[0.05,0.49],[0,0],[0,0]],"o":[[4.42,0],[0,4.33],[0,0],[0,0],[-0.51,0],[0,0],[0,0],[0,-4.42]],"v":[[0,-18],[8,-10],[0.25,-2],[0,-2],[-7,-2],[-7.99,-2.88],[-8,-3],[-8,-10]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1,0,1,0.5,0.5,1,0],"ix":9}},"s":{"a":0,"k":[-12.777,-128.328],"ix":5},"e":{"a":0,"k":[2.319,18],"ix":6},"t":1,"nm":"gradient 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"形状","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":33,"st":3,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"形状 - 形状","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[24,42,0],"ix":2},"a":{"a":0,"k":[0,18,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,11]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p833_0p333_0","0p833_0p833_0p333_0","0p833_11_0p333_0"],"t":3,"s":[80,80,100],"e":[100,100,100]},{"t":5}],"ix":6,"x":"var $bm_rt;\nvar nearestKeyIndex, nearestKeyIndex, currentTime, currentTime, calculatedVelocity, amplitude, frequency, decay;\n$bm_rt = nearestKeyIndex = 0;\nif (numKeys > 0) {\n $bm_rt = nearestKeyIndex = nearestKey(time).index;\n if (key(nearestKeyIndex).time > time) {\n nearestKeyIndex--;\n }\n}\nif (nearestKeyIndex == 0) {\n $bm_rt = currentTime = 0;\n} else {\n $bm_rt = currentTime = sub(time, key(nearestKeyIndex).time);\n}\nif (nearestKeyIndex > 0 && currentTime < 1) {\n calculatedVelocity = velocityAtTime(sub(key(nearestKeyIndex).time, div(thisComp.frameDuration, 10)));\n amplitude = 0.06;\n frequency = 2;\n decay = 6;\n $bm_rt = sum(value, div(mul(mul(calculatedVelocity, amplitude), Math.sin(mul(mul(mul(frequency, currentTime), 2), Math.PI))), Math.exp(mul(decay, currentTime))));\n} else {\n $bm_rt = value;\n}"}},"ao":0,"ef":[{"ty":25,"nm":"投影","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"阴影颜色","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[1,0.2666670084,0.313724994659,0.40000000596],"ix":1}},{"ty":0,"nm":"不透明度","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":102,"ix":2}},{"ty":0,"nm":"方向","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180,"ix":3}},{"ty":0,"nm":"距离","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":2,"ix":4}},{"ty":0,"nm":"柔和度","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":5,"ix":5}},{"ty":7,"nm":"仅阴影","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0,"ix":6}}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.05,-0.49],[0,0],[0,0],[4.42,0],[0,4.42],[-4.3,0.14],[0,0]],"o":[[0.51,0],[0,0],[0,0],[0,4.42],[-4.42,0],[0,-4.33],[0,0],[0,0]],"v":[[-3,2],[-2.01,2.88],[-2,3],[-2,10],[-10,18],[-18,10],[-10.25,2],[-10,2]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,-4.42],[4.42,0],[0.14,4.3],[0,0],[0,0],[-0.49,0.05],[0,0]],"o":[[4.42,0],[0,4.42],[-4.33,0],[0,0],[0,0],[0,-0.51],[0,0],[0,0]],"v":[[10,2],[18,10],[10,18],[2,10.25],[2,10],[2,3],[2.88,2.01],[3,2]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[-0.13,-3.76],[0,0],[0,0],[0.49,-0.05],[0,0],[0,0],[0,4.42],[-4.3,0.14],[0,0]],"o":[[3.79,0],[0,0],[0,0],[0,0.51],[0,0],[0,0],[-4.42,0],[0,-4.33],[0,0],[0,0]],"v":[[-9,-18],[-2,-11.24],[-2,-11],[-2,-3],[-2.88,-2.01],[-3,-2],[-10,-2],[-18,-10],[-10.25,-18],[-10,-18]],"c":true},"ix":2},"nm":"路径 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[-4.42,0],[0,-4.42],[4.3,-0.14],[0,0],[0,0],[0.05,0.49],[0,0],[0,0]],"o":[[4.42,0],[0,4.33],[0,0],[0,0],[-0.51,0],[0,0],[0,0],[0,-4.42]],"v":[[10,-18],[18,-10],[10.25,-2],[10,-2],[3,-2],[2.01,-2.88],[2,-3],[2,-10]],"c":true},"ix":2},"nm":"路径 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,0.447,0.474,0.5,1,0.356,0.393,1,1,0.265,0.312],"ix":9}},"s":{"a":0,"k":[0,-16.97],"ix":5},"e":{"a":0,"k":[0,18],"ix":6},"t":1,"nm":"gradient 2","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"形状","np":6,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":33,"st":3,"bm":0}],"markers":[{"tm":0,"cm":"1","dr":0},{"tm":20,"cm":"2","dr":0},{"tm":33,"cm":"3","dr":0}]} -------------------------------------------------------------------------------- /demo/src/main/assets/mine.json: -------------------------------------------------------------------------------- 1 | {"v":"5.1.16","fr":30,"ip":0,"op":37,"w":48,"h":48,"nm":"icon/Tabbar_静态_01/我的_48_Sel","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Merged Shape Layer","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[24,24,0],"ix":2},"a":{"a":0,"k":[24,24,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.58,0.39],[0,0],[-0.98,0],[0,0],[-0.95,-0.24],[0,0],[-0.23,-1.61],[0,0],[0,0],[0,0]],"o":[[0,0],[0.95,-0.24],[0,0],[0.98,0],[0,0],[1.58,0.39],[0,0],[0,0],[0,0],[0.23,-1.61]],"v":[[-11.39,-2.65],[-3.43,-4.64],[-0.52,-5],[0.52,-5],[3.43,-4.64],[11.39,-2.65],[14.38,0.66],[15,5],[-15,5],[-14.38,0.66]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[24,35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.97,0],[0,-4.97],[0,0],[4.97,0],[0,4.97],[0,0]],"o":[[4.97,0],[0,0],[0,4.97],[-4.97,0],[0,0],[0,-4.97]],"v":[[0,-10],[9,-1],[9,1],[0,10],[-9,1],[-9,-1]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.172548994422,0.172548994422,0.172548994422,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[24,16],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":1,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[48,48],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002861,0.847000002861,0.847000002861,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[24,24],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":0,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":1,"cix":2,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":4,"st":-67,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"嘴巴","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,5.384,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.9,0],[-0.58,0.75]],"o":[[0.58,0.75],[0.9,0],[0,0]],"v":[[-2.378,-0.616],[0.002,0.614],[2.382,-0.616]],"c":false},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":17,"s":[0],"e":[100]},{"t":21}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":37,"st":-67,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"形状","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_1_0p167_0p167"],"t":13,"s":[0],"e":[-6]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":15,"s":[-6],"e":[6]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"n":["0p833_0p833_0p333_0"],"t":17,"s":[6],"e":[0]},{"t":19}],"ix":10,"x":"var $bm_rt;\nvar nearestKeyIndex, nearestKeyIndex, currentTime, currentTime, calculatedVelocity, amplitude, frequency, decay;\n$bm_rt = nearestKeyIndex = 0;\nif (numKeys > 0) {\n $bm_rt = nearestKeyIndex = nearestKey(time).index;\n if (key(nearestKeyIndex).time > time) {\n nearestKeyIndex--;\n }\n}\nif (nearestKeyIndex == 0) {\n $bm_rt = currentTime = 0;\n} else {\n $bm_rt = currentTime = sub(time, key(nearestKeyIndex).time);\n}\nif (nearestKeyIndex > 0 && currentTime < 1) {\n calculatedVelocity = velocityAtTime(sub(key(nearestKeyIndex).time, div(thisComp.frameDuration, 10)));\n amplitude = 0.06;\n frequency = 2;\n decay = 6;\n $bm_rt = sum(value, div(mul(mul(calculatedVelocity, amplitude), Math.sin(mul(mul(mul(frequency, currentTime), 2), Math.PI))), Math.exp(mul(decay, currentTime))));\n} else {\n $bm_rt = value;\n}"},"p":{"a":0,"k":[0,-12,0],"ix":2},"a":{"a":0,"k":[-8,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.89,-0.19],[0,0],[0,0],[-0.36,-1.91],[0,0],[0,0],[0.56,-0.06],[0,0],[0,0],[0,0]],"o":[[0.91,0],[0,0],[0,0],[1.9,0.47],[0,0],[0,0],[0.08,0.56],[0,0],[0,0],[0,0],[0,0]],"v":[[-7.48,6],[-4.77,6.29],[-4.32,6.39],[3.63,8.38],[7.33,12.3],[7.37,12.52],[7.99,16.86],[7.11,17.99],[7,18],[-8,18],[-8,6]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-0.15,-5.39],[0,0],[0,0],[5.52,0]],"o":[[5.43,0],[0,0],[0,0],[0,5.52],[0,0]],"v":[[-8,-18],[2,-8.28],[2,-8],[2,-6],[-8,4]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1,0,1,0.5,0.5,1,0],"ix":9}},"s":{"a":0,"k":[-12.777,-128.328],"ix":5},"e":{"a":0,"k":[2.319,18],"ix":6},"t":1,"nm":"gradient 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"形状","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":37,"st":-67,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"头部","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_1_0p167_0p167"],"t":13,"s":[0],"e":[-6]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":15,"s":[-6],"e":[6]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"n":["0p833_0p833_0p333_0"],"t":17,"s":[6],"e":[0]},{"t":19}],"ix":10,"x":"var $bm_rt;\nvar nearestKeyIndex, nearestKeyIndex, currentTime, currentTime, calculatedVelocity, amplitude, frequency, decay;\n$bm_rt = nearestKeyIndex = 0;\nif (numKeys > 0) {\n $bm_rt = nearestKeyIndex = nearestKey(time).index;\n if (key(nearestKeyIndex).time > time) {\n nearestKeyIndex--;\n }\n}\nif (nearestKeyIndex == 0) {\n $bm_rt = currentTime = 0;\n} else {\n $bm_rt = currentTime = sub(time, key(nearestKeyIndex).time);\n}\nif (nearestKeyIndex > 0 && currentTime < 1) {\n calculatedVelocity = velocityAtTime(sub(key(nearestKeyIndex).time, div(thisComp.frameDuration, 10)));\n amplitude = 0.06;\n frequency = 2;\n decay = 6;\n $bm_rt = sum(value, div(mul(mul(calculatedVelocity, amplitude), Math.sin(mul(mul(mul(frequency, currentTime), 2), Math.PI))), Math.exp(mul(decay, currentTime))));\n} else {\n $bm_rt = value;\n}"},"p":{"a":0,"k":[0,-8,0],"ix":2},"a":{"a":0,"k":[0,11,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":25,"nm":"投影","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"阴影颜色","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[1,0.2666670084,0.313724994659,0.40000000596],"ix":1}},{"ty":0,"nm":"不透明度","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":102,"ix":2}},{"ty":0,"nm":"方向","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180,"ix":3}},{"ty":0,"nm":"距离","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":2,"ix":4}},{"ty":0,"nm":"柔和度","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":5,"ix":5}},{"ty":7,"nm":"仅阴影","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0,"ix":6}}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.52,0],[0,-5.52],[0,0],[-5.52,0],[0,5.52],[0,0]],"o":[[-5.52,0],[0,0],[0,5.52],[5.52,0],[0,0],[0,-5.52]],"v":[[0,-11],[-10,-1],[-10,1],[0,11],[10,1],[10,-1]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,0.447,0.474,0.5,1,0.356,0.393,1,1,0.265,0.312],"ix":9}},"s":{"a":0,"k":[0,-10.371],"ix":5},"e":{"a":0,"k":[0,11],"ix":6},"t":1,"nm":"gradient 2","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":37,"st":-67,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"身体","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[24,41,0],"ix":2},"a":{"a":0,"k":[0,6,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,-0.818]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p833_0p333_0","0p833_0p833_0p333_0","0p833_-0p818_0p333_0"],"t":3,"s":[80,80,100],"e":[100,100,100]},{"t":5}],"ix":6,"x":"var $bm_rt;\nvar nearestKeyIndex, nearestKeyIndex, currentTime, currentTime, calculatedVelocity, amplitude, frequency, decay;\n$bm_rt = nearestKeyIndex = 0;\nif (numKeys > 0) {\n $bm_rt = nearestKeyIndex = nearestKey(time).index;\n if (key(nearestKeyIndex).time > time) {\n nearestKeyIndex--;\n }\n}\nif (nearestKeyIndex == 0) {\n $bm_rt = currentTime = 0;\n} else {\n $bm_rt = currentTime = sub(time, key(nearestKeyIndex).time);\n}\nif (nearestKeyIndex > 0 && currentTime < 1) {\n calculatedVelocity = velocityAtTime(sub(key(nearestKeyIndex).time, div(thisComp.frameDuration, 10)));\n amplitude = 0.06;\n frequency = 2;\n decay = 6;\n $bm_rt = sum(value, div(mul(mul(calculatedVelocity, amplitude), Math.sin(mul(mul(mul(frequency, currentTime), 2), Math.PI))), Math.exp(mul(decay, currentTime))));\n} else {\n $bm_rt = value;\n}"}},"ao":0,"ef":[{"ty":25,"nm":"投影","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"阴影颜色","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[1,0.2666670084,0.313724994659,0.40000000596],"ix":1}},{"ty":0,"nm":"不透明度","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":102,"ix":2}},{"ty":0,"nm":"方向","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180,"ix":3}},{"ty":0,"nm":"距离","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":2,"ix":4}},{"ty":0,"nm":"柔和度","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":5,"ix":5}},{"ty":7,"nm":"仅阴影","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0,"ix":6}}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.07,0],[0,0],[1.04,-0.26],[0,0],[0.29,-2.01],[0,0],[-0.61,0],[0,0],[0.09,0.6],[0,0],[1.98,0.49],[0,0]],"o":[[0,0],[-1.07,0],[0,0],[-1.98,0.49],[0,0],[-0.09,0.6],[0,0],[0.61,0],[0,0],[-0.29,-2.01],[0,0],[-1.04,-0.26]],"v":[[0.52,-6],[-0.52,-6],[-3.68,-5.61],[-11.63,-3.62],[-15.37,0.52],[-15.99,4.86],[-15,6],[15,6],[15.99,4.86],[15.37,0.52],[11.63,-3.62],[3.68,-5.61]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,0.447,0.474,0.5,1,0.356,0.393,1,1,0.265,0.312],"ix":9}},"s":{"a":0,"k":[0,-5.657],"ix":5},"e":{"a":0,"k":[0,6],"ix":6},"t":1,"nm":"gradient 3","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":37,"st":-67,"bm":0}],"markers":[{"tm":3,"cm":"1","dr":0},{"tm":26,"cm":"2","dr":0},{"tm":36,"cm":"3","dr":0}]} -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/BaseViewPagerActivity.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import android.os.Bundle; 4 | import android.view.Menu; 5 | import android.view.MenuItem; 6 | 7 | import com.chaychan.library.BottomBarLayout; 8 | import com.chaychan.library.TabData; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import androidx.appcompat.app.AppCompatActivity; 14 | import androidx.fragment.app.Fragment; 15 | import androidx.fragment.app.FragmentManager; 16 | import androidx.fragment.app.FragmentStatePagerAdapter; 17 | import androidx.viewpager.widget.ViewPager; 18 | 19 | public abstract class BaseViewPagerActivity extends AppCompatActivity { 20 | 21 | private ViewPager mVpContent; 22 | protected BottomBarLayout mBottomBarLayout; 23 | 24 | private List mFragmentList = new ArrayList<>(); 25 | 26 | protected abstract String[] getFragmentContents(); 27 | 28 | protected abstract List getTabData(); 29 | 30 | protected abstract int getLayoutResId(); 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(getLayoutResId()); 36 | 37 | initView(); 38 | initData(); 39 | initListener(); 40 | } 41 | 42 | public void initView() { 43 | mVpContent = findViewById(R.id.vp_content); 44 | mBottomBarLayout = findViewById(R.id.bbl); 45 | } 46 | 47 | private void initData() { 48 | for (String tabContent : getFragmentContents()) { 49 | TabFragment fragment = new TabFragment(); 50 | Bundle bundle = new Bundle(); 51 | bundle.putString(TabFragment.CONTENT, tabContent); 52 | fragment.setArguments(bundle); 53 | mFragmentList.add(fragment); 54 | } 55 | mBottomBarLayout.setData(getTabData()); 56 | } 57 | 58 | public void initListener() { 59 | mVpContent.setAdapter(new MyAdapter(getSupportFragmentManager())); 60 | mBottomBarLayout.setViewPager(mVpContent); 61 | 62 | mBottomBarLayout.setUnread(0, 20);//设置第一个页签的未读数为20 63 | mBottomBarLayout.setUnread(1, 1001);//设置第二个页签的未读数 64 | mBottomBarLayout.showNotify(2);//设置第三个页签显示提示的小红点 65 | mBottomBarLayout.setMsg(3, "NEW");//设置第四个页签显示NEW提示文字 66 | } 67 | 68 | class MyAdapter extends FragmentStatePagerAdapter { 69 | 70 | public MyAdapter(FragmentManager fm) { 71 | super(fm); 72 | } 73 | 74 | @Override 75 | public Fragment getItem(int position) { 76 | return mFragmentList.get(position); 77 | } 78 | 79 | @Override 80 | public int getCount() { 81 | return mFragmentList.size(); 82 | } 83 | } 84 | 85 | @Override 86 | public boolean onCreateOptionsMenu(Menu menu) { 87 | getMenuInflater().inflate(R.menu.menu_demo, menu); 88 | return true; 89 | } 90 | 91 | @Override 92 | public boolean onOptionsItemSelected(MenuItem item) { 93 | int id = item.getItemId(); 94 | switch (id) { 95 | case R.id.action_clear_unread: 96 | mBottomBarLayout.setUnread(0, 0); 97 | mBottomBarLayout.setUnread(1, 0); 98 | break; 99 | case R.id.action_clear_notify: 100 | mBottomBarLayout.hideNotify(2); 101 | break; 102 | case R.id.action_clear_msg: 103 | mBottomBarLayout.hideMsg(3); 104 | break; 105 | } 106 | return super.onOptionsItemSelected(item); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/DemoBean.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | public class DemoBean { 4 | public String name; 5 | public Class clazz; 6 | 7 | public DemoBean(String name, Class clazz) { 8 | this.name = name; 9 | this.clazz = clazz; 10 | } 11 | 12 | @Override 13 | public String toString() { 14 | return name; 15 | } 16 | } -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/DynamicAddItemActivity.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | import android.widget.FrameLayout; 8 | 9 | import com.chaychan.library.BottomBarItem; 10 | import com.chaychan.library.BottomBarLayout; 11 | import com.chaychan.library.UIUtils; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Random; 16 | 17 | import androidx.annotation.NonNull; 18 | import androidx.appcompat.app.AppCompatActivity; 19 | import androidx.fragment.app.FragmentTransaction; 20 | 21 | /** 22 | * @author ChayChan 23 | * @description: 动态添加条目 24 | * @date 2018/12/13 14:45 25 | */ 26 | public class DynamicAddItemActivity extends AppCompatActivity { 27 | 28 | private List mFragmentList = new ArrayList<>(); 29 | private BottomBarLayout mBottomBarLayout; 30 | 31 | private int[] mNormalIconIds = new int[]{ 32 | R.mipmap.tab_home_normal, R.mipmap.tab_video_normal, 33 | R.mipmap.tab_micro_normal, R.mipmap.tab_me_normal 34 | }; 35 | 36 | private int[] mSelectedIconIds = new int[]{ 37 | R.mipmap.tab_home_selected, R.mipmap.tab_video_selected, 38 | R.mipmap.tab_micro_selected, R.mipmap.tab_me_selected 39 | }; 40 | 41 | private int[] mTitleIds = new int[]{ 42 | R.string.tab_home, 43 | R.string.tab_video, 44 | R.string.tab_micro, 45 | R.string.tab_me 46 | }; 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | setContentView(R.layout.activity_dynamic_add_item); 52 | 53 | initView(); 54 | initData(); 55 | initListener(); 56 | } 57 | 58 | private void initView() { 59 | getSupportActionBar().setTitle(DynamicAddItemActivity.class.getSimpleName()); 60 | mBottomBarLayout = findViewById(R.id.bbl); 61 | } 62 | 63 | private void initData() { 64 | for (int i = 0; i < mTitleIds.length; i++) { 65 | //创建item 66 | BottomBarItem item = createBottomBarItem(i); 67 | mBottomBarLayout.addItem(item); 68 | 69 | TabFragment homeFragment = createFragment(mTitleIds[i]); 70 | mFragmentList.add(homeFragment); 71 | } 72 | mBottomBarLayout.setCurrentItem(0); 73 | } 74 | 75 | @NonNull 76 | private TabFragment createFragment(int titleId) { 77 | TabFragment homeFragment = new TabFragment(); 78 | Bundle bundle = new Bundle(); 79 | bundle.putString(TabFragment.CONTENT, getString(titleId)); 80 | homeFragment.setArguments(bundle); 81 | return homeFragment; 82 | } 83 | 84 | private BottomBarItem createBottomBarItem(int i) { 85 | BottomBarItem item = new BottomBarItem.Builder(this) 86 | .titleTextBold(true) 87 | .titleTextSize(UIUtils.dip2Px(this, 8)) 88 | .titleNormalColor(getResources().getColor(R.color.tab_normal_color)) 89 | .titleSelectedColor(getResources().getColor(R.color.tab_selected_color)) 90 | .marginTop(UIUtils.dip2Px(this, -5)) 91 | // .itemPadding(5) 92 | // .unreadNumThreshold(99) 93 | // .unreadTextColor(getResources().getColor(R.color.white)) 94 | 95 | //还有很多属性,详情请查看Builder里面的方法 96 | //There are still many properties, please see the methods in the Builder for details. 97 | .create(mNormalIconIds[i], mSelectedIconIds[i], getString(mTitleIds[i])); 98 | return item; 99 | } 100 | 101 | private void initListener() { 102 | mBottomBarLayout.setOnItemSelectedListener((bottomBarItem, previousPosition, currentPosition) -> { 103 | Log.i("MainActivity", "position: " + currentPosition); 104 | 105 | changeFragment(currentPosition); 106 | }); 107 | } 108 | 109 | private void changeFragment(int currentPosition) { 110 | FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 111 | transaction.replace(R.id.fl_content, mFragmentList.get(currentPosition)); 112 | transaction.commit(); 113 | } 114 | 115 | @Override 116 | public boolean onCreateOptionsMenu(Menu menu) { 117 | getMenuInflater().inflate(R.menu.menu_dynamic, menu); 118 | return true; 119 | } 120 | 121 | @Override 122 | public boolean onOptionsItemSelected(MenuItem item) { 123 | int id = item.getItemId(); 124 | switch (id) { 125 | case R.id.action_add_item: 126 | int random = new Random().nextInt(3); 127 | //addFragment 128 | mFragmentList.add(createFragment(mTitleIds[random])); 129 | //addItem 130 | BottomBarItem bottomBarItem = createBottomBarItem(random); 131 | mBottomBarLayout.addItem(bottomBarItem); 132 | 133 | mBottomBarLayout.setCurrentItem(mFragmentList.size() - 1); 134 | break; 135 | case R.id.action_remove_item: 136 | //移除条目 137 | mBottomBarLayout.removeItem(0); 138 | 139 | if (mFragmentList.size() != 0) { 140 | mFragmentList.remove(0); 141 | 142 | if (mFragmentList.size() != 0) { 143 | mBottomBarLayout.setCurrentItem(0); 144 | } 145 | } 146 | break; 147 | } 148 | return super.onOptionsItemSelected(item); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/FragmentManagerActivity.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.util.Log; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.view.animation.Animation; 9 | import android.view.animation.RotateAnimation; 10 | import android.widget.FrameLayout; 11 | import android.widget.ImageView; 12 | 13 | import com.chaychan.library.BottomBarItem; 14 | import com.chaychan.library.BottomBarLayout; 15 | import com.chaychan.library.TabData; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import androidx.appcompat.app.AppCompatActivity; 21 | import androidx.fragment.app.FragmentTransaction; 22 | 23 | public class FragmentManagerActivity extends AppCompatActivity { 24 | 25 | private List mFragmentList = new ArrayList<>(); 26 | private BottomBarLayout mBottomBarLayout; 27 | 28 | private List getTabData() { 29 | List tabData = new ArrayList<>(); 30 | tabData.add(new TabData("首页", R.mipmap.tab_home_normal, R.mipmap.tab_home_selected)); 31 | tabData.add(new TabData("视频", R.mipmap.tab_video_normal, R.mipmap.tab_video_selected)); 32 | tabData.add(new TabData("微头条", R.mipmap.tab_micro_normal, R.mipmap.tab_micro_selected)); 33 | tabData.add(new TabData("我的", R.mipmap.tab_me_normal, R.mipmap.tab_me_selected)); 34 | return tabData; 35 | } 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_fragment_manager); 41 | 42 | initView(); 43 | initData(); 44 | initListener(); 45 | } 46 | 47 | private void initView() { 48 | getSupportActionBar().setTitle(FragmentManagerActivity.class.getSimpleName()); 49 | mBottomBarLayout = findViewById(R.id.bbl); 50 | } 51 | 52 | private void initData() { 53 | 54 | TabFragment homeFragment = new TabFragment(); 55 | Bundle bundle1 = new Bundle(); 56 | bundle1.putString(TabFragment.CONTENT, "首页"); 57 | homeFragment.setArguments(bundle1); 58 | mFragmentList.add(homeFragment); 59 | 60 | TabFragment videoFragment = new TabFragment(); 61 | Bundle bundle2 = new Bundle(); 62 | bundle2.putString(TabFragment.CONTENT, "视频"); 63 | videoFragment.setArguments(bundle2); 64 | mFragmentList.add(videoFragment); 65 | 66 | TabFragment microFragment = new TabFragment(); 67 | Bundle bundle3 = new Bundle(); 68 | bundle3.putString(TabFragment.CONTENT, "微头条"); 69 | microFragment.setArguments(bundle3); 70 | mFragmentList.add(microFragment); 71 | 72 | TabFragment meFragment = new TabFragment(); 73 | Bundle bundle4 = new Bundle(); 74 | bundle4.putString(TabFragment.CONTENT, "我的"); 75 | meFragment.setArguments(bundle4); 76 | mFragmentList.add(meFragment); 77 | 78 | mBottomBarLayout.setData(getTabData()); 79 | 80 | changeFragment(0); //默认显示第一页 81 | } 82 | 83 | private void initListener() { 84 | mBottomBarLayout.setOnItemSelectedListener((bottomBarItem, previousPosition, currentPosition) -> { 85 | Log.i("MainActivity", "position: " + currentPosition); 86 | changeFragment(currentPosition); 87 | }); 88 | 89 | mBottomBarLayout.setUnread(0, 20);//设置第一个页签的未读数为20 90 | mBottomBarLayout.setUnread(1, 1001);//设置第二个页签的未读数 91 | mBottomBarLayout.showNotify(2);//设置第三个页签显示提示的小红点 92 | mBottomBarLayout.setMsg(3, "NEW");//设置第四个页签显示NEW提示文字 93 | } 94 | 95 | private void changeFragment(int currentPosition) { 96 | FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 97 | transaction.replace(R.id.fl_content, mFragmentList.get(currentPosition)); 98 | transaction.commit(); 99 | } 100 | 101 | @Override 102 | public boolean onCreateOptionsMenu(Menu menu) { 103 | getMenuInflater().inflate(R.menu.menu_demo, menu); 104 | return true; 105 | } 106 | 107 | @Override 108 | public boolean onOptionsItemSelected(MenuItem item) { 109 | int id = item.getItemId(); 110 | switch (id) { 111 | case R.id.action_clear_unread: 112 | mBottomBarLayout.setUnread(0, 0); 113 | mBottomBarLayout.setUnread(1, 0); 114 | break; 115 | case R.id.action_clear_notify: 116 | mBottomBarLayout.hideNotify(2); 117 | break; 118 | case R.id.action_clear_msg: 119 | mBottomBarLayout.hideMsg(3); 120 | break; 121 | } 122 | return super.onOptionsItemSelected(item); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/LottieDemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import com.chaychan.library.TabData; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @author ChayChan 10 | * @description: viewPager demo 11 | * @date 2020/11/21 15:18 12 | */ 13 | public class LottieDemoActivity extends BaseViewPagerActivity{ 14 | 15 | @Override 16 | protected String[] getFragmentContents() { 17 | return new String[]{"首页", "分类", "购物车", "我的"}; 18 | } 19 | 20 | @Override 21 | protected List getTabData() { 22 | List tabData = new ArrayList<>(); 23 | tabData.add(new TabData(getFragmentContents()[0], "home.json")); 24 | tabData.add(new TabData(getFragmentContents()[1], "category.json")); 25 | tabData.add(new TabData(getFragmentContents()[2], "cart.json")); 26 | tabData.add(new TabData(getFragmentContents()[3], "mine.json")); 27 | return tabData; 28 | } 29 | 30 | @Override 31 | protected int getLayoutResId() { 32 | return R.layout.activity_lottie_demo; 33 | } 34 | 35 | @Override 36 | public void initView() { 37 | super.initView(); 38 | getSupportActionBar().setTitle(LottieDemoActivity.class.getSimpleName()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.Gravity; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.appcompat.app.AppCompatActivity; 12 | import androidx.recyclerview.widget.DividerItemDecoration; 13 | import androidx.recyclerview.widget.LinearLayoutManager; 14 | import androidx.recyclerview.widget.RecyclerView; 15 | 16 | import static androidx.recyclerview.widget.RecyclerView.ViewHolder; 17 | 18 | public class MainActivity extends AppCompatActivity { 19 | 20 | private DemoBean[] mDatas= { 21 | new DemoBean("UseWithViewPager2", ViewPager2DemoActivity.class), 22 | new DemoBean("UseWithViewPager", ViewPagerDemoActivity.class), 23 | new DemoBean("UseWithoutViewPager",FragmentManagerActivity.class), 24 | new DemoBean("DynamicAddItem",DynamicAddItemActivity.class), 25 | new DemoBean("UseLottieDemo",LottieDemoActivity.class), 26 | }; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_main); 32 | 33 | RecyclerView rvList = findViewById(R.id.rv_list); 34 | rvList.setLayoutManager(new LinearLayoutManager(this)); 35 | rvList.setHasFixedSize(true); 36 | rvList.setAdapter(new MyRvAdapter()); 37 | rvList.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); 38 | } 39 | 40 | private class MyRvAdapter extends RecyclerView.Adapter{ 41 | @NonNull 42 | @Override 43 | public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 44 | TextView textView = new TextView(parent.getContext()); 45 | ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 46 | textView.setLayoutParams(params); 47 | textView.setPadding(30,30,30,30); 48 | textView.setTextSize(18); 49 | textView.setGravity(Gravity.CENTER); 50 | textView.setBackground(getResources().getDrawable(R.drawable.selector_bg)); 51 | return new MyViewHolder(textView); 52 | } 53 | 54 | @Override 55 | public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { 56 | final DemoBean data = mDatas[position]; 57 | ((TextView)holder.itemView).setText(data.name); 58 | holder.itemView.setOnClickListener(v -> v.getContext().startActivity(new Intent(v.getContext(), data.clazz))); 59 | } 60 | 61 | @Override 62 | public int getItemCount() { 63 | return mDatas.length; 64 | } 65 | } 66 | 67 | private class MyViewHolder extends ViewHolder{ 68 | 69 | public MyViewHolder(@NonNull View itemView) { 70 | super(itemView); 71 | } 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/TabFragment.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 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 | /** 14 | * @author ChayChan 15 | * @date 2017/6/23 11:22 16 | */ 17 | public class TabFragment extends Fragment { 18 | 19 | public static final String CONTENT = "content"; 20 | private TextView mTextView; 21 | 22 | 23 | @Nullable 24 | @Override 25 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle bundle) { 26 | mTextView = new TextView(getActivity()); 27 | mTextView.setGravity(Gravity.CENTER); 28 | String content = getArguments().getString(CONTENT); 29 | mTextView.setText(content); 30 | return mTextView; 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/ViewPager2DemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | import android.widget.Toast; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.appcompat.app.AppCompatActivity; 11 | import androidx.fragment.app.Fragment; 12 | import androidx.fragment.app.FragmentActivity; 13 | import androidx.viewpager2.adapter.FragmentStateAdapter; 14 | import androidx.viewpager2.widget.ViewPager2; 15 | 16 | import com.chaychan.library.BottomBarLayout; 17 | import com.chaychan.library.TabData; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * @author ChayChan 24 | * @description: viewPager2 demo 25 | * @date 2024/07/10 14:58 26 | */ 27 | public class ViewPager2DemoActivity extends AppCompatActivity { 28 | 29 | private ViewPager2 mVpContent; 30 | protected BottomBarLayout mBottomBarLayout; 31 | 32 | private List mFragmentList = new ArrayList<>(); 33 | 34 | private String[] mTitle = new String[]{"首页", "视频", "微头条", "我的"}; 35 | 36 | private List tabData = new ArrayList<>(); 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_view_pager2_demo); 42 | 43 | initView(); 44 | initData(); 45 | initListener(); 46 | } 47 | 48 | public void initView() { 49 | mVpContent = findViewById(R.id.vp_content); 50 | mBottomBarLayout = findViewById(R.id.bbl); 51 | } 52 | 53 | private void initData() { 54 | mFragmentList.add(createFragment(mTitle[0])); 55 | mFragmentList.add(createFragment(mTitle[1])); 56 | mFragmentList.add(createFragment("中间add")); //中间凸起占位的fragment 可以是空白的 然后在拦截处理 57 | mFragmentList.add(createFragment(mTitle[2])); 58 | mFragmentList.add(createFragment(mTitle[3])); 59 | 60 | tabData.add(new TabData(mTitle[0], R.mipmap.tab_home_normal, R.mipmap.tab_home_selected)); 61 | tabData.add(new TabData(mTitle[1], R.mipmap.tab_video_normal, R.mipmap.tab_video_selected)); 62 | tabData.add(new TabData(mTitle[2], R.mipmap.tab_micro_normal, R.mipmap.tab_micro_selected)); 63 | tabData.add(new TabData(mTitle[3], R.mipmap.tab_me_normal, R.mipmap.tab_me_selected)); 64 | mBottomBarLayout.setData(tabData); 65 | } 66 | 67 | public TabFragment createFragment(String content){ 68 | TabFragment fragment = new TabFragment(); 69 | Bundle bundle = new Bundle(); 70 | bundle.putString(TabFragment.CONTENT, content); 71 | fragment.setArguments(bundle); 72 | return fragment; 73 | } 74 | 75 | public void initListener() { 76 | mVpContent.setAdapter(new MyAdapter(this)); 77 | mVpContent.setUserInputEnabled(false); 78 | mBottomBarLayout.setViewPager2(mVpContent); 79 | 80 | mBottomBarLayout.setUnread(0, 20);//设置第一个页签的未读数为20 81 | mBottomBarLayout.setUnread(1, 1001);//设置第二个页签的未读数 82 | mBottomBarLayout.showNotify(3);//设置第三个页签显示提示的小红点 83 | mBottomBarLayout.setMsg(4, "NEW");//设置第四个页签显示NEW提示文字 84 | 85 | mBottomBarLayout.setOnPageChangeInterceptor(position -> { 86 | if(position == 2){ 87 | //中间凸起图标的位置 88 | Toast.makeText(ViewPager2DemoActivity.this, "可以跳转别的页面,比如发布页", Toast.LENGTH_SHORT).show(); 89 | return true; 90 | } 91 | boolean isLogin = false; //Simulate no login 92 | if (position == 4 && !isLogin){ 93 | //no login intercept to other tab or to LoginActivity 94 | Toast.makeText(ViewPager2DemoActivity.this, "Test intercept, Login first please", Toast.LENGTH_SHORT).show(); 95 | return true; 96 | } 97 | return false; 98 | }); 99 | mBottomBarLayout.setOnItemSelectedListener((bottomBarItem, previousPosition, currentPosition) -> { 100 | Log.i("ViewPager2DemoActivity", "position: " + currentPosition + " pre: " + previousPosition); 101 | }); 102 | } 103 | 104 | class MyAdapter extends FragmentStateAdapter { 105 | 106 | public MyAdapter(@NonNull FragmentActivity fragmentActivity) { 107 | super(fragmentActivity); 108 | } 109 | 110 | @NonNull 111 | @Override 112 | public Fragment createFragment(int position) { 113 | return mFragmentList.get(position); 114 | } 115 | 116 | @Override 117 | public int getItemCount() { 118 | return mFragmentList.size(); 119 | } 120 | } 121 | 122 | @Override 123 | public boolean onCreateOptionsMenu(Menu menu) { 124 | getMenuInflater().inflate(R.menu.menu_demo, menu); 125 | return true; 126 | } 127 | 128 | @Override 129 | public boolean onOptionsItemSelected(MenuItem item) { 130 | int id = item.getItemId(); 131 | switch (id) { 132 | case R.id.action_clear_unread: 133 | mBottomBarLayout.setUnread(0, 0); 134 | mBottomBarLayout.setUnread(1, 0); 135 | break; 136 | case R.id.action_clear_notify: 137 | mBottomBarLayout.hideNotify(3); 138 | break; 139 | case R.id.action_clear_msg: 140 | mBottomBarLayout.hideMsg(4); 141 | break; 142 | } 143 | return super.onOptionsItemSelected(item); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /demo/src/main/java/com/chaychan/bottombarlayout/ViewPagerDemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import android.os.Handler; 4 | import android.util.Log; 5 | import android.view.animation.Animation; 6 | import android.view.animation.RotateAnimation; 7 | import android.widget.ImageView; 8 | 9 | import com.chaychan.library.BottomBarItem; 10 | import com.chaychan.library.BottomBarLayout; 11 | import com.chaychan.library.TabData; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * @author ChayChan 18 | * @description: viewPager demo 19 | * @date 2020/11/21 15:18 20 | */ 21 | public class ViewPagerDemoActivity extends BaseViewPagerActivity{ 22 | 23 | private RotateAnimation mRotateAnimation; 24 | private Handler mHandler = new Handler(); 25 | 26 | @Override 27 | protected String[] getFragmentContents() { 28 | return new String[]{"首页", "视频", "微头条", "我的"}; 29 | } 30 | 31 | @Override 32 | protected List getTabData() { 33 | List tabData = new ArrayList<>(); 34 | tabData.add(new TabData(getFragmentContents()[0], R.mipmap.tab_home_normal, R.mipmap.tab_home_selected)); 35 | tabData.add(new TabData(getFragmentContents()[1], R.mipmap.tab_video_normal, R.mipmap.tab_video_selected)); 36 | tabData.add(new TabData(getFragmentContents()[2], R.mipmap.tab_micro_normal, R.mipmap.tab_micro_selected)); 37 | tabData.add(new TabData(getFragmentContents()[3], R.mipmap.tab_me_normal, R.mipmap.tab_me_selected)); 38 | return tabData; 39 | } 40 | 41 | @Override 42 | protected int getLayoutResId() { 43 | return R.layout.activity_view_pager_demo; 44 | } 45 | 46 | @Override 47 | public void initView() { 48 | super.initView(); 49 | getSupportActionBar().setTitle(ViewPagerDemoActivity.class.getSimpleName()); 50 | } 51 | 52 | @Override 53 | public void initListener() { 54 | super.initListener(); 55 | mBottomBarLayout.setOnItemSelectedListener((bottomBarItem, previousPosition, currentPosition) -> { 56 | Log.i("ViewPagerDemoActivity", "position: " + currentPosition); 57 | if (currentPosition == 0) { 58 | //如果是第一个,即首页 59 | if (previousPosition == currentPosition) { 60 | //如果是在原来位置上点击,更换首页图标并播放旋转动画 61 | if (mRotateAnimation != null && !mRotateAnimation.hasEnded()){ 62 | //如果当前动画正在执行 63 | return; 64 | } 65 | 66 | bottomBarItem.setSelectedIcon(R.mipmap.tab_loading);//更换成加载图标 67 | 68 | //播放旋转动画 69 | if (mRotateAnimation == null) { 70 | mRotateAnimation = new RotateAnimation(0, 360, 71 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 72 | 0.5f); 73 | mRotateAnimation.setDuration(800); 74 | mRotateAnimation.setRepeatCount(-1); 75 | } 76 | ImageView bottomImageView = bottomBarItem.getImageView(); 77 | bottomImageView.setAnimation(mRotateAnimation); 78 | bottomImageView.startAnimation(mRotateAnimation);//播放旋转动画 79 | 80 | //模拟数据刷新完毕 81 | mHandler.postDelayed(() -> { 82 | boolean tabNotChanged = mBottomBarLayout.getCurrentItem() == currentPosition; //是否还停留在当前页签 83 | bottomBarItem.setSelectedIcon(R.mipmap.tab_home_selected);//更换成首页原来选中图标 84 | cancelTabLoading(bottomBarItem); 85 | }, 3000); 86 | return; 87 | } 88 | } 89 | 90 | //如果点击了其他条目 91 | BottomBarItem bottomItem = mBottomBarLayout.getBottomItem(0); 92 | bottomItem.setSelectedIcon(R.mipmap.tab_home_selected);//更换为原来的图标 93 | 94 | cancelTabLoading(bottomItem);//停止旋转动画 95 | }); 96 | } 97 | 98 | /** 99 | * 停止首页页签的旋转动画 100 | */ 101 | private void cancelTabLoading(BottomBarItem bottomItem) { 102 | Animation animation = bottomItem.getImageView().getAnimation(); 103 | if (animation != null) { 104 | animation.cancel(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/selector_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_dynamic_add_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 26 | 27 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_fragment_manager.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_lottie_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 27 | 28 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_view_pager2_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_view_pager_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demo/src/main/res/menu/menu_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | -------------------------------------------------------------------------------- /demo/src/main/res/menu/menu_dynamic.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/icon_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/icon_add.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_home_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_home_normal.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_home_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_home_selected.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_loading.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_me_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_me_normal.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_me_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_me_selected.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_micro_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_micro_normal.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_micro_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_micro_selected.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_video_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_video_normal.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/tab_video_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxhdpi/tab_video_selected.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #515051 8 | #D33D3C 9 | 10 | #00ff00 11 | #00ff00 12 | 13 | -------------------------------------------------------------------------------- /demo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BottomBarLayout 3 | 首页 4 | 视频 5 | 微头条 6 | 我的 7 | 分类 8 | 购物车 9 | 10 | -------------------------------------------------------------------------------- /demo/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/src/test/java/com/chaychan/bottombarlayout/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.bottombarlayout; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | android.useAndroidX=true 19 | 20 | KEY_FILE_PATH=demo.jks 21 | KEY_PASSWORD=123456 22 | KEY_ALIAS=demo 23 | KEY_STORE_PASSWORD=123456 24 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jun 25 09:11:31 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-6.7.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /intro_img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/intro_img/4.png -------------------------------------------------------------------------------- /intro_img/display1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/intro_img/display1.gif -------------------------------------------------------------------------------- /intro_img/display2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/intro_img/display2.gif -------------------------------------------------------------------------------- /intro_img/display3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/intro_img/display3.gif -------------------------------------------------------------------------------- /intro_img/download_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/intro_img/download_qr.png -------------------------------------------------------------------------------- /intro_img/float.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/intro_img/float.gif -------------------------------------------------------------------------------- /intro_img/lottie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/intro_img/lottie.gif -------------------------------------------------------------------------------- /intro_img/transfer_code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaychan/BottomBarLayout/502ab812eabcab63ec55c5ed4baa746f40e41343/intro_img/transfer_code.jpg -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion compile_sdk_version 5 | buildToolsVersion build_tools_version 6 | 7 | defaultConfig { 8 | minSdkVersion min_sdk_version 9 | targetSdkVersion target_sdk_version 10 | versionCode 6 11 | versionName "1.2.1" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(include: ['*.jar'], dir: 'libs') 26 | implementation "androidx.appcompat:appcompat:1.2.0" 27 | implementation "com.airbnb.android:lottie:3.5.0" 28 | implementation "androidx.viewpager2:viewpager2:1.0.0" 29 | } 30 | -------------------------------------------------------------------------------- /library/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 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/chaychan/library/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.library; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.chaychan.library.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /library/src/main/java/com/chaychan/library/BottomBarItem.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.library; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.drawable.Drawable; 6 | import android.text.TextUtils; 7 | import android.util.AttributeSet; 8 | import android.util.TypedValue; 9 | import android.view.Gravity; 10 | import android.view.View; 11 | import android.widget.FrameLayout; 12 | import android.widget.ImageView; 13 | import android.widget.LinearLayout; 14 | import android.widget.TextView; 15 | 16 | import com.airbnb.lottie.LottieAnimationView; 17 | 18 | import java.util.Locale; 19 | 20 | import androidx.annotation.NonNull; 21 | import androidx.annotation.Nullable; 22 | 23 | 24 | /** 25 | * @author ChayChan 26 | * @description: 底部tab条目 27 | * @date 2017/6/23 9:14 28 | */ 29 | 30 | public class BottomBarItem extends LinearLayout { 31 | 32 | private Context context; 33 | private ImageView mImageView; 34 | private LottieAnimationView mLottieView; 35 | private TextView mTvUnread; 36 | private TextView mTvNotify; 37 | private TextView mTvMsg; 38 | private TextView mTextView; 39 | 40 | private Builder mBuilder; 41 | 42 | public BottomBarItem(Context context) { 43 | super(context); 44 | } 45 | 46 | public BottomBarItem(Context context, @Nullable AttributeSet attrs) { 47 | this(context, attrs, 0); 48 | } 49 | 50 | public BottomBarItem(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | this.context = context; 53 | checkValues();//检查值是否合法 54 | init();//初始化相关操作 55 | } 56 | 57 | /** 58 | * 检查传入的值是否完善 59 | */ 60 | private void checkValues() { 61 | if (mBuilder == null){ 62 | throw new IllegalStateException("Builder is null"); 63 | } 64 | 65 | if (mBuilder.unreadTextBg == null) { 66 | mBuilder.unreadTextBg = getResources().getDrawable(R.drawable.shape_unread); 67 | } 68 | 69 | if (mBuilder.msgTextBg == null) { 70 | mBuilder.msgTextBg = getResources().getDrawable(R.drawable.shape_msg); 71 | } 72 | 73 | if (mBuilder.notifyPointBg == null) { 74 | mBuilder.notifyPointBg = getResources().getDrawable(R.drawable.shape_notify_point); 75 | } 76 | } 77 | 78 | private void init() { 79 | setOrientation(VERTICAL); 80 | setGravity(Gravity.CENTER); 81 | 82 | View view = initView(); 83 | 84 | FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mImageView.getLayoutParams(); 85 | if (mBuilder.iconWidth != 0 && mBuilder.iconHeight != 0) { 86 | //如果有设置图标的宽度和高度,则设置ImageView的宽高 87 | layoutParams.width = mBuilder.iconWidth; 88 | layoutParams.height = mBuilder.iconHeight; 89 | } 90 | 91 | if (!TextUtils.isEmpty(mBuilder.lottieJson)){ 92 | mLottieView.setLayoutParams(layoutParams); 93 | mLottieView.setAnimation(mBuilder.lottieJson); 94 | mLottieView.setRepeatCount(0); 95 | }else{ 96 | mImageView.setImageDrawable(mBuilder.normalIcon); 97 | mImageView.setLayoutParams(layoutParams); 98 | } 99 | 100 | mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mBuilder.titleTextSize);//设置底部文字字体大小 101 | mTextView.getPaint().setFakeBoldText(mBuilder.titleTextBold); 102 | mTvUnread.setTextSize(TypedValue.COMPLEX_UNIT_PX, mBuilder.unreadTextSize);//设置未读数的字体大小 103 | mTvUnread.setTextColor(mBuilder.unreadTextColor);//设置未读数字体颜色 104 | mTvUnread.setBackground(mBuilder.unreadTextBg);//设置未读数背景 105 | 106 | mTvMsg.setTextSize(TypedValue.COMPLEX_UNIT_PX, mBuilder.msgTextSize);//设置提示文字的字体大小 107 | mTvMsg.setTextColor(mBuilder.msgTextColor);//设置提示文字的字体颜色 108 | mTvMsg.setBackground(mBuilder.msgTextBg);//设置提示文字的背景颜色 109 | 110 | mTvNotify.setBackground(mBuilder.notifyPointBg);//设置提示点的背景颜色 111 | 112 | mTextView.setTextColor(mBuilder.titleNormalColor);//设置底部文字字体颜色 113 | mTextView.setText(mBuilder.title);//设置标签文字 114 | 115 | LayoutParams textLayoutParams = (LayoutParams) mTextView.getLayoutParams(); 116 | textLayoutParams.topMargin = mBuilder.marginTop; 117 | mTextView.setLayoutParams(textLayoutParams); 118 | 119 | addView(view); 120 | } 121 | 122 | @NonNull 123 | private View initView() { 124 | View view = View.inflate(context, R.layout.item_bottom_bar, null); 125 | if (mBuilder.itemPadding != 0) { 126 | //如果有设置item的padding 127 | view.setPadding(mBuilder.itemPadding, mBuilder.itemPadding, mBuilder.itemPadding, mBuilder.itemPadding); 128 | } 129 | mImageView = view.findViewById(R.id.iv_icon); 130 | mLottieView = view.findViewById(R.id.lottieView); 131 | mTvUnread = view.findViewById(R.id.tv_unred_num); 132 | mTvMsg = view.findViewById(R.id.tv_msg); 133 | mTvNotify = view.findViewById(R.id.tv_point); 134 | mTextView = view.findViewById(R.id.tv_text); 135 | 136 | mImageView.setVisibility(!TextUtils.isEmpty(mBuilder.lottieJson) ? GONE : VISIBLE); 137 | mLottieView.setVisibility(!TextUtils.isEmpty(mBuilder.lottieJson) ? VISIBLE : GONE); 138 | 139 | return view; 140 | } 141 | 142 | public String getTitle(){ 143 | return mBuilder.title; 144 | } 145 | 146 | public ImageView getImageView() { 147 | return mImageView; 148 | } 149 | 150 | public TextView getTextView() { 151 | return mTextView; 152 | } 153 | 154 | public void setNormalIcon(Drawable normalIcon) { 155 | mBuilder.normalIcon = normalIcon; 156 | refreshTab(); 157 | } 158 | 159 | public void setNormalIcon(int resId) { 160 | setNormalIcon(UIUtils.getDrawable(context, resId)); 161 | } 162 | 163 | public void setSelectedIcon(Drawable selectedIcon) { 164 | mBuilder.selectedIcon = selectedIcon; 165 | refreshTab(); 166 | } 167 | 168 | public void setSelectedIcon(int resId) { 169 | setSelectedIcon(UIUtils.getDrawable(context, resId)); 170 | } 171 | 172 | public void refreshTab(boolean isSelected) { 173 | setSelected(isSelected); 174 | refreshTab(); 175 | } 176 | 177 | public void refreshTab() { 178 | if (!TextUtils.isEmpty(mBuilder.lottieJson)){ 179 | if (isSelected()){ 180 | mLottieView.playAnimation(); 181 | }else{ 182 | //取消动画 进度设置为0 183 | mLottieView.cancelAnimation(); 184 | mLottieView.setProgress(0); 185 | } 186 | }else{ 187 | mImageView.setImageDrawable(isSelected() ? mBuilder.selectedIcon : mBuilder.normalIcon); 188 | } 189 | 190 | mTextView.setTextColor(isSelected() ? mBuilder.titleSelectedColor : mBuilder.titleNormalColor); 191 | } 192 | 193 | private void setTvVisible(TextView tv) { 194 | //都设置为不可见 195 | mTvUnread.setVisibility(GONE); 196 | mTvMsg.setVisibility(GONE); 197 | mTvNotify.setVisibility(GONE); 198 | 199 | tv.setVisibility(VISIBLE);//设置为可见 200 | } 201 | 202 | public int getUnreadNumThreshold() { 203 | return mBuilder.unreadNumThreshold; 204 | } 205 | 206 | public void setUnreadNumThreshold(int unreadNumThreshold) { 207 | mBuilder.unreadNumThreshold = unreadNumThreshold; 208 | } 209 | 210 | public void setUnreadNum(int unreadNum) { 211 | setTvVisible(mTvUnread); 212 | if (unreadNum <= 0) { 213 | mTvUnread.setVisibility(GONE); 214 | } else if (unreadNum <= mBuilder.unreadNumThreshold) { 215 | mTvUnread.setText(String.valueOf(unreadNum)); 216 | } else { 217 | mTvUnread.setText(String.format(Locale.CHINA, "%d+", mBuilder.unreadNumThreshold)); 218 | } 219 | } 220 | 221 | public void setMsg(String msg) { 222 | setTvVisible(mTvMsg); 223 | mTvMsg.setText(msg); 224 | } 225 | 226 | public void hideMsg() { 227 | mTvMsg.setVisibility(GONE); 228 | } 229 | 230 | public void showNotify() { 231 | setTvVisible(mTvNotify); 232 | } 233 | 234 | public void hideNotify() { 235 | mTvNotify.setVisibility(GONE); 236 | } 237 | 238 | public BottomBarItem create(Builder builder) { 239 | this.context = builder.context; 240 | mBuilder = builder; 241 | checkValues(); 242 | init(); 243 | return this; 244 | } 245 | 246 | public static final class Builder { 247 | private Context context; 248 | private Drawable normalIcon;//普通状态图标的资源id 249 | private Drawable selectedIcon;//选中状态图标的资源id 250 | private String title;//标题 251 | private boolean titleTextBold;//文字加粗 252 | private int titleTextSize;//字体大小 253 | private int titleNormalColor; //描述文本的默认显示颜色 254 | private int titleSelectedColor; //述文本的默认选中显示颜色 255 | private int marginTop;//文字和图标的距离 256 | private int iconWidth;//图标的宽度 257 | private int iconHeight;//图标的高度 258 | private int itemPadding;//BottomBarItem的padding 259 | private int unreadTextSize; //未读数字体大小 260 | private int unreadNumThreshold;//未读数阈值 261 | private int unreadTextColor;//未读数字体颜色 262 | private Drawable unreadTextBg;//未读数文字背景 263 | private int msgTextSize; //消息字体大小 264 | private int msgTextColor;//消息文字颜色 265 | private Drawable msgTextBg;//消息提醒背景颜色 266 | private Drawable notifyPointBg;//小红点背景颜色 267 | private String lottieJson; //lottie文件名 268 | 269 | public Builder(Context context) { 270 | this.context = context; 271 | titleTextBold = false; 272 | titleTextSize = UIUtils.sp2px(context, 12); 273 | titleNormalColor = getColor(R.color.bbl_999999); 274 | titleSelectedColor = getColor(R.color.bbl_ff0000); 275 | unreadTextSize = UIUtils.sp2px(context, 10); 276 | msgTextSize = UIUtils.sp2px(context, 6); 277 | unreadTextColor = getColor(R.color.white); 278 | unreadNumThreshold = 99; 279 | msgTextColor = getColor(R.color.white); 280 | } 281 | 282 | /** 283 | * Sets the default icon's resourceId 284 | */ 285 | public Builder normalIcon(Drawable normalIcon) { 286 | this.normalIcon = normalIcon; 287 | return this; 288 | } 289 | 290 | /** 291 | * Sets the selected icon's resourceId 292 | */ 293 | public Builder selectedIcon(Drawable selectedIcon) { 294 | this.selectedIcon = selectedIcon; 295 | return this; 296 | } 297 | 298 | /** 299 | * Sets the title's resourceId 300 | */ 301 | public Builder title(int titleId) { 302 | this.title = context.getString(titleId); 303 | return this; 304 | } 305 | 306 | /** 307 | * Sets the title string 308 | */ 309 | public Builder title(String title) { 310 | this.title = title; 311 | return this; 312 | } 313 | 314 | /** 315 | * Sets the title's text bold 316 | */ 317 | public Builder titleTextBold(boolean titleTextBold) { 318 | this.titleTextBold = titleTextBold; 319 | return this; 320 | } 321 | 322 | /** 323 | * Sets the title's text size 324 | */ 325 | public Builder titleTextSize(int titleTextSize) { 326 | this.titleTextSize = titleTextSize; 327 | return this; 328 | } 329 | 330 | /** 331 | * Sets the title's normal color resourceId 332 | */ 333 | public Builder titleNormalColor(int titleNormalColor) { 334 | this.titleNormalColor = titleNormalColor; 335 | return this; 336 | } 337 | 338 | /** 339 | * Sets the title's selected color resourceId 340 | */ 341 | public Builder titleSelectedColor(int titleSelectedColor) { 342 | this.titleSelectedColor = titleSelectedColor; 343 | return this; 344 | } 345 | 346 | /** 347 | * Sets the item's margin top 348 | */ 349 | public Builder marginTop(int marginTop) { 350 | this.marginTop = marginTop; 351 | return this; 352 | } 353 | 354 | /** 355 | * Sets icon's width 356 | */ 357 | public Builder iconWidth(int iconWidth) { 358 | this.iconWidth = iconWidth; 359 | return this; 360 | } 361 | 362 | /** 363 | * Sets icon's height 364 | */ 365 | public Builder iconHeight(int iconHeight) { 366 | this.iconHeight = iconHeight; 367 | return this; 368 | } 369 | 370 | 371 | /** 372 | * Sets padding for item 373 | */ 374 | public Builder itemPadding(int itemPadding) { 375 | this.itemPadding = itemPadding; 376 | return this; 377 | } 378 | 379 | /** 380 | * Sets unread font size 381 | */ 382 | public Builder unreadTextSize(int unreadTextSize) { 383 | this.unreadTextSize = unreadTextSize; 384 | return this; 385 | } 386 | 387 | /** 388 | * Sets the number of unread array thresholds greater than the threshold to be displayed as n + n as the set threshold 389 | */ 390 | public Builder unreadNumThreshold(int unreadNumThreshold) { 391 | this.unreadNumThreshold = unreadNumThreshold; 392 | return this; 393 | } 394 | 395 | /** 396 | * Sets the message font size 397 | */ 398 | public Builder msgTextSize(int msgTextSize) { 399 | this.msgTextSize = msgTextSize; 400 | return this; 401 | } 402 | 403 | /** 404 | * Sets the message font background 405 | */ 406 | public Builder unreadTextBg(Drawable unreadTextBg) { 407 | this.unreadTextBg = unreadTextBg; 408 | return this; 409 | } 410 | 411 | /** 412 | * Sets unread font color 413 | */ 414 | public Builder unreadTextColor(int unreadTextColor) { 415 | this.unreadTextColor = unreadTextColor; 416 | return this; 417 | } 418 | 419 | /** 420 | * Sets the message font color 421 | */ 422 | public Builder msgTextColor(int msgTextColor) { 423 | this.msgTextColor =msgTextColor; 424 | return this; 425 | } 426 | 427 | /** 428 | * Sets the message font background 429 | */ 430 | public Builder msgTextBg(Drawable msgTextBg) { 431 | this.msgTextBg = msgTextBg; 432 | return this; 433 | } 434 | 435 | /** 436 | * Set the message prompt point background 437 | */ 438 | public Builder notifyPointBg(Drawable notifyPointBg) { 439 | this.notifyPointBg = notifyPointBg; 440 | return this; 441 | } 442 | 443 | /** 444 | * Set the name of lottie json file 445 | */ 446 | public Builder lottieJson(String lottieJson) { 447 | this.lottieJson = lottieJson; 448 | return this; 449 | } 450 | 451 | /** 452 | * Create a BottomBarItem object 453 | */ 454 | public BottomBarItem create(Drawable normalIcon, Drawable selectedIcon, String text) { 455 | this.normalIcon = normalIcon; 456 | this.selectedIcon = selectedIcon; 457 | title = text; 458 | 459 | BottomBarItem bottomBarItem = new BottomBarItem(context); 460 | return bottomBarItem.create(this); 461 | } 462 | 463 | public BottomBarItem create(int normalIconId, int selectedIconId, String text) { 464 | return create(UIUtils.getDrawable(context, normalIconId), UIUtils.getDrawable(context, selectedIconId), text); 465 | } 466 | 467 | private int getColor(int colorId) { 468 | return context.getResources().getColor(colorId); 469 | } 470 | 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /library/src/main/java/com/chaychan/library/BottomBarLayout.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.library; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.drawable.Drawable; 6 | import android.text.TextUtils; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.Gravity; 10 | import android.view.View; 11 | import android.widget.FrameLayout; 12 | import android.widget.LinearLayout; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import androidx.viewpager.widget.ViewPager; 18 | import androidx.viewpager2.widget.ViewPager2; 19 | 20 | /** 21 | * @author ChayChan 22 | * @description: 底部页签根节点 23 | * @date 2017/6/23 11:02 24 | */ 25 | public class BottomBarLayout extends FrameLayout implements ViewPager.OnPageChangeListener { 26 | 27 | private boolean titleTextBold = false;//文字加粗 28 | private int titleTextSize = 12;//文字大小 默认为12sp 29 | private int titleNormalColor; //描述文本的默认显示颜色 30 | private int titleSelectedColor; //述文本的默认选中显示颜色 31 | private int marginTop = 0;//文字和图标的距离,默认0dp 32 | private int iconWidth;//图标的宽度 33 | private int iconHeight;//图标的高度 34 | private int itemPadding;//BottomBarItem的padding 35 | private int unreadTextSize = 10; //未读数默认字体大小10sp 36 | private int unreadNumThreshold = 99;//未读数阈值 37 | private int unreadTextColor;//未读数字体颜色 38 | private Drawable unreadTextBg;//未读数字体背景 39 | private int msgTextSize = 6; //消息默认字体大小6sp 40 | private int msgTextColor;//消息文字颜色 41 | private Drawable msgTextBg;//消息文字背景 42 | private Drawable notifyPointBg;//小红点背景 43 | 44 | private Drawable barBackground; 45 | private int barHeight = 45;// bar的高度 46 | 47 | private Drawable floatIcon; //凸起图标 48 | private boolean floatEnable; //是否中间图标凸起 49 | private int floatMarginBottom = 0;//凸起按钮底部间距 50 | private int floatIconWidth; //凸起图标的宽度 51 | private int floatIconHeight; //凸起图标的高度 52 | 53 | private ViewPager mViewPager; 54 | private List mItemViews = new ArrayList<>(); 55 | private int mCurrentItem;//当前条目的索引 56 | private boolean mSmoothScroll; 57 | 58 | //相同tab点击是否回调 59 | private boolean mSameTabClickCallBack; 60 | 61 | private ViewPager2 mViewPager2; 62 | 63 | private LinearLayout mLlTab; 64 | 65 | public BottomBarLayout(Context context) { 66 | this(context, null); 67 | } 68 | 69 | public BottomBarLayout(Context context, AttributeSet attrs) { 70 | this(context, attrs, 0); 71 | } 72 | 73 | public BottomBarLayout(Context context, AttributeSet attrs, int defStyleAttr) { 74 | super(context, attrs, defStyleAttr); 75 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BottomBarLayout); 76 | initAttrs(ta, context); 77 | mLlTab = new LinearLayout(context); 78 | mLlTab.setOrientation(LinearLayout.HORIZONTAL); 79 | if (barBackground != null){ 80 | mLlTab.setBackground(barBackground); 81 | }else{ 82 | mLlTab.setBackgroundColor(UIUtils.getColor(context, R.color.tab_gb)); 83 | } 84 | addView(mLlTab); 85 | ta.recycle(); 86 | } 87 | 88 | @Override 89 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 90 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 91 | Log.i("bottomBarLayout", "width: " + getMeasuredWidth() + " height: " + barHeight); 92 | FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(getMeasuredWidth(), barHeight); 93 | params.gravity = Gravity.BOTTOM; 94 | mLlTab.setLayoutParams(params); 95 | } 96 | 97 | private void initAttrs(TypedArray ta, Context context) { 98 | mSmoothScroll = ta.getBoolean(R.styleable.BottomBarLayout_smoothScroll, mSmoothScroll); 99 | mSameTabClickCallBack = ta.getBoolean(R.styleable.BottomBarLayout_sameTabClickCallBack, mSameTabClickCallBack); 100 | barBackground = ta.getDrawable(R.styleable.BottomBarLayout_barBackground); 101 | barHeight = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_barHeight, UIUtils.dip2Px(context, barHeight)); 102 | floatEnable = ta.getBoolean(R.styleable.BottomBarLayout_floatEnable, floatEnable); 103 | floatIcon = ta.getDrawable(R.styleable.BottomBarLayout_floatIcon); 104 | floatMarginBottom = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_floatMarginBottom, UIUtils.dip2Px(context, floatMarginBottom)); 105 | floatIconWidth = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_floatIconWidth, UIUtils.dip2Px(context, floatIconWidth)); 106 | floatIconHeight = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_floatIconHeight, UIUtils.dip2Px(context, floatIconHeight)); 107 | 108 | 109 | titleTextBold = ta.getBoolean(R.styleable.BottomBarLayout_itemTextBold, titleTextBold); 110 | titleTextSize = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_itemTextSize, UIUtils.sp2px(context, titleTextSize)); 111 | 112 | titleNormalColor = ta.getColor(R.styleable.BottomBarLayout_textColorNormal, UIUtils.getColor(context, R.color.bbl_999999)); 113 | titleSelectedColor = ta.getColor(R.styleable.BottomBarLayout_textColorSelected, UIUtils.getColor(context, R.color.bbl_ff0000)); 114 | 115 | marginTop = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_itemMarginTop, UIUtils.dip2Px(context, marginTop)); 116 | 117 | iconWidth = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_iconWidth, 0); 118 | iconHeight = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_iconHeight, 0); 119 | itemPadding = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_itemPadding, 0); 120 | 121 | unreadTextSize = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_unreadTextSize, UIUtils.sp2px(context, unreadTextSize)); 122 | unreadTextColor = ta.getColor(R.styleable.BottomBarLayout_unreadTextColor, UIUtils.getColor(context, R.color.white)); 123 | unreadTextBg = ta.getDrawable(R.styleable.BottomBarLayout_unreadTextBg); 124 | 125 | msgTextSize = ta.getDimensionPixelSize(R.styleable.BottomBarLayout_msgTextSize, UIUtils.sp2px(context, msgTextSize)); 126 | msgTextColor = ta.getColor(R.styleable.BottomBarLayout_msgTextColor, UIUtils.getColor(context, R.color.white)); 127 | msgTextBg = ta.getDrawable(R.styleable.BottomBarLayout_msgTextBg); 128 | 129 | notifyPointBg = ta.getDrawable(R.styleable.BottomBarLayout_notifyPointBg); 130 | 131 | unreadNumThreshold = ta.getInteger(R.styleable.BottomBarLayout_unreadThreshold, unreadNumThreshold); 132 | } 133 | 134 | public void setViewPager(ViewPager viewPager) { 135 | this.mViewPager = viewPager; 136 | 137 | if (mViewPager != null) { 138 | mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 139 | @Override 140 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 141 | } 142 | @Override 143 | public void onPageSelected(int position) { 144 | handlePageSelected(position); 145 | } 146 | @Override 147 | public void onPageScrollStateChanged(int state) { 148 | } 149 | }); 150 | } 151 | } 152 | 153 | public void setViewPager2(androidx.viewpager2.widget.ViewPager2 viewPager2) { 154 | this.mViewPager2 = viewPager2; 155 | 156 | if (mViewPager2 != null) { 157 | mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { 158 | @Override 159 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 160 | } 161 | @Override 162 | public void onPageSelected(int position) { 163 | handlePageSelected(position); 164 | } 165 | @Override 166 | public void onPageScrollStateChanged(int state) { 167 | } 168 | }); 169 | } 170 | } 171 | 172 | private BottomBarItem createBottomBarItem(Drawable normalIcon, Drawable selectedIcon, String title, int iconWidth, int iconHeight, String lottieJson){ 173 | return new BottomBarItem.Builder(getContext()) 174 | .titleTextBold(titleTextBold) 175 | .titleTextSize(titleTextSize) 176 | .titleNormalColor(titleNormalColor) 177 | .iconHeight(iconHeight) 178 | .iconWidth(iconWidth) 179 | .marginTop(marginTop) 180 | .itemPadding(itemPadding) 181 | .titleSelectedColor(titleSelectedColor) 182 | .lottieJson(lottieJson) 183 | .unreadNumThreshold(unreadNumThreshold) 184 | .unreadTextBg(unreadTextBg) 185 | .unreadTextSize(unreadTextSize) 186 | .unreadTextColor(unreadTextColor) 187 | .msgTextBg(msgTextBg) 188 | .msgTextColor(msgTextColor) 189 | .msgTextSize(msgTextSize) 190 | .notifyPointBg(notifyPointBg) 191 | .create(normalIcon, selectedIcon, title); 192 | } 193 | 194 | public void setData(List tabData){ 195 | if (tabData == null || tabData.size() == 0){ 196 | throw new IllegalArgumentException("tabData is null"); 197 | } 198 | mItemViews.clear(); 199 | mLlTab.removeAllViews(); 200 | 201 | //添加tab 202 | for (int i = 0; i < tabData.size(); i++) { 203 | TabData itemData = tabData.get(i); 204 | Drawable normalIcon = !TextUtils.isEmpty(itemData.getLottieJson()) ? null : itemData.getNormalIcon() != null ? itemData.getNormalIcon() : getContext().getResources().getDrawable(itemData.getNormalIconResId()); 205 | Drawable selectedIcon = !TextUtils.isEmpty(itemData.getLottieJson()) ? null : itemData.getSelectedIcon() != null ? itemData.getSelectedIcon() : getContext().getResources().getDrawable(itemData.getSelectedIconResId()); 206 | int iconWidth = itemData.getIconWidth() == 0 ? this.iconWidth : itemData.getIconWidth(); 207 | int iconHeight = itemData.getIconHeight() == 0 ? this.iconHeight : itemData.getIconHeight(); 208 | BottomBarItem item = createBottomBarItem(normalIcon, selectedIcon, itemData.getTitle(), iconWidth, iconHeight, itemData.getLottieJson()); 209 | addItem(item); 210 | } 211 | 212 | //如果开启凸起 且是 其他tab总数是偶数 213 | if (floatEnable && tabData.size() % 2 == 0){ 214 | BottomBarItem item = createBottomBarItem(floatIcon, floatIcon, "", floatIconWidth, floatIconHeight, ""); 215 | addItem(item, (tabData.size() + 1) / 2, true); 216 | } 217 | 218 | mItemViews.get(0).refreshTab(true); 219 | } 220 | 221 | public void addItem(BottomBarItem item){ 222 | addItem(item, -1, false); 223 | } 224 | 225 | public void addItem(BottomBarItem item, int index, boolean isFloatItem) { 226 | if (index == -1){ 227 | mItemViews.add(item); 228 | }else{ 229 | mItemViews.add(index, item); 230 | } 231 | 232 | int position = index != -1 ? index : mItemViews.size() - 1; 233 | Log.e("bottomBarLayout", "position: " + position); 234 | 235 | View view = item; 236 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT); 237 | layoutParams.weight = 1; 238 | layoutParams.gravity = Gravity.CENTER; 239 | view.setLayoutParams(layoutParams); 240 | 241 | if (isFloatItem){ 242 | FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(floatIconWidth, floatIconHeight); 243 | params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; 244 | params.bottomMargin = floatMarginBottom; 245 | addView(item, params); 246 | view = new View(getContext()); 247 | } 248 | 249 | mLlTab.addView(view, position, layoutParams); 250 | 251 | //tab添加点击事件 252 | for (int i = 0; i < mItemViews.size(); i++) { 253 | mItemViews.get(i).setOnClickListener(new MyOnClickListener(i)); 254 | } 255 | } 256 | 257 | public void removeItem(int position) { 258 | if (position >= 0 && position < mItemViews.size()) { 259 | BottomBarItem item = mItemViews.get(position); 260 | if (mItemViews.contains(item)) { 261 | resetState(); 262 | mLlTab.removeViewAt(position); 263 | } 264 | mItemViews.remove(item); 265 | 266 | //tab添加点击事件 267 | for (int i = 0; i < mItemViews.size(); i++) { 268 | mItemViews.get(i).setOnClickListener(new MyOnClickListener(i)); 269 | } 270 | } 271 | } 272 | 273 | private void handlePageSelected(int position){ 274 | //滑动时判断是否需要拦截跳转 275 | if (mOnPageChangeInterceptor != null 276 | && mOnPageChangeInterceptor.onIntercepted(position)){ 277 | setCurrentItem(mCurrentItem); 278 | return; 279 | } 280 | resetState(); 281 | mItemViews.get(position).refreshTab(true); 282 | int prePos = mCurrentItem; 283 | mCurrentItem = position;//记录当前位置 284 | if (onItemSelectedListener != null) { 285 | onItemSelectedListener.onItemSelected(getBottomItem(mCurrentItem), prePos, mCurrentItem); 286 | } 287 | } 288 | 289 | @Override 290 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 291 | 292 | } 293 | 294 | @Override 295 | public void onPageSelected(int position) { 296 | handlePageSelected(position); 297 | } 298 | 299 | @Override 300 | public void onPageScrollStateChanged(int state) { 301 | 302 | } 303 | 304 | private class MyOnClickListener implements OnClickListener { 305 | 306 | private int currentIndex; 307 | 308 | public MyOnClickListener(int i) { 309 | this.currentIndex = i; 310 | } 311 | 312 | @Override 313 | public void onClick(View v) { 314 | //点击时判断是否需要拦截跳转 315 | if (mOnPageChangeInterceptor != null 316 | && mOnPageChangeInterceptor.onIntercepted(currentIndex)){ 317 | return; 318 | } 319 | if (currentIndex == mCurrentItem) { 320 | //如果还是同个页签,判断是否要回调 321 | if (onItemSelectedListener != null && mSameTabClickCallBack){ 322 | onItemSelectedListener.onItemSelected(getBottomItem(currentIndex), mCurrentItem, currentIndex); 323 | } 324 | }else{ 325 | if (mViewPager != null || mViewPager2 != null) { 326 | if (mViewPager != null){ 327 | mViewPager.setCurrentItem(currentIndex, mSmoothScroll); 328 | }else { 329 | mViewPager2.setCurrentItem(currentIndex, mSmoothScroll); 330 | } 331 | return; 332 | } 333 | if (onItemSelectedListener != null){ 334 | onItemSelectedListener.onItemSelected(getBottomItem(currentIndex), mCurrentItem, currentIndex); 335 | } 336 | updateTabState(currentIndex); 337 | } 338 | } 339 | } 340 | 341 | private void updateTabState(int position) { 342 | resetState(); 343 | mCurrentItem = position; 344 | mItemViews.get(mCurrentItem).refreshTab(true); 345 | } 346 | 347 | /** 348 | * 重置当前按钮的状态 349 | */ 350 | private void resetState() { 351 | if (mCurrentItem < mItemViews.size()) { 352 | if (mItemViews.get(mCurrentItem).isSelected()){ 353 | mItemViews.get(mCurrentItem).refreshTab(false); 354 | } 355 | } 356 | } 357 | 358 | public void setCurrentItem(int currentItem) { 359 | if (mViewPager != null || mViewPager2 != null) { 360 | if (mViewPager != null){ 361 | mViewPager.setCurrentItem(currentItem, mSmoothScroll); 362 | }else { 363 | mViewPager2.setCurrentItem(currentItem, mSmoothScroll); 364 | } 365 | } else { 366 | if (onItemSelectedListener != null) { 367 | onItemSelectedListener.onItemSelected(getBottomItem(currentItem), mCurrentItem, currentItem); 368 | } 369 | updateTabState(currentItem); 370 | } 371 | } 372 | 373 | /** 374 | * 设置未读数 375 | * 376 | * @param position 底部标签的下标 377 | * @param unreadNum 未读数 378 | */ 379 | public void setUnread(int position, int unreadNum) { 380 | mItemViews.get(position).setUnreadNum(unreadNum); 381 | } 382 | 383 | /** 384 | * 设置提示消息 385 | * 386 | * @param position 底部标签的下标 387 | * @param msg 未读数 388 | */ 389 | public void setMsg(int position, String msg) { 390 | mItemViews.get(position).setMsg(msg); 391 | } 392 | 393 | /** 394 | * 隐藏提示消息 395 | * 396 | * @param position 底部标签的下标 397 | */ 398 | public void hideMsg(int position) { 399 | mItemViews.get(position).hideMsg(); 400 | } 401 | 402 | /** 403 | * 显示提示的小红点 404 | * 405 | * @param position 底部标签的下标 406 | */ 407 | public void showNotify(int position) { 408 | mItemViews.get(position).showNotify(); 409 | } 410 | 411 | /** 412 | * 隐藏提示的小红点 413 | * 414 | * @param position 底部标签的下标 415 | */ 416 | public void hideNotify(int position) { 417 | mItemViews.get(position).hideNotify(); 418 | } 419 | 420 | public int getCurrentItem() { 421 | return mCurrentItem; 422 | } 423 | 424 | public void setSmoothScroll(boolean smoothScroll) { 425 | this.mSmoothScroll = smoothScroll; 426 | } 427 | 428 | public BottomBarItem getBottomItem(int position) { 429 | return mItemViews.get(position); 430 | } 431 | 432 | private OnItemSelectedListener onItemSelectedListener; 433 | 434 | public interface OnItemSelectedListener { 435 | void onItemSelected(BottomBarItem bottomBarItem, int previousPosition, int currentPosition); 436 | } 437 | 438 | public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) { 439 | this.onItemSelectedListener = onItemSelectedListener; 440 | } 441 | 442 | private OnPageChangeInterceptor mOnPageChangeInterceptor; 443 | 444 | public void setOnPageChangeInterceptor(OnPageChangeInterceptor onPageChangedInterceptor) { 445 | mOnPageChangeInterceptor = onPageChangedInterceptor; 446 | } 447 | 448 | public interface OnPageChangeInterceptor { 449 | boolean onIntercepted(int position); 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /library/src/main/java/com/chaychan/library/MyLottieAnimationView.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.library; 2 | 3 | import android.content.Context; 4 | import android.os.Parcelable; 5 | import android.util.AttributeSet; 6 | 7 | import com.airbnb.lottie.LottieAnimationView; 8 | 9 | /** 10 | * @author ChayChan 11 | * @description: 去除LottieAnimationView的缓存 12 | * @date 2020/11/23 16:02 13 | */ 14 | class MyLottieAnimationView extends LottieAnimationView { 15 | 16 | public MyLottieAnimationView(Context context) { 17 | super(context); 18 | } 19 | 20 | public MyLottieAnimationView(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | } 23 | 24 | public MyLottieAnimationView(Context context, AttributeSet attrs, int defStyleAttr) { 25 | super(context, attrs, defStyleAttr); 26 | } 27 | 28 | /** 29 | * 重写此方法将LottieAnimationView的缓存去除 30 | * 解决因异常情况或旋转方向后页面重新加载 31 | * 导致lottie文件读取成最后一个tab文件的bug 32 | * @return 33 | */ 34 | @Override 35 | protected Parcelable onSaveInstanceState() { 36 | Parcelable parcelable = super.onSaveInstanceState(); 37 | parcelable = null; 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/java/com/chaychan/library/TabData.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.library; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | /** 6 | * @author chay 7 | * @description: 8 | * @date 2024/7/14 16:58 9 | */ 10 | public class TabData { 11 | private String title; 12 | 13 | private int normalIconResId; 14 | private Drawable normalIcon; 15 | 16 | private int selectedIconResId; 17 | private Drawable selectedIcon; 18 | 19 | private String lottieJson; 20 | 21 | private int iconWidth; 22 | private int iconHeight; 23 | 24 | public TabData(String title, Drawable normalIcon, Drawable selectedIcon){ 25 | this.title = title; 26 | this.normalIcon = normalIcon; 27 | this.selectedIcon = selectedIcon; 28 | } 29 | 30 | public TabData(String title, int normalIconResId, int selectedIconResId){ 31 | this.title = title; 32 | this.normalIconResId = normalIconResId; 33 | this.selectedIconResId = selectedIconResId; 34 | } 35 | 36 | public TabData(String title, String lottieJson){ 37 | this.title = title; 38 | this.lottieJson = lottieJson; 39 | } 40 | 41 | public String getLottieJson() { 42 | return lottieJson; 43 | } 44 | 45 | public void setLottieJson(String lottieJson) { 46 | this.lottieJson = lottieJson; 47 | } 48 | 49 | public int getIconWidth() { 50 | return iconWidth; 51 | } 52 | 53 | public void setIconWidth(int iconWidth) { 54 | this.iconWidth = iconWidth; 55 | } 56 | 57 | public int getIconHeight() { 58 | return iconHeight; 59 | } 60 | 61 | public void setIconHeight(int iconHeight) { 62 | this.iconHeight = iconHeight; 63 | } 64 | 65 | public int getNormalIconResId() { 66 | return normalIconResId; 67 | } 68 | 69 | public void setNormalIconResId(int normalIconResId) { 70 | this.normalIconResId = normalIconResId; 71 | } 72 | 73 | public int getSelectedIconResId() { 74 | return selectedIconResId; 75 | } 76 | 77 | public void setSelectedIconResId(int selectedIconResId) { 78 | this.selectedIconResId = selectedIconResId; 79 | } 80 | 81 | public String getTitle() { 82 | return title; 83 | } 84 | 85 | public void setTitle(String title) { 86 | this.title = title; 87 | } 88 | 89 | public Drawable getNormalIcon() { 90 | return normalIcon; 91 | } 92 | 93 | public void setNormalIcon(Drawable normalIcon) { 94 | this.normalIcon = normalIcon; 95 | } 96 | 97 | public Drawable getSelectedIcon() { 98 | return selectedIcon; 99 | } 100 | 101 | public void setSelectedIcon(Drawable selectedIcon) { 102 | this.selectedIcon = selectedIcon; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /library/src/main/java/com/chaychan/library/UIUtils.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.library; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | 6 | /** 7 | * @author chaychan 8 | * @date 2017/3/7 17:19 9 | */ 10 | public class UIUtils { 11 | /** 12 | * dip-->px 13 | */ 14 | public static int dip2Px(Context context,int dip) { 15 | // px/dip = density; 16 | // density = dpi/160 17 | // 320*480 density = 1 1px = 1dp 18 | // 1280*720 density = 2 2px = 1dp 19 | 20 | float density = context.getResources().getDisplayMetrics().density; 21 | int px = (int) (dip * density + 0.5f); 22 | return px; 23 | } 24 | 25 | /** 26 | * 将sp值转换为px值,保证文字大小不变 27 | * 28 | * @param spValue 29 | * @return 30 | */ 31 | public static int sp2px(Context context,float spValue) { 32 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 33 | return (int) (spValue * fontScale + 0.5f); 34 | } 35 | 36 | public static int getColor(Context context,int colorId){ 37 | return context.getResources().getColor(colorId); 38 | } 39 | 40 | public static Drawable getDrawable(Context context,int resId){ 41 | return context.getResources().getDrawable(resId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/shape_msg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/shape_notify_point.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/shape_unread.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/layout/item_bottom_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 13 | 14 | 20 | 21 | 27 | 28 | 41 | 42 | 54 | 55 | 65 | 66 | 67 | 68 | 69 | 74 | 75 | -------------------------------------------------------------------------------- /library/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #999999 4 | #ff0000 5 | 6 | #ffffff 7 | #DDDDDD 8 | #F3F5F4 9 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | library 3 | 4 | -------------------------------------------------------------------------------- /library/src/test/java/com/chaychan/library/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.chaychan.library; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':demo', ':library' 2 | -------------------------------------------------------------------------------- /update-note-en.md: -------------------------------------------------------------------------------- 1 | ### V3.0.0 版本更新(2024-07-17) 2 | - Refactored BottomBarLayout, no need to specify item layout in xml, import tab by setting data source; 3 | - Support middle icon bulge, middle icon width and height setting, height from bottom 4 | 5 | ### V2.1.0 版本更新说明(2024-07-10) 6 | 7 | - Support ViewPager2 8 | - Add a pre-jump interceptor 9 | 10 | ### V2.0.0 update instructions (2020-11-21) 11 | 12 | - Migrate to AndroidX 13 | - Support to use lottie 14 | 15 | ### V1.1.2 update instructions (2018-12-13) 16 | 17 | - Support for dynamically adding and removing item 18 | 19 | ### V1.1.2 update instructions(2018-03-20) 20 | 21 | - Add attributes to modify unread font colors and background 22 | 23 | - Add attributes to modify prompt text font colors and background 24 | 25 | - Add attributes to modify prompt point background 26 | 27 | 28 | ### V1.1.1 update instructions(2018-02-27) 29 | 30 | - Repair must be set up ViewPager problems, can be modified to set or not set; 31 | 32 | - Modify the click callback, callback more than a previousPosition (last page location) 33 | 34 | - Add two usage patterns 35 | 36 | 37 | ### V1.0.7 update instructions(2018-01-05) 38 | 39 | - Increase the threshold for unreadable attributes and set the properties for smooth transitions; 40 | 41 | - Remove oriention restrictions。 42 | 43 | ### V1.0.6 update instructions(2017-12-19) 44 | 45 | - Add slide listener, callback onItemSelected () 46 | 47 | ### V1.0.4 update instructions(2017-10-10) 48 | 49 | - Increase the number of unread, tips red dot, suggesting the function of the message -------------------------------------------------------------------------------- /update-note.md: -------------------------------------------------------------------------------- 1 | ### V3.0.0 版本更新(2024-07-17) 2 | - 重构BottomBarLayout,xml中不需要指定item的布局,通过设置数据源导入tab; 3 | - 支持中间图标凸起、中间图标宽高设置、距离底部高度等 4 | 5 | 6 | ### V2.1.0 版本更新说明(2024-07-10) 7 | 8 | - 支持ViewPager2 9 | - 添加跳转前拦截监听 10 | 11 | ### V2.0.0版本更新说明 (2020-11-21) 12 | 13 | - 迁移至AndroidX 14 | - 支持lottie 15 | 16 | ### V1.2.0版本更新说明 (2018-12-13) 17 | 18 | - 支持动态添加、移除条目 19 | 20 | ### V1.1.2版本更新说明(2018-03-20) 21 | 22 | - 添加修改未读数字体颜色和背景的属性 23 | 24 | - 添加修改提示文字字体颜色和背景的属性 25 | 26 | - 添加修改提示点背景的属性 27 | 28 | ### V1.1.1版本更新说明(2018-02-27) 29 | 30 | - 修复一定要设置ViewPager的问题,修改成可设置或不设置; 31 | 32 | - 修改点击回调,回调多一个previousPosition(上个页签的位置) 33 | 34 | - 添加两种使用方式的demo演示 35 | 36 | 37 | ### V1.0.7版本更新说明(2018-01-05) 38 | 39 | - 增加未读数阈值属性和设置是否平滑切换的属性; 40 | 41 | - 去除oriention的限制。 42 | 43 | ### V1.0.6版本更新说明(2017-12-19) 44 | 45 | - 添加滑动监听,回调onItemSelected() 46 | 47 | ### V1.0.4版本更新说明(2017-10-10) 48 | 49 | - 增加未读数、提示小红点、提示消息的功能 --------------------------------------------------------------------------------