├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dbnavigator.xml ├── encodings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── ViewPagerIndicator.iml ├── app ├── app.iml ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── caiying │ │ └── indicator │ │ └── demo │ │ ├── ApiDemos.java │ │ ├── CirclePagerActivity.java │ │ ├── FadingTabPagerActivity.java │ │ ├── LinePagerActivity.java │ │ ├── SampleFragment.java │ │ └── SampleFragmentPagerAdapter.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ ├── bg_tabs.9.png │ ├── ic_1_0.png │ ├── ic_1_1.png │ ├── ic_2_0.png │ ├── ic_2_1.png │ ├── ic_3_0.png │ ├── ic_3_1.png │ ├── ic_4_0.png │ ├── ic_4_1.png │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_circle.xml │ ├── activity_fading_tab.xml │ └── activity_line.xml │ ├── values-v11 │ └── styles.xml │ ├── values-v14 │ └── styles.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── import-summary.txt ├── screenshots ├── fading_tab.gif └── line.gif ├── settings.gradle └── viewpagerindicator ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src ├── androidTest │ └── java │ │ └── me │ │ └── caiying │ │ └── library │ │ └── viewpagerindicator │ │ └── ApplicationTest.java ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── me │ │ │ └── caiying │ │ │ └── library │ │ │ └── viewpagerindicator │ │ │ ├── BadgeView.java │ │ │ ├── BasicViewPagerIndicator.java │ │ │ ├── CirclePagerIndicator.java │ │ │ ├── FadingTabPagerIndicator.java │ │ │ ├── LinePagerIndicator.java │ │ │ └── Utils.java │ └── res │ │ ├── drawable │ │ └── mark_none_tips.xml │ │ └── values │ │ └── strings.xml └── test │ └── java │ └── me │ └── caiying │ └── library │ └── viewpagerindicator │ └── ExampleUnitTest.java └── viewpagerindicator.iml /.gitignore: -------------------------------------------------------------------------------- 1 | ## Android 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | ## OSX 36 | .DS_Store 37 | .AppleDouble 38 | .LSOverride 39 | 40 | # Icon must end with two \r 41 | Icon 42 | 43 | # Thumbnails 44 | ._* 45 | 46 | # Files that might appear in the root of a volume 47 | .DocumentRevisions-V100 48 | .fseventsd 49 | .Spotlight-V100 50 | .TemporaryItems 51 | .Trashes 52 | .VolumeIcon.icns 53 | 54 | # Directories potentially created on remote AFP share 55 | .AppleDB 56 | .AppleDesktop 57 | Network Trash Folder 58 | Temporary Items 59 | .apdisk 60 | 61 | ## Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 62 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 63 | 64 | # User-specific stuff: 65 | .idea/workspace.xml 66 | .idea/tasks.xml 67 | .idea/dictionaries 68 | 69 | # Sensitive or high-churn files: 70 | .idea/dataSources.ids 71 | .idea/dataSources.xml 72 | .idea/sqlDataSources.xml 73 | .idea/dynamic.xml 74 | .idea/uiDesigner.xml 75 | 76 | # Gradle: 77 | .idea/gradle.xml 78 | .idea/libraries 79 | 80 | # Mongo Explorer plugin: 81 | .idea/mongoSettings.xml 82 | 83 | ## File-based project format: 84 | *.iws 85 | 86 | ## Plugin-specific files: 87 | 88 | # IntelliJ 89 | /out/ 90 | 91 | # mpeltonen/sbt-idea plugin 92 | .idea_modules/ 93 | 94 | # JIRA plugin 95 | atlassian-ide-plugin.xml 96 | 97 | # Crashlytics plugin (for Android Studio and IntelliJ) 98 | com_crashlytics_export_strings.xml 99 | crashlytics.properties 100 | crashlytics-build.properties 101 | fabric.properties 102 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ViewPagerIndicator -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dbnavigator.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 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 26 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Android 46 | 47 | 48 | Android Lint 49 | 50 | 51 | Java language level migration aids 52 | 53 | 54 | 55 | 56 | Abstraction issues 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 80 | 81 | 82 | 1.8 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ViewPagerIndicator.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "me.caiying.indicator" 9 | minSdkVersion 9 10 | targetSdkVersion 23 11 | } 12 | 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile 'com.android.support:support-v4:23.1.1' 23 | compile project(':viewpagerindicator') 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/me/caiying/indicator/demo/ApiDemos.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.caiying.indicator.demo; 18 | 19 | import android.app.ListActivity; 20 | import android.content.Intent; 21 | import android.content.pm.PackageManager; 22 | import android.content.pm.ResolveInfo; 23 | import android.os.Bundle; 24 | import android.view.View; 25 | import android.widget.ListView; 26 | import android.widget.SimpleAdapter; 27 | 28 | import java.text.Collator; 29 | import java.util.ArrayList; 30 | import java.util.Collections; 31 | import java.util.Comparator; 32 | import java.util.HashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | 36 | public class ApiDemos extends ListActivity { 37 | 38 | @Override 39 | public void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | 42 | Intent intent = getIntent(); 43 | String path = intent.getStringExtra("me.caiying.indicator.demo.Path"); 44 | 45 | if (path == null) { 46 | path = ""; 47 | } 48 | 49 | setListAdapter(new SimpleAdapter(this, getData(path), 50 | android.R.layout.simple_list_item_1, new String[] { "title" }, 51 | new int[] { android.R.id.text1 })); 52 | getListView().setTextFilterEnabled(true); 53 | } 54 | 55 | protected List getData(String prefix) { 56 | List myData = new ArrayList(); 57 | 58 | Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 59 | mainIntent.addCategory(Intent.CATEGORY_SAMPLE_CODE); 60 | 61 | PackageManager pm = getPackageManager(); 62 | List list = pm.queryIntentActivities(mainIntent, 0); 63 | 64 | if (null == list) 65 | return myData; 66 | 67 | String[] prefixPath; 68 | 69 | if (prefix.equals("")) { 70 | prefixPath = null; 71 | } else { 72 | prefixPath = prefix.split("/"); 73 | } 74 | 75 | int len = list.size(); 76 | 77 | Map entries = new HashMap(); 78 | 79 | for (int i = 0; i < len; i++) { 80 | ResolveInfo info = list.get(i); 81 | CharSequence labelSeq = info.loadLabel(pm); 82 | String label = labelSeq != null 83 | ? labelSeq.toString() 84 | : info.activityInfo.name; 85 | 86 | if (prefix.length() == 0 || label.startsWith(prefix)) { 87 | 88 | String[] labelPath = label.split("/"); 89 | 90 | String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length]; 91 | 92 | if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) { 93 | addItem(myData, nextLabel, activityIntent( 94 | info.activityInfo.applicationInfo.packageName, 95 | info.activityInfo.name)); 96 | } else { 97 | if (entries.get(nextLabel) == null) { 98 | addItem(myData, nextLabel, browseIntent(prefix.equals("") ? nextLabel : prefix + "/" + nextLabel)); 99 | entries.put(nextLabel, true); 100 | } 101 | } 102 | } 103 | } 104 | 105 | Collections.sort(myData, sDisplayNameComparator); 106 | 107 | return myData; 108 | } 109 | 110 | private final static Comparator sDisplayNameComparator = new Comparator() { 111 | private final Collator collator = Collator.getInstance(); 112 | 113 | public int compare(Map map1, Map map2) { 114 | return collator.compare(map1.get("title"), map2.get("title")); 115 | } 116 | }; 117 | 118 | protected Intent activityIntent(String pkg, String componentName) { 119 | Intent result = new Intent(); 120 | result.setClassName(pkg, componentName); 121 | return result; 122 | } 123 | 124 | protected Intent browseIntent(String path) { 125 | Intent result = new Intent(); 126 | result.setClass(this, ApiDemos.class); 127 | result.putExtra("me.caiying.indicator.demo.Path", path); 128 | return result; 129 | } 130 | 131 | protected void addItem(List data, String name, Intent intent) { 132 | Map temp = new HashMap(); 133 | temp.put("title", name); 134 | temp.put("intent", intent); 135 | data.add(temp); 136 | } 137 | 138 | @Override 139 | protected void onListItemClick(ListView l, View v, int position, long id) { 140 | Map map = (Map) l.getItemAtPosition(position); 141 | 142 | Intent intent = (Intent) map.get("intent"); 143 | startActivity(intent); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/me/caiying/indicator/demo/CirclePagerActivity.java: -------------------------------------------------------------------------------- 1 | package me.caiying.indicator.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.FragmentActivity; 5 | import android.support.v4.view.ViewPager; 6 | 7 | import me.caiying.indicator.R; 8 | import me.caiying.library.viewpagerindicator.CirclePagerIndicator; 9 | 10 | /** 11 | * Created by caiying on 06/02/2016. 12 | */ 13 | public class CirclePagerActivity extends FragmentActivity { 14 | private CirclePagerIndicator circlePagerIndicator; 15 | private ViewPager viewPager; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_circle); 21 | circlePagerIndicator = (CirclePagerIndicator) findViewById(R.id.circle_pager_indicator); 22 | viewPager = (ViewPager) findViewById(R.id.view_pager); 23 | viewPager.setAdapter(new SampleFragmentPagerAdapter(this, getSupportFragmentManager())); 24 | circlePagerIndicator.setViewPager(viewPager); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/me/caiying/indicator/demo/FadingTabPagerActivity.java: -------------------------------------------------------------------------------- 1 | 2 | package me.caiying.indicator.demo; 3 | 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.support.v4.app.FragmentActivity; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v4.view.ViewPager; 9 | 10 | import me.caiying.indicator.R; 11 | import me.caiying.library.viewpagerindicator.FadingTabPagerIndicator; 12 | 13 | public class FadingTabPagerActivity extends FragmentActivity { 14 | private FadingTabPagerIndicator fadingTabPagerIndicator; 15 | private ViewPager viewPager; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_fading_tab); 21 | fadingTabPagerIndicator = (FadingTabPagerIndicator) findViewById(R.id.fade_tab_indicator); 22 | viewPager = (ViewPager) findViewById(R.id.view_pager); 23 | viewPager.setAdapter(new FadeTabFragmentPagerAdapter(this, getSupportFragmentManager())); 24 | fadingTabPagerIndicator.setViewPager(viewPager); 25 | fadingTabPagerIndicator.setBackgroundResource(R.drawable.bg_tabs); 26 | fadingTabPagerIndicator.setBadge(0, 3); 27 | fadingTabPagerIndicator.setBadge(1, 23); 28 | fadingTabPagerIndicator.setBadge(2, 999); 29 | fadingTabPagerIndicator.setNoneBadge(3); 30 | 31 | // fadingTabPagerIndicator.hideBadge(0); 32 | // fadingTabPagerIndicator.hideBadge(1); 33 | // fadingTabPagerIndicator.hideBadge(2); 34 | // fadingTabPagerIndicator.hideBadge(3); 35 | } 36 | 37 | private class FadeTabFragmentPagerAdapter extends SampleFragmentPagerAdapter implements FadingTabPagerIndicator.FadingTab { 38 | 39 | public FadeTabFragmentPagerAdapter(Context context, FragmentManager fm) { 40 | super(context, fm); 41 | } 42 | 43 | @Override 44 | public int getTabNormalIconResId(int position) { 45 | return new int[]{R.drawable.ic_1_1, R.drawable.ic_2_1, 46 | R.drawable.ic_3_1, R.drawable.ic_4_1}[position]; 47 | } 48 | 49 | @Override 50 | public int getTabSelectIconResId(int position) { 51 | return new int[]{R.drawable.ic_1_0, R.drawable.ic_2_0, 52 | R.drawable.ic_3_0, R.drawable.ic_4_0}[position]; 53 | } 54 | 55 | @Override 56 | public int getTabNormalTextColor(int position) { 57 | return getResources().getColor(R.color.text_normal); 58 | } 59 | 60 | @Override 61 | public int getTabSelectTextColor(int position) { 62 | return getResources().getColor(R.color.text_select); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/me/caiying/indicator/demo/LinePagerActivity.java: -------------------------------------------------------------------------------- 1 | package me.caiying.indicator.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.FragmentActivity; 5 | import android.support.v4.view.ViewPager; 6 | 7 | import me.caiying.indicator.R; 8 | import me.caiying.library.viewpagerindicator.LinePagerIndicator; 9 | 10 | public class LinePagerActivity extends FragmentActivity { 11 | private LinePagerIndicator linePagerIndicator; 12 | private ViewPager viewPager; 13 | 14 | @Override 15 | protected void onCreate(Bundle bundle) { 16 | super.onCreate(bundle); 17 | setContentView(R.layout.activity_line); 18 | linePagerIndicator = (LinePagerIndicator) findViewById(R.id.line_tab_indicator); 19 | viewPager = (ViewPager) findViewById(R.id.view_pager); 20 | viewPager.setAdapter(new SampleFragmentPagerAdapter(this, getSupportFragmentManager())); 21 | linePagerIndicator.setViewPager(viewPager); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/me/caiying/indicator/demo/SampleFragment.java: -------------------------------------------------------------------------------- 1 | package me.caiying.indicator.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.view.Gravity; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | import android.widget.LinearLayout.LayoutParams; 11 | import android.widget.TextView; 12 | 13 | public class SampleFragment extends Fragment { 14 | private static final String KEY_CONTENT = "SampleFragment:Content"; 15 | 16 | public static SampleFragment newInstance(String content) { 17 | SampleFragment fragment = new SampleFragment(); 18 | 19 | StringBuilder builder = new StringBuilder(); 20 | for (int i = 0; i < 20; i++) { 21 | builder.append(content).append(" "); 22 | } 23 | builder.deleteCharAt(builder.length() - 1); 24 | fragment.mContent = builder.toString(); 25 | 26 | return fragment; 27 | } 28 | 29 | private String mContent = "???"; 30 | 31 | @Override 32 | public void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | 35 | if ((savedInstanceState != null) && savedInstanceState.containsKey(KEY_CONTENT)) { 36 | mContent = savedInstanceState.getString(KEY_CONTENT); 37 | } 38 | } 39 | 40 | @Override 41 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 42 | Bundle savedInstanceState) { 43 | TextView text = new TextView(getActivity()); 44 | text.setGravity(Gravity.CENTER); 45 | text.setText(mContent); 46 | text.setTextSize(20 * getResources().getDisplayMetrics().density); 47 | text.setPadding(20, 20, 20, 20); 48 | 49 | LinearLayout layout = new LinearLayout(getActivity()); 50 | layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 51 | LayoutParams.MATCH_PARENT)); 52 | layout.setGravity(Gravity.CENTER); 53 | layout.addView(text); 54 | 55 | return layout; 56 | } 57 | 58 | @Override 59 | public void onSaveInstanceState(Bundle outState) { 60 | super.onSaveInstanceState(outState); 61 | outState.putString(KEY_CONTENT, mContent); 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/me/caiying/indicator/demo/SampleFragmentPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package me.caiying.indicator.demo; 2 | 3 | import android.content.Context; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | 8 | import me.caiying.indicator.R; 9 | 10 | public class SampleFragmentPagerAdapter extends FragmentPagerAdapter { 11 | private static final String[] CONTENT = new String[] { 12 | "A", "B", "C", "D" 13 | }; 14 | 15 | private Context mContext; 16 | public SampleFragmentPagerAdapter(Context context, FragmentManager fm) { 17 | super(fm); 18 | this.mContext = context; 19 | } 20 | 21 | @Override 22 | public Fragment getItem(int position) { 23 | return SampleFragment.newInstance(CONTENT[position % CONTENT.length]); 24 | } 25 | 26 | @Override 27 | public CharSequence getPageTitle(int position) { 28 | return new String[]{mContext.getString(R.string.tab1_text), mContext.getString(R.string.tab2_text), 29 | mContext.getString(R.string.tab3_text), mContext.getString(R.string.tab4_text)}[position]; 30 | } 31 | 32 | @Override 33 | public int getCount() { 34 | return CONTENT.length; 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/bg_tabs.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/bg_tabs.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_1_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_1_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_1_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_2_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_2_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_2_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_3_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_3_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_3_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_4_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_4_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_4_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_4_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_fading_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #878787 4 | #37ab14 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ViewPagerIndicator 4 | Chats 5 | Contacts 6 | Discover 7 | Me 8 | 9 | YC#DEMO#FadingTabPagerIndicator 10 | YC#DEMO#LinePagerIndicator 11 | YC#DEMO#CirclePagerIndicator 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.2.0' 8 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' 9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | jcenter() 16 | mavenCentral() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Mon Sep 26 20:29:54 CST 2016 16 | systemProp.https.proxyPort=8118 17 | systemProp.http.proxyHost=127.0.0.1 18 | systemProp.https.proxyHost=127.0.0.1 19 | systemProp.http.proxyPort=8118 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 26 20:31:05 CST 2016 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-2.14.1-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 | -------------------------------------------------------------------------------- /import-summary.txt: -------------------------------------------------------------------------------- 1 | ECLIPSE ANDROID PROJECT IMPORT SUMMARY 2 | ====================================== 3 | 4 | Ignored Files: 5 | -------------- 6 | The following files were *not* copied into the new Gradle project; you 7 | should evaluate whether these are still needed in your project and if 8 | so manually move them: 9 | 10 | * .DS_Store 11 | * .gitignore 12 | * LICENSE 13 | * README.md 14 | * fade_tab_demo.gif 15 | * ic_launcher-web.png 16 | * line_tab_demo.gif 17 | * proguard-project.txt 18 | 19 | Replaced Jars with Dependencies: 20 | -------------------------------- 21 | The importer recognized the following .jar files as third party 22 | libraries and replaced them with Gradle dependencies instead. This has 23 | the advantage that more explicit version information is known, and the 24 | libraries can be updated automatically. However, it is possible that 25 | the .jar file in your project was of an older version than the 26 | dependency we picked, which could render the project not compileable. 27 | You can disable the jar replacement in the import wizard and try again: 28 | 29 | android-support-v4.jar => com.android.support:support-v4:19.1.0 30 | 31 | Moved Files: 32 | ------------ 33 | Android Gradle projects use a different directory structure than ADT 34 | Eclipse projects. Here's how the projects were restructured: 35 | 36 | * AndroidManifest.xml => app/src/main/AndroidManifest.xml 37 | * assets/ => app/src/main/assets 38 | * res/ => app/src/main/res/ 39 | * src/ => app/src/main/java/ 40 | 41 | Next Steps: 42 | ----------- 43 | You can now build the project. The Gradle project needs network 44 | connectivity to download dependencies. 45 | 46 | Bugs: 47 | ----- 48 | If for some reason your project does not build, and you determine that 49 | it is due to a bug or limitation of the Eclipse to Gradle importer, 50 | please file a bug at http://b.android.com with category 51 | Component-Tools. 52 | 53 | (This import summary is for your information only, and can be deleted 54 | after import once you are satisfied with the results.) 55 | -------------------------------------------------------------------------------- /screenshots/fading_tab.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/screenshots/fading_tab.gif -------------------------------------------------------------------------------- /screenshots/line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingca1/ViewPagerIndicator/f1a6879625f30250117562f854b941e2f75269a3/screenshots/line.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':viewpagerindicator' 2 | -------------------------------------------------------------------------------- /viewpagerindicator/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /viewpagerindicator/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply plugin: 'com.jfrog.bintray' 4 | 5 | android { 6 | compileSdkVersion 23 7 | buildToolsVersion "23.0.3" 8 | 9 | defaultConfig { 10 | minSdkVersion 9 11 | targetSdkVersion 23 12 | versionCode 5 13 | versionName "1.0.5" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | testCompile 'junit:junit:4.12' 26 | compile 'com.android.support:appcompat-v7:23.1.1' 27 | } 28 | 29 | ext { 30 | bintrayRepo = 'maven' 31 | bintrayName = 'ViewPagerIndicator' 32 | 33 | publishedGroupId = 'me.caiying.library' 34 | artifact = 'viewpagerindicator' 35 | 36 | siteUrl = 'https://github.com/yc10x/ViewPagerIndicator' 37 | gitUrl = 'https://github.com/yc10x/ViewPagerIndicator.git' 38 | 39 | libraryVersion = '1.0.5' 40 | libraryName = 'ViewPagerIndicator' 41 | libraryDescription = 'A ViewPager Indicator library for Android' 42 | 43 | developerId = 'yc10x' 44 | developerName = 'caiying' 45 | developerEmail = 'i@caiying.me' 46 | 47 | licenseName = 'MIT' 48 | licenseUrl = 'https://opensource.org/licenses/MIT' 49 | allLicenses = ["MIT"] 50 | } 51 | apply from:'https://raw.githubusercontent.com/yc10x/static/master/jcenter/install.gradle' 52 | apply from:'https://raw.githubusercontent.com/yc10x/static/master/jcenter/bintray.gradle' 53 | -------------------------------------------------------------------------------- /viewpagerindicator/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/caiying/Android/android-sdk-macosx/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 | -------------------------------------------------------------------------------- /viewpagerindicator/src/androidTest/java/me/caiying/library/viewpagerindicator/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package me.caiying.library.viewpagerindicator; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /viewpagerindicator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/BadgeView.java: -------------------------------------------------------------------------------- 1 | package me.caiying.library.viewpagerindicator; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.graphics.Color; 7 | import android.graphics.Typeface; 8 | import android.graphics.drawable.Drawable; 9 | import android.graphics.drawable.ShapeDrawable; 10 | import android.graphics.drawable.shapes.RoundRectShape; 11 | import android.text.TextUtils; 12 | import android.util.AttributeSet; 13 | import android.util.TypedValue; 14 | import android.view.Gravity; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.view.ViewGroup.LayoutParams; 18 | import android.view.ViewParent; 19 | import android.view.animation.AccelerateInterpolator; 20 | import android.view.animation.AlphaAnimation; 21 | import android.view.animation.Animation; 22 | import android.view.animation.DecelerateInterpolator; 23 | import android.widget.FrameLayout; 24 | import android.widget.ImageView; 25 | import android.widget.TabWidget; 26 | import android.widget.TextView; 27 | 28 | 29 | /** 30 | * A simple text label view that can be applied as a "badge" to any given {@link android.view.View}. 31 | * This class is intended to be instantiated at runtime rather than included in XML layouts. 32 | */ 33 | public class BadgeView extends TextView { 34 | 35 | public static final int POSITION_TOP_LEFT = 1; 36 | public static final int POSITION_TOP_RIGHT = 2; 37 | public static final int POSITION_BOTTOM_LEFT = 3; 38 | public static final int POSITION_BOTTOM_RIGHT = 4; 39 | public static final int POSITION_CENTER = 5; 40 | 41 | private static final int DEFAULT_MARGIN_DIP = 5; 42 | private static final int DEFAULT_LR_PADDING_DIP = 5; 43 | private static final int DEFAULT_CORNER_RADIUS_DIP = 8; 44 | private static final int DEFAULT_POSITION = POSITION_TOP_RIGHT; 45 | private static final int DEFAULT_BADGE_COLOR = Color.parseColor("#ff0000"); 46 | private static final int DEFAULT_TEXT_COLOR = Color.WHITE; 47 | 48 | private static Animation fadeIn; 49 | private static Animation fadeOut; 50 | 51 | private Context context; 52 | private View target; 53 | private ImageView mNoneTipsView; 54 | 55 | private int badgePosition; 56 | private int badgeMarginH; 57 | private int badgeMarginV; 58 | private int badgeColor; 59 | 60 | private boolean isShown; 61 | 62 | private Drawable badgeBg; 63 | 64 | private int targetTabIndex; 65 | 66 | public BadgeView(Context context) { 67 | this(context, (AttributeSet) null, android.R.attr.textViewStyle); 68 | } 69 | 70 | public BadgeView(Context context, AttributeSet attrs) { 71 | this(context, attrs, android.R.attr.textViewStyle); 72 | } 73 | 74 | /** 75 | * Constructor - 76 | * 77 | * create a new BadgeView instance attached to a target {@link android.view.View}. 78 | * 79 | * @param context context for this view. 80 | * @param target the View to attach the badge to. 81 | */ 82 | public BadgeView(Context context, View target) { 83 | this(context, null, android.R.attr.textViewStyle, target, 0); 84 | } 85 | 86 | /** 87 | * Constructor - 88 | * 89 | * create a new BadgeView instance attached to a target {@link android.widget.TabWidget} 90 | * tab at a given index. 91 | * 92 | * @param context context for this view. 93 | * @param target the TabWidget to attach the badge to. 94 | * @param index the position of the tab within the target. 95 | */ 96 | public BadgeView(Context context, TabWidget target, int index) { 97 | this(context, null, android.R.attr.textViewStyle, target, index); 98 | } 99 | 100 | public BadgeView(Context context, AttributeSet attrs, int defStyle) { 101 | this(context, attrs, defStyle, null, 0); 102 | } 103 | 104 | public BadgeView(Context context, AttributeSet attrs, int defStyle, View target, int tabIndex) { 105 | super(context, attrs, defStyle); 106 | init(context, target, tabIndex); 107 | } 108 | 109 | private void init(Context context, View target, int tabIndex) { 110 | setTextSize(12f / getResources().getConfiguration().fontScale); 111 | this.context = context; 112 | this.target = target; 113 | this.targetTabIndex = tabIndex; 114 | 115 | // apply defaults 116 | badgePosition = DEFAULT_POSITION; 117 | badgeMarginH = dipToPixels(DEFAULT_MARGIN_DIP); 118 | badgeMarginV = badgeMarginH; 119 | badgeColor = DEFAULT_BADGE_COLOR; 120 | 121 | setTypeface(Typeface.DEFAULT_BOLD); 122 | int paddingPixels = dipToPixels(DEFAULT_LR_PADDING_DIP); 123 | setPadding(paddingPixels, 0, paddingPixels, 0); 124 | setTextColor(DEFAULT_TEXT_COLOR); 125 | 126 | fadeIn = new AlphaAnimation(0, 1); 127 | fadeIn.setInterpolator(new DecelerateInterpolator()); 128 | fadeIn.setDuration(200); 129 | 130 | fadeOut = new AlphaAnimation(1, 0); 131 | fadeOut.setInterpolator(new AccelerateInterpolator()); 132 | fadeOut.setDuration(200); 133 | 134 | isShown = false; 135 | 136 | if (this.target != null) { 137 | applyTo(this.target); 138 | } else { 139 | show(); 140 | } 141 | 142 | } 143 | 144 | private void applyTo(View target) { 145 | 146 | LayoutParams lp = target.getLayoutParams(); 147 | ViewParent parent = target.getParent(); 148 | FrameLayout container = new FrameLayout(context); 149 | 150 | if (target instanceof TabWidget) { 151 | 152 | // setPhoto target to the relevant tab child container 153 | target = ((TabWidget) target).getChildTabViewAt(targetTabIndex); 154 | this.target = target; 155 | 156 | ((ViewGroup) target).addView(container, 157 | new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 158 | 159 | this.setVisibility(View.INVISIBLE); 160 | container.addView(this); 161 | 162 | mNoneTipsView = new ImageView(getContext()); 163 | mNoneTipsView.setImageDrawable(getResources().getDrawable(R.drawable.mark_none_tips)); 164 | container.addView(mNoneTipsView, new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 165 | mNoneTipsView.setVisibility(View.INVISIBLE); 166 | 167 | } else { 168 | 169 | // TODO verify that parent is indeed a ViewGroup 170 | ViewGroup group = (ViewGroup) parent; 171 | int index = group.indexOfChild(target); 172 | 173 | group.removeView(target); 174 | group.addView(container, index, lp); 175 | 176 | container.addView(target); 177 | 178 | this.setVisibility(View.INVISIBLE); 179 | container.addView(this); 180 | 181 | mNoneTipsView = new ImageView(getContext()); 182 | mNoneTipsView.setImageDrawable(getResources().getDrawable(R.drawable.mark_none_tips)); 183 | container.addView(mNoneTipsView, new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 184 | mNoneTipsView.setVisibility(View.INVISIBLE); 185 | 186 | group.invalidate(); 187 | 188 | } 189 | 190 | } 191 | 192 | /** 193 | * Make the badge visible in the UI. 194 | */ 195 | public void show() { 196 | show(false, null); 197 | } 198 | 199 | /** 200 | * Make the badge visible in the UI. 201 | * 202 | * @param animate flag to apply the default fade-in animation. 203 | */ 204 | public void show(boolean animate) { 205 | show(animate, fadeIn); 206 | } 207 | 208 | /** 209 | * Make the badge visible in the UI. 210 | * 211 | * @param anim Animation to apply to the view when made visible. 212 | */ 213 | public void show(Animation anim) { 214 | show(true, anim); 215 | } 216 | 217 | /** 218 | * Make the badge non-visible in the UI. 219 | */ 220 | public void hide() { 221 | hide(false, null); 222 | } 223 | 224 | /** 225 | * Make the badge non-visible in the UI. 226 | * 227 | * @param animate flag to apply the default fade-out animation. 228 | */ 229 | public void hide(boolean animate) { 230 | hide(animate, fadeOut); 231 | } 232 | 233 | /** 234 | * Make the badge non-visible in the UI. 235 | * 236 | * @param anim Animation to apply to the view when made non-visible. 237 | */ 238 | public void hide(Animation anim) { 239 | hide(true, anim); 240 | } 241 | 242 | /** 243 | * Toggle the badge visibility in the UI. 244 | */ 245 | public void toggle() { 246 | toggle(false, null, null); 247 | } 248 | 249 | /** 250 | * Toggle the badge visibility in the UI. 251 | * 252 | * @param animate flag to apply the default fade-in/out animation. 253 | */ 254 | public void toggle(boolean animate) { 255 | toggle(animate, fadeIn, fadeOut); 256 | } 257 | 258 | /** 259 | * Toggle the badge visibility in the UI. 260 | * 261 | * @param animIn Animation to apply to the view when made visible. 262 | * @param animOut Animation to apply to the view when made non-visible. 263 | */ 264 | public void toggle(Animation animIn, Animation animOut) { 265 | toggle(true, animIn, animOut); 266 | } 267 | 268 | private void show(boolean animate, Animation anim) { 269 | if (getBackground() == null) { 270 | if (badgeBg == null) { 271 | badgeBg = getDefaultBackground(); 272 | } 273 | setBackgroundDrawable(badgeBg); 274 | } 275 | applyLayoutParams(); 276 | 277 | if (TextUtils.isEmpty(getText())) { 278 | if (animate) { 279 | mNoneTipsView.startAnimation(anim); 280 | } 281 | this.setVisibility(View.GONE); 282 | mNoneTipsView.setVisibility(View.VISIBLE); 283 | } else { 284 | if (animate) { 285 | this.startAnimation(anim); 286 | } 287 | this.setVisibility(View.VISIBLE); 288 | mNoneTipsView.setVisibility(View.GONE); 289 | } 290 | isShown = true; 291 | } 292 | 293 | private void hide(boolean animate, Animation anim) { 294 | this.setVisibility(View.INVISIBLE); 295 | if (TextUtils.isEmpty(getText())) { 296 | mNoneTipsView.setVisibility(INVISIBLE); 297 | } 298 | if (animate) { 299 | this.startAnimation(anim); 300 | mNoneTipsView.startAnimation(anim); 301 | } 302 | isShown = false; 303 | } 304 | 305 | private void toggle(boolean animate, Animation animIn, Animation animOut) { 306 | if (isShown) { 307 | hide(animate && (animOut != null), animOut); 308 | } else { 309 | show(animate && (animIn != null), animIn); 310 | } 311 | } 312 | 313 | /** 314 | * Increment the numeric badge label. If the current badge label cannot be converted to 315 | * an integer value, its label will be setPhoto to "0". 316 | * 317 | * @param offset the increment offset. 318 | */ 319 | public int increment(int offset) { 320 | CharSequence txt = getText(); 321 | int i; 322 | if (txt != null) { 323 | try { 324 | i = Integer.parseInt(txt.toString()); 325 | } catch (NumberFormatException e) { 326 | i = 0; 327 | } 328 | } else { 329 | i = 0; 330 | } 331 | i = i + offset; 332 | setText(String.valueOf(i)); 333 | return i; 334 | } 335 | 336 | /** 337 | * Decrement the numeric badge label. If the current badge label cannot be converted to 338 | * an integer value, its label will be setPhoto to "0". 339 | * 340 | * @param offset the decrement offset. 341 | */ 342 | public int decrement(int offset) { 343 | return increment(-offset); 344 | } 345 | 346 | private ShapeDrawable getDefaultBackground() { 347 | 348 | int r = dipToPixels(DEFAULT_CORNER_RADIUS_DIP); 349 | float[] outerR = new float[]{r, r, r, r, r, r, r, r}; 350 | 351 | RoundRectShape rr = new RoundRectShape(outerR, null, null); 352 | ShapeDrawable drawable = new ShapeDrawable(rr); 353 | drawable.getPaint().setColor(badgeColor); 354 | 355 | return drawable; 356 | 357 | } 358 | 359 | private void applyLayoutParams() { 360 | 361 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 362 | 363 | int offsetH = badgeMarginH; 364 | int offsetV = badgeMarginV; 365 | if (TextUtils.isEmpty(getText())) { 366 | offsetH = badgeMarginH + Utils.dp2px(getContext(), DEFAULT_MARGIN_DIP * 2); 367 | offsetV = badgeMarginV + Utils.dp2px(getContext(), DEFAULT_MARGIN_DIP / 2); 368 | } 369 | switch (badgePosition) { 370 | case POSITION_TOP_LEFT: 371 | lp.gravity = Gravity.LEFT | Gravity.TOP; 372 | lp.setMargins(offsetH, offsetV, 0, 0); 373 | break; 374 | case POSITION_TOP_RIGHT: 375 | lp.gravity = Gravity.RIGHT | Gravity.TOP; 376 | lp.setMargins(0, offsetV, offsetH, 0); 377 | break; 378 | case POSITION_BOTTOM_LEFT: 379 | lp.gravity = Gravity.LEFT | Gravity.BOTTOM; 380 | lp.setMargins(offsetH, 0, 0, offsetV); 381 | break; 382 | case POSITION_BOTTOM_RIGHT: 383 | lp.gravity = Gravity.RIGHT | Gravity.BOTTOM; 384 | lp.setMargins(0, 0, offsetH, offsetV); 385 | break; 386 | case POSITION_CENTER: 387 | lp.gravity = Gravity.CENTER; 388 | lp.setMargins(0, 0, 0, 0); 389 | break; 390 | default: 391 | break; 392 | } 393 | 394 | if (TextUtils.isEmpty(getText())) { 395 | mNoneTipsView.setLayoutParams(lp); 396 | } else { 397 | setLayoutParams(lp); 398 | } 399 | } 400 | 401 | /** 402 | * Returns the target View this badge has been attached to. 403 | */ 404 | public View getTarget() { 405 | return target; 406 | } 407 | 408 | /** 409 | * Is this badge currently visible in the UI? 410 | */ 411 | @Override 412 | public boolean isShown() { 413 | return isShown; 414 | } 415 | 416 | /** 417 | * Returns the positioning of this badge. 418 | * 419 | * one of POSITION_TOP_LEFT, POSITION_TOP_RIGHT, POSITION_BOTTOM_LEFT, POSITION_BOTTOM_RIGHT, POSTION_CENTER. 420 | */ 421 | public int getBadgePosition() { 422 | return badgePosition; 423 | } 424 | 425 | /** 426 | * Set the positioning of this badge. 427 | * 428 | * @param layoutPosition one of POSITION_TOP_LEFT, POSITION_TOP_RIGHT, POSITION_BOTTOM_LEFT, POSITION_BOTTOM_RIGHT, POSTION_CENTER. 429 | */ 430 | public void setBadgePosition(int layoutPosition) { 431 | this.badgePosition = layoutPosition; 432 | } 433 | 434 | /** 435 | * Returns the horizontal margin from the target View that is applied to this badge. 436 | */ 437 | public int getHorizontalBadgeMargin() { 438 | return badgeMarginH; 439 | } 440 | 441 | /** 442 | * Returns the vertical margin from the target View that is applied to this badge. 443 | */ 444 | public int getVerticalBadgeMargin() { 445 | return badgeMarginV; 446 | } 447 | 448 | /** 449 | * Set the horizontal/vertical margin from the target View that is applied to this badge. 450 | * 451 | * @param badgeMargin the margin in pixels. 452 | */ 453 | public void setBadgeMargin(int badgeMargin) { 454 | this.badgeMarginH = badgeMargin; 455 | this.badgeMarginV = badgeMargin; 456 | } 457 | 458 | /** 459 | * Set the horizontal/vertical margin from the target View that is applied to this badge. 460 | * 461 | * @param horizontal margin in pixels. 462 | * @param vertical margin in pixels. 463 | */ 464 | public void setBadgeMargin(int horizontal, int vertical) { 465 | this.badgeMarginH = horizontal; 466 | this.badgeMarginV = vertical; 467 | } 468 | 469 | /** 470 | * Returns the color value of the badge background. 471 | */ 472 | public int getBadgeBackgroundColor() { 473 | return badgeColor; 474 | } 475 | 476 | /** 477 | * Set the color value of the badge background. 478 | * 479 | * @param badgeColor the badge background color. 480 | */ 481 | public void setBadgeBackgroundColor(int badgeColor) { 482 | this.badgeColor = badgeColor; 483 | badgeBg = getDefaultBackground(); 484 | } 485 | 486 | private int dipToPixels(int dip) { 487 | Resources r = getResources(); 488 | float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, r.getDisplayMetrics()); 489 | return (int) px; 490 | } 491 | 492 | } -------------------------------------------------------------------------------- /viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/BasicViewPagerIndicator.java: -------------------------------------------------------------------------------- 1 | package me.caiying.library.viewpagerindicator; 2 | 3 | import android.support.v4.view.ViewPager; 4 | 5 | /** 6 | * A PageIndicator is responsible to show an visual indicator on the total views 7 | * number and the current visible view. 8 | */ 9 | public interface BasicViewPagerIndicator extends ViewPager.OnPageChangeListener { 10 | /** 11 | * Bind the indicator to a ViewPager. 12 | * 13 | * @param view 14 | */ 15 | void setViewPager(ViewPager view); 16 | 17 | /** 18 | * Bind the indicator to a ViewPager. 19 | * 20 | * @param view 21 | * @param initialPosition 22 | */ 23 | void setViewPager(ViewPager view, int initialPosition); 24 | 25 | /** 26 | *

Set the current page of both the ViewPager and indicator.

27 | * 28 | *

This must be used if you need to set the page before 29 | * the views are drawn on screen (e.g., default start page).

30 | * 31 | * @param item 32 | */ 33 | void setCurrentItem(int item); 34 | 35 | /** 36 | * Set a page change listener which will receive forwarded events. 37 | * 38 | * @param listener 39 | */ 40 | void setOnPageChangeListener(ViewPager.OnPageChangeListener listener); 41 | 42 | /** 43 | * Notify the indicator that the fragment list has changed. 44 | */ 45 | void notifyDataSetChanged(); 46 | } -------------------------------------------------------------------------------- /viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/CirclePagerIndicator.java: -------------------------------------------------------------------------------- 1 | package me.caiying.library.viewpagerindicator; 2 | 3 | /** 4 | * Created by caiying on 06/02/2016. 5 | */ 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Paint.Style; 11 | import android.os.Parcel; 12 | import android.os.Parcelable; 13 | import android.support.v4.view.MotionEventCompat; 14 | import android.support.v4.view.ViewConfigurationCompat; 15 | import android.support.v4.view.ViewPager; 16 | import android.util.AttributeSet; 17 | import android.view.MotionEvent; 18 | import android.view.View; 19 | import android.view.ViewConfiguration; 20 | 21 | import static android.graphics.Paint.ANTI_ALIAS_FLAG; 22 | import static android.widget.LinearLayout.HORIZONTAL; 23 | import static android.widget.LinearLayout.VERTICAL; 24 | 25 | /** 26 | * Draws circles (one for each view). The current view position is filled and 27 | * others are only stroked. 28 | */ 29 | public class CirclePagerIndicator extends View implements BasicViewPagerIndicator { 30 | private static final int INVALID_POINTER = -1; 31 | 32 | private float mRadius; 33 | private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG); 34 | private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG); 35 | private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG); 36 | private ViewPager mViewPager; 37 | private ViewPager.OnPageChangeListener mListener; 38 | private int mCurrentPage; 39 | private int mSnapPage; 40 | private float mPageOffset; 41 | private int mScrollState; 42 | private int mOrientation; 43 | private boolean mCentered; 44 | private boolean mSnap = true; 45 | 46 | private int mTouchSlop; 47 | private float mLastMotionX = -1; 48 | private int mActivePointerId = INVALID_POINTER; 49 | private boolean mIsDragging; 50 | 51 | 52 | public CirclePagerIndicator(Context context) { 53 | super(context); 54 | init(); 55 | } 56 | 57 | public CirclePagerIndicator(Context context, AttributeSet attrs) { 58 | super(context, attrs); 59 | init(); 60 | } 61 | 62 | public CirclePagerIndicator(Context context, AttributeSet attrs, int defStyle) { 63 | super(context, attrs, defStyle); 64 | init(); 65 | } 66 | 67 | private void init() { 68 | if (isInEditMode()) return; 69 | 70 | //Load defaults from resources 71 | final int defaultPageColor = Color.parseColor("#e9e9e9"); 72 | final int defaultFillColor = Color.parseColor("#9b9b9b"); 73 | final int defaultOrientation = HORIZONTAL; 74 | final int defaultStrokeColor = Color.parseColor("#e9e9e9"); 75 | final float defaultStrokeWidth = Utils.dp2px(getContext(), 1); 76 | final float defaultRadius = Utils.dp2px(getContext(), 2); 77 | final boolean defaultCentered = true; 78 | 79 | //Retrieve styles attributes 80 | mCentered = defaultCentered; 81 | mOrientation = defaultOrientation; 82 | mPaintPageFill.setStyle(Style.FILL); 83 | mPaintPageFill.setColor(defaultPageColor); 84 | mPaintStroke.setStyle(Style.STROKE); 85 | mPaintStroke.setColor(defaultStrokeColor); 86 | mPaintStroke.setStrokeWidth(defaultStrokeWidth); 87 | mPaintFill.setStyle(Style.FILL); 88 | mPaintFill.setColor(defaultFillColor); 89 | mRadius = defaultRadius; 90 | 91 | final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 92 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 93 | } 94 | 95 | public void setCentered(boolean centered) { 96 | mCentered = centered; 97 | invalidate(); 98 | } 99 | 100 | public boolean isCentered() { 101 | return mCentered; 102 | } 103 | 104 | public void setPageColor(int pageColor) { 105 | mPaintPageFill.setColor(pageColor); 106 | invalidate(); 107 | } 108 | 109 | public int getPageColor() { 110 | return mPaintPageFill.getColor(); 111 | } 112 | 113 | public void setFillColor(int fillColor) { 114 | mPaintFill.setColor(fillColor); 115 | invalidate(); 116 | } 117 | 118 | public int getFillColor() { 119 | return mPaintFill.getColor(); 120 | } 121 | 122 | public void setOrientation(int orientation) { 123 | switch (orientation) { 124 | case HORIZONTAL: 125 | case VERTICAL: 126 | mOrientation = orientation; 127 | requestLayout(); 128 | break; 129 | 130 | default: 131 | throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL."); 132 | } 133 | } 134 | 135 | public int getOrientation() { 136 | return mOrientation; 137 | } 138 | 139 | public void setStrokeColor(int strokeColor) { 140 | mPaintStroke.setColor(strokeColor); 141 | invalidate(); 142 | } 143 | 144 | public int getStrokeColor() { 145 | return mPaintStroke.getColor(); 146 | } 147 | 148 | public void setStrokeWidth(float strokeWidth) { 149 | mPaintStroke.setStrokeWidth(strokeWidth); 150 | invalidate(); 151 | } 152 | 153 | public float getStrokeWidth() { 154 | return mPaintStroke.getStrokeWidth(); 155 | } 156 | 157 | public void setRadius(float radius) { 158 | mRadius = radius; 159 | invalidate(); 160 | } 161 | 162 | public float getRadius() { 163 | return mRadius; 164 | } 165 | 166 | public void setSnap(boolean snap) { 167 | mSnap = snap; 168 | invalidate(); 169 | } 170 | 171 | public boolean isSnap() { 172 | return mSnap; 173 | } 174 | 175 | @Override 176 | protected void onDraw(Canvas canvas) { 177 | super.onDraw(canvas); 178 | 179 | if (mViewPager == null) { 180 | return; 181 | } 182 | final int count = mViewPager.getAdapter().getCount(); 183 | if (count == 0) { 184 | return; 185 | } 186 | 187 | if (mCurrentPage >= count) { 188 | setCurrentItem(count - 1); 189 | return; 190 | } 191 | 192 | int longSize; 193 | int longPaddingBefore; 194 | int longPaddingAfter; 195 | int shortPaddingBefore; 196 | if (mOrientation == HORIZONTAL) { 197 | longSize = getWidth(); 198 | longPaddingBefore = getPaddingLeft(); 199 | longPaddingAfter = getPaddingRight(); 200 | shortPaddingBefore = getPaddingTop(); 201 | } else { 202 | longSize = getHeight(); 203 | longPaddingBefore = getPaddingTop(); 204 | longPaddingAfter = getPaddingBottom(); 205 | shortPaddingBefore = getPaddingLeft(); 206 | } 207 | 208 | final float threeRadius = mRadius * 3; 209 | final float shortOffset = shortPaddingBefore + mRadius; 210 | float longOffset = longPaddingBefore + mRadius; 211 | if (mCentered) { 212 | longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f); 213 | } 214 | 215 | float dX; 216 | float dY; 217 | 218 | float pageFillRadius = mRadius; 219 | if (mPaintStroke.getStrokeWidth() > 0) { 220 | pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f; 221 | } 222 | 223 | //Draw stroked circles 224 | for (int iLoop = 0; iLoop < count; iLoop++) { 225 | float drawLong = longOffset + (iLoop * threeRadius); 226 | if (mOrientation == HORIZONTAL) { 227 | dX = drawLong; 228 | dY = shortOffset; 229 | } else { 230 | dX = shortOffset; 231 | dY = drawLong; 232 | } 233 | // Only paint fill if not completely transparent 234 | if (mPaintPageFill.getAlpha() > 0) { 235 | canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill); 236 | } 237 | 238 | // Only paint stroke if a stroke width was non-zero 239 | if (pageFillRadius != mRadius) { 240 | canvas.drawCircle(dX, dY, mRadius, mPaintStroke); 241 | } 242 | } 243 | 244 | //Draw the filled circle according to the current scroll 245 | float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius; 246 | if (!mSnap) { 247 | cx += mPageOffset * threeRadius; 248 | } 249 | if (mOrientation == HORIZONTAL) { 250 | dX = longOffset + cx; 251 | dY = shortOffset; 252 | } else { 253 | dX = shortOffset; 254 | dY = longOffset + cx; 255 | } 256 | canvas.drawCircle(dX, dY, mRadius, mPaintFill); 257 | } 258 | 259 | public boolean onTouchEvent(android.view.MotionEvent ev) { 260 | if (super.onTouchEvent(ev)) { 261 | return true; 262 | } 263 | if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) { 264 | return false; 265 | } 266 | 267 | final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 268 | switch (action) { 269 | case MotionEvent.ACTION_DOWN: 270 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 271 | mLastMotionX = ev.getX(); 272 | break; 273 | 274 | case MotionEvent.ACTION_MOVE: { 275 | final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 276 | final float x = MotionEventCompat.getX(ev, activePointerIndex); 277 | final float deltaX = x - mLastMotionX; 278 | 279 | if (!mIsDragging) { 280 | if (Math.abs(deltaX) > mTouchSlop) { 281 | mIsDragging = true; 282 | } 283 | } 284 | 285 | if (mIsDragging) { 286 | mLastMotionX = x; 287 | if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) { 288 | mViewPager.fakeDragBy(deltaX); 289 | } 290 | } 291 | 292 | break; 293 | } 294 | 295 | case MotionEvent.ACTION_CANCEL: 296 | case MotionEvent.ACTION_UP: 297 | if (!mIsDragging) { 298 | final int count = mViewPager.getAdapter().getCount(); 299 | final int width = getWidth(); 300 | final float halfWidth = width / 2f; 301 | final float sixthWidth = width / 6f; 302 | 303 | if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) { 304 | if (action != MotionEvent.ACTION_CANCEL) { 305 | mViewPager.setCurrentItem(mCurrentPage - 1); 306 | } 307 | return true; 308 | } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) { 309 | if (action != MotionEvent.ACTION_CANCEL) { 310 | mViewPager.setCurrentItem(mCurrentPage + 1); 311 | } 312 | return true; 313 | } 314 | } 315 | 316 | mIsDragging = false; 317 | mActivePointerId = INVALID_POINTER; 318 | try { 319 | if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag(); 320 | } catch (Exception e) { 321 | e.printStackTrace(); 322 | } 323 | break; 324 | 325 | case MotionEventCompat.ACTION_POINTER_DOWN: { 326 | final int index = MotionEventCompat.getActionIndex(ev); 327 | mLastMotionX = MotionEventCompat.getX(ev, index); 328 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 329 | break; 330 | } 331 | 332 | case MotionEventCompat.ACTION_POINTER_UP: 333 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 334 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 335 | if (pointerId == mActivePointerId) { 336 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 337 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 338 | } 339 | mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 340 | break; 341 | } 342 | 343 | return true; 344 | } 345 | 346 | @Override 347 | public void setViewPager(ViewPager view) { 348 | if (mViewPager == view) { 349 | return; 350 | } 351 | if (mViewPager != null) { 352 | mViewPager.setOnPageChangeListener(null); 353 | } 354 | if (view.getAdapter() == null) { 355 | throw new IllegalStateException("ViewPager does not have adapter instance."); 356 | } 357 | mViewPager = view; 358 | mViewPager.setOnPageChangeListener(this); 359 | invalidate(); 360 | } 361 | 362 | @Override 363 | public void setViewPager(ViewPager view, int initialPosition) { 364 | setViewPager(view); 365 | setCurrentItem(initialPosition); 366 | } 367 | 368 | @Override 369 | public void setCurrentItem(int item) { 370 | if (mViewPager == null) { 371 | throw new IllegalStateException("ViewPager has not been bound."); 372 | } 373 | mViewPager.setCurrentItem(item); 374 | mCurrentPage = item; 375 | invalidate(); 376 | } 377 | 378 | @Override 379 | public void notifyDataSetChanged() { 380 | invalidate(); 381 | } 382 | 383 | @Override 384 | public void onPageScrollStateChanged(int state) { 385 | mScrollState = state; 386 | 387 | if (mListener != null) { 388 | mListener.onPageScrollStateChanged(state); 389 | } 390 | } 391 | 392 | @Override 393 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 394 | mCurrentPage = position; 395 | mPageOffset = positionOffset; 396 | invalidate(); 397 | 398 | if (mListener != null) { 399 | mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); 400 | } 401 | } 402 | 403 | @Override 404 | public void onPageSelected(int position) { 405 | if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) { 406 | mCurrentPage = position; 407 | mSnapPage = position; 408 | invalidate(); 409 | } 410 | 411 | if (mListener != null) { 412 | mListener.onPageSelected(position); 413 | } 414 | } 415 | 416 | @Override 417 | public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { 418 | mListener = listener; 419 | } 420 | 421 | /* 422 | * (non-Javadoc) 423 | * 424 | * @see android.view.View#onMeasure(int, int) 425 | */ 426 | @Override 427 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 428 | if (mOrientation == HORIZONTAL) { 429 | setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec)); 430 | } else { 431 | setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec)); 432 | } 433 | } 434 | 435 | /** 436 | * Determines the width of this view 437 | * 438 | * @param measureSpec 439 | * A measureSpec packed into an int 440 | * @return The width of the view, honoring constraints from measureSpec 441 | */ 442 | private int measureLong(int measureSpec) { 443 | int result; 444 | int specMode = MeasureSpec.getMode(measureSpec); 445 | int specSize = MeasureSpec.getSize(measureSpec); 446 | 447 | if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) { 448 | //We were told how big to be 449 | result = specSize; 450 | } else { 451 | //Calculate the width according the views count 452 | final int count = mViewPager.getAdapter().getCount(); 453 | result = (int)(getPaddingLeft() + getPaddingRight() 454 | + (count * 2 * mRadius) + (count - 1) * mRadius + 1); 455 | //Respect AT_MOST value if that was what is called for by measureSpec 456 | if (specMode == MeasureSpec.AT_MOST) { 457 | result = Math.min(result, specSize); 458 | } 459 | } 460 | return result; 461 | } 462 | 463 | /** 464 | * Determines the height of this view 465 | * 466 | * @param measureSpec 467 | * A measureSpec packed into an int 468 | * @return The height of the view, honoring constraints from measureSpec 469 | */ 470 | private int measureShort(int measureSpec) { 471 | int result; 472 | int specMode = MeasureSpec.getMode(measureSpec); 473 | int specSize = MeasureSpec.getSize(measureSpec); 474 | 475 | if (specMode == MeasureSpec.EXACTLY) { 476 | //We were told how big to be 477 | result = specSize; 478 | } else { 479 | //Measure the height 480 | result = (int)(2 * mRadius + getPaddingTop() + getPaddingBottom() + 1); 481 | //Respect AT_MOST value if that was what is called for by measureSpec 482 | if (specMode == MeasureSpec.AT_MOST) { 483 | result = Math.min(result, specSize); 484 | } 485 | } 486 | return result; 487 | } 488 | 489 | @Override 490 | public void onRestoreInstanceState(Parcelable state) { 491 | SavedState savedState = (SavedState)state; 492 | super.onRestoreInstanceState(savedState.getSuperState()); 493 | mCurrentPage = savedState.currentPage; 494 | mSnapPage = savedState.currentPage; 495 | requestLayout(); 496 | } 497 | 498 | @Override 499 | public Parcelable onSaveInstanceState() { 500 | Parcelable superState = super.onSaveInstanceState(); 501 | SavedState savedState = new SavedState(superState); 502 | savedState.currentPage = mCurrentPage; 503 | return savedState; 504 | } 505 | 506 | static class SavedState extends BaseSavedState { 507 | int currentPage; 508 | 509 | public SavedState(Parcelable superState) { 510 | super(superState); 511 | } 512 | 513 | private SavedState(Parcel in) { 514 | super(in); 515 | currentPage = in.readInt(); 516 | } 517 | 518 | @Override 519 | public void writeToParcel(Parcel dest, int flags) { 520 | super.writeToParcel(dest, flags); 521 | dest.writeInt(currentPage); 522 | } 523 | 524 | @SuppressWarnings("UnusedDeclaration") 525 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 526 | @Override 527 | public SavedState createFromParcel(Parcel in) { 528 | return new SavedState(in); 529 | } 530 | 531 | @Override 532 | public SavedState[] newArray(int size) { 533 | return new SavedState[size]; 534 | } 535 | }; 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/FadingTabPagerIndicator.java: -------------------------------------------------------------------------------- 1 | 2 | package me.caiying.library.viewpagerindicator; 3 | 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.support.v4.view.ViewPager; 8 | import android.support.v4.view.ViewPager.OnPageChangeListener; 9 | import android.text.TextUtils; 10 | import android.util.AttributeSet; 11 | import android.util.TypedValue; 12 | import android.view.Gravity; 13 | import android.view.View; 14 | import android.widget.FrameLayout; 15 | import android.widget.ImageView; 16 | import android.widget.LinearLayout; 17 | import android.widget.TextView; 18 | 19 | public class FadingTabPagerIndicator extends LinearLayout { 20 | private ViewPager mViewPager; 21 | private PageListener pageListener = new PageListener(); 22 | private LinearLayout.LayoutParams tabLayoutParams; 23 | private int iconHeight = 28; // dp 24 | private int iconWidth = 30; // dp 25 | private int textSize = 12; // sp 26 | private int paddingOffset = 4; // dp 27 | 28 | public FadingTabPagerIndicator(Context context) { 29 | super(context); 30 | init(); 31 | } 32 | 33 | public FadingTabPagerIndicator(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | init(); 36 | } 37 | 38 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 39 | public FadingTabPagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) { 40 | super(context, attrs, defStyleAttr); 41 | init(); 42 | } 43 | 44 | private void init() { 45 | setOrientation(LinearLayout.HORIZONTAL); 46 | tabLayoutParams = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1.0f); 47 | tabLayoutParams.topMargin = Utils.dp2px(getContext(), paddingOffset); 48 | tabLayoutParams.bottomMargin = Utils.dp2px(getContext(), paddingOffset); 49 | iconHeight = (int) TypedValue.applyDimension( 50 | TypedValue.COMPLEX_UNIT_DIP, iconHeight, getResources().getDisplayMetrics()); 51 | iconWidth = (int) TypedValue.applyDimension( 52 | TypedValue.COMPLEX_UNIT_DIP, iconWidth, getResources().getDisplayMetrics()); 53 | } 54 | 55 | public void setViewPager(ViewPager pager) { 56 | this.mViewPager = pager; 57 | 58 | if (pager.getAdapter() == null) { 59 | throw new IllegalStateException("ViewPager does not have adapter instance."); 60 | } 61 | 62 | pager.setOnPageChangeListener(pageListener); 63 | 64 | notifyDataSetChanged(); 65 | } 66 | 67 | public void setBadge(int index, int number) { 68 | if (number > 0) { 69 | BadgeView badgeView = ((TabView) getChildAt(index)).getBadgeView(); 70 | badgeView.setText(String.valueOf(number)); 71 | badgeView.show(); 72 | } else { 73 | setNoneBadge(index); 74 | } 75 | } 76 | 77 | public void setBadge(int index, String number) { 78 | if (!TextUtils.isEmpty(number)) { 79 | BadgeView badgeView = ((TabView) getChildAt(index)).getBadgeView(); 80 | badgeView.setText(number); 81 | badgeView.show(); 82 | } else { 83 | setNoneBadge(index); 84 | } 85 | } 86 | 87 | public void setNoneBadge(int index) { 88 | BadgeView badgeView = ((TabView) getChildAt(index)).getBadgeView(); 89 | badgeView.setText(""); 90 | badgeView.show(); 91 | } 92 | 93 | public void hideBadge(int index) { 94 | ((TabView) getChildAt(index)).getBadgeView().hide(); 95 | } 96 | 97 | public void setCurrentItem(int item) { 98 | mViewPager.setCurrentItem(item, false); 99 | tabSelect(item); 100 | } 101 | 102 | public void notifyDataSetChanged() { 103 | removeAllViews(); 104 | 105 | int tabCount = mViewPager.getAdapter().getCount(); 106 | 107 | FadingTab tabs = (FadingTab) mViewPager.getAdapter(); 108 | for (int i = 0; i < tabCount; i++) { 109 | addTab(i, mViewPager.getAdapter().getPageTitle(i).toString(), 110 | tabs.getTabNormalIconResId(i), tabs.getTabSelectIconResId(i), 111 | tabs.getTabNormalTextColor(i), tabs.getTabSelectTextColor(i)); 112 | } 113 | 114 | setCurrentItem(mViewPager.getCurrentItem()); 115 | } 116 | 117 | private void addTab(final int position, String text, int normalResId, int selectResId, 118 | int textNormalColorResId, int textSelectColorResId) { 119 | TabView tabView = new TabView(getContext(), text, normalResId, selectResId, 120 | textNormalColorResId, textSelectColorResId); 121 | tabView.setOnClickListener(new View.OnClickListener() { 122 | @Override 123 | public void onClick(View v) { 124 | setCurrentItem(position); 125 | } 126 | }); 127 | addView(tabView, position, tabLayoutParams); 128 | } 129 | 130 | private void tabSelect(int index) { 131 | final int tabCount = getChildCount(); 132 | for (int i = 0; i < tabCount; i++) { 133 | getChildAt(i).setSelected(i == index); 134 | } 135 | } 136 | 137 | private class FadingImageView extends FrameLayout { 138 | private ImageView mNormalImage; 139 | private ImageView mSelectImage; 140 | 141 | public FadingImageView(Context context, int normalResId, int selectResId) { 142 | super(context); 143 | mNormalImage = new ImageView(context); 144 | mSelectImage = new ImageView(context); 145 | mNormalImage.setImageResource(normalResId); 146 | mSelectImage.setImageResource(selectResId); 147 | mNormalImage.setAlpha(1.0f); 148 | mSelectImage.setAlpha(0.0f); 149 | addView(mNormalImage, 0, new LayoutParams(iconWidth, iconHeight, Gravity.CENTER)); 150 | addView(mSelectImage, 1, new LayoutParams(iconWidth, iconHeight, Gravity.CENTER)); 151 | } 152 | 153 | @Override 154 | public void setSelected(boolean selected) { 155 | super.setSelected(selected); 156 | setAlpha(selected ? 1.0f : 0.0f); 157 | } 158 | 159 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 160 | @Override 161 | public void setAlpha(float alpha) { 162 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 163 | mNormalImage.setAlpha(1 - alpha); 164 | mSelectImage.setAlpha(alpha); 165 | } else { 166 | mNormalImage.setAlpha((int) (1 - alpha)); 167 | mSelectImage.setAlpha((int) alpha); 168 | } 169 | } 170 | } 171 | 172 | private class FadingTextView extends FrameLayout { 173 | private TextView mNormalTextView; 174 | private TextView mSelectTextView; 175 | 176 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 177 | public FadingTextView(Context context, String text, int normalColor, int selectColor) { 178 | super(context); 179 | mNormalTextView = new TextView(context); 180 | mSelectTextView = new TextView(context); 181 | mNormalTextView.setTextColor(normalColor); 182 | mSelectTextView.setTextColor(selectColor); 183 | mNormalTextView.setAlpha(1.0f); 184 | mSelectTextView.setAlpha(0.0f); 185 | mNormalTextView.setText(text); 186 | mSelectTextView.setText(text); 187 | mNormalTextView.setTextSize(textSize); 188 | mSelectTextView.setTextSize(textSize); 189 | addView(mNormalTextView, 0, new LayoutParams( 190 | LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER)); 191 | addView(mSelectTextView, 1, new LayoutParams( 192 | LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER)); 193 | } 194 | 195 | @Override 196 | public void setSelected(boolean selected) { 197 | super.setSelected(selected); 198 | setAlpha(selected ? 1.0f : 0.0f); 199 | } 200 | 201 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 202 | @Override 203 | public void setAlpha(float alpha) { 204 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 205 | mNormalTextView.setAlpha(1 - alpha); 206 | mSelectTextView.setAlpha(alpha); 207 | } else { 208 | mNormalTextView.setAlpha((int) (1 - alpha)); 209 | mSelectTextView.setAlpha((int) alpha); 210 | } 211 | } 212 | } 213 | 214 | private class TabView extends LinearLayout { 215 | private FadingImageView fadingImageView; 216 | private FadingTextView fadingTextView; 217 | private BadgeView badgeView; 218 | 219 | public TabView(Context context, String text, int normalResId, int selectResId, 220 | int textNormalColorResId, int textSelectColorResId) { 221 | super(context); 222 | setOrientation(VERTICAL); 223 | setGravity(Gravity.CENTER); 224 | 225 | LayoutParams wpLayoutParams = 226 | new LayoutParams(Utils.dp2px(getContext(), 58), Utils.dp2px(getContext(), 28)); 227 | LinearLayout wrapperLayout = new LinearLayout(getContext()); 228 | wrapperLayout.setGravity(Gravity.CENTER); 229 | addView(wrapperLayout, wpLayoutParams); 230 | 231 | LayoutParams ivLayoutParams = 232 | new LayoutParams(Utils.dp2px(getContext(), 32), Utils.dp2px(getContext(), 28)); 233 | fadingImageView = new FadingImageView(context, normalResId, selectResId); 234 | wrapperLayout.addView(fadingImageView, ivLayoutParams); 235 | 236 | LayoutParams tvLayoutParams = 237 | new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 238 | fadingTextView = new FadingTextView(context, text, textNormalColorResId, textSelectColorResId); 239 | addView(fadingTextView, tvLayoutParams); 240 | 241 | badgeView = new BadgeView(getContext(), wrapperLayout); 242 | badgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT); 243 | badgeView.setBadgeMargin(0, 0); 244 | } 245 | 246 | public BadgeView getBadgeView() { 247 | return badgeView; 248 | } 249 | 250 | public FadingImageView getFadingImageView() { 251 | return fadingImageView; 252 | } 253 | 254 | public FadingTextView getFadingTextView() { 255 | return fadingTextView; 256 | } 257 | } 258 | 259 | private class PageListener implements OnPageChangeListener { 260 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 261 | @Override 262 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 263 | ((TabView) getChildAt(position)).getFadingImageView().setAlpha(1.0f - positionOffset); 264 | ((TabView) getChildAt(position)).getFadingTextView().setAlpha(1.0f - positionOffset); 265 | if (position + 1 <= getChildCount() - 1) { 266 | ((TabView) getChildAt(position + 1)).getFadingImageView().setAlpha(positionOffset); 267 | ((TabView) getChildAt(position + 1)).getFadingTextView().setAlpha(positionOffset); 268 | } 269 | } 270 | 271 | @Override 272 | public void onPageScrollStateChanged(int state) { 273 | } 274 | 275 | @Override 276 | public void onPageSelected(int position) { 277 | tabSelect(position); 278 | } 279 | } 280 | 281 | public interface FadingTab { 282 | 283 | int getTabNormalIconResId(int position); 284 | 285 | int getTabSelectIconResId(int position); 286 | 287 | int getTabNormalTextColor(int position); 288 | 289 | int getTabSelectTextColor(int position); 290 | } 291 | } 292 | 293 | -------------------------------------------------------------------------------- /viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/LinePagerIndicator.java: -------------------------------------------------------------------------------- 1 | package me.caiying.library.viewpagerindicator; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Paint.Style; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v4.view.ViewPager.OnPageChangeListener; 10 | import android.util.AttributeSet; 11 | import android.util.DisplayMetrics; 12 | import android.util.TypedValue; 13 | import android.view.Gravity; 14 | import android.view.View; 15 | import android.widget.HorizontalScrollView; 16 | import android.widget.LinearLayout; 17 | import android.widget.RelativeLayout; 18 | import android.widget.TextView; 19 | 20 | public class LinePagerIndicator extends HorizontalScrollView { 21 | 22 | public interface OnTabSelectedListener { 23 | 24 | void onTabSelected(int position); 25 | } 26 | 27 | public OnPageChangeListener mOnPageChangeListener; 28 | private OnTabSelectedListener mTabSelectedListener; 29 | 30 | private LinearLayout mTabsContainer; 31 | private ViewPager mPager; 32 | 33 | private int tabCount; 34 | private int currentPosition = 0; 35 | private float currentPositionOffset = 0f; 36 | 37 | private Paint linePaint; 38 | private Paint diviPaint; 39 | 40 | private int indicatorColor = 0xFF52B800; 41 | private int underlineColor = 0x1A000000; 42 | private int dividerColor = 0x1A000000; 43 | private int textSelectedColor = 0xFF52B800; 44 | private int textUnselectColor = 0xFF808080; 45 | 46 | private boolean enableExpand = true; 47 | private boolean enableDivider = false; 48 | private boolean indicatorOnTop = false; 49 | private boolean viewPagerScrollWithAnimation = true; 50 | 51 | private int tabTextSize = 16; 52 | private int scrollOffset = 52; 53 | private float indicatorHeight = 1.5f; 54 | private float underlineHeight = 1.0f; 55 | private int dividerPadding = 12; 56 | private int tabPadding = 24; 57 | private int dividerWidth = 1; 58 | private int lastScrollX = 0; 59 | 60 | public LinePagerIndicator(Context context) { 61 | this(context, null); 62 | } 63 | 64 | public LinePagerIndicator(Context context, AttributeSet attrs) { 65 | this(context, attrs, 0); 66 | } 67 | 68 | public LinePagerIndicator(Context context, AttributeSet attrs, int defStyle) { 69 | super(context, attrs, defStyle); 70 | 71 | setFillViewport(true); 72 | setWillNotDraw(false); 73 | 74 | DisplayMetrics dm = getResources().getDisplayMetrics(); 75 | 76 | mTabsContainer = new LinearLayout(context); 77 | mTabsContainer.setOrientation(LinearLayout.HORIZONTAL); 78 | LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 79 | params.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, dm); 80 | mTabsContainer.setLayoutParams(params); 81 | addView(mTabsContainer); 82 | 83 | scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset, dm); 84 | dividerPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerPadding, dm); 85 | tabPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabPadding, dm); 86 | dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth, dm); 87 | indicatorHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, indicatorHeight, dm); 88 | underlineHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, underlineHeight, dm); 89 | 90 | linePaint = new Paint(); 91 | linePaint.setAntiAlias(true); 92 | linePaint.setStyle(Style.FILL); 93 | 94 | diviPaint = new Paint(); 95 | diviPaint.setAntiAlias(true); 96 | diviPaint.setStrokeWidth(dividerWidth); 97 | } 98 | 99 | @Override 100 | protected void onDraw(Canvas canvas) { 101 | super.onDraw(canvas); 102 | 103 | if (isInEditMode() || tabCount == 0) { 104 | return; 105 | } 106 | 107 | final int height = getHeight(); 108 | 109 | linePaint.setColor(underlineColor); 110 | if(indicatorOnTop) { 111 | canvas.drawRect(0, 0, mTabsContainer.getWidth(), underlineHeight, linePaint); 112 | } else { 113 | canvas.drawRect(0, height - underlineHeight, mTabsContainer.getWidth(), height, linePaint); 114 | } 115 | 116 | linePaint.setColor(indicatorColor); 117 | 118 | View currentTab = mTabsContainer.getChildAt(currentPosition); 119 | float lineLeft = currentTab.getLeft(); 120 | float lineRight = currentTab.getRight(); 121 | 122 | if (currentPositionOffset > 0f && currentPosition < tabCount - 1) { 123 | View nextTab = mTabsContainer.getChildAt(currentPosition + 1); 124 | final float nextTabLeft = nextTab.getLeft(); 125 | final float nextTabRight = nextTab.getRight(); 126 | 127 | lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft); 128 | lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight); 129 | } 130 | 131 | if(indicatorOnTop) { 132 | canvas.drawRect(lineLeft, 0, lineRight, indicatorHeight, linePaint); 133 | } else { 134 | canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, linePaint); 135 | } 136 | 137 | if(enableDivider) { 138 | diviPaint.setColor(dividerColor); 139 | for (int i = 0; i < tabCount - 1; i++) { 140 | View tab = mTabsContainer.getChildAt(i); 141 | canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, diviPaint); 142 | } 143 | } 144 | } 145 | 146 | public void setViewPager(ViewPager pager) { 147 | this.mPager = pager; 148 | 149 | if (pager.getAdapter() == null) { 150 | throw new IllegalStateException("ViewPager does not have adapter instance."); 151 | } 152 | 153 | pager.setOnPageChangeListener(new PageListener()); 154 | 155 | notifyDataSetChanged(); 156 | } 157 | 158 | public void setOnPageChangeListener(OnPageChangeListener listener) { 159 | this.mOnPageChangeListener = listener; 160 | } 161 | 162 | public void setOnTabReselectedListener(OnTabSelectedListener listener) { 163 | mTabSelectedListener = listener; 164 | } 165 | 166 | public void notifyDataSetChanged() { 167 | 168 | mTabsContainer.removeAllViews(); 169 | 170 | tabCount = mPager.getAdapter().getCount(); 171 | 172 | for (int i = 0; i < tabCount; i++) { 173 | addTab(i, mPager.getAdapter().getPageTitle(i).toString()); 174 | } 175 | 176 | updateTabStyles(); 177 | } 178 | 179 | private class TabView extends RelativeLayout { 180 | private TextView mTabText; 181 | 182 | public TabView(Context context) { 183 | super(context); 184 | init(); 185 | } 186 | 187 | public TabView(Context context, AttributeSet attrs) { 188 | super(context, attrs); 189 | init(); 190 | } 191 | 192 | public TabView(Context context, AttributeSet attrs, int defStyle) { 193 | super(context, attrs, defStyle); 194 | init(); 195 | } 196 | 197 | private void init() { 198 | mTabText = new TextView(getContext()); 199 | mTabText.setTextAppearance(getContext(), android.R.style.TextAppearance_Medium); 200 | mTabText.setTextSize(tabTextSize / getResources().getConfiguration().fontScale); 201 | mTabText.setSingleLine(true); 202 | mTabText.setGravity(Gravity.CENTER); 203 | LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 204 | this.addView(mTabText, params); 205 | 206 | } 207 | 208 | public TextView getTextView() { 209 | return mTabText; 210 | } 211 | } 212 | 213 | private void addTab(final int position, String title) { 214 | TabView tab = new TabView(getContext()); 215 | tab.getTextView().setText(title); 216 | tab.setFocusable(true); 217 | tab.setOnClickListener(new OnClickListener() { 218 | @Override 219 | public void onClick(View v) { 220 | final int oldSelected = mPager.getCurrentItem(); 221 | if (oldSelected != position && mTabSelectedListener != null) { 222 | mTabSelectedListener.onTabSelected(position); 223 | } 224 | 225 | mPager.setCurrentItem(position, viewPagerScrollWithAnimation); 226 | } 227 | }); 228 | 229 | if(!enableExpand) { 230 | tab.setPadding(tabPadding, 0, tabPadding, 0); 231 | } 232 | mTabsContainer.addView(tab, position, enableExpand ? 233 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : 234 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); 235 | } 236 | 237 | public void setTabText(int position, String text) { 238 | if (position < 0 || position > (mTabsContainer.getChildCount() - 1)) 239 | throw new RuntimeException("tabs does not have this position."); 240 | 241 | View tab = mTabsContainer.getChildAt(position); 242 | if (tab instanceof TextView) { 243 | ((TextView) tab).setText(text); 244 | } 245 | } 246 | 247 | public boolean isIndicatorOnTop() { 248 | return indicatorOnTop; 249 | } 250 | 251 | public void setIndicatorOnTop(boolean indicatorOnTop) { 252 | this.indicatorOnTop = indicatorOnTop; 253 | } 254 | 255 | public boolean isEnableExpand() { 256 | return enableExpand; 257 | } 258 | 259 | public void setEnableExpand(boolean enableExpand) { 260 | this.enableExpand = enableExpand; 261 | } 262 | 263 | public boolean isEnableDivider() { 264 | return enableDivider; 265 | } 266 | 267 | public void setEnableDivider(boolean enableDivider) { 268 | this.enableDivider = enableDivider; 269 | } 270 | 271 | public void setViewPagerScrollWithAnimation(boolean enable) { 272 | this.viewPagerScrollWithAnimation = enable; 273 | } 274 | 275 | public boolean getViewPagerScrollWithAnimation() { 276 | return this.viewPagerScrollWithAnimation; 277 | } 278 | 279 | public void setCurrentItem(int item) { 280 | mPager.setCurrentItem(item, viewPagerScrollWithAnimation); 281 | } 282 | 283 | private void tabSelect(int index) { 284 | final int tabCount = mTabsContainer.getChildCount(); 285 | for (int i = 0; i < tabCount; i++) { 286 | final View child = mTabsContainer.getChildAt(i); 287 | final boolean isSelected = (i == index); 288 | child.setSelected(isSelected); 289 | if(isSelected) { 290 | ((TabView) child).getTextView().setTextColor(textSelectedColor); 291 | } else { 292 | ((TabView) child).getTextView().setTextColor(textUnselectColor); 293 | } 294 | } 295 | } 296 | 297 | private void updateTabStyles() { 298 | for (int i = 0; i < tabCount; i++) { 299 | View v = mTabsContainer.getChildAt(i); 300 | v.setBackgroundColor(Color.TRANSPARENT); 301 | } 302 | tabSelect(mPager.getCurrentItem()); 303 | } 304 | 305 | private void scrollToChild(int position, int offset) { 306 | if (tabCount == 0) { 307 | return; 308 | } 309 | 310 | int newScrollX = mTabsContainer.getChildAt(position).getLeft() + offset; 311 | 312 | if (position > 0 || offset > 0) { 313 | newScrollX -= scrollOffset; 314 | } 315 | 316 | if (newScrollX != lastScrollX) { 317 | lastScrollX = newScrollX; 318 | scrollTo(newScrollX, 0); 319 | } 320 | } 321 | 322 | private class PageListener implements OnPageChangeListener { 323 | 324 | @Override 325 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 326 | currentPosition = position; 327 | currentPositionOffset = positionOffset; 328 | 329 | scrollToChild(position, (int) (positionOffset * mTabsContainer.getChildAt(position).getWidth())); 330 | 331 | invalidate(); 332 | 333 | if (mOnPageChangeListener != null) { 334 | mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); 335 | } 336 | } 337 | 338 | @Override 339 | public void onPageScrollStateChanged(int state) { 340 | if (state == ViewPager.SCROLL_STATE_IDLE) { 341 | scrollToChild(mPager.getCurrentItem(), 0); 342 | } 343 | 344 | if (mOnPageChangeListener != null) { 345 | mOnPageChangeListener.onPageScrollStateChanged(state); 346 | } 347 | } 348 | 349 | @Override 350 | public void onPageSelected(int position) { 351 | tabSelect(position); 352 | 353 | if (mOnPageChangeListener != null) { 354 | mOnPageChangeListener.onPageSelected(position); 355 | } 356 | } 357 | } 358 | } -------------------------------------------------------------------------------- /viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/Utils.java: -------------------------------------------------------------------------------- 1 | package me.caiying.library.viewpagerindicator; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created by caiying on 06/02/2016. 7 | */ 8 | public class Utils { 9 | public static int dp2px(Context context, float dpValue) { 10 | final float scale = context.getResources().getDisplayMetrics().density; 11 | return (int) (dpValue * scale + 0.5f); 12 | } 13 | 14 | public static int px2dp(Context context, float pxValue) { 15 | final float scale = context.getResources().getDisplayMetrics().density; 16 | return (int) (pxValue / scale + 0.5f); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /viewpagerindicator/src/main/res/drawable/mark_none_tips.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /viewpagerindicator/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ViewPagerIndicator 3 | 4 | -------------------------------------------------------------------------------- /viewpagerindicator/src/test/java/me/caiying/library/viewpagerindicator/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package me.caiying.library.viewpagerindicator; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /viewpagerindicator/viewpagerindicator.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | --------------------------------------------------------------------------------