├── .github └── workflows │ └── maven.yml ├── .gradle ├── 3.3 │ ├── taskArtifacts │ │ ├── fileHashes.bin │ │ ├── fileSnapshots.bin │ │ ├── taskArtifacts.bin │ │ └── taskArtifacts.lock │ └── tasks │ │ ├── _app_compileDebugJavaWithJavac │ │ ├── localClassSetAnalysis │ │ │ ├── localClassSetAnalysis.bin │ │ │ └── localClassSetAnalysis.lock │ │ └── localJarClasspathSnapshot │ │ │ ├── localJarClasspathSnapshot.bin │ │ │ └── localJarClasspathSnapshot.lock │ │ ├── _tabLayoutLibrary_compileDebugJavaWithJavac │ │ ├── localClassSetAnalysis │ │ │ ├── localClassSetAnalysis.bin │ │ │ └── localClassSetAnalysis.lock │ │ └── localJarClasspathSnapshot │ │ │ ├── localJarClasspathSnapshot.bin │ │ │ └── localJarClasspathSnapshot.lock │ │ └── _tabLayoutLibrary_compileReleaseJavaWithJavac │ │ ├── localClassSetAnalysis │ │ ├── localClassSetAnalysis.bin │ │ └── localClassSetAnalysis.lock │ │ └── localJarClasspathSnapshot │ │ ├── localJarClasspathSnapshot.bin │ │ └── localJarClasspathSnapshot.lock ├── 4.6 │ ├── fileChanges │ │ └── last-build.bin │ ├── fileContent │ │ └── fileContent.lock │ ├── fileHashes │ │ ├── fileHashes.bin │ │ ├── fileHashes.lock │ │ └── resourceHashesCache.bin │ ├── javaCompile │ │ ├── classAnalysis.bin │ │ ├── jarAnalysis.bin │ │ ├── javaCompile.lock │ │ ├── taskHistory.bin │ │ └── taskJars.bin │ └── taskHistory │ │ ├── taskHistory.bin │ │ └── taskHistory.lock ├── buildOutputCleanup │ ├── buildOutputCleanup.lock │ ├── cache.properties │ └── outputFiles.bin └── vcsWorkingDirs │ └── gc.properties ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── stefan │ │ └── easybehavior │ │ ├── Demo1Activity.java │ │ ├── StartActivity.java │ │ ├── demo1 │ │ ├── behavior │ │ │ ├── AppBarLayoutOverScrollViewBehavior.java │ │ │ └── CircleImageInUsercBehavior.java │ │ └── widget │ │ │ ├── NoScrollViewPager.java │ │ │ └── RoundProgressBar.java │ │ ├── fragment │ │ ├── ItemFragment1.java │ │ ├── MyFragmentPagerAdapter.java │ │ ├── MyItemRecyclerViewAdapter.java │ │ └── dummy │ │ │ ├── DummyContent.java │ │ │ └── TabEntity.java │ │ └── widget │ │ ├── CircleImageView.java │ │ └── DisInterceptNestedScrollView.java │ └── res │ ├── drawable-xxhdpi │ ├── ic_avater.jpg │ ├── ic_bg.jpg │ ├── icon_msg.png │ ├── icon_msg_black.png │ ├── icon_setting.png │ └── icon_setting_black.png │ ├── drawable │ ├── divider.xml │ ├── ic_ali_middle.png │ ├── ic_ali_top.png │ ├── selector_stroke_roundcir.xml │ └── selector_stroke_roundredcir.xml │ ├── layout │ ├── activity_demo1.xml │ ├── activity_start.xml │ ├── fragment_item.xml │ ├── fragment_item_list1.xml │ ├── fragment_item_list2.xml │ ├── layout_func_in_uc.xml │ ├── layout_uc_content.xml │ ├── layout_uc_head_bg.xml │ ├── layout_uc_head_middle.xml │ └── layout_uc_head_title.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 │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── config.gradle ├── gif ├── Coali.gif └── EasyBehavior.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── tabLayoutLibrary ├── build.gradle └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── flyco │ └── tablayout │ ├── CommonTabLayout.java │ ├── SegmentTabLayout.java │ ├── SlidingTabLayout.java │ ├── listener │ ├── CustomTabEntity.java │ └── OnTabSelectListener.java │ ├── utils │ ├── FragmentChangeManager.java │ └── UnreadMsgUtils.java │ └── widget │ └── MsgView.java └── res ├── layout ├── layout_tab.xml ├── layout_tab_bottom.xml ├── layout_tab_left.xml ├── layout_tab_right.xml ├── layout_tab_segment.xml └── layout_tab_top.xml └── values └── attrs.xml /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | -------------------------------------------------------------------------------- /.gradle/3.3/taskArtifacts/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/taskArtifacts/fileHashes.bin -------------------------------------------------------------------------------- /.gradle/3.3/taskArtifacts/fileSnapshots.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/taskArtifacts/fileSnapshots.bin -------------------------------------------------------------------------------- /.gradle/3.3/taskArtifacts/taskArtifacts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/taskArtifacts/taskArtifacts.bin -------------------------------------------------------------------------------- /.gradle/3.3/taskArtifacts/taskArtifacts.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/taskArtifacts/taskArtifacts.lock -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_tabLayoutLibrary_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_tabLayoutLibrary_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_tabLayoutLibrary_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_tabLayoutLibrary_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_tabLayoutLibrary_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_tabLayoutLibrary_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_tabLayoutLibrary_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_tabLayoutLibrary_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_tabLayoutLibrary_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_tabLayoutLibrary_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_tabLayoutLibrary_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_tabLayoutLibrary_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_tabLayoutLibrary_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_tabLayoutLibrary_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin -------------------------------------------------------------------------------- /.gradle/3.3/tasks/_tabLayoutLibrary_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/3.3/tasks/_tabLayoutLibrary_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock -------------------------------------------------------------------------------- /.gradle/4.6/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gradle/4.6/fileContent/fileContent.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/fileContent/fileContent.lock -------------------------------------------------------------------------------- /.gradle/4.6/fileHashes/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/fileHashes/fileHashes.bin -------------------------------------------------------------------------------- /.gradle/4.6/fileHashes/fileHashes.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/fileHashes/fileHashes.lock -------------------------------------------------------------------------------- /.gradle/4.6/fileHashes/resourceHashesCache.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/fileHashes/resourceHashesCache.bin -------------------------------------------------------------------------------- /.gradle/4.6/javaCompile/classAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/javaCompile/classAnalysis.bin -------------------------------------------------------------------------------- /.gradle/4.6/javaCompile/jarAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/javaCompile/jarAnalysis.bin -------------------------------------------------------------------------------- /.gradle/4.6/javaCompile/javaCompile.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/javaCompile/javaCompile.lock -------------------------------------------------------------------------------- /.gradle/4.6/javaCompile/taskHistory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/javaCompile/taskHistory.bin -------------------------------------------------------------------------------- /.gradle/4.6/javaCompile/taskJars.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/javaCompile/taskJars.bin -------------------------------------------------------------------------------- /.gradle/4.6/taskHistory/taskHistory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/taskHistory/taskHistory.bin -------------------------------------------------------------------------------- /.gradle/4.6/taskHistory/taskHistory.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/4.6/taskHistory/taskHistory.lock -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/buildOutputCleanup.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/buildOutputCleanup/buildOutputCleanup.lock -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/cache.properties: -------------------------------------------------------------------------------- 1 | #Fri Feb 28 14:46:12 CST 2020 2 | gradle.version=4.6 3 | -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/outputFiles.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/buildOutputCleanup/outputFiles.bin -------------------------------------------------------------------------------- /.gradle/vcsWorkingDirs/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/.gradle/vcsWorkingDirs/gc.properties -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyBehavior 2 | - 如果你正苦于实现一个酷炫的个人信息页面效果 3 | - 如果产品要求你实现下拉放大背景图,上滑能看到详细信息 4 | - 如果还要求一系列同步动画效果 5 | - 通过Behavior实现它将是你的不二选择,本项目旨在帮助各位轻松实现自己的Behavior! 6 | ---------- 7 | ### 注意: 8 | - demo2已在master分支中移除,需要的话请前往backupv1分支,切换版本到低于26 9 | - androidx适配版本已发布,请拉取androidx分支 10 | ---------- 11 | ## 博文地址 12 |  [例子1 链接](http://blog.csdn.net/gjm15881133824/article/details/73742219) 13 |  [例子2 链接](http://blog.csdn.net/gjm15881133824/article/details/74946322) 14 | 15 | ---------- 16 | ## DEMO下载 17 | https://fir.im/ckh1 18 | ---------- 19 | ## 效果图 20 | ![EasyBehavior](/gif/EasyBehavior.gif) 21 | ![CoAliBehavior](/gif/Coali.gif) 22 | ---------- 23 | ## 例子的实现 24 | 注意:以下内容可能引起您的轻度不适(xing fen),请慎重阅读,例子中呢,用到了两个Behavior。

25 | 1.用户头像的放大以及缩小,按照上面的方法,我们可以很明白的知道实现步骤了 26 | 27 | 28 | - 继承 29 | 30 | ``` 31 | public class CircleImageInUsercBehavior extends CoordinatorLayout.Behavior { 32 | ``` 33 | - 重写onDependentViewChanged, 34 | ``` 35 | //当dependency变化的时候调用 36 | @Override 37 | public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) { 38 | //初始化一些基础参数 39 | init(parent, child, dependency); 40 | //计算比例 41 | ... 42 | //设置头像的大小 43 | ViewCompat.setScaleX(child, percent); 44 | ViewCompat.setScaleY(child, percent); 45 | return false; 46 | } 47 | 48 | ``` 49 | 啊?这样就搞定了?是的!就是这么easy!!
50 | ![这里写图片描述](http://img.blog.csdn.net/20170627112207279?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ2ptMTU4ODExMzM4MjQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
51 | **那我有一个问题了,是不是说每一个view想要做跟随动画,都得创建一个相应的Behavior呢?答案很明显是NO~!**
52 | 看完下一个例子你就会明白了
53 | 54 | ---------- 55 | 56 | 2.这个Behavior用途主要有以下3点: 57 | 58 | - 控制背景图的放大以及回弹 59 | - 中间middle部分跟随背景图的放大缩小做相应的移动 60 | - Toolbar的背景Alpha的改变 61 | 62 | 第一步:初始化参数,通过tag查找每一个View,这里需要注意,我们需要在布局文件中,每个相应的View都需要声明相同的tag 如 `android:tag="你的tag"`,当然,也可以用原始的findViewById,这里只是希望id改动时,我们的Behavior可以不受到影响 63 | 64 | ``` 65 | @Override 66 | public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) { 67 | ... 68 | if (mToolBar == null) { 69 | mToolBar = (Toolbar) parent.findViewWithTag(TAG_TOOLBAR); 70 | } 71 | ... 72 | abl.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { 73 | ...//实现Toolbar的背景变化 74 | }); 75 | ... 76 | } 77 | 78 | ``` 79 | 第二步:开始scale动画(下拉上划滑动过程中) 80 | 81 | ``` 82 | @Override 83 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) { 84 | if (!isRecovering) {//未在回弹动画中,开始我们的变化动画 85 | if (...) { 86 | scale(child, target, dy); 87 | return; 88 | } 89 | } 90 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); 91 | 92 | @Override 93 | public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) { 94 | if (velocityY > 100) {//当y速度>100,就秒弹回 95 | isAnimate = false; 96 | } 97 | return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); 98 | } 99 | } 100 | ``` 101 | 第三步:松手的回弹 102 | 103 | ``` 104 | @Override 105 | public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) { 106 | recovery(abl);//回弹,这个方法详细请看源码 107 | super.onStopNestedScroll(coordinatorLayout, abl, target); 108 | } 109 | ``` 110 | 111 | ok,步骤就是这样,是不是很easy呢? 112 | 113 | 114 | ---------- 115 | 116 | 117 | 附:AppBarLayout的跟随动画,不仅仅是上面的一种方式 118 | 我们也可以在逻辑代码中通过原生的Listener来实现 119 | 120 | ``` 121 | mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { 122 | @Override 123 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { 124 | //计算进度百分比 125 | float percent = Float.valueOf(Math.abs(verticalOffset)) / Float.valueOf(appBarLayout.getTotalScrollRange()); 126 | ...//根据百分比做你想做的 127 | } 128 | }); 129 | ``` 130 | ## License 131 | -------- 132 | ``` 133 | Copyright (C) 2017 JmStefanAndroid 134 | 135 | Licensed under the Apache License, Version 2.0 (the "License"); 136 | you may not use this file except in compliance with the License. 137 | You may obtain a copy of the License at 138 | 139 | http://www.apache.org/licenses/LICENSE-2.0 140 | 141 | Unless required by applicable law or agreed to in writing, software 142 | distributed under the License is distributed on an "AS IS" BASIS, 143 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 144 | See the License for the specific language governing permissions and 145 | limitations under the License. 146 | ``` 147 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def libs = rootProject.ext.libraries 4 | def testLibs = rootProject.ext.testingLibraries 5 | def cfg = rootProject.ext.configuration 6 | 7 | android { 8 | 9 | compileSdkVersion ANDROID_BUILD_SDK_VERSION as int 10 | buildToolsVersion ANDROID_BUILD_TOOLS_VERSION 11 | 12 | defaultConfig { 13 | minSdkVersion ANDROID_BUILD_MIN_SDK_VERSION as int 14 | targetSdkVersion ANDROID_BUILD_TARGET_SDK_VERSION as int 15 | versionCode VERSION_CODE as int 16 | versionName VERSION_NAME 17 | applicationId cfg.applicationId 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | //请在使用demo或相同配置时一定要加上这句 26 | aaptOptions { 27 | cruncherEnabled = false 28 | useNewCruncher = false 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(include: ['*.jar'], dir: 'libs') 34 | implementation libs["appcompat-v7"] 35 | implementation libs["design"] 36 | implementation libs["glide"] 37 | implementation libs["statusbar_util"] 38 | implementation project(':tabLayoutLibrary') 39 | implementation libs["support-v4"] 40 | implementation libs["recyclerview-v7"] 41 | } 42 | -------------------------------------------------------------------------------- /app/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 /Users/meierbei/Library/Android/sdk/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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/Demo1Activity.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.design.widget.AppBarLayout; 7 | import android.support.design.widget.CollapsingToolbarLayout; 8 | import android.support.design.widget.CoordinatorLayout; 9 | import android.support.v4.app.Fragment; 10 | import android.support.v4.view.ViewPager; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.support.v7.widget.Toolbar; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.ImageView; 16 | 17 | import com.flyco.tablayout.CommonTabLayout; 18 | import com.flyco.tablayout.listener.CustomTabEntity; 19 | import com.flyco.tablayout.listener.OnTabSelectListener; 20 | import com.jaeger.library.StatusBarUtil; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Random; 25 | 26 | import me.stefan.easybehavior.demo1.behavior.AppBarLayoutOverScrollViewBehavior; 27 | import me.stefan.easybehavior.demo1.widget.NoScrollViewPager; 28 | import me.stefan.easybehavior.demo1.widget.RoundProgressBar; 29 | import me.stefan.easybehavior.fragment.ItemFragment1; 30 | import me.stefan.easybehavior.fragment.MyFragmentPagerAdapter; 31 | import me.stefan.easybehavior.fragment.dummy.TabEntity; 32 | import me.stefan.easybehavior.widget.CircleImageView; 33 | 34 | /** 35 | * @author stefan 邮箱:648701906@qq.com 36 | */ 37 | public class Demo1Activity extends AppCompatActivity { 38 | 39 | private ImageView mZoomIv; 40 | private Toolbar mToolBar; 41 | private ViewGroup titleContainer; 42 | private AppBarLayout mAppBarLayout; 43 | private ViewGroup titleCenterLayout; 44 | private RoundProgressBar progressBar; 45 | private ImageView mSettingIv, mMsgIv; 46 | private CircleImageView mAvater; 47 | private CommonTabLayout mTablayout; 48 | private NoScrollViewPager mViewPager; 49 | 50 | private ArrayList mTabEntities = new ArrayList<>(); 51 | private List fragments; 52 | private int lastState = 1; 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | setContentView(R.layout.activity_demo1); 58 | 59 | findId(); 60 | initListener(); 61 | initTab(); 62 | initStatus(); 63 | 64 | } 65 | 66 | /** 67 | * 初始化id 68 | */ 69 | private void findId() { 70 | mZoomIv = (ImageView) findViewById(R.id.uc_zoomiv); 71 | mToolBar = (Toolbar) findViewById(R.id.toolbar); 72 | titleContainer = (ViewGroup) findViewById(R.id.title_layout); 73 | mAppBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout); 74 | titleCenterLayout = (ViewGroup) findViewById(R.id.title_center_layout); 75 | progressBar = (RoundProgressBar) findViewById(R.id.uc_progressbar); 76 | mSettingIv = (ImageView) findViewById(R.id.uc_setting_iv); 77 | mMsgIv = (ImageView) findViewById(R.id.uc_msg_iv); 78 | mAvater = (CircleImageView) findViewById(R.id.uc_avater); 79 | mTablayout = (CommonTabLayout) findViewById(R.id.uc_tablayout); 80 | mViewPager = (NoScrollViewPager) findViewById(R.id.uc_viewpager); 81 | } 82 | 83 | /** 84 | * 初始化tab 85 | */ 86 | private void initTab() { 87 | fragments = getFragments(); 88 | MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments, getNames()); 89 | 90 | mTablayout.setTabData(mTabEntities); 91 | mViewPager.setAdapter(myFragmentPagerAdapter); 92 | } 93 | 94 | /** 95 | * 绑定事件 96 | */ 97 | private void initListener() { 98 | mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { 99 | @Override 100 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { 101 | float percent = Float.valueOf(Math.abs(verticalOffset)) / Float.valueOf(appBarLayout.getTotalScrollRange()); 102 | if (titleCenterLayout != null && mAvater != null && mSettingIv != null && mMsgIv != null) { 103 | titleCenterLayout.setAlpha(percent); 104 | StatusBarUtil.setTranslucentForImageView(Demo1Activity.this, (int) (255f * percent), null); 105 | if (percent == 0) { 106 | groupChange(1f, 1); 107 | } else if (percent == 1) { 108 | if (mAvater.getVisibility() != View.GONE) { 109 | mAvater.setVisibility(View.GONE); 110 | } 111 | groupChange(1f, 2); 112 | } else { 113 | if (mAvater.getVisibility() != View.VISIBLE) { 114 | mAvater.setVisibility(View.VISIBLE); 115 | } 116 | groupChange(percent, 0); 117 | } 118 | 119 | } 120 | } 121 | }); 122 | AppBarLayoutOverScrollViewBehavior myAppBarLayoutBehavoir = (AppBarLayoutOverScrollViewBehavior) 123 | ((CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams()).getBehavior(); 124 | myAppBarLayoutBehavoir.setOnProgressChangeListener(new AppBarLayoutOverScrollViewBehavior.onProgressChangeListener() { 125 | @Override 126 | public void onProgressChange(float progress, boolean isRelease) { 127 | progressBar.setProgress((int) (progress * 360)); 128 | if (progress == 1 && !progressBar.isSpinning && isRelease) { 129 | // 刷新viewpager里的fragment 130 | } 131 | if (mMsgIv != null) { 132 | if (progress == 0 && !progressBar.isSpinning) { 133 | mMsgIv.setVisibility(View.VISIBLE); 134 | } else if (progress > 0 && mSettingIv.getVisibility() == View.VISIBLE) { 135 | mMsgIv.setVisibility(View.INVISIBLE); 136 | } 137 | } 138 | } 139 | }); 140 | mTablayout.setOnTabSelectListener(new OnTabSelectListener() { 141 | @Override 142 | public void onTabSelect(int position) { 143 | mViewPager.setCurrentItem(position); 144 | } 145 | 146 | @Override 147 | public void onTabReselect(int position) { 148 | 149 | } 150 | }); 151 | mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 152 | @Override 153 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 154 | 155 | } 156 | 157 | @Override 158 | public void onPageSelected(int position) { 159 | mTablayout.setCurrentTab(position); 160 | } 161 | 162 | @Override 163 | public void onPageScrollStateChanged(int state) { 164 | 165 | } 166 | }); 167 | } 168 | 169 | /** 170 | * 初始化状态栏位置 171 | */ 172 | private void initStatus() { 173 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//4.4以下不支持状态栏变色 174 | //注意了,这里使用了第三方库 StatusBarUtil,目的是改变状态栏的alpha 175 | StatusBarUtil.setTransparentForImageView(Demo1Activity.this, null); 176 | //这里是重设我们的title布局的topMargin,StatusBarUtil提供了重设的方法,但是我们这里有两个布局 177 | //TODO 关于为什么不把Toolbar和@layout/layout_uc_head_title放到一起,是因为需要Toolbar来占位,防止AppBarLayout折叠时将title顶出视野范围 178 | int statusBarHeight = getStatusBarHeight(Demo1Activity.this); 179 | CollapsingToolbarLayout.LayoutParams lp1 = (CollapsingToolbarLayout.LayoutParams) titleContainer.getLayoutParams(); 180 | lp1.topMargin = statusBarHeight; 181 | titleContainer.setLayoutParams(lp1); 182 | CollapsingToolbarLayout.LayoutParams lp2 = (CollapsingToolbarLayout.LayoutParams) mToolBar.getLayoutParams(); 183 | lp2.topMargin = statusBarHeight; 184 | mToolBar.setLayoutParams(lp2); 185 | } 186 | } 187 | 188 | /** 189 | * @param alpha 190 | * @param state 0-正在变化 1展开 2 关闭 191 | */ 192 | public void groupChange(float alpha, int state) { 193 | lastState = state; 194 | 195 | mSettingIv.setAlpha(alpha); 196 | mMsgIv.setAlpha(alpha); 197 | 198 | switch (state) { 199 | case 1://完全展开 显示白色 200 | mMsgIv.setImageResource(R.drawable.icon_msg); 201 | mSettingIv.setImageResource(R.drawable.icon_setting); 202 | mViewPager.setNoScroll(false); 203 | break; 204 | case 2://完全关闭 显示黑色 205 | mMsgIv.setImageResource(R.drawable.icon_msg_black); 206 | mSettingIv.setImageResource(R.drawable.icon_setting_black); 207 | mViewPager.setNoScroll(false); 208 | break; 209 | case 0://介于两种临界值之间 显示黑色 210 | if (lastState != 0) { 211 | mMsgIv.setImageResource(R.drawable.icon_msg_black); 212 | mSettingIv.setImageResource(R.drawable.icon_setting_black); 213 | } 214 | //为什么禁止滑动?在介于开关状态之间,不允许滑动,开启可能会导致不好的体验 215 | mViewPager.setNoScroll(true); 216 | break; 217 | } 218 | } 219 | 220 | 221 | /** 222 | * 获取状态栏高度 223 | * !!这个方法来自StatusBarUtil,因为作者将之设为private,所以直接copy出来 224 | * 225 | * @param context context 226 | * @return 状态栏高度 227 | */ 228 | private int getStatusBarHeight(Context context) { 229 | // 获得状态栏高度 230 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 231 | return context.getResources().getDimensionPixelSize(resourceId); 232 | } 233 | 234 | /** 235 | * 假数据 236 | * 237 | * @return 238 | */ 239 | public String[] getNames() { 240 | String[] mNames = new String[]{"Weather", "Moon", "Like", "Fans"}; 241 | for (String str : mNames) { 242 | mTabEntities.add(new TabEntity(String.valueOf(new Random().nextInt(200)), str)); 243 | } 244 | 245 | return mNames; 246 | } 247 | 248 | public List getFragments() { 249 | List fragments = new ArrayList<>(); 250 | fragments.add(new ItemFragment1()); 251 | fragments.add(new ItemFragment1()); 252 | fragments.add(new ItemFragment1()); 253 | fragments.add(new ItemFragment1()); 254 | return fragments; 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/StartActivity.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.content.ContextCompat; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.view.Gravity; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.LinearLayout; 12 | import android.widget.TextView; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * @author stefan 邮箱:648701906@qq.com 19 | */ 20 | public class StartActivity extends AppCompatActivity { 21 | 22 | @Override 23 | protected void onCreate(@Nullable Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_start); 26 | 27 | ViewGroup mContainer = (ViewGroup) findViewById(R.id.container); 28 | List mActivitys = createList(); 29 | 30 | for (final String string : mActivitys) { 31 | TextView textView = new TextView(this); 32 | textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300)); 33 | textView.setGravity(Gravity.CENTER); 34 | textView.setText(string); 35 | textView.setTextColor(ContextCompat.getColor(this, R.color.colorPrimary)); 36 | textView.setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View v) { 39 | try { 40 | startActivity(new Intent(StartActivity.this, Class.forName(string))); 41 | } catch (ClassNotFoundException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | }); 46 | mContainer.addView(textView); 47 | } 48 | } 49 | 50 | private List createList() { 51 | List activitys = new ArrayList<>(); 52 | activitys.add("me.stefan.easybehavior.Demo1Activity"); 53 | activitys.add("me.stefan.easybehavior.Demo2Activity"); 54 | return activitys; 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/demo1/behavior/AppBarLayoutOverScrollViewBehavior.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.demo1.behavior; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.support.design.widget.AppBarLayout; 7 | import android.support.design.widget.CoordinatorLayout; 8 | import android.support.v4.view.ViewCompat; 9 | import android.support.v7.widget.Toolbar; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import me.stefan.easybehavior.widget.DisInterceptNestedScrollView; 15 | 16 | 17 | /** 18 | * Created by gjm on 2017/5/24. 19 | * 目前包括的事件: 20 | * 图片放大回弹 21 | * 个人信息布局的top和botoom跟随图片位移 22 | * toolbar背景变色 23 | */ 24 | public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior { 25 | private static final String TAG = "overScroll"; 26 | private static final String TAG_TOOLBAR = "toolbar"; 27 | private static final String TAG_MIDDLE = "middle"; 28 | private static final float TARGET_HEIGHT = 1500; 29 | private View mTargetView; 30 | private int mParentHeight; 31 | private int mTargetViewHeight; 32 | private float mTotalDy; 33 | private float mLastScale; 34 | private int mLastBottom; 35 | private boolean isAnimate; 36 | private Toolbar mToolBar; 37 | private ViewGroup middleLayout;//个人信息布局 38 | private int mMiddleHeight; 39 | private boolean isRecovering = false;//是否正在自动回弹中 40 | 41 | private final float MAX_REFRESH_LIMIT = 0.3f;//达到这个下拉临界值就开始刷新动画 42 | 43 | public AppBarLayoutOverScrollViewBehavior() { 44 | } 45 | 46 | public AppBarLayoutOverScrollViewBehavior(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | } 49 | 50 | 51 | @Override 52 | public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) { 53 | boolean handled = super.onLayoutChild(parent, abl, layoutDirection); 54 | 55 | if (mToolBar == null) { 56 | mToolBar = (Toolbar) parent.findViewWithTag(TAG_TOOLBAR); 57 | } 58 | if (middleLayout == null) { 59 | middleLayout = (ViewGroup) parent.findViewWithTag(TAG_MIDDLE); 60 | } 61 | // 需要在调用过super.onLayoutChild()方法之后获取 62 | if (mTargetView == null) { 63 | mTargetView = parent.findViewWithTag(TAG); 64 | if (mTargetView != null) { 65 | initial(abl); 66 | } 67 | } 68 | abl.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { 69 | 70 | 71 | @Override 72 | public final void onOffsetChanged(AppBarLayout appBarLayout, int i) { 73 | mToolBar.setAlpha(Float.valueOf(Math.abs(i)) / Float.valueOf(appBarLayout.getTotalScrollRange())); 74 | 75 | } 76 | 77 | }); 78 | return handled; 79 | } 80 | 81 | @Override 82 | public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes,int type) { 83 | isAnimate = true; 84 | if (target instanceof DisInterceptNestedScrollView) return true;//这个布局就是middleLayout 85 | return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes,type); 86 | } 87 | 88 | @Override 89 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) { 90 | if (!isRecovering) { 91 | if (mTargetView != null && ((dy < 0 && child.getBottom() >= mParentHeight) 92 | || (dy > 0 && child.getBottom() > mParentHeight))) { 93 | scale(child, target, dy); 94 | return; 95 | } 96 | } 97 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed,type); 98 | } 99 | 100 | @Override 101 | public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) { 102 | if (velocityY > 100) {//当y速度>100,就秒弹回 103 | isAnimate = false; 104 | } 105 | return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); 106 | } 107 | 108 | 109 | @Override 110 | public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target, int type) { 111 | recovery(abl); 112 | super.onStopNestedScroll(coordinatorLayout, abl, target,type); 113 | } 114 | 115 | private void initial(AppBarLayout abl) { 116 | abl.setClipChildren(false); 117 | mParentHeight = abl.getHeight(); 118 | mTargetViewHeight = mTargetView.getHeight(); 119 | mMiddleHeight = middleLayout.getHeight(); 120 | } 121 | 122 | private void scale(AppBarLayout abl, View target, int dy) { 123 | mTotalDy += -dy; 124 | mTotalDy = Math.min(mTotalDy, TARGET_HEIGHT); 125 | mLastScale = Math.max(1f, 1f + mTotalDy / TARGET_HEIGHT); 126 | ViewCompat.setScaleX(mTargetView, mLastScale); 127 | ViewCompat.setScaleY(mTargetView, mLastScale); 128 | mLastBottom = mParentHeight + (int) (mTargetViewHeight / 2 * (mLastScale - 1)); 129 | abl.setBottom(mLastBottom); 130 | target.setScrollY(0); 131 | 132 | middleLayout.setTop(mLastBottom - mMiddleHeight); 133 | middleLayout.setBottom(mLastBottom); 134 | 135 | if (onProgressChangeListener != null) { 136 | float progress = Math.min((mLastScale - 1) / MAX_REFRESH_LIMIT, 1);//计算0~1的进度 137 | onProgressChangeListener.onProgressChange(progress, false); 138 | } 139 | 140 | } 141 | 142 | public interface onProgressChangeListener { 143 | /** 144 | * 范围 0~1 145 | * 146 | * @param progress 147 | * @param isRelease 是否是释放状态 148 | */ 149 | void onProgressChange(float progress, boolean isRelease); 150 | } 151 | 152 | public void setOnProgressChangeListener(AppBarLayoutOverScrollViewBehavior.onProgressChangeListener onProgressChangeListener) { 153 | this.onProgressChangeListener = onProgressChangeListener; 154 | } 155 | 156 | onProgressChangeListener onProgressChangeListener; 157 | 158 | private void recovery(final AppBarLayout abl) { 159 | if (isRecovering) return; 160 | if (mTotalDy > 0) { 161 | isRecovering = true; 162 | mTotalDy = 0; 163 | if (isAnimate) { 164 | ValueAnimator anim = ValueAnimator.ofFloat(mLastScale, 1f).setDuration(200); 165 | anim.addUpdateListener( 166 | new ValueAnimator.AnimatorUpdateListener() { 167 | @Override 168 | public void onAnimationUpdate(ValueAnimator animation) { 169 | 170 | float value = (float) animation.getAnimatedValue(); 171 | ViewCompat.setScaleX(mTargetView, value); 172 | ViewCompat.setScaleY(mTargetView, value); 173 | abl.setBottom((int) (mLastBottom - (mLastBottom - mParentHeight) * animation.getAnimatedFraction())); 174 | middleLayout.setTop((int) (mLastBottom - 175 | (mLastBottom - mParentHeight) * animation.getAnimatedFraction() - mMiddleHeight)); 176 | 177 | if (onProgressChangeListener != null) { 178 | float progress = Math.min((value - 1) / MAX_REFRESH_LIMIT, 1);//计算0~1的进度 179 | onProgressChangeListener.onProgressChange(progress, true); 180 | } 181 | } 182 | } 183 | ); 184 | anim.addListener(new Animator.AnimatorListener() { 185 | @Override 186 | public void onAnimationStart(Animator animation) { 187 | } 188 | 189 | @Override 190 | public void onAnimationEnd(Animator animation) { 191 | isRecovering = false; 192 | } 193 | 194 | @Override 195 | public void onAnimationCancel(Animator animation) { 196 | } 197 | 198 | @Override 199 | public void onAnimationRepeat(Animator animation) { 200 | } 201 | }); 202 | anim.start(); 203 | } else { 204 | ViewCompat.setScaleX(mTargetView, 1f); 205 | ViewCompat.setScaleY(mTargetView, 1f); 206 | abl.setBottom(mParentHeight); 207 | middleLayout.setTop(mParentHeight - mMiddleHeight); 208 | // middleLayout.setBottom(mParentHeight); 209 | isRecovering = false; 210 | 211 | if (onProgressChangeListener != null) 212 | onProgressChangeListener.onProgressChange(0, true); 213 | } 214 | } 215 | } 216 | 217 | 218 | } -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/demo1/behavior/CircleImageInUsercBehavior.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.demo1.behavior; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.CoordinatorLayout; 5 | import android.support.v4.view.ViewCompat; 6 | import android.support.v7.widget.Toolbar; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import me.stefan.easybehavior.widget.CircleImageView; 11 | import me.stefan.easybehavior.widget.DisInterceptNestedScrollView; 12 | 13 | 14 | /** 15 | * @author stefan 邮箱:648701906@qq.com 16 | *

17 | * @describe 在app中,这个类用于头像的行为 具体使用在Strings.xml中的引用 18 | */ 19 | public class CircleImageInUsercBehavior extends CoordinatorLayout.Behavior { 20 | 21 | private final String TAG_TOOLBAR = "toolbar"; 22 | 23 | private float mStartAvatarY; 24 | 25 | private float mStartAvatarX; 26 | 27 | private int mAvatarMaxHeight; 28 | 29 | private int mToolBarHeight; 30 | 31 | private float mStartDependencyY; 32 | 33 | public CircleImageInUsercBehavior(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | } 36 | 37 | 38 | @Override 39 | public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) { 40 | return dependency instanceof DisInterceptNestedScrollView; 41 | } 42 | 43 | 44 | //当dependency变化的时候调用 45 | @Override 46 | public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) { 47 | //初始化一些基础参数 48 | init(parent, child, dependency); 49 | //计算比例 50 | if (child.getY() <= 0) return false; 51 | float percent = (child.getY() - mToolBarHeight) / (mStartAvatarY - mToolBarHeight); 52 | 53 | if (percent < 0) { 54 | percent = 0; 55 | } 56 | if (this.percent == percent || percent > 1) return true; 57 | this.percent = percent; 58 | //设置头像的大小 59 | ViewCompat.setScaleX(child, percent); 60 | ViewCompat.setScaleY(child, percent); 61 | 62 | return false; 63 | } 64 | 65 | /** 66 | * 初始化数据 67 | * @param parent 68 | * @param child 69 | * @param dependency 70 | */ 71 | private void init(CoordinatorLayout parent, CircleImageView child, View dependency) { 72 | if (mStartAvatarY == 0) { 73 | mStartAvatarY = child.getY(); 74 | } 75 | if (mStartDependencyY == 0) { 76 | mStartDependencyY = dependency.getY(); 77 | } 78 | if (mStartAvatarX == 0) { 79 | mStartAvatarX = child.getX(); 80 | } 81 | 82 | if (mAvatarMaxHeight == 0) { 83 | mAvatarMaxHeight = child.getHeight(); 84 | } 85 | if (mToolBarHeight == 0) { 86 | Toolbar toolbar = (Toolbar) parent.findViewWithTag(TAG_TOOLBAR); 87 | mToolBarHeight = toolbar.getHeight(); 88 | } 89 | } 90 | 91 | float percent = 0; 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/demo1/widget/NoScrollViewPager.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.demo1.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.ViewPager; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | import android.view.MotionEvent; 8 | 9 | /** 10 | * 动态控制滚动 11 | */ 12 | public class NoScrollViewPager extends ViewPager { 13 | private boolean noScroll = false; 14 | 15 | public NoScrollViewPager(Context context, AttributeSet attrs) { 16 | super(context, attrs); 17 | // TODO Auto-generated constructor stub 18 | } 19 | 20 | public NoScrollViewPager(Context context) { 21 | super(context); 22 | } 23 | 24 | public void setNoScroll(boolean noScroll) { 25 | this.noScroll = noScroll; 26 | } 27 | 28 | @Override 29 | public void scrollTo(int x, int y) { 30 | super.scrollTo(x, y); 31 | } 32 | 33 | @Override 34 | public boolean onTouchEvent(MotionEvent arg0) { 35 | if (noScroll) 36 | return false; 37 | else 38 | return super.onTouchEvent(arg0); 39 | } 40 | 41 | @Override 42 | public boolean onInterceptTouchEvent(MotionEvent arg0) { 43 | if (noScroll) 44 | return false; 45 | else 46 | return super.onInterceptTouchEvent(arg0); 47 | } 48 | 49 | @Override 50 | public void setCurrentItem(int item, boolean smoothScroll) { 51 | super.setCurrentItem(item, smoothScroll); 52 | } 53 | 54 | @Override 55 | public void setCurrentItem(int item) { 56 | super.setCurrentItem(item); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/demo1/widget/RoundProgressBar.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.demo1.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.RectF; 9 | import android.os.Handler; 10 | import android.os.Message; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | 14 | import me.stefan.easybehavior.R; 15 | 16 | 17 | /** 18 | * 带进度的进度条,线程安全的View,可直接在线程中更新进度 19 | * 20 | * @author stefan 修正改进 21 | */ 22 | public class RoundProgressBar extends View { 23 | /** 24 | * 画笔对象的引用 25 | */ 26 | private Paint paint; 27 | 28 | /** 29 | * 圆环的颜色 30 | */ 31 | private int roundColor; 32 | 33 | /** 34 | * 圆环进度的颜色 35 | */ 36 | private int roundProgressColor; 37 | 38 | /** 39 | * 中间进度百分比的字符串的颜色 40 | */ 41 | private int textColor; 42 | 43 | /** 44 | * 中间进度百分比的字符串的字体 45 | */ 46 | private float textSize; 47 | 48 | /** 49 | * 圆环的宽度 50 | */ 51 | private float roundWidth; 52 | 53 | /** 54 | * 最大进度 55 | */ 56 | private int max; 57 | 58 | /** 59 | * 当前进度 60 | */ 61 | private int progress; 62 | /** 63 | * 是否循环 64 | **/ 65 | public boolean isSpinning = false; 66 | 67 | public RoundProgressBar(Context context) { 68 | this(context, null); 69 | } 70 | 71 | public RoundProgressBar(Context context, AttributeSet attrs) { 72 | this(context, attrs, 0); 73 | } 74 | 75 | public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) { 76 | super(context, attrs, defStyle); 77 | 78 | paint = new Paint(); 79 | 80 | TypedArray mTypedArray = context.obtainStyledAttributes(attrs, 81 | R.styleable.RoundProgressBar); 82 | 83 | //获取自定义属性和默认值 84 | roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_round_color, Color.RED); 85 | roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_round_progressColor, Color.GREEN); 86 | textColor = mTypedArray.getColor(R.styleable.RoundProgressBar_round_textColor, Color.GREEN); 87 | textSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_round_textsize, 15); 88 | roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_round_width, 5); 89 | max = mTypedArray.getInteger(R.styleable.RoundProgressBar_round_max, 360); 90 | 91 | mTypedArray.recycle();//必须Recycle 92 | } 93 | 94 | 95 | @Override 96 | protected void onDraw(Canvas canvas) { 97 | super.onDraw(canvas); 98 | 99 | /** 100 | * 画最外层的大圆环 101 | */ 102 | int centre = getWidth()/2; //获取圆心的x坐标 103 | int radius = (int) (centre - roundWidth/2); //圆环的半径 104 | paint.setColor(roundColor); //设置圆环的颜色 105 | paint.setStyle(Paint.Style.STROKE); //设置空心 106 | paint.setStrokeWidth(roundWidth); //设置圆环的宽度 107 | paint.setAntiAlias(true); //消除锯齿 108 | 109 | 110 | if(isSpinning){ 111 | /** 112 | * 画圆弧 ,画圆环的进度 113 | */ 114 | //设置进度是实心还是空心 115 | paint.setStrokeWidth(roundWidth); //设置圆环的宽度 116 | paint.setColor(roundProgressColor); //设置进度的颜色 117 | RectF oval = new RectF(centre - radius, centre - radius, centre 118 | + radius, centre + radius); //用于定义的圆弧的形状和大小的界限 119 | paint.setStyle(Paint.Style.STROKE); 120 | canvas.drawArc(oval, progress-90, 320 , false, paint); //根据进度画圆弧 121 | }else{ 122 | /** 123 | * 画圆弧 ,画圆环的进度 124 | */ 125 | //设置进度是实心还是空心 126 | paint.setStrokeWidth(roundWidth); //设置圆环的宽度 127 | paint.setColor(roundProgressColor); //设置进度的颜色 128 | RectF oval = new RectF(centre - radius, centre - radius, centre 129 | + radius, centre + radius); //用于定义的圆弧的形状和大小的界限 130 | paint.setStyle(Paint.Style.STROKE); 131 | canvas.drawArc(oval, -90, progress , false, paint); //根据进度画圆弧 132 | } 133 | 134 | 135 | 136 | } 137 | 138 | 139 | /** 140 | * Reset the count (in increment mode) 141 | */ 142 | public void resetCount() { 143 | progress = 0; 144 | invalidate(); 145 | } 146 | 147 | /** 148 | * Turn off spin mode 149 | */ 150 | public void stopSpinning() { 151 | isSpinning = false; 152 | progress = 0; 153 | spinHandler.removeMessages(0); 154 | } 155 | 156 | /** 157 | * Puts the view on spin mode 158 | */ 159 | public void spin() { 160 | isSpinning = true; 161 | spinHandler.sendEmptyMessage(0); 162 | } 163 | 164 | /** 165 | * Increment the progress by 1 (of 360) 166 | */ 167 | public void incrementProgress() { 168 | isSpinning = false; 169 | if (progress > 360) 170 | progress = 0; 171 | spinHandler.sendEmptyMessage(0); 172 | } 173 | 174 | private Handler spinHandler = new Handler() { 175 | /** 176 | * This is the code that will increment the progress variable 177 | * and so spin the wheel 178 | */ 179 | @Override 180 | public void handleMessage(Message msg) { 181 | invalidate(); 182 | if (isSpinning) { 183 | progress += 10; 184 | if (progress > 360) { 185 | progress = 0; 186 | } 187 | spinHandler.sendEmptyMessageDelayed(0, 0); 188 | } 189 | //super.handleMessage(msg); 190 | } 191 | }; 192 | 193 | /** 194 | * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步 195 | * 刷新界面调用postInvalidate()能在非UI线程刷新 196 | * 197 | * @param progress 198 | */ 199 | public synchronized void setProgress(int progress) { 200 | if (progress < 0) { 201 | throw new IllegalArgumentException("progress not less than 0"); 202 | } 203 | if (progress > max) { 204 | progress = max; 205 | } 206 | if (progress <= max) { 207 | this.progress = progress; 208 | postInvalidate(); 209 | } 210 | 211 | } 212 | 213 | public synchronized int getMax() { 214 | return max; 215 | } 216 | 217 | /** 218 | * 设置进度的最大值 219 | * 220 | * @param max 221 | */ 222 | public synchronized void setMax(int max) { 223 | if (max < 0) { 224 | throw new IllegalArgumentException("max not less than 0"); 225 | } 226 | this.max = max; 227 | } 228 | 229 | /** 230 | * 获取进度.需要同步 231 | * 232 | * @return 233 | */ 234 | public synchronized int getProgress() { 235 | return progress; 236 | } 237 | 238 | public int getCricleColor() { 239 | return roundColor; 240 | } 241 | 242 | public void setCricleColor(int cricleColor) { 243 | this.roundColor = cricleColor; 244 | } 245 | 246 | public int getCricleProgressColor() { 247 | return roundProgressColor; 248 | } 249 | 250 | public void setCricleProgressColor(int cricleProgressColor) { 251 | this.roundProgressColor = cricleProgressColor; 252 | } 253 | 254 | public int getTextColor() { 255 | return textColor; 256 | } 257 | 258 | public void setTextColor(int textColor) { 259 | this.textColor = textColor; 260 | } 261 | 262 | public float getTextSize() { 263 | return textSize; 264 | } 265 | 266 | public void setTextSize(float textSize) { 267 | this.textSize = textSize; 268 | } 269 | 270 | public float getRoundWidth() { 271 | return roundWidth; 272 | } 273 | 274 | public void setRoundWidth(float roundWidth) { 275 | this.roundWidth = roundWidth; 276 | } 277 | 278 | 279 | } -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/fragment/ItemFragment1.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.fragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v7.widget.GridLayoutManager; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import me.stefan.easybehavior.R; 14 | import me.stefan.easybehavior.fragment.dummy.DummyContent; 15 | 16 | 17 | /** 18 | * A fragment representing a list of Items. 19 | *

20 | */ 21 | public class ItemFragment1 extends Fragment { 22 | 23 | // TODO: Customize parameters 24 | private int mColumnCount = 1; 25 | private final static String PARAMS_ID = "PARAMS_ID"; 26 | 27 | public ItemFragment1() { 28 | } 29 | 30 | @Override 31 | public void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | 34 | } 35 | 36 | @Override 37 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 38 | Bundle savedInstanceState) { 39 | View view = inflater.inflate(R.layout.fragment_item_list1, container, false); 40 | 41 | // Set the adapter 42 | if (view instanceof RecyclerView) { 43 | Context context = view.getContext(); 44 | RecyclerView recyclerView = (RecyclerView) view; 45 | if (mColumnCount <= 1) { 46 | recyclerView.setLayoutManager(new LinearLayoutManager(context)); 47 | } else { 48 | recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount)); 49 | } 50 | recyclerView.setAdapter(new MyItemRecyclerViewAdapter(DummyContent.ITEMS)); 51 | } 52 | return view; 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/fragment/MyFragmentPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.fragment; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentStatePagerAdapter; 6 | import android.view.ViewGroup; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by wxy on 16/6/24. 13 | */ 14 | public class MyFragmentPagerAdapter extends FragmentStatePagerAdapter { 15 | private List fragments = new ArrayList<>(0); 16 | private String[] names; 17 | 18 | public MyFragmentPagerAdapter(FragmentManager fm, List fragments, String[] names) { 19 | super(fm); 20 | this.fragments = fragments; 21 | this.names = names; 22 | } 23 | 24 | @Override 25 | public Fragment getItem(int position) { 26 | return fragments.get(position); 27 | } 28 | 29 | @Override 30 | public int getCount() { 31 | return fragments.size(); 32 | } 33 | 34 | @Override 35 | public CharSequence getPageTitle(int position) { 36 | if (names != null){ 37 | return names[position]; 38 | }else { 39 | return ""; 40 | } 41 | } 42 | 43 | @Override 44 | public void destroyItem(ViewGroup container, int position, Object object) { 45 | // super.destroyItem(container, position, object); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/fragment/MyItemRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.fragment; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import android.widget.Toast; 9 | 10 | import java.util.List; 11 | 12 | import me.stefan.easybehavior.R; 13 | import me.stefan.easybehavior.fragment.dummy.DummyContent.DummyItem; 14 | 15 | /** 16 | * {@link RecyclerView.Adapter} that can display a {@link DummyItem} and makes a call to the 17 | * TODO: Replace the implementation with code for your data type. 18 | */ 19 | public class MyItemRecyclerViewAdapter extends RecyclerView.Adapter { 20 | 21 | private final List mValues; 22 | 23 | public MyItemRecyclerViewAdapter(List items) { 24 | mValues = items; 25 | } 26 | 27 | @Override 28 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 29 | View view = LayoutInflater.from(parent.getContext()) 30 | .inflate(R.layout.fragment_item, parent, false); 31 | return new ViewHolder(view); 32 | } 33 | 34 | @Override 35 | public void onBindViewHolder(final ViewHolder holder, int position) { 36 | holder.mItem = mValues.get(position); 37 | holder.mIdView.setText(mValues.get(position).id); 38 | holder.mContentView.setText(mValues.get(position).content); 39 | } 40 | 41 | @Override 42 | public int getItemCount() { 43 | return mValues.size(); 44 | } 45 | 46 | public class ViewHolder extends RecyclerView.ViewHolder { 47 | public final View mView; 48 | public final TextView mIdView; 49 | public final TextView mContentView; 50 | public DummyItem mItem; 51 | 52 | public ViewHolder(View view) { 53 | super(view); 54 | mView = view; 55 | mIdView = (TextView) view.findViewById(R.id.id); 56 | mContentView = (TextView) view.findViewById(R.id.content); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return super.toString() + " '" + mContentView.getText() + "'"; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/fragment/dummy/DummyContent.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.fragment.dummy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Helper class for providing sample content for user interfaces created by 10 | * Android template wizards. 11 | *

12 | * TODO: Replace all uses of this class before publishing your app. 13 | */ 14 | public class DummyContent { 15 | 16 | /** 17 | * An array of sample (dummy) items. 18 | */ 19 | public static final List ITEMS = new ArrayList(); 20 | 21 | /** 22 | * A map of sample (dummy) items, by ID. 23 | */ 24 | public static final Map ITEM_MAP = new HashMap(); 25 | 26 | private static final int COUNT = 25; 27 | 28 | static { 29 | // Add some sample items. 30 | for (int i = 1; i <= COUNT; i++) { 31 | addItem(createDummyItem(i)); 32 | } 33 | } 34 | 35 | private static void addItem(DummyItem item) { 36 | ITEMS.add(item); 37 | ITEM_MAP.put(item.id, item); 38 | } 39 | 40 | private static DummyItem createDummyItem(int position) { 41 | return new DummyItem(String.valueOf(position), "Item " + position, makeDetails(position)); 42 | } 43 | 44 | private static String makeDetails(int position) { 45 | StringBuilder builder = new StringBuilder(); 46 | builder.append("Details about Item: ").append(position); 47 | for (int i = 0; i < position; i++) { 48 | builder.append("\nMore details information here."); 49 | } 50 | return builder.toString(); 51 | } 52 | 53 | /** 54 | * A dummy item representing a piece of content. 55 | */ 56 | public static class DummyItem { 57 | public final String id; 58 | public final String content; 59 | public final String details; 60 | 61 | public DummyItem(String id, String content, String details) { 62 | this.id = id; 63 | this.content = content; 64 | this.details = details; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return content; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/fragment/dummy/TabEntity.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.fragment.dummy; 2 | 3 | import com.flyco.tablayout.listener.CustomTabEntity; 4 | 5 | public class TabEntity implements CustomTabEntity { 6 | public String title; 7 | public int selectedIcon; 8 | public int unSelectedIcon; 9 | public String subTitle; 10 | 11 | public TabEntity(String title, int selectedIcon, int unSelectedIcon) { 12 | this.title = title; 13 | this.selectedIcon = selectedIcon; 14 | this.unSelectedIcon = unSelectedIcon; 15 | } 16 | 17 | public TabEntity(String title, String subTitle) { 18 | this.title = title; 19 | this.subTitle = subTitle; 20 | } 21 | 22 | @Override 23 | public String getTabTitle() { 24 | return title; 25 | } 26 | 27 | @Override 28 | public int getTabSelectedIcon() { 29 | return selectedIcon; 30 | } 31 | 32 | @Override 33 | public int getTabUnselectedIcon() { 34 | return unSelectedIcon; 35 | } 36 | 37 | @Override 38 | public String getSubTitle() { 39 | return subTitle; 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/widget/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.widget; 2 | import android.content.Context; 3 | import android.content.res.TypedArray; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapShader; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.ColorFilter; 9 | import android.graphics.Matrix; 10 | import android.graphics.Paint; 11 | import android.graphics.RectF; 12 | import android.graphics.Shader; 13 | import android.graphics.drawable.BitmapDrawable; 14 | import android.graphics.drawable.ColorDrawable; 15 | import android.graphics.drawable.Drawable; 16 | import android.net.Uri; 17 | import android.support.annotation.ColorInt; 18 | import android.support.annotation.ColorRes; 19 | import android.support.annotation.DrawableRes; 20 | import android.support.v7.widget.AppCompatImageView; 21 | import android.util.AttributeSet; 22 | import android.widget.ImageView; 23 | 24 | import me.stefan.easybehavior.R; 25 | 26 | 27 | /** 28 | * Created by wxy on 16/6/27. 29 | */ 30 | public class CircleImageView extends ImageView { 31 | private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; 32 | 33 | private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; 34 | private static final int COLORDRAWABLE_DIMENSION = 2; 35 | 36 | private static final int DEFAULT_BORDER_WIDTH = 0; 37 | private static final int DEFAULT_BORDER_COLOR = Color.BLACK; 38 | private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT; 39 | private static final boolean DEFAULT_BORDER_OVERLAY = false; 40 | 41 | private final RectF mDrawableRect = new RectF(); 42 | private final RectF mBorderRect = new RectF(); 43 | 44 | private final Matrix mShaderMatrix = new Matrix(); 45 | private final Paint mBitmapPaint = new Paint(); 46 | private final Paint mBorderPaint = new Paint(); 47 | private final Paint mFillPaint = new Paint(); 48 | 49 | private int mBorderColor = DEFAULT_BORDER_COLOR; 50 | private int mBorderWidth = DEFAULT_BORDER_WIDTH; 51 | private int mFillColor = DEFAULT_FILL_COLOR; 52 | 53 | private Bitmap mBitmap; 54 | private BitmapShader mBitmapShader; 55 | private int mBitmapWidth; 56 | private int mBitmapHeight; 57 | 58 | private float mDrawableRadius; 59 | private float mBorderRadius; 60 | 61 | private ColorFilter mColorFilter; 62 | 63 | private boolean mReady; 64 | private boolean mSetupPending; 65 | private boolean mBorderOverlay; 66 | private boolean mDisableCircularTransformation; 67 | 68 | public CircleImageView(Context context) { 69 | super(context); 70 | 71 | init(); 72 | } 73 | 74 | public CircleImageView(Context context, AttributeSet attrs) { 75 | this(context, attrs, 0); 76 | } 77 | 78 | public CircleImageView(Context context, AttributeSet attrs, int defStyle) { 79 | super(context, attrs, defStyle); 80 | 81 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0); 82 | 83 | mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH); 84 | mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR); 85 | mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY); 86 | mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR); 87 | 88 | a.recycle(); 89 | 90 | init(); 91 | } 92 | 93 | private void init() { 94 | super.setScaleType(SCALE_TYPE); 95 | mReady = true; 96 | 97 | if (mSetupPending) { 98 | setup(); 99 | mSetupPending = false; 100 | } 101 | } 102 | 103 | @Override 104 | public ScaleType getScaleType() { 105 | return SCALE_TYPE; 106 | } 107 | 108 | @Override 109 | public void setScaleType(ScaleType scaleType) { 110 | if (scaleType != SCALE_TYPE) { 111 | throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType)); 112 | } 113 | } 114 | 115 | @Override 116 | public void setAdjustViewBounds(boolean adjustViewBounds) { 117 | if (adjustViewBounds) { 118 | throw new IllegalArgumentException("adjustViewBounds not supported."); 119 | } 120 | } 121 | 122 | @Override 123 | protected void onDraw(Canvas canvas) { 124 | if (mDisableCircularTransformation) { 125 | super.onDraw(canvas); 126 | return; 127 | } 128 | 129 | if (mBitmap == null) { 130 | return; 131 | } 132 | 133 | if (mFillColor != Color.TRANSPARENT) { 134 | canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint); 135 | } 136 | canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint); 137 | if (mBorderWidth > 0) { 138 | canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint); 139 | } 140 | } 141 | 142 | @Override 143 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 144 | super.onSizeChanged(w, h, oldw, oldh); 145 | setup(); 146 | } 147 | 148 | public int getBorderColor() { 149 | return mBorderColor; 150 | } 151 | 152 | public void setBorderColor(@ColorInt int borderColor) { 153 | if (borderColor == mBorderColor) { 154 | return; 155 | } 156 | 157 | mBorderColor = borderColor; 158 | mBorderPaint.setColor(mBorderColor); 159 | invalidate(); 160 | } 161 | 162 | /** 163 | * @deprecated Use {@link #setBorderColor(int)} instead 164 | */ 165 | @Deprecated 166 | public void setBorderColorResource(@ColorRes int borderColorRes) { 167 | setBorderColor(getContext().getResources().getColor(borderColorRes)); 168 | } 169 | 170 | /** 171 | * Return the color drawn behind the circle-shaped drawable. 172 | * 173 | * @return The color drawn behind the drawable 174 | * 175 | * @deprecated Fill color support is going to be removed in the future 176 | */ 177 | @Deprecated 178 | public int getFillColor() { 179 | return mFillColor; 180 | } 181 | 182 | /** 183 | * Set a color to be drawn behind the circle-shaped drawable. Note that 184 | * this has no effect if the drawable is opaque or no drawable is set. 185 | * 186 | * @param fillColor The color to be drawn behind the drawable 187 | * 188 | * @deprecated Fill color support is going to be removed in the future 189 | */ 190 | @Deprecated 191 | public void setFillColor(@ColorInt int fillColor) { 192 | if (fillColor == mFillColor) { 193 | return; 194 | } 195 | 196 | mFillColor = fillColor; 197 | mFillPaint.setColor(fillColor); 198 | invalidate(); 199 | } 200 | 201 | /** 202 | * Set a color to be drawn behind the circle-shaped drawable. Note that 203 | * this has no effect if the drawable is opaque or no drawable is set. 204 | * 205 | * @param fillColorRes The color resource to be resolved to a color and 206 | * drawn behind the drawable 207 | * 208 | * @deprecated Fill color support is going to be removed in the future 209 | */ 210 | @Deprecated 211 | public void setFillColorResource(@ColorRes int fillColorRes) { 212 | setFillColor(getContext().getResources().getColor(fillColorRes)); 213 | } 214 | 215 | public int getBorderWidth() { 216 | return mBorderWidth; 217 | } 218 | 219 | public void setBorderWidth(int borderWidth) { 220 | if (borderWidth == mBorderWidth) { 221 | return; 222 | } 223 | 224 | mBorderWidth = borderWidth; 225 | setup(); 226 | } 227 | 228 | public boolean isBorderOverlay() { 229 | return mBorderOverlay; 230 | } 231 | 232 | public void setBorderOverlay(boolean borderOverlay) { 233 | if (borderOverlay == mBorderOverlay) { 234 | return; 235 | } 236 | 237 | mBorderOverlay = borderOverlay; 238 | setup(); 239 | } 240 | 241 | public boolean isDisableCircularTransformation() { 242 | return mDisableCircularTransformation; 243 | } 244 | 245 | public void setDisableCircularTransformation(boolean disableCircularTransformation) { 246 | if (mDisableCircularTransformation == disableCircularTransformation) { 247 | return; 248 | } 249 | 250 | mDisableCircularTransformation = disableCircularTransformation; 251 | initializeBitmap(); 252 | } 253 | 254 | @Override 255 | public void setImageBitmap(Bitmap bm) { 256 | super.setImageBitmap(bm); 257 | initializeBitmap(); 258 | } 259 | 260 | @Override 261 | public void setImageDrawable(Drawable drawable) { 262 | super.setImageDrawable(drawable); 263 | initializeBitmap(); 264 | } 265 | 266 | @Override 267 | public void setImageResource(@DrawableRes int resId) { 268 | super.setImageResource(resId); 269 | initializeBitmap(); 270 | } 271 | 272 | @Override 273 | public void setImageURI(Uri uri) { 274 | super.setImageURI(uri); 275 | initializeBitmap(); 276 | } 277 | 278 | @Override 279 | public void setColorFilter(ColorFilter cf) { 280 | if (cf == mColorFilter) { 281 | return; 282 | } 283 | 284 | mColorFilter = cf; 285 | applyColorFilter(); 286 | invalidate(); 287 | } 288 | 289 | @Override 290 | public ColorFilter getColorFilter() { 291 | return mColorFilter; 292 | } 293 | 294 | private void applyColorFilter() { 295 | if (mBitmapPaint != null) { 296 | mBitmapPaint.setColorFilter(mColorFilter); 297 | } 298 | } 299 | 300 | private Bitmap getBitmapFromDrawable(Drawable drawable) { 301 | if (drawable == null) { 302 | return null; 303 | } 304 | 305 | if (drawable instanceof BitmapDrawable) { 306 | return ((BitmapDrawable) drawable).getBitmap(); 307 | } 308 | 309 | try { 310 | Bitmap bitmap; 311 | 312 | if (drawable instanceof ColorDrawable) { 313 | bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG); 314 | } else { 315 | bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG); 316 | } 317 | 318 | Canvas canvas = new Canvas(bitmap); 319 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 320 | drawable.draw(canvas); 321 | return bitmap; 322 | } catch (Exception e) { 323 | e.printStackTrace(); 324 | return null; 325 | } 326 | } 327 | 328 | private void initializeBitmap() { 329 | if (mDisableCircularTransformation) { 330 | mBitmap = null; 331 | } else { 332 | mBitmap = getBitmapFromDrawable(getDrawable()); 333 | } 334 | setup(); 335 | } 336 | 337 | private void setup() { 338 | if (!mReady) { 339 | mSetupPending = true; 340 | return; 341 | } 342 | 343 | if (getWidth() == 0 && getHeight() == 0) { 344 | return; 345 | } 346 | 347 | if (mBitmap == null) { 348 | invalidate(); 349 | return; 350 | } 351 | 352 | mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 353 | 354 | mBitmapPaint.setAntiAlias(true); 355 | mBitmapPaint.setShader(mBitmapShader); 356 | 357 | mBorderPaint.setStyle(Paint.Style.STROKE); 358 | mBorderPaint.setAntiAlias(true); 359 | mBorderPaint.setColor(mBorderColor); 360 | mBorderPaint.setStrokeWidth(mBorderWidth); 361 | 362 | mFillPaint.setStyle(Paint.Style.FILL); 363 | mFillPaint.setAntiAlias(true); 364 | mFillPaint.setColor(mFillColor); 365 | 366 | mBitmapHeight = mBitmap.getHeight(); 367 | mBitmapWidth = mBitmap.getWidth(); 368 | 369 | mBorderRect.set(calculateBounds()); 370 | mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f); 371 | 372 | mDrawableRect.set(mBorderRect); 373 | if (!mBorderOverlay && mBorderWidth > 0) { 374 | mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f); 375 | } 376 | mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f); 377 | 378 | applyColorFilter(); 379 | updateShaderMatrix(); 380 | invalidate(); 381 | } 382 | 383 | private RectF calculateBounds() { 384 | int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight(); 385 | int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 386 | 387 | int sideLength = Math.min(availableWidth, availableHeight); 388 | 389 | float left = getPaddingLeft() + (availableWidth - sideLength) / 2f; 390 | float top = getPaddingTop() + (availableHeight - sideLength) / 2f; 391 | 392 | return new RectF(left, top, left + sideLength, top + sideLength); 393 | } 394 | 395 | private void updateShaderMatrix() { 396 | float scale; 397 | float dx = 0; 398 | float dy = 0; 399 | 400 | mShaderMatrix.set(null); 401 | 402 | if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { 403 | scale = mDrawableRect.height() / (float) mBitmapHeight; 404 | dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f; 405 | } else { 406 | scale = mDrawableRect.width() / (float) mBitmapWidth; 407 | dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f; 408 | } 409 | 410 | mShaderMatrix.setScale(scale, scale); 411 | mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top); 412 | mBitmapShader.setLocalMatrix(mShaderMatrix); 413 | } 414 | 415 | } 416 | -------------------------------------------------------------------------------- /app/src/main/java/me/stefan/easybehavior/widget/DisInterceptNestedScrollView.java: -------------------------------------------------------------------------------- 1 | package me.stefan.easybehavior.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v4.widget.NestedScrollView; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | 8 | /** 9 | * Created by stefan on 2017/5/26. 10 | * Func:用于子类防止父类拦截子类的事件 11 | */ 12 | public class DisInterceptNestedScrollView extends NestedScrollView { 13 | public DisInterceptNestedScrollView(Context context) { 14 | super(context); 15 | requestDisallowInterceptTouchEvent(true); 16 | } 17 | 18 | public DisInterceptNestedScrollView(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | requestDisallowInterceptTouchEvent(true); 21 | } 22 | 23 | public DisInterceptNestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | requestDisallowInterceptTouchEvent(true); 26 | } 27 | 28 | 29 | public boolean dispatchTouchEvent(MotionEvent ev) { 30 | getParent().requestDisallowInterceptTouchEvent(true); 31 | return super.dispatchTouchEvent(ev); 32 | } 33 | 34 | @Override 35 | public boolean onTouchEvent(MotionEvent event) { 36 | switch (event.getAction()) { 37 | case MotionEvent.ACTION_MOVE: 38 | requestDisallowInterceptTouchEvent(true); 39 | break; 40 | case MotionEvent.ACTION_UP: 41 | case MotionEvent.ACTION_CANCEL: 42 | requestDisallowInterceptTouchEvent(false); 43 | break; 44 | } 45 | return super.onTouchEvent(event); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_avater.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/drawable-xxhdpi/ic_avater.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/drawable-xxhdpi/ic_bg.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon_msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/drawable-xxhdpi/icon_msg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon_msg_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/drawable-xxhdpi/icon_msg_black.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/drawable-xxhdpi/icon_setting.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon_setting_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/drawable-xxhdpi/icon_setting_black.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_ali_middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/drawable/ic_ali_middle.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_ali_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/drawable/ic_ali_top.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_stroke_roundcir.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_stroke_roundredcir.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_demo1.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_item_list1.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_item_list2.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_func_in_uc.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | 20 | 30 | 31 | 41 | 42 | 52 | 53 | 63 | 64 | 65 | 66 | 72 | 73 | 83 | 84 | 94 | 95 | 105 | 106 | 114 | 115 | 116 | 117 | 121 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_uc_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 14 | 15 | 20 | 21 | 22 | 41 | 42 | 46 | 47 | 51 | 52 | 60 | 61 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 77 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_uc_head_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 18 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_uc_head_middle.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 24 | 25 | 33 | 34 | 41 | 42 | 50 | 51 | 62 | 63 | 64 | 65 | 66 | 67 | 71 | 72 | 83 | 84 | 85 | 86 | 87 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_uc_head_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 23 | 34 | 35 | 36 | 46 | 47 | 56 | 57 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.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 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #f5f5f5 8 | #333333 9 | #fc4f74 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48dp 4 | 300dp 5 | 16dp 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | EasyBehavior 3 | 4 | me.stefan.easybehavior.demo1.behavior.AppBarLayoutOverScrollViewBehavior 5 | me.stefan.easybehavior.demo1.behavior.CircleImageInUsercBehavior 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply from: "config.gradle" 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | maven { 7 | url 'https://maven.google.com/' 8 | name 'Google' 9 | } 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.2.0' 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | jcenter() 22 | maven { 23 | url 'https://maven.google.com/' 24 | name 'Google' 25 | } 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | 3 | configuration = [ 4 | applicationId: 'me.stefan.easybehavior' 5 | ] 6 | 7 | 8 | libraries = [ 9 | "support-v4" : "com.android.support:support-v4:$ANDROID_DEPENDENCE_SDK_VERSION", 10 | "appcompat-v7" : "com.android.support:appcompat-v7:$ANDROID_DEPENDENCE_SDK_VERSION", 11 | "design" : "com.android.support:design:$ANDROID_DEPENDENCE_SDK_VERSION", 12 | "palette" : "com.android.support:palette-v7:$ANDROID_DEPENDENCE_SDK_VERSION", 13 | "recyclerview-v7": "com.android.support:recyclerview-v7:$ANDROID_DEPENDENCE_SDK_VERSION", 14 | "glide" : 'com.github.bumptech.glide:glide:3.7.0',//图片加载 15 | "statusbar_util" : 'com.jaeger.statusbarutil:library:1.4.0', 16 | ] 17 | 18 | sdkKey = [ 19 | ] 20 | 21 | testingLibraries = [ 22 | "junit": 'junit:junit:4.12' 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /gif/Coali.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/gif/Coali.gif -------------------------------------------------------------------------------- /gif/EasyBehavior.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/gif/EasyBehavior.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | ANDROID_BUILD_MIN_SDK_VERSION=16 10 | android.enableBuildCache=true 11 | android.useDeprecatedNdk=true 12 | VERSION_NAME=2.0.2 13 | org.gradle.daemon=true 14 | VERSION_CODE=5 15 | ANDROID_BUILD_TARGET_SDK_VERSION=28 16 | org.gradle.parallel=true 17 | ANDROID_BUILD_TOOLS_VERSION=28.0.2 18 | ANDROID_BUILD_SDK_VERSION=28 19 | ANDROID_DEPENDENCE_SDK_VERSION=28.0.0 20 | org.gradle.jvmargs=-Xmx2048m 21 | org.gradle.configureondemand=true 22 | # When configured, Gradle will run in incubating parallel mode. 23 | # This option should only be used with decoupled projects. More details, visit 24 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 25 | # org.gradle.parallel=true 26 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmStefanAndroid/EasyBehavior/2f5f14754f425405a6387acda48462e40fd6b5b9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Feb 28 14:44:22 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app',':tabLayoutLibrary' 2 | -------------------------------------------------------------------------------- /tabLayoutLibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | def libs = rootProject.ext.libraries 3 | android { 4 | compileSdkVersion ANDROID_BUILD_SDK_VERSION as int 5 | buildToolsVersion ANDROID_BUILD_TOOLS_VERSION 6 | defaultConfig { 7 | minSdkVersion ANDROID_BUILD_MIN_SDK_VERSION as int 8 | targetSdkVersion ANDROID_BUILD_TARGET_SDK_VERSION as int 9 | } 10 | } 11 | 12 | dependencies { 13 | implementation fileTree(dir: 'libs', include: ['*.jar']) 14 | implementation libs["support-v4"] 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/java/com/flyco/tablayout/SegmentTabLayout.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout; 2 | 3 | import android.animation.TypeEvaluator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Rect; 11 | import android.graphics.drawable.GradientDrawable; 12 | import android.os.Bundle; 13 | import android.os.Parcelable; 14 | import android.support.v4.app.Fragment; 15 | import android.support.v4.app.FragmentActivity; 16 | import android.util.AttributeSet; 17 | import android.util.SparseArray; 18 | import android.util.TypedValue; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.view.animation.OvershootInterpolator; 22 | import android.widget.FrameLayout; 23 | import android.widget.LinearLayout; 24 | import android.widget.TextView; 25 | 26 | import com.flyco.tablayout.listener.OnTabSelectListener; 27 | import com.flyco.tablayout.utils.FragmentChangeManager; 28 | import com.flyco.tablayout.utils.UnreadMsgUtils; 29 | import com.flyco.tablayout.widget.MsgView; 30 | 31 | import java.util.ArrayList; 32 | 33 | public class SegmentTabLayout extends FrameLayout implements ValueAnimator.AnimatorUpdateListener { 34 | private Context mContext; 35 | private String[] mTitles; 36 | private LinearLayout mTabsContainer; 37 | private int mCurrentTab; 38 | private int mLastTab; 39 | private int mTabCount; 40 | /** 用于绘制显示器 */ 41 | private Rect mIndicatorRect = new Rect(); 42 | private GradientDrawable mIndicatorDrawable = new GradientDrawable(); 43 | private GradientDrawable mRectDrawable = new GradientDrawable(); 44 | 45 | private Paint mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 46 | 47 | private float mTabPadding; 48 | private boolean mTabSpaceEqual; 49 | private float mTabWidth; 50 | 51 | /** indicator */ 52 | private int mIndicatorColor; 53 | private float mIndicatorHeight; 54 | private float mIndicatorCornerRadius; 55 | private float mIndicatorMarginLeft; 56 | private float mIndicatorMarginTop; 57 | private float mIndicatorMarginRight; 58 | private float mIndicatorMarginBottom; 59 | private long mIndicatorAnimDuration; 60 | private boolean mIndicatorAnimEnable; 61 | private boolean mIndicatorBounceEnable; 62 | 63 | /** divider */ 64 | private int mDividerColor; 65 | private float mDividerWidth; 66 | private float mDividerPadding; 67 | 68 | /** title */ 69 | private float mTextsize; 70 | private int mTextSelectColor; 71 | private int mTextUnselectColor; 72 | private boolean mTextBold; 73 | private boolean mTextAllCaps; 74 | 75 | private int mBarColor; 76 | private int mBarStrokeColor; 77 | private float mBarStrokeWidth; 78 | 79 | private int mHeight; 80 | 81 | /** anim */ 82 | private ValueAnimator mValueAnimator; 83 | private OvershootInterpolator mInterpolator = new OvershootInterpolator(0.8f); 84 | 85 | private FragmentChangeManager mFragmentChangeManager; 86 | private float[] mRadiusArr = new float[8]; 87 | 88 | public SegmentTabLayout(Context context) { 89 | this(context, null, 0); 90 | } 91 | 92 | public SegmentTabLayout(Context context, AttributeSet attrs) { 93 | this(context, attrs, 0); 94 | } 95 | 96 | public SegmentTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { 97 | super(context, attrs, defStyleAttr); 98 | setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag 99 | setClipChildren(false); 100 | setClipToPadding(false); 101 | 102 | this.mContext = context; 103 | mTabsContainer = new LinearLayout(context); 104 | addView(mTabsContainer); 105 | 106 | obtainAttributes(context, attrs); 107 | 108 | //get layout_height 109 | String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height"); 110 | 111 | //create ViewPager 112 | if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) { 113 | } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) { 114 | } else { 115 | int[] systemAttrs = {android.R.attr.layout_height}; 116 | TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs); 117 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT); 118 | a.recycle(); 119 | } 120 | 121 | mValueAnimator = ValueAnimator.ofObject(new PointEvaluator(), mLastP, mCurrentP); 122 | mValueAnimator.addUpdateListener(this); 123 | } 124 | 125 | private void obtainAttributes(Context context, AttributeSet attrs) { 126 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SegmentTabLayout); 127 | 128 | mIndicatorColor = ta.getColor(R.styleable.SegmentTabLayout_tl_indicator_color, Color.parseColor("#222831")); 129 | mIndicatorHeight = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_height, -1); 130 | mIndicatorCornerRadius = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_corner_radius, -1); 131 | mIndicatorMarginLeft = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_left, dp2px(0)); 132 | mIndicatorMarginTop = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_top, 0); 133 | mIndicatorMarginRight = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_right, dp2px(0)); 134 | mIndicatorMarginBottom = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_bottom, 0); 135 | mIndicatorAnimEnable = ta.getBoolean(R.styleable.SegmentTabLayout_tl_indicator_anim_enable, false); 136 | mIndicatorBounceEnable = ta.getBoolean(R.styleable.SegmentTabLayout_tl_indicator_bounce_enable, true); 137 | mIndicatorAnimDuration = ta.getInt(R.styleable.SegmentTabLayout_tl_indicator_anim_duration, -1); 138 | 139 | mDividerColor = ta.getColor(R.styleable.SegmentTabLayout_tl_divider_color, mIndicatorColor); 140 | mDividerWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_divider_width, dp2px(1)); 141 | mDividerPadding = ta.getDimension(R.styleable.SegmentTabLayout_tl_divider_padding, 0); 142 | 143 | mTextsize = ta.getDimension(R.styleable.SegmentTabLayout_tl_textsize, sp2px(13f)); 144 | mTextSelectColor = ta.getColor(R.styleable.SegmentTabLayout_tl_textSelectColor, Color.parseColor("#ffffff")); 145 | mTextUnselectColor = ta.getColor(R.styleable.SegmentTabLayout_tl_textUnselectColor, mIndicatorColor); 146 | mTextBold = ta.getBoolean(R.styleable.SegmentTabLayout_tl_textBold, false); 147 | mTextAllCaps = ta.getBoolean(R.styleable.SegmentTabLayout_tl_textAllCaps, false); 148 | 149 | mTabSpaceEqual = ta.getBoolean(R.styleable.SegmentTabLayout_tl_tab_space_equal, true); 150 | mTabWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_tab_width, dp2px(-1)); 151 | mTabPadding = ta.getDimension(R.styleable.SegmentTabLayout_tl_tab_padding, mTabSpaceEqual || mTabWidth > 0 ? dp2px(0) : dp2px(10)); 152 | 153 | mBarColor = ta.getColor(R.styleable.SegmentTabLayout_tl_bar_color, Color.TRANSPARENT); 154 | mBarStrokeColor = ta.getColor(R.styleable.SegmentTabLayout_tl_bar_stroke_color, mIndicatorColor); 155 | mBarStrokeWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_bar_stroke_width, dp2px(1)); 156 | 157 | ta.recycle(); 158 | } 159 | 160 | public void setTabData(String[] titles) { 161 | if (titles == null || titles.length == 0) { 162 | throw new IllegalStateException("Titles can not be NULL or EMPTY !"); 163 | } 164 | 165 | this.mTitles = titles; 166 | 167 | notifyDataSetChanged(); 168 | } 169 | 170 | /** 关联数据支持同时切换fragments */ 171 | public void setTabData(String[] titles, FragmentActivity fa, int containerViewId, ArrayList fragments) { 172 | mFragmentChangeManager = new FragmentChangeManager(fa.getSupportFragmentManager(), containerViewId, fragments); 173 | setTabData(titles); 174 | } 175 | 176 | /** 更新数据 */ 177 | public void notifyDataSetChanged() { 178 | mTabsContainer.removeAllViews(); 179 | this.mTabCount = mTitles.length; 180 | View tabView; 181 | for (int i = 0; i < mTabCount; i++) { 182 | tabView = View.inflate(mContext, R.layout.layout_tab_segment, null); 183 | tabView.setTag(i); 184 | addTab(i, tabView); 185 | } 186 | 187 | updateTabStyles(); 188 | } 189 | 190 | /** 创建并添加tab */ 191 | private void addTab(final int position, View tabView) { 192 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 193 | tv_tab_title.setText(mTitles[position]); 194 | 195 | tabView.setOnClickListener(new OnClickListener() { 196 | @Override 197 | public void onClick(View v) { 198 | int position = (Integer) v.getTag(); 199 | if (mCurrentTab != position) { 200 | setCurrentTab(position); 201 | if (mListener != null) { 202 | mListener.onTabSelect(position); 203 | } 204 | } else { 205 | if (mListener != null) { 206 | mListener.onTabReselect(position); 207 | } 208 | } 209 | } 210 | }); 211 | 212 | /** 每一个Tab的布局参数 */ 213 | LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ? 214 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : 215 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 216 | if (mTabWidth > 0) { 217 | lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT); 218 | } 219 | mTabsContainer.addView(tabView, position, lp_tab); 220 | } 221 | 222 | private void updateTabStyles() { 223 | for (int i = 0; i < mTabCount; i++) { 224 | View tabView = mTabsContainer.getChildAt(i); 225 | tabView.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 226 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 227 | tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor); 228 | tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize); 229 | // tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 230 | if (mTextAllCaps) { 231 | tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase()); 232 | } 233 | 234 | if (mTextBold) { 235 | tv_tab_title.getPaint().setFakeBoldText(mTextBold); 236 | } 237 | } 238 | } 239 | 240 | private void updateTabSelection(int position) { 241 | for (int i = 0; i < mTabCount; ++i) { 242 | View tabView = mTabsContainer.getChildAt(i); 243 | final boolean isSelect = i == position; 244 | TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 245 | tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor); 246 | } 247 | } 248 | 249 | private void calcOffset() { 250 | final View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 251 | mCurrentP.left = currentTabView.getLeft(); 252 | mCurrentP.right = currentTabView.getRight(); 253 | 254 | final View lastTabView = mTabsContainer.getChildAt(this.mLastTab); 255 | mLastP.left = lastTabView.getLeft(); 256 | mLastP.right = lastTabView.getRight(); 257 | 258 | // Log.d("AAA", "mLastP--->" + mLastP.left + "&" + mLastP.right); 259 | // Log.d("AAA", "mCurrentP--->" + mCurrentP.left + "&" + mCurrentP.right); 260 | if (mLastP.left == mCurrentP.left && mLastP.right == mCurrentP.right) { 261 | invalidate(); 262 | } else { 263 | mValueAnimator.setObjectValues(mLastP, mCurrentP); 264 | if (mIndicatorBounceEnable) { 265 | mValueAnimator.setInterpolator(mInterpolator); 266 | } 267 | 268 | if (mIndicatorAnimDuration < 0) { 269 | mIndicatorAnimDuration = mIndicatorBounceEnable ? 500 : 250; 270 | } 271 | mValueAnimator.setDuration(mIndicatorAnimDuration); 272 | mValueAnimator.start(); 273 | } 274 | } 275 | 276 | private void calcIndicatorRect() { 277 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 278 | float left = currentTabView.getLeft(); 279 | float right = currentTabView.getRight(); 280 | 281 | mIndicatorRect.left = (int) left; 282 | mIndicatorRect.right = (int) right; 283 | 284 | if (!mIndicatorAnimEnable) { 285 | if (mCurrentTab == 0) { 286 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/ 287 | mRadiusArr[0] = mIndicatorCornerRadius; 288 | mRadiusArr[1] = mIndicatorCornerRadius; 289 | mRadiusArr[2] = 0; 290 | mRadiusArr[3] = 0; 291 | mRadiusArr[4] = 0; 292 | mRadiusArr[5] = 0; 293 | mRadiusArr[6] = mIndicatorCornerRadius; 294 | mRadiusArr[7] = mIndicatorCornerRadius; 295 | } else if (mCurrentTab == mTabCount - 1) { 296 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/ 297 | mRadiusArr[0] = 0; 298 | mRadiusArr[1] = 0; 299 | mRadiusArr[2] = mIndicatorCornerRadius; 300 | mRadiusArr[3] = mIndicatorCornerRadius; 301 | mRadiusArr[4] = mIndicatorCornerRadius; 302 | mRadiusArr[5] = mIndicatorCornerRadius; 303 | mRadiusArr[6] = 0; 304 | mRadiusArr[7] = 0; 305 | } else { 306 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/ 307 | mRadiusArr[0] = 0; 308 | mRadiusArr[1] = 0; 309 | mRadiusArr[2] = 0; 310 | mRadiusArr[3] = 0; 311 | mRadiusArr[4] = 0; 312 | mRadiusArr[5] = 0; 313 | mRadiusArr[6] = 0; 314 | mRadiusArr[7] = 0; 315 | } 316 | } else { 317 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/ 318 | mRadiusArr[0] = mIndicatorCornerRadius; 319 | mRadiusArr[1] = mIndicatorCornerRadius; 320 | mRadiusArr[2] = mIndicatorCornerRadius; 321 | mRadiusArr[3] = mIndicatorCornerRadius; 322 | mRadiusArr[4] = mIndicatorCornerRadius; 323 | mRadiusArr[5] = mIndicatorCornerRadius; 324 | mRadiusArr[6] = mIndicatorCornerRadius; 325 | mRadiusArr[7] = mIndicatorCornerRadius; 326 | } 327 | } 328 | 329 | @Override 330 | public void onAnimationUpdate(ValueAnimator animation) { 331 | IndicatorPoint p = (IndicatorPoint) animation.getAnimatedValue(); 332 | mIndicatorRect.left = (int) p.left; 333 | mIndicatorRect.right = (int) p.right; 334 | invalidate(); 335 | } 336 | 337 | private boolean mIsFirstDraw = true; 338 | 339 | @Override 340 | protected void onDraw(Canvas canvas) { 341 | super.onDraw(canvas); 342 | 343 | if (isInEditMode() || mTabCount <= 0) { 344 | return; 345 | } 346 | 347 | int height = getHeight(); 348 | int paddingLeft = getPaddingLeft(); 349 | 350 | if (mIndicatorHeight < 0) { 351 | mIndicatorHeight = height - mIndicatorMarginTop - mIndicatorMarginBottom; 352 | } 353 | 354 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) { 355 | mIndicatorCornerRadius = mIndicatorHeight / 2; 356 | } 357 | 358 | //draw rect 359 | mRectDrawable.setColor(mBarColor); 360 | mRectDrawable.setStroke((int) mBarStrokeWidth, mBarStrokeColor); 361 | mRectDrawable.setCornerRadius(mIndicatorCornerRadius); 362 | mRectDrawable.setBounds(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); 363 | mRectDrawable.draw(canvas); 364 | 365 | // draw divider 366 | if (!mIndicatorAnimEnable && mDividerWidth > 0) { 367 | mDividerPaint.setStrokeWidth(mDividerWidth); 368 | mDividerPaint.setColor(mDividerColor); 369 | for (int i = 0; i < mTabCount - 1; i++) { 370 | View tab = mTabsContainer.getChildAt(i); 371 | canvas.drawLine(paddingLeft + tab.getRight(), mDividerPadding, paddingLeft + tab.getRight(), height - mDividerPadding, mDividerPaint); 372 | } 373 | } 374 | 375 | 376 | //draw indicator line 377 | if (mIndicatorAnimEnable) { 378 | if (mIsFirstDraw) { 379 | mIsFirstDraw = false; 380 | calcIndicatorRect(); 381 | } 382 | } else { 383 | calcIndicatorRect(); 384 | } 385 | 386 | mIndicatorDrawable.setColor(mIndicatorColor); 387 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 388 | (int) mIndicatorMarginTop, (int) (paddingLeft + mIndicatorRect.right - mIndicatorMarginRight), 389 | (int) (mIndicatorMarginTop + mIndicatorHeight)); 390 | mIndicatorDrawable.setCornerRadii(mRadiusArr); 391 | mIndicatorDrawable.draw(canvas); 392 | 393 | } 394 | 395 | //setter and getter 396 | public void setCurrentTab(int currentTab) { 397 | mLastTab = this.mCurrentTab; 398 | this.mCurrentTab = currentTab; 399 | updateTabSelection(currentTab); 400 | if (mFragmentChangeManager != null) { 401 | mFragmentChangeManager.setFragments(currentTab); 402 | } 403 | if (mIndicatorAnimEnable) { 404 | calcOffset(); 405 | } else { 406 | invalidate(); 407 | } 408 | } 409 | 410 | public void setTabPadding(float tabPadding) { 411 | this.mTabPadding = dp2px(tabPadding); 412 | updateTabStyles(); 413 | } 414 | 415 | public void setTabSpaceEqual(boolean tabSpaceEqual) { 416 | this.mTabSpaceEqual = tabSpaceEqual; 417 | updateTabStyles(); 418 | } 419 | 420 | public void setTabWidth(float tabWidth) { 421 | this.mTabWidth = dp2px(tabWidth); 422 | updateTabStyles(); 423 | } 424 | 425 | public void setIndicatorColor(int indicatorColor) { 426 | this.mIndicatorColor = indicatorColor; 427 | invalidate(); 428 | } 429 | 430 | public void setIndicatorHeight(float indicatorHeight) { 431 | this.mIndicatorHeight = dp2px(indicatorHeight); 432 | invalidate(); 433 | } 434 | 435 | public void setIndicatorCornerRadius(float indicatorCornerRadius) { 436 | this.mIndicatorCornerRadius = dp2px(indicatorCornerRadius); 437 | invalidate(); 438 | } 439 | 440 | public void setIndicatorMargin(float indicatorMarginLeft, float indicatorMarginTop, 441 | float indicatorMarginRight, float indicatorMarginBottom) { 442 | this.mIndicatorMarginLeft = dp2px(indicatorMarginLeft); 443 | this.mIndicatorMarginTop = dp2px(indicatorMarginTop); 444 | this.mIndicatorMarginRight = dp2px(indicatorMarginRight); 445 | this.mIndicatorMarginBottom = dp2px(indicatorMarginBottom); 446 | invalidate(); 447 | } 448 | 449 | public void setIndicatorAnimDuration(long indicatorAnimDuration) { 450 | this.mIndicatorAnimDuration = indicatorAnimDuration; 451 | } 452 | 453 | public void setIndicatorAnimEnable(boolean indicatorAnimEnable) { 454 | this.mIndicatorAnimEnable = indicatorAnimEnable; 455 | } 456 | 457 | public void setIndicatorBounceEnable(boolean indicatorBounceEnable) { 458 | this.mIndicatorBounceEnable = indicatorBounceEnable; 459 | } 460 | 461 | public void setDividerColor(int dividerColor) { 462 | this.mDividerColor = dividerColor; 463 | invalidate(); 464 | } 465 | 466 | public void setDividerWidth(float dividerWidth) { 467 | this.mDividerWidth = dp2px(dividerWidth); 468 | invalidate(); 469 | } 470 | 471 | public void setDividerPadding(float dividerPadding) { 472 | this.mDividerPadding = dp2px(dividerPadding); 473 | invalidate(); 474 | } 475 | 476 | public void setTextsize(float textsize) { 477 | this.mTextsize = sp2px(textsize); 478 | updateTabStyles(); 479 | } 480 | 481 | public void setTextSelectColor(int textSelectColor) { 482 | this.mTextSelectColor = textSelectColor; 483 | updateTabStyles(); 484 | } 485 | 486 | public void setTextUnselectColor(int textUnselectColor) { 487 | this.mTextUnselectColor = textUnselectColor; 488 | updateTabStyles(); 489 | } 490 | 491 | public void setTextBold(boolean textBold) { 492 | this.mTextBold = textBold; 493 | updateTabStyles(); 494 | } 495 | 496 | public void setTextAllCaps(boolean textAllCaps) { 497 | this.mTextAllCaps = textAllCaps; 498 | updateTabStyles(); 499 | } 500 | 501 | public int getTabCount() { 502 | return mTabCount; 503 | } 504 | 505 | public int getCurrentTab() { 506 | return mCurrentTab; 507 | } 508 | 509 | public float getTabPadding() { 510 | return mTabPadding; 511 | } 512 | 513 | public boolean isTabSpaceEqual() { 514 | return mTabSpaceEqual; 515 | } 516 | 517 | public float getTabWidth() { 518 | return mTabWidth; 519 | } 520 | 521 | public int getIndicatorColor() { 522 | return mIndicatorColor; 523 | } 524 | 525 | public float getIndicatorHeight() { 526 | return mIndicatorHeight; 527 | } 528 | 529 | public float getIndicatorCornerRadius() { 530 | return mIndicatorCornerRadius; 531 | } 532 | 533 | public float getIndicatorMarginLeft() { 534 | return mIndicatorMarginLeft; 535 | } 536 | 537 | public float getIndicatorMarginTop() { 538 | return mIndicatorMarginTop; 539 | } 540 | 541 | public float getIndicatorMarginRight() { 542 | return mIndicatorMarginRight; 543 | } 544 | 545 | public float getIndicatorMarginBottom() { 546 | return mIndicatorMarginBottom; 547 | } 548 | 549 | public long getIndicatorAnimDuration() { 550 | return mIndicatorAnimDuration; 551 | } 552 | 553 | public boolean isIndicatorAnimEnable() { 554 | return mIndicatorAnimEnable; 555 | } 556 | 557 | public boolean isIndicatorBounceEnable() { 558 | return mIndicatorBounceEnable; 559 | } 560 | 561 | public int getDividerColor() { 562 | return mDividerColor; 563 | } 564 | 565 | public float getDividerWidth() { 566 | return mDividerWidth; 567 | } 568 | 569 | public float getDividerPadding() { 570 | return mDividerPadding; 571 | } 572 | 573 | public float getTextsize() { 574 | return mTextsize; 575 | } 576 | 577 | public int getTextSelectColor() { 578 | return mTextSelectColor; 579 | } 580 | 581 | public int getTextUnselectColor() { 582 | return mTextUnselectColor; 583 | } 584 | 585 | public boolean isTextBold() { 586 | return mTextBold; 587 | } 588 | 589 | public boolean isTextAllCaps() { 590 | return mTextAllCaps; 591 | } 592 | 593 | public TextView getTitleView(int tab) { 594 | View tabView = mTabsContainer.getChildAt(tab); 595 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 596 | return tv_tab_title; 597 | } 598 | 599 | //setter and getter 600 | // show MsgTipView 601 | private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 602 | private SparseArray mInitSetMap = new SparseArray<>(); 603 | 604 | /** 605 | * 显示未读消息 606 | * 607 | * @param position 显示tab位置 608 | * @param num num小于等于0显示红点,num大于0显示数字 609 | */ 610 | public void showMsg(int position, int num) { 611 | if (position >= mTabCount) { 612 | position = mTabCount - 1; 613 | } 614 | 615 | View tabView = mTabsContainer.getChildAt(position); 616 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 617 | if (tipView != null) { 618 | UnreadMsgUtils.show(tipView, num); 619 | 620 | if (mInitSetMap.get(position) != null && mInitSetMap.get(position)) { 621 | return; 622 | } 623 | 624 | setMsgMargin(position, 2, 2); 625 | 626 | mInitSetMap.put(position, true); 627 | } 628 | } 629 | 630 | /** 631 | * 显示未读红点 632 | * 633 | * @param position 显示tab位置 634 | */ 635 | public void showDot(int position) { 636 | if (position >= mTabCount) { 637 | position = mTabCount - 1; 638 | } 639 | showMsg(position, 0); 640 | } 641 | 642 | public void hideMsg(int position) { 643 | if (position >= mTabCount) { 644 | position = mTabCount - 1; 645 | } 646 | 647 | View tabView = mTabsContainer.getChildAt(position); 648 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 649 | if (tipView != null) { 650 | tipView.setVisibility(View.GONE); 651 | } 652 | } 653 | 654 | /** 655 | * 设置提示红点偏移,注意 656 | * 1.控件为固定高度:参照点为tab内容的右上角 657 | * 2.控件高度不固定(WRAP_CONTENT):参照点为tab内容的右上角,此时高度已是红点的最高显示范围,所以这时bottomPadding其实就是topPadding 658 | */ 659 | public void setMsgMargin(int position, float leftPadding, float bottomPadding) { 660 | if (position >= mTabCount) { 661 | position = mTabCount - 1; 662 | } 663 | View tabView = mTabsContainer.getChildAt(position); 664 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 665 | if (tipView != null) { 666 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 667 | mTextPaint.setTextSize(mTextsize); 668 | float textWidth = mTextPaint.measureText(tv_tab_title.getText().toString()); 669 | float textHeight = mTextPaint.descent() - mTextPaint.ascent(); 670 | MarginLayoutParams lp = (MarginLayoutParams) tipView.getLayoutParams(); 671 | 672 | lp.leftMargin = dp2px(leftPadding); 673 | lp.topMargin = mHeight > 0 ? (int) (mHeight - textHeight) / 2 - dp2px(bottomPadding) : dp2px(bottomPadding); 674 | 675 | tipView.setLayoutParams(lp); 676 | } 677 | } 678 | 679 | /** 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置 */ 680 | public MsgView getMsgView(int position) { 681 | if (position >= mTabCount) { 682 | position = mTabCount - 1; 683 | } 684 | View tabView = mTabsContainer.getChildAt(position); 685 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 686 | return tipView; 687 | } 688 | 689 | private OnTabSelectListener mListener; 690 | 691 | public void setOnTabSelectListener(OnTabSelectListener listener) { 692 | this.mListener = listener; 693 | } 694 | 695 | @Override 696 | protected Parcelable onSaveInstanceState() { 697 | Bundle bundle = new Bundle(); 698 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 699 | bundle.putInt("mCurrentTab", mCurrentTab); 700 | return bundle; 701 | } 702 | 703 | @Override 704 | protected void onRestoreInstanceState(Parcelable state) { 705 | if (state instanceof Bundle) { 706 | Bundle bundle = (Bundle) state; 707 | mCurrentTab = bundle.getInt("mCurrentTab"); 708 | state = bundle.getParcelable("instanceState"); 709 | if (mCurrentTab != 0 && mTabsContainer.getChildCount() > 0) { 710 | updateTabSelection(mCurrentTab); 711 | } 712 | } 713 | super.onRestoreInstanceState(state); 714 | } 715 | 716 | class IndicatorPoint { 717 | public float left; 718 | public float right; 719 | } 720 | 721 | private IndicatorPoint mCurrentP = new IndicatorPoint(); 722 | private IndicatorPoint mLastP = new IndicatorPoint(); 723 | 724 | class PointEvaluator implements TypeEvaluator { 725 | @Override 726 | public IndicatorPoint evaluate(float fraction, IndicatorPoint startValue, IndicatorPoint endValue) { 727 | float left = startValue.left + fraction * (endValue.left - startValue.left); 728 | float right = startValue.right + fraction * (endValue.right - startValue.right); 729 | IndicatorPoint point = new IndicatorPoint(); 730 | point.left = left; 731 | point.right = right; 732 | return point; 733 | } 734 | } 735 | 736 | protected int dp2px(float dp) { 737 | final float scale = mContext.getResources().getDisplayMetrics().density; 738 | return (int) (dp * scale + 0.5f); 739 | } 740 | 741 | protected int sp2px(float sp) { 742 | final float scale = this.mContext.getResources().getDisplayMetrics().scaledDensity; 743 | return (int) (sp * scale + 0.5f); 744 | } 745 | } 746 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/java/com/flyco/tablayout/SlidingTabLayout.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Rect; 10 | import android.graphics.drawable.GradientDrawable; 11 | import android.os.Bundle; 12 | import android.os.Parcelable; 13 | import android.support.v4.app.Fragment; 14 | import android.support.v4.app.FragmentActivity; 15 | import android.support.v4.app.FragmentManager; 16 | import android.support.v4.app.FragmentPagerAdapter; 17 | import android.support.v4.view.PagerAdapter; 18 | import android.support.v4.view.ViewPager; 19 | import android.util.AttributeSet; 20 | import android.util.SparseArray; 21 | import android.util.TypedValue; 22 | import android.view.Gravity; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.widget.HorizontalScrollView; 26 | import android.widget.LinearLayout; 27 | import android.widget.TextView; 28 | 29 | import com.flyco.tablayout.listener.OnTabSelectListener; 30 | import com.flyco.tablayout.utils.UnreadMsgUtils; 31 | import com.flyco.tablayout.widget.MsgView; 32 | 33 | import java.util.ArrayList; 34 | import java.util.Collections; 35 | 36 | /** 滑动TabLayout,对于ViewPager的依赖性强 */ 37 | public class SlidingTabLayout extends HorizontalScrollView implements ViewPager.OnPageChangeListener { 38 | private Context mContext; 39 | private ViewPager mViewPager; 40 | private ArrayList mTitles; 41 | private LinearLayout mTabsContainer; 42 | private int mCurrentTab; 43 | private float mCurrentPositionOffset; 44 | private int mTabCount; 45 | /** 用于绘制显示器 */ 46 | private Rect mIndicatorRect = new Rect(); 47 | /** 用于实现滚动居中 */ 48 | private Rect mTabRect = new Rect(); 49 | private GradientDrawable mIndicatorDrawable = new GradientDrawable(); 50 | 51 | private Paint mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 52 | private Paint mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 53 | private Paint mTrianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 54 | private Path mTrianglePath = new Path(); 55 | private static final int STYLE_NORMAL = 0; 56 | private static final int STYLE_TRIANGLE = 1; 57 | private static final int STYLE_BLOCK = 2; 58 | private int mIndicatorStyle = STYLE_NORMAL; 59 | 60 | private float mTabPadding; 61 | private boolean mTabSpaceEqual; 62 | private float mTabWidth; 63 | 64 | /** indicator */ 65 | private int mIndicatorColor; 66 | private float mIndicatorHeight; 67 | private float mIndicatorWidth; 68 | private float mIndicatorCornerRadius; 69 | private float mIndicatorMarginLeft; 70 | private float mIndicatorMarginTop; 71 | private float mIndicatorMarginRight; 72 | private float mIndicatorMarginBottom; 73 | private int mIndicatorGravity; 74 | private boolean mIndicatorWidthEqualTitle; 75 | 76 | /** underline */ 77 | private int mUnderlineColor; 78 | private float mUnderlineHeight; 79 | private int mUnderlineGravity; 80 | 81 | /** divider */ 82 | private int mDividerColor; 83 | private float mDividerWidth; 84 | private float mDividerPadding; 85 | 86 | /** title */ 87 | private float mTextsize; 88 | private int mTextSelectColor; 89 | private int mTextUnselectColor; 90 | private boolean mTextBold; 91 | private boolean mTextAllCaps; 92 | 93 | private int mLastScrollX; 94 | private int mHeight; 95 | 96 | public SlidingTabLayout(Context context) { 97 | this(context, null, 0); 98 | } 99 | 100 | public SlidingTabLayout(Context context, AttributeSet attrs) { 101 | this(context, attrs, 0); 102 | } 103 | 104 | public SlidingTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { 105 | super(context, attrs, defStyleAttr); 106 | setFillViewport(true);//设置滚动视图是否可以伸缩其内容以填充视口 107 | setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag 108 | setClipChildren(false); 109 | setClipToPadding(false); 110 | 111 | this.mContext = context; 112 | mTabsContainer = new LinearLayout(context); 113 | addView(mTabsContainer); 114 | 115 | obtainAttributes(context, attrs); 116 | 117 | //get layout_height 118 | String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height"); 119 | 120 | //create ViewPager 121 | if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) { 122 | } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) { 123 | } else { 124 | int[] systemAttrs = {android.R.attr.layout_height}; 125 | TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs); 126 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT); 127 | a.recycle(); 128 | } 129 | } 130 | 131 | private void obtainAttributes(Context context, AttributeSet attrs) { 132 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingTabLayout); 133 | 134 | mIndicatorStyle = ta.getInt(R.styleable.SlidingTabLayout_tl_indicator_style, STYLE_NORMAL); 135 | mIndicatorColor = ta.getColor(R.styleable.SlidingTabLayout_tl_indicator_color, Color.parseColor(mIndicatorStyle == STYLE_BLOCK ? "#4B6A87" : "#ffffff")); 136 | mIndicatorHeight = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_height, 137 | dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 4 : (mIndicatorStyle == STYLE_BLOCK ? -1 : 2))); 138 | mIndicatorWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_width, dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 10 : -1)); 139 | mIndicatorCornerRadius = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_corner_radius, dp2px(mIndicatorStyle == STYLE_BLOCK ? -1 : 0)); 140 | mIndicatorMarginLeft = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_left, dp2px(0)); 141 | mIndicatorMarginTop = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_top, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0)); 142 | mIndicatorMarginRight = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_right, dp2px(0)); 143 | mIndicatorMarginBottom = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_bottom, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0)); 144 | mIndicatorGravity = ta.getInt(R.styleable.SlidingTabLayout_tl_indicator_gravity, Gravity.BOTTOM); 145 | mIndicatorWidthEqualTitle = ta.getBoolean(R.styleable.SlidingTabLayout_tl_indicator_width_equal_title, false); 146 | 147 | mUnderlineColor = ta.getColor(R.styleable.SlidingTabLayout_tl_underline_color, Color.parseColor("#ffffff")); 148 | mUnderlineHeight = ta.getDimension(R.styleable.SlidingTabLayout_tl_underline_height, dp2px(0)); 149 | mUnderlineGravity = ta.getInt(R.styleable.SlidingTabLayout_tl_underline_gravity, Gravity.BOTTOM); 150 | 151 | mDividerColor = ta.getColor(R.styleable.SlidingTabLayout_tl_divider_color, Color.parseColor("#ffffff")); 152 | mDividerWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_divider_width, dp2px(0)); 153 | mDividerPadding = ta.getDimension(R.styleable.SlidingTabLayout_tl_divider_padding, dp2px(12)); 154 | 155 | mTextsize = ta.getDimension(R.styleable.SlidingTabLayout_tl_textsize, sp2px(14)); 156 | mTextSelectColor = ta.getColor(R.styleable.SlidingTabLayout_tl_textSelectColor, Color.parseColor("#ffffff")); 157 | mTextUnselectColor = ta.getColor(R.styleable.SlidingTabLayout_tl_textUnselectColor, Color.parseColor("#AAffffff")); 158 | mTextBold = ta.getBoolean(R.styleable.SlidingTabLayout_tl_textBold, false); 159 | mTextAllCaps = ta.getBoolean(R.styleable.SlidingTabLayout_tl_textAllCaps, false); 160 | 161 | mTabSpaceEqual = ta.getBoolean(R.styleable.SlidingTabLayout_tl_tab_space_equal, false); 162 | mTabWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_tab_width, dp2px(-1)); 163 | mTabPadding = ta.getDimension(R.styleable.SlidingTabLayout_tl_tab_padding, mTabSpaceEqual || mTabWidth > 0 ? dp2px(0) : dp2px(20)); 164 | 165 | ta.recycle(); 166 | } 167 | 168 | /** 关联ViewPager */ 169 | public void setViewPager(ViewPager vp) { 170 | if (vp == null || vp.getAdapter() == null) { 171 | throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !"); 172 | } 173 | 174 | this.mViewPager = vp; 175 | 176 | this.mViewPager.removeOnPageChangeListener(this); 177 | this.mViewPager.addOnPageChangeListener(this); 178 | notifyDataSetChanged(); 179 | } 180 | 181 | /** 关联ViewPager,用于不想在ViewPager适配器中设置titles数据的情况 */ 182 | public void setViewPager(ViewPager vp, String[] titles) { 183 | if (vp == null || vp.getAdapter() == null) { 184 | throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !"); 185 | } 186 | 187 | if (titles == null || titles.length == 0) { 188 | throw new IllegalStateException("Titles can not be EMPTY !"); 189 | } 190 | 191 | if (titles.length != vp.getAdapter().getCount()) { 192 | throw new IllegalStateException("Titles length must be the same as the page count !"); 193 | } 194 | 195 | this.mViewPager = vp; 196 | mTitles = new ArrayList<>(); 197 | Collections.addAll(mTitles, titles); 198 | 199 | this.mViewPager.removeOnPageChangeListener(this); 200 | this.mViewPager.addOnPageChangeListener(this); 201 | notifyDataSetChanged(); 202 | } 203 | 204 | /** 关联ViewPager,用于连适配器都不想自己实例化的情况 */ 205 | public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList fragments) { 206 | if (vp == null) { 207 | throw new IllegalStateException("ViewPager can not be NULL !"); 208 | } 209 | 210 | if (titles == null || titles.length == 0) { 211 | throw new IllegalStateException("Titles can not be EMPTY !"); 212 | } 213 | 214 | this.mViewPager = vp; 215 | this.mViewPager.setAdapter(new InnerPagerAdapter(fa.getSupportFragmentManager(), fragments, titles)); 216 | 217 | this.mViewPager.removeOnPageChangeListener(this); 218 | this.mViewPager.addOnPageChangeListener(this); 219 | notifyDataSetChanged(); 220 | } 221 | 222 | /** 更新数据 */ 223 | public void notifyDataSetChanged() { 224 | mTabsContainer.removeAllViews(); 225 | this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size(); 226 | View tabView; 227 | for (int i = 0; i < mTabCount; i++) { 228 | tabView = View.inflate(mContext, R.layout.layout_tab, null); 229 | CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(i) : mTitles.get(i); 230 | addTab(i, pageTitle.toString(), tabView); 231 | } 232 | 233 | updateTabStyles(); 234 | } 235 | 236 | public void addNewTab(String title) { 237 | View tabView = View.inflate(mContext, R.layout.layout_tab, null); 238 | if (mTitles != null) { 239 | mTitles.add(title); 240 | } 241 | 242 | CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(mTabCount) : mTitles.get(mTabCount); 243 | addTab(mTabCount, pageTitle.toString(), tabView); 244 | this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size(); 245 | 246 | updateTabStyles(); 247 | } 248 | 249 | /** 创建并添加tab */ 250 | private void addTab(final int position, String title, View tabView) { 251 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 252 | if (tv_tab_title != null) { 253 | if (title != null) tv_tab_title.setText(title); 254 | } 255 | 256 | tabView.setOnClickListener(new OnClickListener() { 257 | @Override 258 | public void onClick(View v) { 259 | int position = mTabsContainer.indexOfChild(v); 260 | if (position != -1) { 261 | if (mViewPager.getCurrentItem() != position) { 262 | mViewPager.setCurrentItem(position); 263 | if (mListener != null) { 264 | mListener.onTabSelect(position); 265 | } 266 | } else { 267 | if (mListener != null) { 268 | mListener.onTabReselect(position); 269 | } 270 | } 271 | } 272 | } 273 | }); 274 | 275 | /** 每一个Tab的布局参数 */ 276 | LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ? 277 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : 278 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 279 | if (mTabWidth > 0) { 280 | lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT); 281 | } 282 | 283 | mTabsContainer.addView(tabView, position, lp_tab); 284 | } 285 | 286 | private void updateTabStyles() { 287 | for (int i = 0; i < mTabCount; i++) { 288 | View v = mTabsContainer.getChildAt(i); 289 | // v.setPadding((int) mTabPadding, v.getPaddingTop(), (int) mTabPadding, v.getPaddingBottom()); 290 | TextView tv_tab_title = (TextView) v.findViewById(R.id.tv_tab_title); 291 | if (tv_tab_title != null) { 292 | tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor); 293 | tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize); 294 | tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 295 | if (mTextAllCaps) { 296 | tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase()); 297 | } 298 | 299 | if (mTextBold) { 300 | tv_tab_title.getPaint().setFakeBoldText(mTextBold); 301 | } 302 | } 303 | } 304 | } 305 | 306 | @Override 307 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 308 | /** 309 | * position:当前View的位置 310 | * mCurrentPositionOffset:当前View的偏移量比例.[0,1) 311 | */ 312 | this.mCurrentTab = position; 313 | this.mCurrentPositionOffset = positionOffset; 314 | scrollToCurrentTab(); 315 | invalidate(); 316 | } 317 | 318 | @Override 319 | public void onPageSelected(int position) { 320 | updateTabSelection(position); 321 | } 322 | 323 | @Override 324 | public void onPageScrollStateChanged(int state) { 325 | } 326 | 327 | /** HorizontalScrollView滚到当前tab,并且居中显示 */ 328 | private void scrollToCurrentTab() { 329 | if (mTabCount <= 0) { 330 | return; 331 | } 332 | 333 | int offset = (int) (mCurrentPositionOffset * mTabsContainer.getChildAt(mCurrentTab).getWidth()); 334 | /**当前Tab的left+当前Tab的Width乘以positionOffset*/ 335 | int newScrollX = mTabsContainer.getChildAt(mCurrentTab).getLeft() + offset; 336 | 337 | if (mCurrentTab > 0 || offset > 0) { 338 | /**HorizontalScrollView移动到当前tab,并居中*/ 339 | newScrollX -= getWidth() / 2 - getPaddingLeft(); 340 | calcIndicatorRect(); 341 | newScrollX += ((mTabRect.right - mTabRect.left) / 2); 342 | } 343 | 344 | if (newScrollX != mLastScrollX) { 345 | mLastScrollX = newScrollX; 346 | /** scrollTo(int x,int y):x,y代表的不是坐标点,而是偏移量 347 | * x:表示离起始位置的x水平方向的偏移量 348 | * y:表示离起始位置的y垂直方向的偏移量 349 | */ 350 | scrollTo(newScrollX, 0); 351 | } 352 | } 353 | 354 | private void updateTabSelection(int position) { 355 | for (int i = 0; i < mTabCount; ++i) { 356 | View tabView = mTabsContainer.getChildAt(i); 357 | final boolean isSelect = i == position; 358 | if (tabView != null) { 359 | TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 360 | 361 | if (tab_title != null) { 362 | tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor); 363 | } 364 | } 365 | 366 | } 367 | } 368 | 369 | private float margin; 370 | 371 | private void calcIndicatorRect() { 372 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 373 | float left = currentTabView.getLeft(); 374 | float right = currentTabView.getRight(); 375 | 376 | //for mIndicatorWidthEqualTitle 377 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 378 | TextView tab_title = (TextView) currentTabView.findViewById(R.id.tv_tab_title); 379 | mTextPaint.setTextSize(mTextsize); 380 | float textWidth = mTextPaint.measureText(tab_title.getText().toString()); 381 | margin = (right - left - textWidth) / 2; 382 | } 383 | 384 | if (this.mCurrentTab < mTabCount - 1) { 385 | View nextTabView = mTabsContainer.getChildAt(this.mCurrentTab + 1); 386 | float nextTabLeft = nextTabView.getLeft(); 387 | float nextTabRight = nextTabView.getRight(); 388 | 389 | left = left + mCurrentPositionOffset * (nextTabLeft - left); 390 | right = right + mCurrentPositionOffset * (nextTabRight - right); 391 | 392 | //for mIndicatorWidthEqualTitle 393 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 394 | TextView next_tab_title = (TextView) nextTabView.findViewById(R.id.tv_tab_title); 395 | mTextPaint.setTextSize(mTextsize); 396 | float nextTextWidth = mTextPaint.measureText(next_tab_title.getText().toString()); 397 | float nextMargin = (nextTabRight - nextTabLeft - nextTextWidth) / 2; 398 | margin = margin + mCurrentPositionOffset * (nextMargin - margin); 399 | } 400 | } 401 | 402 | mIndicatorRect.left = (int) left; 403 | mIndicatorRect.right = (int) right; 404 | //for mIndicatorWidthEqualTitle 405 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 406 | mIndicatorRect.left = (int) (left + margin - 1); 407 | mIndicatorRect.right = (int) (right - margin - 1); 408 | } 409 | 410 | mTabRect.left = (int) left; 411 | mTabRect.right = (int) right; 412 | 413 | if (mIndicatorWidth < 0) { //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip 414 | 415 | } else {//indicatorWidth大于0时,圆角矩形以及三角形 416 | float indicatorLeft = currentTabView.getLeft() + (currentTabView.getWidth() - mIndicatorWidth) / 2; 417 | 418 | if (this.mCurrentTab < mTabCount - 1) { 419 | View nextTab = mTabsContainer.getChildAt(this.mCurrentTab + 1); 420 | indicatorLeft = indicatorLeft + mCurrentPositionOffset * (currentTabView.getWidth() / 2 + nextTab.getWidth() / 2); 421 | } 422 | 423 | mIndicatorRect.left = (int) indicatorLeft; 424 | mIndicatorRect.right = (int) (mIndicatorRect.left + mIndicatorWidth); 425 | } 426 | } 427 | 428 | @Override 429 | protected void onDraw(Canvas canvas) { 430 | super.onDraw(canvas); 431 | 432 | if (isInEditMode() || mTabCount <= 0) { 433 | return; 434 | } 435 | 436 | int height = getHeight(); 437 | int paddingLeft = getPaddingLeft(); 438 | // draw divider 439 | if (mDividerWidth > 0) { 440 | mDividerPaint.setStrokeWidth(mDividerWidth); 441 | mDividerPaint.setColor(mDividerColor); 442 | for (int i = 0; i < mTabCount - 1; i++) { 443 | View tab = mTabsContainer.getChildAt(i); 444 | canvas.drawLine(paddingLeft + tab.getRight(), mDividerPadding, paddingLeft + tab.getRight(), height - mDividerPadding, mDividerPaint); 445 | } 446 | } 447 | 448 | // draw underline 449 | if (mUnderlineHeight > 0) { 450 | mRectPaint.setColor(mUnderlineColor); 451 | if (mUnderlineGravity == Gravity.BOTTOM) { 452 | canvas.drawRect(paddingLeft, height - mUnderlineHeight, mTabsContainer.getWidth() + paddingLeft, height, mRectPaint); 453 | } else { 454 | canvas.drawRect(paddingLeft, 0, mTabsContainer.getWidth() + paddingLeft, mUnderlineHeight, mRectPaint); 455 | } 456 | } 457 | 458 | //draw indicator line 459 | 460 | calcIndicatorRect(); 461 | if (mIndicatorStyle == STYLE_TRIANGLE) { 462 | if (mIndicatorHeight > 0) { 463 | mTrianglePaint.setColor(mIndicatorColor); 464 | mTrianglePath.reset(); 465 | mTrianglePath.moveTo(paddingLeft + mIndicatorRect.left, height); 466 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.left / 2 + mIndicatorRect.right / 2, height - mIndicatorHeight); 467 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.right, height); 468 | mTrianglePath.close(); 469 | canvas.drawPath(mTrianglePath, mTrianglePaint); 470 | } 471 | } else if (mIndicatorStyle == STYLE_BLOCK) { 472 | if (mIndicatorHeight < 0) { 473 | mIndicatorHeight = height - mIndicatorMarginTop - mIndicatorMarginBottom; 474 | } else { 475 | 476 | } 477 | 478 | if (mIndicatorHeight > 0) { 479 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) { 480 | mIndicatorCornerRadius = mIndicatorHeight / 2; 481 | } 482 | 483 | mIndicatorDrawable.setColor(mIndicatorColor); 484 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 485 | (int) mIndicatorMarginTop, (int) (paddingLeft + mIndicatorRect.right - mIndicatorMarginRight), 486 | (int) (mIndicatorMarginTop + mIndicatorHeight)); 487 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius); 488 | mIndicatorDrawable.draw(canvas); 489 | } 490 | } else { 491 | /* mRectPaint.setColor(mIndicatorColor); 492 | calcIndicatorRect(); 493 | canvas.drawRect(getPaddingLeft() + mIndicatorRect.left, getHeight() - mIndicatorHeight, 494 | mIndicatorRect.right + getPaddingLeft(), getHeight(), mRectPaint);*/ 495 | 496 | if (mIndicatorHeight > 0) { 497 | mIndicatorDrawable.setColor(mIndicatorColor); 498 | 499 | if (mIndicatorGravity == Gravity.BOTTOM) { 500 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 501 | height - (int) mIndicatorHeight - (int) mIndicatorMarginBottom, 502 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight, 503 | height - (int) mIndicatorMarginBottom); 504 | } else { 505 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 506 | (int) mIndicatorMarginTop, 507 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight, 508 | (int) mIndicatorHeight + (int) mIndicatorMarginTop); 509 | } 510 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius); 511 | mIndicatorDrawable.draw(canvas); 512 | } 513 | } 514 | } 515 | 516 | //setter and getter 517 | public void setCurrentTab(int currentTab) { 518 | this.mCurrentTab = currentTab; 519 | mViewPager.setCurrentItem(currentTab); 520 | } 521 | 522 | public void setIndicatorStyle(int indicatorStyle) { 523 | this.mIndicatorStyle = indicatorStyle; 524 | invalidate(); 525 | } 526 | 527 | public void setTabPadding(float tabPadding) { 528 | this.mTabPadding = dp2px(tabPadding); 529 | updateTabStyles(); 530 | } 531 | 532 | public void setTabSpaceEqual(boolean tabSpaceEqual) { 533 | this.mTabSpaceEqual = tabSpaceEqual; 534 | updateTabStyles(); 535 | } 536 | 537 | public void setTabWidth(float tabWidth) { 538 | this.mTabWidth = dp2px(tabWidth); 539 | updateTabStyles(); 540 | } 541 | 542 | public void setIndicatorColor(int indicatorColor) { 543 | this.mIndicatorColor = indicatorColor; 544 | invalidate(); 545 | } 546 | 547 | public void setIndicatorHeight(float indicatorHeight) { 548 | this.mIndicatorHeight = dp2px(indicatorHeight); 549 | invalidate(); 550 | } 551 | 552 | public void setIndicatorWidth(float indicatorWidth) { 553 | this.mIndicatorWidth = dp2px(indicatorWidth); 554 | invalidate(); 555 | } 556 | 557 | public void setIndicatorCornerRadius(float indicatorCornerRadius) { 558 | this.mIndicatorCornerRadius = dp2px(indicatorCornerRadius); 559 | invalidate(); 560 | } 561 | 562 | public void setIndicatorGravity(int indicatorGravity) { 563 | this.mIndicatorGravity = indicatorGravity; 564 | invalidate(); 565 | } 566 | 567 | public void setIndicatorMargin(float indicatorMarginLeft, float indicatorMarginTop, 568 | float indicatorMarginRight, float indicatorMarginBottom) { 569 | this.mIndicatorMarginLeft = dp2px(indicatorMarginLeft); 570 | this.mIndicatorMarginTop = dp2px(indicatorMarginTop); 571 | this.mIndicatorMarginRight = dp2px(indicatorMarginRight); 572 | this.mIndicatorMarginBottom = dp2px(indicatorMarginBottom); 573 | invalidate(); 574 | } 575 | 576 | public void setIndicatorWidthEqualTitle(boolean indicatorWidthEqualTitle) { 577 | this.mIndicatorWidthEqualTitle = indicatorWidthEqualTitle; 578 | invalidate(); 579 | } 580 | 581 | public void setUnderlineColor(int underlineColor) { 582 | this.mUnderlineColor = underlineColor; 583 | invalidate(); 584 | } 585 | 586 | public void setUnderlineHeight(float underlineHeight) { 587 | this.mUnderlineHeight = dp2px(underlineHeight); 588 | invalidate(); 589 | } 590 | 591 | public void setUnderlineGravity(int underlineGravity) { 592 | this.mUnderlineGravity = underlineGravity; 593 | invalidate(); 594 | } 595 | 596 | public void setDividerColor(int dividerColor) { 597 | this.mDividerColor = dividerColor; 598 | invalidate(); 599 | } 600 | 601 | public void setDividerWidth(float dividerWidth) { 602 | this.mDividerWidth = dp2px(dividerWidth); 603 | invalidate(); 604 | } 605 | 606 | public void setDividerPadding(float dividerPadding) { 607 | this.mDividerPadding = dp2px(dividerPadding); 608 | invalidate(); 609 | } 610 | 611 | public void setTextsize(float textsize) { 612 | this.mTextsize = sp2px(textsize); 613 | updateTabStyles(); 614 | } 615 | 616 | public void setTextSelectColor(int textSelectColor) { 617 | this.mTextSelectColor = textSelectColor; 618 | updateTabStyles(); 619 | } 620 | 621 | public void setTextUnselectColor(int textUnselectColor) { 622 | this.mTextUnselectColor = textUnselectColor; 623 | updateTabStyles(); 624 | } 625 | 626 | public void setTextBold(boolean textBold) { 627 | this.mTextBold = textBold; 628 | updateTabStyles(); 629 | } 630 | 631 | public void setTextAllCaps(boolean textAllCaps) { 632 | this.mTextAllCaps = textAllCaps; 633 | updateTabStyles(); 634 | } 635 | 636 | 637 | public int getTabCount() { 638 | return mTabCount; 639 | } 640 | 641 | public int getCurrentTab() { 642 | return mCurrentTab; 643 | } 644 | 645 | public int getIndicatorStyle() { 646 | return mIndicatorStyle; 647 | } 648 | 649 | public float getTabPadding() { 650 | return mTabPadding; 651 | } 652 | 653 | public boolean isTabSpaceEqual() { 654 | return mTabSpaceEqual; 655 | } 656 | 657 | public float getTabWidth() { 658 | return mTabWidth; 659 | } 660 | 661 | public int getIndicatorColor() { 662 | return mIndicatorColor; 663 | } 664 | 665 | public float getIndicatorHeight() { 666 | return mIndicatorHeight; 667 | } 668 | 669 | public float getIndicatorWidth() { 670 | return mIndicatorWidth; 671 | } 672 | 673 | public float getIndicatorCornerRadius() { 674 | return mIndicatorCornerRadius; 675 | } 676 | 677 | public float getIndicatorMarginLeft() { 678 | return mIndicatorMarginLeft; 679 | } 680 | 681 | public float getIndicatorMarginTop() { 682 | return mIndicatorMarginTop; 683 | } 684 | 685 | public float getIndicatorMarginRight() { 686 | return mIndicatorMarginRight; 687 | } 688 | 689 | public float getIndicatorMarginBottom() { 690 | return mIndicatorMarginBottom; 691 | } 692 | 693 | public int getUnderlineColor() { 694 | return mUnderlineColor; 695 | } 696 | 697 | public float getUnderlineHeight() { 698 | return mUnderlineHeight; 699 | } 700 | 701 | public int getDividerColor() { 702 | return mDividerColor; 703 | } 704 | 705 | public float getDividerWidth() { 706 | return mDividerWidth; 707 | } 708 | 709 | public float getDividerPadding() { 710 | return mDividerPadding; 711 | } 712 | 713 | public float getTextsize() { 714 | return mTextsize; 715 | } 716 | 717 | public int getTextSelectColor() { 718 | return mTextSelectColor; 719 | } 720 | 721 | public int getTextUnselectColor() { 722 | return mTextUnselectColor; 723 | } 724 | 725 | public boolean isTextBold() { 726 | return mTextBold; 727 | } 728 | 729 | public boolean isTextAllCaps() { 730 | return mTextAllCaps; 731 | } 732 | 733 | public TextView getTitleView(int tab) { 734 | View tabView = mTabsContainer.getChildAt(tab); 735 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 736 | return tv_tab_title; 737 | } 738 | 739 | //setter and getter 740 | 741 | // show MsgTipView 742 | private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 743 | private SparseArray mInitSetMap = new SparseArray<>(); 744 | 745 | /** 746 | * 显示未读消息 747 | * 748 | * @param position 显示tab位置 749 | * @param num num小于等于0显示红点,num大于0显示数字 750 | */ 751 | public void showMsg(int position, int num) { 752 | if (position >= mTabCount) { 753 | position = mTabCount - 1; 754 | } 755 | 756 | View tabView = mTabsContainer.getChildAt(position); 757 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 758 | if (tipView != null) { 759 | UnreadMsgUtils.show(tipView, num); 760 | 761 | if (mInitSetMap.get(position) != null && mInitSetMap.get(position)) { 762 | return; 763 | } 764 | 765 | setMsgMargin(position, 4, 2); 766 | mInitSetMap.put(position, true); 767 | } 768 | } 769 | 770 | /** 771 | * 显示未读红点 772 | * 773 | * @param position 显示tab位置 774 | */ 775 | public void showDot(int position) { 776 | if (position >= mTabCount) { 777 | position = mTabCount - 1; 778 | } 779 | showMsg(position, 0); 780 | } 781 | 782 | /** 隐藏未读消息 */ 783 | public void hideMsg(int position) { 784 | if (position >= mTabCount) { 785 | position = mTabCount - 1; 786 | } 787 | 788 | View tabView = mTabsContainer.getChildAt(position); 789 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 790 | 791 | if (tipView != null) { 792 | tipView.setVisibility(View.GONE); 793 | } 794 | } 795 | 796 | /** 设置未读消息偏移,原点为文字的右上角.当控件高度固定,消息提示位置易控制,显示效果佳 */ 797 | public void setMsgMargin(int position, float leftPadding, float bottomPadding) { 798 | if (position >= mTabCount) { 799 | position = mTabCount - 1; 800 | } 801 | View tabView = mTabsContainer.getChildAt(position); 802 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 803 | if (tipView != null) { 804 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 805 | mTextPaint.setTextSize(mTextsize); 806 | float textWidth = mTextPaint.measureText(tv_tab_title.getText().toString()); 807 | float textHeight = mTextPaint.descent() - mTextPaint.ascent(); 808 | MarginLayoutParams lp = (MarginLayoutParams) tipView.getLayoutParams(); 809 | lp.leftMargin = mTabWidth >= 0 ? (int) (mTabWidth / 2 + textWidth / 2 + dp2px(leftPadding)) : (int) (mTabPadding + textWidth + dp2px(leftPadding)); 810 | lp.topMargin = mHeight > 0 ? (int) (mHeight - textHeight) / 2 - dp2px(bottomPadding) : 0; 811 | tipView.setLayoutParams(lp); 812 | } 813 | } 814 | 815 | /** 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置 */ 816 | public MsgView getMsgView(int position) { 817 | if (position >= mTabCount) { 818 | position = mTabCount - 1; 819 | } 820 | View tabView = mTabsContainer.getChildAt(position); 821 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 822 | return tipView; 823 | } 824 | 825 | private OnTabSelectListener mListener; 826 | 827 | public void setOnTabSelectListener(OnTabSelectListener listener) { 828 | this.mListener = listener; 829 | } 830 | 831 | class InnerPagerAdapter extends FragmentPagerAdapter { 832 | private ArrayList fragments = new ArrayList<>(); 833 | private String[] titles; 834 | 835 | public InnerPagerAdapter(FragmentManager fm, ArrayList fragments, String[] titles) { 836 | super(fm); 837 | this.fragments = fragments; 838 | this.titles = titles; 839 | } 840 | 841 | @Override 842 | public int getCount() { 843 | return fragments.size(); 844 | } 845 | 846 | @Override 847 | public CharSequence getPageTitle(int position) { 848 | return titles[position]; 849 | } 850 | 851 | @Override 852 | public Fragment getItem(int position) { 853 | return fragments.get(position); 854 | } 855 | 856 | @Override 857 | public void destroyItem(ViewGroup container, int position, Object object) { 858 | // 覆写destroyItem并且空实现,这样每个Fragment中的视图就不会被销毁 859 | // super.destroyItem(container, position, object); 860 | } 861 | 862 | @Override 863 | public int getItemPosition(Object object) { 864 | return PagerAdapter.POSITION_NONE; 865 | } 866 | } 867 | 868 | @Override 869 | protected Parcelable onSaveInstanceState() { 870 | Bundle bundle = new Bundle(); 871 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 872 | bundle.putInt("mCurrentTab", mCurrentTab); 873 | return bundle; 874 | } 875 | 876 | @Override 877 | protected void onRestoreInstanceState(Parcelable state) { 878 | if (state instanceof Bundle) { 879 | Bundle bundle = (Bundle) state; 880 | mCurrentTab = bundle.getInt("mCurrentTab"); 881 | state = bundle.getParcelable("instanceState"); 882 | if (mCurrentTab != 0 && mTabsContainer.getChildCount() > 0) { 883 | updateTabSelection(mCurrentTab); 884 | scrollToCurrentTab(); 885 | } 886 | } 887 | super.onRestoreInstanceState(state); 888 | } 889 | 890 | protected int dp2px(float dp) { 891 | final float scale = mContext.getResources().getDisplayMetrics().density; 892 | return (int) (dp * scale + 0.5f); 893 | } 894 | 895 | protected int sp2px(float sp) { 896 | final float scale = this.mContext.getResources().getDisplayMetrics().scaledDensity; 897 | return (int) (sp * scale + 0.5f); 898 | } 899 | } 900 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/java/com/flyco/tablayout/listener/CustomTabEntity.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.listener; 2 | 3 | import android.support.annotation.DrawableRes; 4 | 5 | public interface CustomTabEntity { 6 | String getTabTitle(); 7 | 8 | @DrawableRes 9 | int getTabSelectedIcon(); 10 | 11 | @DrawableRes 12 | int getTabUnselectedIcon(); 13 | 14 | String getSubTitle(); 15 | } -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/java/com/flyco/tablayout/listener/OnTabSelectListener.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.listener; 2 | 3 | public interface OnTabSelectListener { 4 | void onTabSelect(int position); 5 | void onTabReselect(int position); 6 | } -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/java/com/flyco/tablayout/utils/FragmentChangeManager.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.utils; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentTransaction; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class FragmentChangeManager { 10 | private FragmentManager mFragmentManager; 11 | private int mContainerViewId; 12 | /** Fragment切换数组 */ 13 | private ArrayList mFragments; 14 | /** 当前选中的Tab */ 15 | private int mCurrentTab; 16 | 17 | public FragmentChangeManager(FragmentManager fm, int containerViewId, ArrayList fragments) { 18 | this.mFragmentManager = fm; 19 | this.mContainerViewId = containerViewId; 20 | this.mFragments = fragments; 21 | initFragments(); 22 | } 23 | 24 | /** 初始化fragments */ 25 | private void initFragments() { 26 | for (Fragment fragment : mFragments) { 27 | mFragmentManager.beginTransaction().add(mContainerViewId, fragment).hide(fragment).commit(); 28 | } 29 | 30 | setFragments(0); 31 | } 32 | 33 | /** 界面切换控制 */ 34 | public void setFragments(int index) { 35 | for (int i = 0; i < mFragments.size(); i++) { 36 | FragmentTransaction ft = mFragmentManager.beginTransaction(); 37 | Fragment fragment = mFragments.get(i); 38 | if (i == index) { 39 | ft.show(fragment); 40 | } else { 41 | ft.hide(fragment); 42 | } 43 | ft.commit(); 44 | } 45 | mCurrentTab = index; 46 | } 47 | 48 | public int getCurrentTab() { 49 | return mCurrentTab; 50 | } 51 | 52 | public Fragment getCurrentFragment() { 53 | return mFragments.get(mCurrentTab); 54 | } 55 | } -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/java/com/flyco/tablayout/utils/UnreadMsgUtils.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.utils; 2 | 3 | 4 | import android.util.DisplayMetrics; 5 | import android.view.View; 6 | import android.widget.FrameLayout; 7 | import android.widget.LinearLayout; 8 | import android.widget.RelativeLayout; 9 | 10 | import com.flyco.tablayout.widget.MsgView; 11 | 12 | /** 13 | * 未读消息提示View,显示小红点或者带有数字的红点: 14 | * 数字一位,圆 15 | * 数字两位,圆角矩形,圆角是高度的一半 16 | * 数字超过两位,显示99+ 17 | */ 18 | public class UnreadMsgUtils { 19 | public static void show(MsgView msgView, int num) { 20 | if (msgView == null) { 21 | return; 22 | } 23 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) msgView.getLayoutParams(); 24 | DisplayMetrics dm = msgView.getResources().getDisplayMetrics(); 25 | msgView.setVisibility(View.VISIBLE); 26 | if (num <= 0) {//圆点,设置默认宽高 27 | msgView.setStrokeWidth(0); 28 | msgView.setText(""); 29 | 30 | lp.width = (int) (8 * dm.density); 31 | lp.height = (int) (8 * dm.density); 32 | msgView.setLayoutParams(lp); 33 | } else { 34 | lp.height = (int) (18 * dm.density); 35 | if (num > 0 && num < 10) {//圆 36 | lp.width = (int) (18 * dm.density); 37 | msgView.setText(num + ""); 38 | } else if (num > 9 && num < 100) {//圆角矩形,圆角是高度的一半,设置默认padding 39 | lp.width = RelativeLayout.LayoutParams.WRAP_CONTENT; 40 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0); 41 | msgView.setText(num + ""); 42 | } else {//数字超过两位,显示99+ 43 | lp.width = RelativeLayout.LayoutParams.WRAP_CONTENT; 44 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0); 45 | msgView.setText("99+"); 46 | } 47 | msgView.setLayoutParams(lp); 48 | } 49 | } 50 | 51 | public static void showRight(MsgView msgView, int num) { 52 | if (msgView == null) { 53 | return; 54 | } 55 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) msgView.getLayoutParams(); 56 | DisplayMetrics dm = msgView.getResources().getDisplayMetrics(); 57 | msgView.setVisibility(View.VISIBLE); 58 | if (num <= 0) {//圆点,设置默认宽高 59 | msgView.setStrokeWidth(0); 60 | msgView.setText(""); 61 | lp.width = (int) (8 * dm.density); 62 | lp.height = (int) (8 * dm.density); 63 | msgView.setLayoutParams(lp); 64 | msgView.setVisibility(View.INVISIBLE); 65 | } else { 66 | lp.height = (int) (18 * dm.density); 67 | if (num > 0 && num < 10) {//圆 68 | lp.width = (int) (18 * dm.density); 69 | msgView.setPadding(0, 0, 0, 0); 70 | msgView.setText(num + ""); 71 | } else if (num > 9 && num < 100) {//圆角矩形,圆角是高度的一半,设置默认padding 72 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; 73 | msgView.setPadding((int) (6 * dm.density), -1, (int) (6 * dm.density), 0); 74 | msgView.setText(num + ""); 75 | } else {//数字超过两位,显示99+ 76 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; 77 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0); 78 | msgView.setText("99+"); 79 | } 80 | msgView.setLayoutParams(lp); 81 | 82 | } 83 | } 84 | public static void setSize(MsgView rtv, int size) { 85 | if (rtv == null) { 86 | return; 87 | } 88 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) rtv.getLayoutParams(); 89 | lp.width = size; 90 | lp.height = size; 91 | rtv.setLayoutParams(lp); 92 | } 93 | 94 | public static void showPoint(MsgView msgView, int num) { 95 | if (msgView == null) { 96 | return; 97 | } 98 | FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) msgView.getLayoutParams(); 99 | DisplayMetrics dm = msgView.getResources().getDisplayMetrics(); 100 | msgView.setVisibility(View.VISIBLE); 101 | if (num <= 0) {//圆点,设置默认宽高 102 | msgView.setStrokeWidth(0); 103 | msgView.setText(""); 104 | lp.width = (int) (8 * dm.density); 105 | lp.height = (int) (8 * dm.density); 106 | msgView.setLayoutParams(lp); 107 | msgView.setVisibility(View.GONE); 108 | } else { 109 | lp.height = (int) (18 * dm.density); 110 | if (num > 0 && num < 10) {//圆 111 | lp.width = (int) (18 * dm.density); 112 | msgView.setPadding(0, 0, 0, 0); 113 | msgView.setText(num + ""); 114 | } else if (num > 9 && num < 100) {//圆角矩形,圆角是高度的一半,设置默认padding 115 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; 116 | msgView.setPadding((int) (6 * dm.density), -1, (int) (6 * dm.density), 0); 117 | msgView.setText(num + ""); 118 | } else {//数字超过两位,显示99+ 119 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; 120 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0); 121 | msgView.setText("99+"); 122 | } 123 | msgView.setLayoutParams(lp); 124 | 125 | } 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/java/com/flyco/tablayout/widget/MsgView.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.graphics.drawable.GradientDrawable; 7 | import android.graphics.drawable.StateListDrawable; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.widget.TextView; 11 | 12 | import com.flyco.tablayout.R; 13 | 14 | /** 用于需要圆角矩形框背景的TextView的情况,减少直接使用TextView时引入的shape资源文件 */ 15 | public class MsgView extends TextView { 16 | private Context context; 17 | private GradientDrawable gd_background = new GradientDrawable(); 18 | private int backgroundColor; 19 | private int cornerRadius; 20 | private int strokeWidth; 21 | private int strokeColor; 22 | private boolean isRadiusHalfHeight; 23 | private boolean isWidthHeightEqual; 24 | 25 | public MsgView(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public MsgView(Context context, AttributeSet attrs) { 30 | this(context, attrs, 0); 31 | } 32 | 33 | public MsgView(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | this.context = context; 36 | obtainAttributes(context, attrs); 37 | } 38 | 39 | 40 | private void obtainAttributes(Context context, AttributeSet attrs) { 41 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MsgView); 42 | backgroundColor = ta.getColor(R.styleable.MsgView_mv_backgroundColor, Color.TRANSPARENT); 43 | cornerRadius = ta.getDimensionPixelSize(R.styleable.MsgView_mv_cornerRadius, 0); 44 | strokeWidth = ta.getDimensionPixelSize(R.styleable.MsgView_mv_strokeWidth, 0); 45 | strokeColor = ta.getColor(R.styleable.MsgView_mv_strokeColor, Color.TRANSPARENT); 46 | isRadiusHalfHeight = ta.getBoolean(R.styleable.MsgView_mv_isRadiusHalfHeight, false); 47 | isWidthHeightEqual = ta.getBoolean(R.styleable.MsgView_mv_isWidthHeightEqual, false); 48 | 49 | ta.recycle(); 50 | } 51 | 52 | @Override 53 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 54 | if (isWidthHeightEqual() && getWidth() > 0 && getHeight() > 0) { 55 | int max = Math.max(getWidth(), getHeight()); 56 | int measureSpec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY); 57 | super.onMeasure(measureSpec, measureSpec); 58 | return; 59 | } 60 | 61 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 62 | } 63 | 64 | @Override 65 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 66 | super.onLayout(changed, left, top, right, bottom); 67 | if (isRadiusHalfHeight()) { 68 | setCornerRadius(getHeight() / 2); 69 | } else { 70 | setBgSelector(); 71 | } 72 | } 73 | 74 | 75 | public void setBackgroundColor(int backgroundColor) { 76 | this.backgroundColor = backgroundColor; 77 | setBgSelector(); 78 | } 79 | 80 | public void setCornerRadius(int cornerRadius) { 81 | this.cornerRadius = dp2px(cornerRadius); 82 | setBgSelector(); 83 | } 84 | 85 | public void setStrokeWidth(int strokeWidth) { 86 | this.strokeWidth = dp2px(strokeWidth); 87 | setBgSelector(); 88 | } 89 | 90 | public void setStrokeColor(int strokeColor) { 91 | this.strokeColor = strokeColor; 92 | setBgSelector(); 93 | } 94 | 95 | public void setIsRadiusHalfHeight(boolean isRadiusHalfHeight) { 96 | this.isRadiusHalfHeight = isRadiusHalfHeight; 97 | setBgSelector(); 98 | } 99 | 100 | public void setIsWidthHeightEqual(boolean isWidthHeightEqual) { 101 | this.isWidthHeightEqual = isWidthHeightEqual; 102 | setBgSelector(); 103 | } 104 | 105 | public int getBackgroundColor() { 106 | return backgroundColor; 107 | } 108 | 109 | public int getCornerRadius() { 110 | return cornerRadius; 111 | } 112 | 113 | public int getStrokeWidth() { 114 | return strokeWidth; 115 | } 116 | 117 | public int getStrokeColor() { 118 | return strokeColor; 119 | } 120 | 121 | public boolean isRadiusHalfHeight() { 122 | return isRadiusHalfHeight; 123 | } 124 | 125 | public boolean isWidthHeightEqual() { 126 | return isWidthHeightEqual; 127 | } 128 | 129 | protected int dp2px(float dp) { 130 | final float scale = context.getResources().getDisplayMetrics().density; 131 | return (int) (dp * scale + 0.5f); 132 | } 133 | 134 | protected int sp2px(float sp) { 135 | final float scale = this.context.getResources().getDisplayMetrics().scaledDensity; 136 | return (int) (sp * scale + 0.5f); 137 | } 138 | 139 | private void setDrawable(GradientDrawable gd, int color, int strokeColor) { 140 | gd.setColor(color); 141 | gd.setCornerRadius(cornerRadius); 142 | gd.setStroke(strokeWidth, strokeColor); 143 | } 144 | 145 | public void setBgSelector() { 146 | StateListDrawable bg = new StateListDrawable(); 147 | 148 | setDrawable(gd_background, backgroundColor, strokeColor); 149 | bg.addState(new int[]{-android.R.attr.state_pressed}, gd_background); 150 | 151 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {//16 152 | setBackground(bg); 153 | } else { 154 | //noinspection deprecation 155 | setBackgroundDrawable(bg); 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/res/layout/layout_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 21 | 22 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/res/layout/layout_tab_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 27 | 31 | 32 | 33 | 34 | 48 | 49 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/res/layout/layout_tab_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 20 | 21 | 26 | 27 | 32 | 33 | 34 | 47 | 48 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/res/layout/layout_tab_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 27 | 31 | 32 | 33 | 34 | 48 | 49 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/res/layout/layout_tab_segment.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 21 | 22 | 27 | 28 | 29 | 42 | 43 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/res/layout/layout_tab_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 21 | 22 | 27 | 28 | 33 | 34 | 35 | 49 | 50 | -------------------------------------------------------------------------------- /tabLayoutLibrary/src/main/res/values/attrs.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 | 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 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | --------------------------------------------------------------------------------