├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yhy │ │ └── tabpager │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yhy │ │ │ └── tabpager │ │ │ ├── App.java │ │ │ ├── HybridActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── NavActivity.java │ │ │ ├── RecyclerActivity.java │ │ │ ├── TpgActivity.java │ │ │ ├── TpgCustomActivity.java │ │ │ ├── entity │ │ │ └── User.java │ │ │ ├── pager │ │ │ ├── APager.java │ │ │ ├── BPager.java │ │ │ ├── CPager.java │ │ │ ├── HybridPager.java │ │ │ ├── NavPager.java │ │ │ ├── UserPager.java │ │ │ └── factory │ │ │ │ └── PagerFactory.java │ │ │ ├── utils │ │ │ ├── ImgUrls.java │ │ │ └── ToastUtils.java │ │ │ └── widget │ │ │ └── RecyclerScrollView.java │ └── res │ │ ├── drawable │ │ ├── loading_progress_ring_layer.xml │ │ ├── tab_background_selector.xml │ │ ├── tab_home_selector.xml │ │ ├── tab_loan_selector.xml │ │ ├── tab_mine_selector.xml │ │ └── tab_notice_selector.xml │ │ ├── layout │ │ ├── activity_hybrid.xml │ │ ├── activity_main.xml │ │ ├── activity_nav.xml │ │ ├── activity_recycler.xml │ │ ├── activity_tpg.xml │ │ ├── activity_tpg_custom.xml │ │ ├── item_user_rv.xml │ │ ├── layout_view_empty.xml │ │ ├── layout_view_loading_b.xml │ │ ├── pager_user_rv.xml │ │ └── tab_custom_view.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_progress_loading.png │ │ ├── tab_home_checked.png │ │ ├── tab_home_unchecked.png │ │ ├── tab_loan_checked.png │ │ ├── tab_loan_unchecked.png │ │ ├── tab_mine_checked.png │ │ ├── tab_mine_unchecked.png │ │ ├── tab_notice_checked.png │ │ └── tab_notice_unchecked.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── yhy │ └── tabpager │ └── ExampleUnitTest.java ├── build.gradle ├── doc ├── NavView.md └── TpgView.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── imgs ├── download_qr.png └── screenshot.gif ├── jitpack.yml ├── settings.gradle └── tabnav ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── yhy │ └── tabnav │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── yhy │ │ └── tabnav │ │ ├── adapter │ │ ├── NavAdapter.java │ │ ├── TpgAdapter.java │ │ └── base │ │ │ └── BasePagerAdapter.java │ │ ├── cache │ │ └── PagerCache.java │ │ ├── config │ │ └── PagerConfig.java │ │ ├── dispatch │ │ └── DispatchLoading.java │ │ ├── entity │ │ └── NavTab.java │ │ ├── global │ │ └── TpgConst.java │ │ ├── handler │ │ └── ResultHandler.java │ │ ├── helper │ │ ├── PagerHelper.java │ │ └── TpgHelper.java │ │ ├── interceptor │ │ ├── EmptyInterceptor.java │ │ ├── ErrorInterceptor.java │ │ ├── LoadingInterceptor.java │ │ └── SuccessInterceptor.java │ │ ├── listener │ │ └── OnPageChangedListener.java │ │ ├── pager │ │ └── TpgFragment.java │ │ ├── tpg │ │ ├── Pager.java │ │ ├── PagerFace.java │ │ ├── TabBadge.java │ │ └── Tpg.java │ │ ├── utils │ │ ├── DensityUtils.java │ │ └── ViewUtils.java │ │ └── widget │ │ ├── NavView.java │ │ ├── TpgView.java │ │ └── pager │ │ ├── HighestViewPager.java │ │ └── TpgViewPager.java └── res │ ├── layout │ ├── layout_def_empty.xml │ ├── layout_def_error.xml │ ├── layout_def_loading.xml │ ├── view_nav_tab.xml │ ├── widget_nav.xml │ └── widget_tpg.xml │ ├── mipmap-xxhdpi │ └── ic_expand.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ └── strings.xml └── test └── java └── com └── yhy └── tabnav └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | build/ 7 | /captures 8 | .externalNativeBuild 9 | app/libs/ 10 | tabnav/libs/ 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | namespace 'com.yhy.tabpager' 5 | compileSdk 33 6 | 7 | defaultConfig { 8 | applicationId 'com.yhy.tabpager' 9 | minSdk 24 10 | targetSdk 33 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_17 26 | targetCompatibility JavaVersion.VERSION_17 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation 'androidx.appcompat:appcompat:1.6.1' 32 | implementation 'androidx.recyclerview:recyclerview:1.3.0' 33 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 34 | testImplementation 'junit:junit:4.13.2' 35 | 36 | implementation project(':tabnav') 37 | 38 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4' 39 | implementation 'com.github.bumptech.glide:glide:4.14.2' 40 | annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2' 41 | 42 | androidTestImplementation('androidx.test.espresso:espresso-core:3.4.0-alpha05', { 43 | exclude group: 'com.android.support', module: 'support-annotations' 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\android-sdk-windows/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yhy/tabpager/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.yhy.tabpager", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/App.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import android.app.Application; 4 | 5 | import com.yhy.tabpager.utils.ToastUtils; 6 | 7 | /** 8 | * Created by HongYi Yan on 2017/3/12 0:40. 9 | */ 10 | public class App extends Application { 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | 16 | ToastUtils.init(getApplicationContext()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/HybridActivity.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.yhy.tabnav.adapter.NavAdapter; 6 | import com.yhy.tabnav.config.PagerConfig; 7 | import com.yhy.tabnav.entity.NavTab; 8 | import com.yhy.tabnav.listener.OnPageChangedListener; 9 | import com.yhy.tabnav.tpg.PagerFace; 10 | import com.yhy.tabnav.widget.NavView; 11 | import com.yhy.tabpager.pager.NavPager; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import androidx.appcompat.app.AppCompatActivity; 17 | import androidx.fragment.app.FragmentManager; 18 | 19 | public class HybridActivity extends AppCompatActivity { 20 | 21 | private static final List TAB_LIST = new ArrayList<>(); 22 | 23 | private NavView bvContent; 24 | private PagerConfig mConfig; 25 | private ContentAdapter mAdapter; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_hybrid); 31 | 32 | if (TAB_LIST.size() < 4) { 33 | TAB_LIST.add(new NavTab("首页", R.drawable.tab_home_selector)); 34 | TAB_LIST.add(new NavTab("贷款", R.drawable.tab_loan_selector)); 35 | TAB_LIST.add(new NavTab("通知", R.drawable.tab_notice_selector)); 36 | TAB_LIST.add(new NavTab("我的", R.drawable.tab_mine_selector)); 37 | } 38 | 39 | bvContent = (NavView) findViewById(R.id.bv_content); 40 | 41 | //页面配置参数 42 | mConfig = new PagerConfig(); 43 | mConfig.setEmptyViewLayoutId(R.layout.layout_view_empty, R.id.tv_retry); 44 | 45 | //设置适配器 46 | mAdapter = new ContentAdapter(getSupportFragmentManager(), TAB_LIST, mConfig); 47 | bvContent.setAdapter(mAdapter); 48 | 49 | //徽章测试 50 | bvContent.showCirclePointBadge(0); 51 | bvContent.showTextBadge(1, "2"); 52 | 53 | //页面切换事件测试 54 | bvContent.setOnPageChangedListener(new OnPageChangedListener() { 55 | @Override 56 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 57 | } 58 | 59 | @Override 60 | public void onPageSelected(int position) { 61 | } 62 | 63 | @Override 64 | public void onPageScrollStateChanged(int state) { 65 | } 66 | }); 67 | } 68 | 69 | /** 70 | * 适配器 71 | */ 72 | private class ContentAdapter extends NavAdapter { 73 | public ContentAdapter(FragmentManager fm, List tabList, PagerConfig config) { 74 | super(fm, tabList, config); 75 | } 76 | 77 | @Override 78 | public PagerFace getPager(int position) { 79 | Bundle args = new Bundle(); 80 | args.putBoolean("isHybrid", true); 81 | args.putBoolean("firstPage", position == 0); 82 | PagerFace fragment = new NavPager(); 83 | fragment.getFragment().setArguments(args); 84 | return fragment; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | 15 | findViewById(R.id.tv_tpg).setOnClickListener(v -> to(TpgActivity.class)); 16 | findViewById(R.id.tv_tpg_custom).setOnClickListener(v -> to(TpgCustomActivity.class)); 17 | findViewById(R.id.tv_nav).setOnClickListener(v -> to(NavActivity.class)); 18 | findViewById(R.id.tv_hybrid).setOnClickListener(v -> to(HybridActivity.class)); 19 | findViewById(R.id.tv_recycler).setOnClickListener(v -> to(RecyclerActivity.class)); 20 | } 21 | 22 | private void to(Class activity) { 23 | startActivity(new Intent(this, activity)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/NavActivity.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import android.graphics.BitmapFactory; 4 | import android.os.Bundle; 5 | 6 | import com.yhy.tabnav.adapter.NavAdapter; 7 | import com.yhy.tabnav.config.PagerConfig; 8 | import com.yhy.tabnav.entity.NavTab; 9 | import com.yhy.tabnav.listener.OnPageChangedListener; 10 | import com.yhy.tabnav.tpg.PagerFace; 11 | import com.yhy.tabnav.tpg.TabBadge; 12 | import com.yhy.tabnav.widget.NavView; 13 | import com.yhy.tabpager.pager.NavPager; 14 | import com.yhy.tabpager.utils.ToastUtils; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import androidx.appcompat.app.AppCompatActivity; 20 | import androidx.fragment.app.FragmentManager; 21 | 22 | public class NavActivity extends AppCompatActivity { 23 | 24 | private static final List TAB_LIST = new ArrayList<>(); 25 | 26 | private NavView bvContent; 27 | private PagerConfig mConfig; 28 | private ContentAdapter mAdapter; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_nav); 34 | 35 | if (TAB_LIST.size() < 4) { 36 | TAB_LIST.add(new NavTab("首页", R.drawable.tab_home_selector)); 37 | TAB_LIST.add(new NavTab("贷款", R.drawable.tab_loan_selector)); 38 | TAB_LIST.add(new NavTab("通知", R.drawable.tab_notice_selector)); 39 | TAB_LIST.add(new NavTab("我的", R.drawable.tab_mine_selector)); 40 | } 41 | 42 | bvContent = (NavView) findViewById(R.id.bv_content); 43 | 44 | //页面配置参数 45 | mConfig = new PagerConfig(); 46 | mConfig.setEmptyViewLayoutId(R.layout.layout_view_empty, R.id.tv_retry); 47 | 48 | //设置适配器 49 | mAdapter = new ContentAdapter(getSupportFragmentManager(), TAB_LIST, mConfig); 50 | bvContent.setAdapter(mAdapter); 51 | 52 | //徽章测试 53 | bvContent.showCirclePointBadge(0); 54 | bvContent.showTextBadge(1, "2"); 55 | bvContent.setBadgeDragEnable(1, true); 56 | bvContent.setOnDismissListener(1, new TabBadge.OnDismissBadgeListener() { 57 | @Override 58 | public void onDismiss() { 59 | ToastUtils.shortToast("消失了"); 60 | } 61 | }); 62 | bvContent.showDrawableBadge(3, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); 63 | 64 | //页面切换事件测试 65 | bvContent.setOnPageChangedListener(new OnPageChangedListener() { 66 | @Override 67 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 68 | } 69 | 70 | @Override 71 | public void onPageSelected(int position) { 72 | ToastUtils.shortToast("第" + (position + 1) + "页"); 73 | 74 | bvContent.setNavHeight(position == 1 ? 0 : 48); 75 | } 76 | 77 | @Override 78 | public void onPageScrollStateChanged(int state) { 79 | } 80 | }); 81 | } 82 | 83 | /** 84 | * 适配器 85 | */ 86 | private class ContentAdapter extends NavAdapter { 87 | public ContentAdapter(FragmentManager fm, List tabList, PagerConfig config) { 88 | super(fm, tabList, config); 89 | } 90 | 91 | @Override 92 | public PagerFace getPager(int position) { 93 | Bundle args = new Bundle(); 94 | args.putBoolean("isHybrid", false); 95 | args.putBoolean("firstPage", position == 0); 96 | PagerFace pager = new NavPager(); 97 | // fragment.getFragment().setArguments(args); 98 | pager.setParams(args); 99 | return pager; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/RecyclerActivity.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import android.os.Bundle; 4 | import android.widget.ImageView; 5 | 6 | import androidx.annotation.Nullable; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import androidx.recyclerview.widget.LinearLayoutManager; 9 | import androidx.recyclerview.widget.RecyclerView; 10 | 11 | import com.bumptech.glide.Glide; 12 | import com.chad.library.adapter.base.BaseQuickAdapter; 13 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 14 | import com.yhy.tabnav.adapter.TpgAdapter; 15 | import com.yhy.tabnav.tpg.PagerFace; 16 | import com.yhy.tabnav.widget.TpgView; 17 | import com.yhy.tabpager.entity.User; 18 | import com.yhy.tabpager.pager.UserPager; 19 | import com.yhy.tabpager.utils.ImgUrls; 20 | 21 | import org.jetbrains.annotations.NotNull; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * author : 颜洪毅 28 | * e-mail : yhyzgn@gmail.com 29 | * time : 2017-11-06 9:53 30 | * version: 1.0.0 31 | * desc : 32 | */ 33 | public class RecyclerActivity extends AppCompatActivity { 34 | private final List mUserList = new ArrayList<>(); 35 | private final List mTitleList = new ArrayList<>(); 36 | 37 | private RecyclerView rvContent; 38 | private TpgView tvContent; 39 | 40 | @Override 41 | protected void onCreate(@Nullable Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_recycler); 44 | rvContent = findViewById(R.id.rv_content); 45 | tvContent = findViewById(R.id.tv_content); 46 | 47 | mUserList.add(new User("王生安", ImgUrls.getAImgUrl(), 23, "王生安王生安王生安王生安王生安王生安王生安王生安")); 48 | mUserList.add(new User("夏劲釜", ImgUrls.getAImgUrl(), 53, "夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜")); 49 | mUserList.add(new User("汤丞昱", ImgUrls.getAImgUrl(), 32, "汤丞昱")); 50 | mUserList.add(new User("欧贡界", ImgUrls.getAImgUrl(), 76, "欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界")); 51 | mUserList.add(new User("梁夜翊", ImgUrls.getAImgUrl(), 12, "梁夜翊梁夜翊梁夜翊梁夜翊梁夜翊")); 52 | 53 | rvContent.setLayoutManager(new LinearLayoutManager(this)); 54 | rvContent.setAdapter(new BaseQuickAdapter<>(R.layout.item_user_rv, mUserList) { 55 | @Override 56 | protected void convert(@NotNull BaseViewHolder holder, User item) { 57 | Glide.with(RecyclerActivity.this).load(item.avatar).into((ImageView) holder.getView(R.id.iv_avatar)); 58 | holder.setText(R.id.tv_name, item.name); 59 | holder.setText(R.id.tv_introduction, item.introduction); 60 | } 61 | }); 62 | 63 | mTitleList.add("第一页"); 64 | mTitleList.add("第二页"); 65 | mTitleList.add("第三页"); 66 | 67 | tvContent.setAdapter(new TpgAdapter(getSupportFragmentManager(), mTitleList) { 68 | @Override 69 | public PagerFace getPager(int position) { 70 | return new UserPager(); 71 | } 72 | 73 | @Override 74 | public CharSequence getTitle(int position, String data) { 75 | return data; 76 | } 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/TpgActivity.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import com.yhy.tabnav.adapter.TpgAdapter; 7 | import com.yhy.tabnav.config.PagerConfig; 8 | import com.yhy.tabnav.listener.OnPageChangedListener; 9 | import com.yhy.tabnav.tpg.PagerFace; 10 | import com.yhy.tabnav.widget.TpgView; 11 | import com.yhy.tabpager.pager.factory.PagerFactory; 12 | 13 | import java.util.Arrays; 14 | 15 | import androidx.appcompat.app.AppCompatActivity; 16 | import androidx.fragment.app.FragmentManager; 17 | 18 | public class TpgActivity extends AppCompatActivity { 19 | private static final String[] TABS = {"菜单A", "菜单B菜单B菜单B", "菜单C"}; 20 | 21 | private TpgView tvContent; 22 | //页面配置,只在当前TpgView有效 23 | private PagerConfig mConfig; 24 | private PagersAdapter mAdapter; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_tpg); 30 | 31 | setTpgEmptyPage(); 32 | 33 | tvContent = findViewById(R.id.tv_content); 34 | 35 | initData(); 36 | 37 | initListener(); 38 | } 39 | 40 | private void setTpgEmptyPage() { 41 | mConfig = new PagerConfig(); 42 | mConfig.setEmptyViewLayoutId(R.layout.layout_view_empty, R.id.tv_retry); 43 | } 44 | 45 | private void initData() { 46 | mAdapter = new PagersAdapter(getSupportFragmentManager(), mConfig); 47 | tvContent.setAdapter(mAdapter); 48 | } 49 | 50 | private void initListener() { 51 | //扩展按钮点击事件 52 | tvContent.setOnExpandListener(new TpgView.OnExpandListener() { 53 | @Override 54 | public void onExpand(View view) { 55 | if (null != mAdapter) { 56 | Bundle args = new Bundle(); 57 | args.putString("args", TABS[tvContent.getCurrentPager()]); 58 | mAdapter.reloadDataForCurrentPager(args); 59 | } 60 | } 61 | }); 62 | 63 | //页面滑动事件 64 | tvContent.setOnPageChangedListener(new OnPageChangedListener() { 65 | @Override 66 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 67 | } 68 | 69 | @Override 70 | public void onPageSelected(int position) { 71 | //第二页不可滑动 72 | tvContent.setScrollAble(position != 1); 73 | 74 | //第三页选项卡栏高度为0 75 | if (position == 2) { 76 | tvContent.setTabHeight(0); 77 | } else { 78 | tvContent.setTabHeight(48); 79 | } 80 | } 81 | 82 | @Override 83 | public void onPageScrollStateChanged(int state) { 84 | } 85 | }); 86 | } 87 | 88 | private class PagersAdapter extends TpgAdapter { 89 | 90 | public PagersAdapter(FragmentManager fm, PagerConfig config) { 91 | super(fm, Arrays.asList(TABS), config); 92 | } 93 | 94 | @Override 95 | public PagerFace getPager(int position) { 96 | return PagerFactory.create(position); 97 | } 98 | 99 | @Override 100 | public CharSequence getTitle(int position, String data) { 101 | return data; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/TpgCustomActivity.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.yhy.tabnav.adapter.TpgAdapter; 9 | import com.yhy.tabnav.config.PagerConfig; 10 | import com.yhy.tabnav.listener.OnPageChangedListener; 11 | import com.yhy.tabnav.tpg.PagerFace; 12 | import com.yhy.tabnav.widget.TpgView; 13 | import com.yhy.tabpager.pager.factory.PagerFactory; 14 | 15 | import java.util.Arrays; 16 | 17 | import androidx.appcompat.app.AppCompatActivity; 18 | import androidx.fragment.app.FragmentManager; 19 | 20 | public class TpgCustomActivity extends AppCompatActivity { 21 | private static final String[] TABS = {"菜单A", "菜单B菜单B菜单B", "菜单C"}; 22 | 23 | private TpgView tvContent; 24 | //页面配置,只在当前TpgView有效 25 | private PagerConfig mConfig; 26 | private PagersAdapter mAdapter; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_tpg_custom); 32 | 33 | setTpgEmptyPage(); 34 | 35 | tvContent = findViewById(R.id.tv_content); 36 | 37 | initData(); 38 | 39 | initListener(); 40 | } 41 | 42 | private void setTpgEmptyPage() { 43 | mConfig = new PagerConfig(); 44 | mConfig.setEmptyViewLayoutId(R.layout.layout_view_empty, R.id.tv_retry); 45 | } 46 | 47 | private void initData() { 48 | mAdapter = new PagersAdapter(getSupportFragmentManager(), mConfig); 49 | tvContent.setAdapter(mAdapter); 50 | } 51 | 52 | private void initListener() { 53 | //扩展按钮点击事件 54 | tvContent.setOnExpandListener(new TpgView.OnExpandListener() { 55 | @Override 56 | public void onExpand(View view) { 57 | if (null != mAdapter) { 58 | Bundle args = new Bundle(); 59 | args.putString("args", TABS[tvContent.getCurrentPager()]); 60 | mAdapter.reloadDataForCurrentPager(args); 61 | } 62 | } 63 | }); 64 | 65 | //页面滑动事件 66 | tvContent.setOnPageChangedListener(new OnPageChangedListener() { 67 | @Override 68 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 69 | } 70 | 71 | @Override 72 | public void onPageSelected(int position) { 73 | //第二页不可滑动 74 | tvContent.setScrollAble(position != 1); 75 | 76 | //第三页选项卡栏高度为0 77 | if (position == 2) { 78 | tvContent.setTabHeight(0); 79 | } else { 80 | tvContent.setTabHeight(48); 81 | } 82 | } 83 | 84 | @Override 85 | public void onPageScrollStateChanged(int state) { 86 | } 87 | }); 88 | } 89 | 90 | private class PagersAdapter extends TpgAdapter { 91 | 92 | public PagersAdapter(FragmentManager fm, PagerConfig config) { 93 | super(fm, Arrays.asList(TABS), config); 94 | } 95 | 96 | @Override 97 | public PagerFace getPager(int position) { 98 | return PagerFactory.create(position); 99 | } 100 | 101 | @Override 102 | public View getCustomTabView(int position, String data) { 103 | TextView tv = (TextView) LayoutInflater.from(TpgCustomActivity.this).inflate(R.layout.tab_custom_view, null); 104 | tv.setText(data); 105 | return tv; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.entity; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2017-11-06 10:05 7 | * version: 1.0.0 8 | * desc : 9 | */ 10 | public class User { 11 | 12 | public String name; 13 | public String avatar; 14 | public int age; 15 | public String introduction; 16 | 17 | public User(String name, String avatar, int age, String introduction) { 18 | this.name = name; 19 | this.avatar = avatar; 20 | this.age = age; 21 | this.introduction = introduction; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/pager/APager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.pager; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.os.SystemClock; 6 | import android.view.Gravity; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.yhy.tabnav.interceptor.LoadingInterceptor; 13 | import com.yhy.tabnav.pager.TpgFragment; 14 | import com.yhy.tabpager.utils.ToastUtils; 15 | 16 | import java.util.Random; 17 | 18 | public class APager extends TpgFragment { 19 | 20 | @Override 21 | public LoadingInterceptor getLoadingInterceptor() { 22 | return new LoadingInterceptor() { 23 | @Override 24 | public boolean processAhead() { 25 | ToastUtils.longToast("APager加载中"); 26 | return true; 27 | } 28 | }; 29 | } 30 | 31 | @Override 32 | public View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 33 | TextView tv = new TextView(getContext()); 34 | tv.setText("A页面加载成功"); 35 | tv.setTextColor(Color.RED); 36 | tv.setTextSize(32); 37 | tv.setGravity(Gravity.CENTER); 38 | return tv; 39 | } 40 | 41 | @Override 42 | public void initData() { 43 | getDataFromServer(); 44 | } 45 | 46 | @Override 47 | public void reloadData(Bundle args) { 48 | //子类的具体操作... 49 | String temp = args.getString("args"); 50 | ToastUtils.shortToast(temp + "页面重新加载数据"); 51 | 52 | getDataFromServer(); 53 | } 54 | 55 | private void getDataFromServer() { 56 | final Random random = new Random(); 57 | new Thread() { 58 | @Override 59 | public void run() { 60 | //模拟网络加载延迟 61 | SystemClock.sleep(2000); 62 | 63 | //数据加载结束后,需要手动刷新页面状态 64 | int temp = random.nextInt(3); 65 | switch (temp) { 66 | case 0: 67 | // mRltHandler.tpgSuccess(); 68 | tpgSuccess(); 69 | break; 70 | case 1: 71 | // mRltHandler.tpgError(); 72 | tpgError(); 73 | break; 74 | case 2: 75 | // mRltHandler.tpgEmpty(); 76 | tpgEmpty(); 77 | break; 78 | default: 79 | break; 80 | } 81 | } 82 | }.start(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/pager/BPager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.pager; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.os.SystemClock; 6 | import android.view.Gravity; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.yhy.tabnav.interceptor.LoadingInterceptor; 13 | import com.yhy.tabnav.pager.TpgFragment; 14 | import com.yhy.tabpager.R; 15 | import com.yhy.tabpager.utils.ToastUtils; 16 | 17 | import java.util.Random; 18 | 19 | public class BPager extends TpgFragment { 20 | 21 | @Override 22 | public LoadingInterceptor getLoadingInterceptor() { 23 | return new LoadingInterceptor() { 24 | @Override 25 | public boolean processAhead() { 26 | ToastUtils.longToast("BPager加载中拦截器过不去"); 27 | return false; 28 | } 29 | }; 30 | } 31 | 32 | @Override 33 | public View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 34 | TextView tv = new TextView(getContext()); 35 | tv.setText("B页面加载成功"); 36 | tv.setTextColor(Color.RED); 37 | tv.setTextSize(32); 38 | tv.setGravity(Gravity.CENTER); 39 | return tv; 40 | } 41 | 42 | @Override 43 | public View getLoadingView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 44 | return inflater.inflate(R.layout.layout_view_loading_b, container, false); 45 | } 46 | 47 | @Override 48 | public void initData() { 49 | getDataFromServer(); 50 | } 51 | 52 | @Override 53 | public void reloadData(Bundle args) { 54 | String temp = args.getString("args"); 55 | ToastUtils.shortToast(temp + "页面重新加载数据"); 56 | 57 | getDataFromServer(); 58 | } 59 | 60 | private void getDataFromServer() { 61 | final Random random = new Random(); 62 | new Thread() { 63 | @Override 64 | public void run() { 65 | //模拟网络加载延迟 66 | SystemClock.sleep(2000); 67 | 68 | //数据加载结束后,需要手动刷新页面状态 69 | int temp = random.nextInt(3); 70 | switch (temp) { 71 | case 0: 72 | // mRltHandler.tpgSuccess(); 73 | tpgSuccess(); 74 | break; 75 | case 1: 76 | // mRltHandler.tpgError(); 77 | tpgError(); 78 | break; 79 | case 2: 80 | // mRltHandler.tpgEmpty(); 81 | tpgEmpty(); 82 | break; 83 | default: 84 | break; 85 | } 86 | } 87 | }.start(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/pager/CPager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.pager; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.os.SystemClock; 6 | import android.view.Gravity; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.yhy.tabnav.pager.TpgFragment; 13 | import com.yhy.tabpager.utils.ToastUtils; 14 | 15 | import java.util.Random; 16 | 17 | public class CPager extends TpgFragment { 18 | 19 | @Override 20 | public View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 21 | TextView tv = new TextView(getContext()); 22 | tv.setText("C页面加载成功"); 23 | tv.setTextColor(Color.RED); 24 | tv.setTextSize(32); 25 | tv.setGravity(Gravity.CENTER); 26 | return tv; 27 | } 28 | 29 | @Override 30 | public void initData() { 31 | getDataFromServer(); 32 | } 33 | 34 | @Override 35 | public void reloadData(Bundle args) { 36 | String temp = args.getString("args"); 37 | ToastUtils.shortToast(temp + "页面重新加载数据"); 38 | 39 | getDataFromServer(); 40 | } 41 | 42 | private void getDataFromServer() { 43 | final Random random = new Random(); 44 | new Thread() { 45 | @Override 46 | public void run() { 47 | //模拟网络加载延迟 48 | SystemClock.sleep(2000); 49 | 50 | //数据加载结束后,需要手动刷新页面状态 51 | int temp = random.nextInt(3); 52 | switch (temp) { 53 | case 0: 54 | // mRltHandler.tpgSuccess(); 55 | tpgSuccess(); 56 | break; 57 | case 1: 58 | // mRltHandler.tpgError(); 59 | tpgError(); 60 | break; 61 | case 2: 62 | // mRltHandler.tpgEmpty(); 63 | tpgEmpty(); 64 | break; 65 | default: 66 | break; 67 | } 68 | } 69 | }.start(); 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/pager/HybridPager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.pager; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.os.SystemClock; 7 | import android.view.Gravity; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.TextView; 12 | 13 | import com.yhy.tabnav.pager.TpgFragment; 14 | import com.yhy.tabpager.TpgActivity; 15 | import com.yhy.tabpager.utils.ToastUtils; 16 | 17 | import java.util.Random; 18 | 19 | public class HybridPager extends TpgFragment { 20 | 21 | @Override 22 | public View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 23 | TextView tv = new TextView(getContext()); 24 | tv.setText("嵌套页面加载成功"); 25 | tv.setTextColor(Color.RED); 26 | tv.setTextSize(32); 27 | tv.setGravity(Gravity.CENTER); 28 | 29 | tv.setOnClickListener(new View.OnClickListener() { 30 | @Override 31 | public void onClick(View v) { 32 | Intent intent = new Intent(mActivity, TpgActivity.class); 33 | startActivity(intent); 34 | } 35 | }); 36 | 37 | return tv; 38 | } 39 | 40 | @Override 41 | public void initData() { 42 | getDataFromServer(); 43 | } 44 | 45 | @Override 46 | public void reloadData(Bundle args) { 47 | //调用父类方法才会重新显示加载中页面,否则只是执行重新加载操作,不会显示加载中页面 48 | super.reloadData(args); 49 | 50 | //子类的具体操作... 51 | String temp = args.getString("args"); 52 | ToastUtils.shortToast(temp + "页面重新加载数据"); 53 | 54 | getDataFromServer(); 55 | } 56 | 57 | private void getDataFromServer() { 58 | final Random random = new Random(); 59 | new Thread() { 60 | @Override 61 | public void run() { 62 | //模拟网络加载延迟 63 | SystemClock.sleep(2000); 64 | 65 | //数据加载结束后,需要手动刷新页面状态 66 | int temp = random.nextInt(3); 67 | switch (temp) { 68 | case 0: 69 | // mRltHandler.tpgSuccess(); 70 | tpgSuccess(); 71 | break; 72 | case 1: 73 | // mRltHandler.tpgError(); 74 | tpgError(); 75 | break; 76 | case 2: 77 | // mRltHandler.tpgEmpty(); 78 | tpgEmpty(); 79 | break; 80 | default: 81 | break; 82 | } 83 | } 84 | }.start(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/pager/NavPager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.pager; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.os.SystemClock; 6 | import android.view.Gravity; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.yhy.tabnav.adapter.TpgAdapter; 13 | import com.yhy.tabnav.listener.OnPageChangedListener; 14 | import com.yhy.tabnav.pager.TpgFragment; 15 | import com.yhy.tabnav.tpg.PagerFace; 16 | import com.yhy.tabnav.widget.TpgView; 17 | import com.yhy.tabpager.MainActivity; 18 | import com.yhy.tabpager.R; 19 | import com.yhy.tabpager.utils.ToastUtils; 20 | 21 | import java.util.Arrays; 22 | import java.util.Random; 23 | 24 | import androidx.fragment.app.FragmentManager; 25 | 26 | public class NavPager extends TpgFragment { 27 | private static final String[] TABS = {"菜单A", "菜单B", "菜单C"}; 28 | private TpgView tpgView; 29 | private PagersAdapter mAdapter; 30 | private Bundle mArgs; 31 | private boolean mIsHybrid; 32 | 33 | @Override 34 | public View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 35 | // mArgs = getArguments(); 36 | mArgs = getParams(); 37 | mIsHybrid = mArgs.getBoolean("isHybrid"); 38 | if (mIsHybrid) { 39 | View view = LayoutInflater.from(mActivity).inflate(R.layout.activity_tpg, null); 40 | tpgView = (TpgView) view.findViewById(R.id.tv_content); 41 | return view; 42 | } 43 | 44 | TextView tv = new TextView(getContext()); 45 | tv.setText("Nav页面加载成功"); 46 | tv.setTextColor(Color.RED); 47 | tv.setTextSize(32); 48 | tv.setGravity(Gravity.CENTER); 49 | return tv; 50 | } 51 | 52 | @Override 53 | public void initData() { 54 | final Random random = new Random(); 55 | new Thread() { 56 | @Override 57 | public void run() { 58 | //模拟网络加载延迟 59 | SystemClock.sleep(2000); 60 | 61 | //数据加载结束后,需要手动刷新页面状态 62 | int temp = random.nextInt(3); 63 | switch (temp) { 64 | case 0: 65 | // mRltHandler.tpgSuccess(); 66 | tpgSuccess(); 67 | break; 68 | case 1: 69 | // mRltHandler.tpgError(); 70 | tpgError(); 71 | break; 72 | case 2: 73 | // mRltHandler.tpgEmpty(); 74 | tpgEmpty(); 75 | break; 76 | default: 77 | break; 78 | } 79 | } 80 | }.start(); 81 | 82 | if (mIsHybrid) { 83 | //mAdapter = new PagersAdapter(getFragmentManager()); 84 | /* 85 | //这里需要用getChildFragmentManager() 86 | getChildFragmentManager()是fragment中的方法, 返回的是管理当前fragment内部子fragments的manager. 87 | getFragmentManager()在activity和fragment中都有. 88 | 在activity中, 如果用的是v4 support库, 方法应该用getSupportFragmentManager(), 89 | 返回的是管理activity中fragments的manager. 90 | 在fragment中, 还叫getFragmentManager(), 返回的是把自己加进来的那个manager. 91 | 92 | 也即, 如果fragment在activity中, fragment.getFragmentManager()得到的是activity中管理fragments的那个manager. 93 | 如果fragment是嵌套在另一个fragment中, fragment.getFragmentManager() 94 | 得到的是它的parent的getChildFragmentManager(). 95 | 96 | 总结就是: getFragmentManager()是本级别管理者, getChildFragmentManager()是下一级别管理者. 97 | 这实际上是一个树形管理结构. 98 | */ 99 | mAdapter = new PagersAdapter(getChildFragmentManager()); 100 | tpgView.setAdapter(mAdapter); 101 | } 102 | } 103 | 104 | @Override 105 | public void initListener() { 106 | if (mIsHybrid) { 107 | tpgView.setOnPageChangedListener(new OnPageChangedListener() { 108 | @Override 109 | public void onPageScrolled(int position, float positionOffset, int 110 | positionOffsetPixels) { 111 | } 112 | 113 | @Override 114 | public void onPageSelected(int position) { 115 | ToastUtils.shortToast("position = " + position); 116 | } 117 | 118 | @Override 119 | public void onPageScrollStateChanged(int state) { 120 | } 121 | }); 122 | } 123 | } 124 | 125 | private class PagersAdapter extends TpgAdapter { 126 | 127 | public PagersAdapter(FragmentManager fm) { 128 | super(fm, Arrays.asList(TABS)); 129 | } 130 | 131 | @Override 132 | public PagerFace getPager(int position) { 133 | Bundle args = new Bundle(); 134 | args.putBoolean("firstPage", position == 0); 135 | PagerFace fragment = new HybridPager(); 136 | fragment.getFragment().setArguments(args); 137 | return fragment; 138 | } 139 | 140 | @Override 141 | public CharSequence getTitle(int position, String data) { 142 | return data; 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/pager/UserPager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.pager; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | 9 | import androidx.recyclerview.widget.LinearLayoutManager; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.chad.library.adapter.base.BaseQuickAdapter; 14 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 15 | import com.yhy.tabnav.pager.TpgFragment; 16 | import com.yhy.tabpager.R; 17 | import com.yhy.tabpager.entity.User; 18 | import com.yhy.tabpager.utils.ImgUrls; 19 | 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * author : 颜洪毅 27 | * e-mail : yhyzgn@gmail.com 28 | * time : 2017-11-06 10:29 29 | * version: 1.0.0 30 | * desc : 31 | */ 32 | public class UserPager extends TpgFragment { 33 | private final List mUserList = new ArrayList<>(); 34 | private RecyclerView rvUser; 35 | 36 | @Override 37 | public View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 38 | View view = inflater.inflate(R.layout.pager_user_rv, null, false); 39 | rvUser = view.findViewById(R.id.rv_user); 40 | return view; 41 | } 42 | 43 | @Override 44 | public void initData() { 45 | mUserList.add(new User("潘恩依", ImgUrls.getAImgUrl(), 23, "王生安王生安王生安王生安王生安王生安王生安王生安")); 46 | mUserList.add(new User("王施峪", ImgUrls.getAImgUrl(), 53, "夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜夏劲釜")); 47 | mUserList.add(new User("郭磊留", ImgUrls.getAImgUrl(), 32, "汤丞昱")); 48 | mUserList.add(new User("柯纤翊", ImgUrls.getAImgUrl(), 76, "欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界欧贡界")); 49 | mUserList.add(new User("张昧谡", ImgUrls.getAImgUrl(), 12, "梁夜翊梁夜翊梁夜翊梁夜翊梁夜翊")); 50 | 51 | tpgSuccess(); 52 | 53 | rvUser.setLayoutManager(new LinearLayoutManager(mActivity)); 54 | rvUser.setAdapter(new BaseQuickAdapter<>(R.layout.item_user_rv, mUserList) { 55 | @Override 56 | protected void convert(@NotNull BaseViewHolder holder, User item) { 57 | Glide.with(mActivity).load(item.avatar).into((ImageView) holder.getView(R.id.iv_avatar)); 58 | holder.setText(R.id.tv_name, item.name); 59 | holder.setText(R.id.tv_introduction, item.introduction); 60 | } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/pager/factory/PagerFactory.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.pager.factory; 2 | 3 | import com.yhy.tabnav.tpg.PagerFace; 4 | import com.yhy.tabpager.pager.APager; 5 | import com.yhy.tabpager.pager.BPager; 6 | import com.yhy.tabpager.pager.CPager; 7 | 8 | public class PagerFactory { 9 | 10 | public static PagerFace create(int position) { 11 | PagerFace pager = null; 12 | 13 | switch (position) { 14 | case 0: 15 | pager = new APager(); 16 | break; 17 | case 1: 18 | pager = new BPager(); 19 | break; 20 | case 2: 21 | pager = new CPager(); 22 | break; 23 | default: 24 | break; 25 | } 26 | 27 | return pager; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/utils/ImgUrls.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.utils; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * author : 颜洪毅 7 | * e-mail : yhyzgn@gmail.com 8 | * time : 2017-11-02 15:56 9 | * version: 1.0.0 10 | * desc : 11 | */ 12 | public class ImgUrls { 13 | private static final Random RAND = new Random(); 14 | 15 | public final static String[] ICON_ARR = { 16 | "http://img.youguoquan.com/uploads/magazine/content/4a5a82b75d606b6aa655a3d35cc4992b_magazine_web_m.jpg", 17 | "http://img.youguoquan.com/uploads/magazine/content/ee4fc46fc0ca0b1272d5a9130c045cd5_magazine_web_m.jpg", 18 | "http://img.youguoquan.com/uploads/magazine/content/20d77b6ccdbc0d12c5f42c1bfe51b211_magazine_web_m.jpg", 19 | "http://img.youguoquan.com/uploads/magazine/content/9d8de6725819f37ac326603b671ee788_magazine_web_m.jpg", 20 | "http://img.youguoquan.com/uploads/magazine/content/7ac9376449feabe6bdca62535a037d76_magazine_web_m.jpg", 21 | "http://img.youguoquan.com/uploads/magazine/content/4297924d06bb1699cccf641fa461a3fb_magazine_web_m.jpg", 22 | "http://img.youguoquan.com/uploads/magazine/content/a811c176420a20f8e035fc3679f19a10_magazine_web_m.jpg", 23 | "http://img.youguoquan.com/uploads/magazine/content/f25f13f7b9a1dd3f1f95504060b24a03_magazine_web_m.jpg", 24 | "http://img.youguoquan.com/uploads/magazine/content/d9c23953cf453550e53f62da99021e21_magazine_web_m.jpg", 25 | "http://img.youguoquan.com/uploads/magazine/content/c5fe93b6fdfbd1e44ef93f0260c0ea34_magazine_web_m.jpg", 26 | "http://img.youguoquan.com/uploads/magazine/content/7b2a0fdbb23c9e63586b7ff6798dbebb_magazine_web_m.jpg", 27 | "http://img.youguoquan.com/uploads/magazine/content/78b3d95fcb669210d214202703eb3c82_magazine_web_m.jpg", 28 | "http://img.youguoquan.com/uploads/magazine/content/94504e0e41d1852f5bf9b1da347261e4_magazine_web_m.jpg", 29 | "http://img.youguoquan.com/uploads/magazine/content/c9c47160b46fceab5afd24dea7f216e6_magazine_web_m.jpg", 30 | "http://img.youguoquan.com/uploads/magazine/content/b8c96c13d94405dcce496e2bcd7e67dc_magazine_web_m.jpg", 31 | "http://img.youguoquan.com/uploads/magazine/content/9e3393bdaaf52c49e2837df2bd7973ad_magazine_web_m.jpg", 32 | "http://img.youguoquan.com/uploads/magazine/content/bc00ef1595e75820718c1dad1483c962_magazine_web_m.jpg", 33 | "http://img.youguoquan.com/uploads/magazine/content/3d3d6f738532fd96113898d0f04c1af3_magazine_web_m.jpg", 34 | "http://img.youguoquan.com/uploads/magazine/content/91dfb9f7e10036547b73e3867e8a9ee6_magazine_web_m.jpg", 35 | "http://img.youguoquan.com/uploads/magazine/content/b99de2be8f74c784f5f9bf7b51c85556_magazine_web_m.jpg", 36 | "http://img.youguoquan.com/uploads/magazine/content/c4bad3d7b2cdfbe3575f48ca3893ef34_magazine_web_m.jpg", 37 | "http://img.youguoquan.com/uploads/magazine/content/fd986a6e0d5fa3a4485e5ce28f40b2ad_magazine_web_m.jpg", 38 | "http://img.youguoquan.com/uploads/magazine/content/e1bdef3c69698ba482417e117e590422_magazine_web_m.jpg", 39 | "http://img.youguoquan.com/uploads/magazine/content/a62e045853ac7237c70a1e989caef932_magazine_web_m.jpg" 40 | }; 41 | 42 | public static String getAImgUrl() { 43 | return ICON_ARR[RAND.nextInt(ICON_ARR.length)]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/utils/ToastUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2012-2016 昆明鞋包网科技有限公司 版权所有 3 | */ 4 | 5 | package com.yhy.tabpager.utils; 6 | 7 | import android.content.Context; 8 | import android.widget.Toast; 9 | 10 | /** 11 | * Toast工具类 12 | */ 13 | public class ToastUtils { 14 | private static Context ctx; 15 | 16 | private ToastUtils() { 17 | } 18 | 19 | /** 20 | * 初始化,在Application中 21 | * 22 | * @param ctx 上下文对象 23 | */ 24 | public static void init(Context ctx) { 25 | ToastUtils.ctx = ctx; 26 | } 27 | 28 | /** 29 | * 短时间显示 30 | * 31 | * @param text 提示的内容 32 | */ 33 | public static void shortToast(String text) { 34 | Toast.makeText(ctx, text, Toast.LENGTH_SHORT).show(); 35 | } 36 | 37 | /** 38 | * 长时间显示 39 | * 40 | * @param text 提示的内容 41 | */ 42 | public static void longToast(String text) { 43 | Toast.makeText(ctx, text, Toast.LENGTH_LONG).show(); 44 | } 45 | 46 | /** 47 | * 短时间显示 48 | * 49 | * @param resId 提示的内容资源id 50 | */ 51 | public static void shortToast(int resId) { 52 | Toast.makeText(ctx, ctx.getResources().getString(resId), Toast.LENGTH_SHORT).show(); 53 | } 54 | 55 | /** 56 | * 长时间显示 57 | * 58 | * @param resId 提示的内容资源id 59 | */ 60 | public static void longToast(int resId) { 61 | Toast.makeText(ctx, ctx.getResources().getString(resId), Toast.LENGTH_LONG).show(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/yhy/tabpager/widget/RecyclerScrollView.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.view.ViewConfiguration; 7 | import android.widget.ScrollView; 8 | 9 | /** 10 | * author : 颜洪毅 11 | * e-mail : yhyzgn@gmail.com 12 | * time : 2017-09-21 14:19 13 | * version: 1.0.0 14 | * desc : 15 | */ 16 | public class RecyclerScrollView extends ScrollView { 17 | private int downX; 18 | private int downY; 19 | private int mTouchSlop; 20 | 21 | public RecyclerScrollView(Context context) { 22 | super(context); 23 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 24 | } 25 | 26 | public RecyclerScrollView(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 29 | } 30 | 31 | public RecyclerScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 32 | super(context, attrs, defStyleAttr); 33 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 34 | } 35 | 36 | @Override 37 | public boolean onInterceptTouchEvent(MotionEvent e) { 38 | int action = e.getAction(); 39 | switch (action) { 40 | case MotionEvent.ACTION_DOWN: 41 | downX = (int) e.getRawX(); 42 | downY = (int) e.getRawY(); 43 | break; 44 | case MotionEvent.ACTION_MOVE: 45 | int moveY = (int) e.getRawY(); 46 | if (Math.abs(moveY - downY) > mTouchSlop) { 47 | return true; 48 | } 49 | } 50 | return super.onInterceptTouchEvent(e); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/loading_progress_ring_layer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_background_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_home_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_loan_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_mine_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_notice_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_hybrid.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 29 | 30 | 39 | 40 | 49 | 50 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_nav.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_recycler.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 17 | 18 | 22 | 23 | 32 | 33 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_tpg.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_tpg_custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_user_rv.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 29 | 30 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_view_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_view_loading_b.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pager_user_rv.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tab_custom_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_progress_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/ic_progress_loading.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tab_home_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/tab_home_checked.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tab_home_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/tab_home_unchecked.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tab_loan_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/tab_loan_checked.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tab_loan_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/tab_loan_unchecked.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tab_mine_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/tab_mine_checked.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tab_mine_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/tab_mine_unchecked.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tab_notice_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/tab_notice_checked.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tab_notice_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxhdpi/tab_notice_unchecked.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TabPager 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/yhy/tabpager/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabpager; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '8.0.2' apply false 4 | id 'com.android.library' version '8.0.2' apply false 5 | } 6 | 7 | allprojects { 8 | tasks.withType(JavaCompile) { 9 | options.encoding = "UTF-8" 10 | } 11 | 12 | tasks.withType(Test) { 13 | systemProperty "file.encoding", "UTF-8" 14 | } 15 | } -------------------------------------------------------------------------------- /doc/NavView.md: -------------------------------------------------------------------------------- 1 | # `NavView` 2 | 3 | > 底部导航栏布局控件 4 | > 5 | > 声明:下文中的`TpgFragment`一律指框架提供的`TpgFragment`或者自定义实现`PagerFace`接口的`Fragment`。 6 | 7 | ### 1. 简单用法 8 | 9 | - 第一步,再布局文件中使用控件 10 | 11 | ```xml 12 | 16 | ``` 17 | 18 | - 第二步,设置底部导航元素‘ 19 | 20 | ```java 21 | private static final List TAB_LIST = new ArrayList<>(); 22 | ... 23 | 24 | TAB_LIST.add(new NavTab("首页", R.drawable.tab_home_selector)); 25 | TAB_LIST.add(new NavTab("贷款", R.drawable.tab_loan_selector)); 26 | TAB_LIST.add(new NavTab("通知", R.drawable.tab_notice_selector)); 27 | TAB_LIST.add(new NavTab("我的", R.drawable.tab_mine_selector)); 28 | 29 | ... 30 | ``` 31 | 32 | - 第二步,给`NavView`设置适配器(“页面参数”在扩展中介绍) 33 | 34 | ```java 35 | //获取到布局中的控件 36 | bvContent = (NavView) findViewById(R.id.bv_content); 37 | 38 | //设置适配器 39 | mAdapter = new ContentAdapter(getSupportFragmentManager(), TAB_LIST, mConfig); 40 | bvContent.setAdapter(mAdapter); 41 | 42 | //内部类中定义适配器 43 | private class ContentAdapter extends NavAdapter { 44 | public ContentAdapter(FragmentManager fm, List tabList, PagerConfig config) { 45 | super(fm, tabList, config); 46 | } 47 | 48 | @Override 49 | public TpgFragment getPager(int position) { 50 | Bundle args = new Bundle(); 51 | args.putBoolean("isHybrid", false); 52 | args.putBoolean("firstPage", position == 0); 53 | TpgFragment fragment = new NavPager(); 54 | fragment.setArguments(args); 55 | return fragment; 56 | } 57 | } 58 | ``` 59 | 60 | - 第四步,在具体页面类必须继承自`TpgFragment`,实现抽象方法`getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)`,并返回加载成功时的页面(以`NavPager.java`为例) 61 | 62 | ```java 63 | public class NavPager extends TpgFragment { 64 | ... 65 | 66 | @Override 67 | protected View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 68 | TextView tv = new TextView(getContext()); 69 | tv.setText("Nav页面加载成功"); 70 | tv.setTextColor(Color.RED); 71 | tv.setTextSize(32); 72 | tv.setGravity(Gravity.CENTER); 73 | //返回加载成功时的页面 74 | return tv; 75 | } 76 | 77 | ... 78 | } 79 | ``` 80 | 81 | - 第五步,实现抽象方法`initData()`,将数据请求部分的操作放在该方法中,便于`NavView`获取到数据加载结果状态。但是加载结果必须要用父类的成员变量`mRltHandler`来操作... 82 | 83 | ```java 84 | @Override 85 | protected void initData() { 86 | //请求服务器数据 87 | getDataFromServer(); 88 | } 89 | 90 | @Override 91 | protected void initListener(){ 92 | //初始化一些事件监听 93 | } 94 | 95 | //模拟请求服务器数据过程 96 | private void getDataFromServer() { 97 | //用来产生一个随机的状态 98 | final Random random = new Random(); 99 | new Thread() { 100 | @Override 101 | public void run() { 102 | //模拟网络加载延迟 103 | SystemClock.sleep(3000); 104 | 105 | //数据加载结束后,需要手动刷新页面状态 106 | int temp = random.nextInt(3); 107 | switch (temp) { 108 | case 0: 109 | //如果加载成功 110 | tpgSuccess(); 111 | break; 112 | case 1: 113 | //加载失败 114 | tpgError(); 115 | break; 116 | case 2: 117 | //数据为空 118 | tpgEmpty(); 119 | break; 120 | default: 121 | break; 122 | } 123 | } 124 | }.start(); 125 | } 126 | ``` 127 | 128 | 以上就是简单用法的详细步骤 :joy::joy: 129 | 130 | ------ 131 | 132 | ### 3. 扩展 133 | 134 | - 自定义属性 135 | 136 | > 在布局文件中自行定义 137 | 138 | | 属性名 | 属性说明 | 默认值 | 139 | | :----------------------- | :------------------------------- | :----------- | 140 | | `nav_bg_color` | 导航栏背景色 | 透明 | 141 | | `nav_height` | 导航栏高度 | `48dp` | 142 | | `nav_bg_img` | 导航栏背景图片 | 无 | 143 | | `nav_text_default_color` | 普通状态下选项字体颜色 | `#000000` | 144 | | `nav_text_checked_color` | 选中状态下选项字体颜色 | `#000000` | 145 | | `nav_bg_checked_color` | 选中状态下选项背景颜色 | 透明 | 146 | | `nav_bg_checked_img` | 选中状态下选项背景图片 | 无 | 147 | | `nav_divider_line_color` | 导航栏与内容页面之间的分割线颜色 | 透明,不显示 | 148 | | `nav_scroll_able` | 是否可滑动 | `true` | 149 | | `nav_badge_text_color` | 徽章字体颜色 | `#ffffffff` | 150 | | `nav_badge_bg_color` | 徽章背景颜色 | `#ffff2200` | 151 | | `nav_badge_drag_enable` | 徽章是否可拖拽 | `false` | 152 | 153 | - 页面配置参数(上文提到过) 154 | 155 | > 框架已提供默认的加载中页面、错误页面和空数据页面,但是可能这些都不太适合你,你可以通过该参数配置自己想要的这些页面。该参数只在当前`TpgView`中有效。 156 | > 157 | > 注意:该参数必须在设置适配器前配置,通过适配器构造方法设置。 158 | 159 | ```java 160 | //页面配置,只在当前TpgView有效 161 | private PagerConfig mConfig; 162 | 163 | //配置页面参数 164 | mConfig = new PagerConfig(); 165 | //设置空页面的资源id和重试按钮资源id 166 | mConfig.setEmptyViewResId(R.layout.layout_view_empty, R.id.tv_retry); 167 | 168 | //设置适配器,传入页面配置参数 169 | mAdapter = new PagersAdapter(getSupportFragmentManager(), mConfig); 170 | tvContent.setAdapter(mAdapter); 171 | ``` 172 | 173 | - 单独页面的页面配置 174 | 175 | > 不仅可以配置`TpgView`范围的页面配置,也可以单独为某一个页面配置相关页面,只需要重写父类的相应方法,并返回页面即可。以配置某个页面的加载中页面为例。 176 | 177 | ```java 178 | @Override 179 | protected View getLoadingView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 180 | //只属于B页面的加载中页面 181 | return inflater.inflate(R.layout.layout_view_loading_b, container, false); 182 | } 183 | ``` 184 | 185 | 186 | - 设置未读消息显示及其拖拽消失 187 | 188 | ```java 189 | bvContent.showCirclePointBadge(0); 190 | bvContent.showTextBadge(1, "2"); 191 | bvContent.setOnDismissListener(1, new BadgeInterface.OnDismissBadgeListener() { 192 | @Override 193 | public void onDismiss() { 194 | ToastUtils.shortToast("消失了"); 195 | } 196 | }); 197 | 198 | ... 199 | //详细方法请参考文章末尾 200 | ``` 201 | 202 | - 重试加载 203 | 204 | > 框架提供的默认页面已经处理了重试加载功能,但是如果你是自定义的页面(通过以上两种方法),可能你也需要重试加载数据功能,比如“点击按钮重试”之类的功能。只需要在具体需要重试加载的地方调用方法`shouldLoadData()`即可。 205 | 206 | ```java 207 | retryBtn.setOnClickListener(new View.OnClickListener() { 208 | @Override 209 | public void onClick(View view) { 210 | //重试加载数据 211 | shouldLoadData(); 212 | } 213 | }); 214 | ``` 215 | 216 | - 对外事件接口 217 | 218 | > 已将`ViewPager`的页面滑动事件和右上角扩展按钮的点击事件提供对外接口 219 | 220 | ```java 221 | //扩展按钮点击事件 222 | tvContent.setOnExpandListener(new TpgView.OnExpandListener() { 223 | @Override 224 | public void onExpand(View view) { 225 | } 226 | }); 227 | 228 | //页面滑动事件 229 | tvContent.setOnPageChangedListener(new TpgView.OnPageChangedListener() { 230 | @Override 231 | public void onPageScrolled(int position, float positionOffset, int 232 | positionOffsetPixels) { 233 | } 234 | 235 | @Override 236 | public void onPageSelected(int position) { 237 | } 238 | 239 | @Override 240 | public void onPageScrollStateChanged(int state) { 241 | } 242 | }); 243 | ``` 244 | 245 | - 重新加载某页数据 246 | 247 | > 也许这个功能是没用的:flushed::flushed:为什么这么说呢?如果在某个页面中,你需要重新加载数据,可以直接调用你加载数据的方法即可,比如`getDataFromServer()`方法,根本不需要框架提供的重新加载数据方法。不过,如果想要在这些页面之外重新加载某个页面的数据的话(比如点击了右上角扩的展按钮之后,一般是要选一些查询条件,然后在重新查询当前页面的数据),就不太好办了:sob::sob:因为你需要调用到具体页面的`getDataFromServer()`方法,而你能想到的方法应该是把所有页面缓存到一个集合中,需要重新加载某页数据时,再取出相应页面,然后调用重新加载方法。当然,这也不失为一种好方法,不过,个人觉得框架中也应该提供默认的重新加载数据的方法,于是才有了该功能。 248 | > 249 | > 如何实现呢?? 250 | > 251 | > 只需要在具体页面中重写`reloadDate(Bundle args)`方法,实现重新加载数据的逻辑,然后在需要出发重新加载操作的地方调用以下方法即可。 252 | > 253 | > 重新加载某个页面的数据:`reloadDataForPager(int index, Bundle args)` 254 | > 255 | > ​ 参数:`index` 当前页面索引 256 | > 257 | > ​ `args` 重新加载时,可能需要的参数 258 | > 259 | > 重新加载当前页面数据:`reloadDataForCurrentPager(Bundle args)` 260 | > 261 | > ​ 参数: `args` 重新加载时,可能需要的参数 262 | 263 | ```java 264 | //在某个页面内时,重写父类方法即可 265 | @Override 266 | public void reloadDate(Bundle args) { 267 | String temp = args.getString("args"); 268 | ToastUtils.shortToast(temp + "页面重新加载数据"); 269 | 270 | //请求数据 271 | getDataFromServer(); 272 | } 273 | 274 | //在页面以外时,通过适配器调用重新加载数据方法 275 | if (null != mAdapter) { 276 | //通过适配器重新加载当前页面的数据 277 | Bundle args = new Bundle(); 278 | args.putString("args", TABS[tvContent.getCurrentPager()]); 279 | mAdapter.reloadDataForCurrentPager(args); 280 | } 281 | ``` 282 | 283 | - 未读消息徽章控件显示及其详细方法的接口 284 | 285 | ```java 286 | public interface BadgeInterface { 287 | 288 | /** 289 | * 显示圆点徽章 290 | * 291 | * @param index Tab的索引 292 | */ 293 | void showCirclePointBadge(int index); 294 | 295 | /** 296 | * 显示文字徽章 297 | * 298 | * @param index Tab的索引 299 | * @param badgeText 显示的文字 300 | */ 301 | void showTextBadge(int index, String badgeText); 302 | 303 | /** 304 | * 隐藏徽章 305 | * 306 | * @param index Tab的索引 307 | */ 308 | void dismissBadge(int index); 309 | 310 | /** 311 | * 显示图像徽章 312 | * 313 | * @param index Tab的索引 314 | * @param bitmap 图标 315 | */ 316 | void showDrawableBadge(int index, Bitmap bitmap); 317 | 318 | /** 319 | * 是否显示徽章 320 | * 321 | * @param index Tab的索引 322 | * @return 是否显示徽章 323 | */ 324 | boolean isShowBadge(int index); 325 | 326 | /** 327 | * 徽章消失的回调方法 328 | * 329 | * @param index Tab的索引 330 | * @param listener 回调事件 331 | */ 332 | void setOnDismissListener(int index, OnDismissBadgeListener listener); 333 | 334 | /** 335 | * 徽章消失事件回调接口 336 | */ 337 | interface OnDismissBadgeListener { 338 | void onDismiss(); 339 | } 340 | } 341 | ``` 342 | 343 | ------------ 344 | 345 | -------------------------------------------------------------------------------- /doc/TpgView.md: -------------------------------------------------------------------------------- 1 | # `TpgView` 2 | 3 | > 顶部选项卡布局控件 4 | > 5 | > 声明:下文中的`TpgFragment`一律指框架提供的`TpgFragment`或者自定义实现`PagerFace`接口的`Fragment`。 6 | 7 | ### 1. 简单用法 8 | 9 | - 第一步,再布局文件中使用控件 10 | 11 | ```xml 12 | 16 | ``` 17 | 18 | - 第二步,给`TpgView`设置适配器(“页面参数”在扩展中介绍) 19 | 20 | - `Activity`中(必须是`FragmentActivity`的子类,才能获取到`FragmentManager`实例) 21 | 22 | ```java 23 | private static final String[] TABS = {"菜单A", "菜单B", "菜单C", "菜单D", "菜单E", "菜单F", "菜单G"}; 24 | 25 | //获取到布局中的控件 26 | tvContent = (TpgView) findViewById(R.id.tv_content); 27 | 28 | //设置适配器 29 | mAdapter = new PagersAdapter(getSupportFragmentManager()); 30 | tvContent.setAdapter(mAdapter); 31 | 32 | //内部类中定义适配器 33 | private class PagersAdapter extends TpgAdapter { 34 | 35 | //没有页面配置参数时使用该构造函数 36 | public PagersAdapter(FragmentManager fm) { 37 | super(fm); 38 | } 39 | 40 | //使用页面配置参数时使用该构造函数 41 | public PagersAdapter(FragmentManager fm, PagerConfig config) { 42 | super(fm, config); 43 | } 44 | 45 | @Override 46 | public TpgFragment getPager(int position) { 47 | return PagerFactory.create(position); 48 | } 49 | } 50 | ``` 51 | 52 | - `PagerFactory`中 53 | 54 | ```java 55 | public static TpgFragment create(int position) { 56 | TpgFragment pager = null; 57 | 58 | switch (position) { 59 | case 0: 60 | pager = new APager(); 61 | break; 62 | case 1: 63 | pager = new BPager(); 64 | break; 65 | case 2: 66 | pager = new CPager(); 67 | break; 68 | case 3: 69 | pager = new DPager(); 70 | break; 71 | case 4: 72 | pager = new EPager(); 73 | break; 74 | case 5: 75 | pager = new FPager(); 76 | break; 77 | case 6: 78 | pager = new GPager(); 79 | break; 80 | case 7: 81 | pager = new HPager(); 82 | break; 83 | default: 84 | break; 85 | } 86 | 87 | return pager; 88 | } 89 | ``` 90 | 91 | - 第四步,在具体页面类必须继承自`TpgFragment`,实现抽象方法`getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)`,并返回加载成功时的页面(以`APager.java`为例) 92 | 93 | ```java 94 | public class APager extends TpgFragment { 95 | ... 96 | 97 | @Override 98 | protected View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 99 | TextView tv = new TextView(getContext()); 100 | tv.setText("A页面加载成功"); 101 | tv.setTextColor(Color.RED); 102 | tv.setTextSize(32); 103 | tv.setGravity(Gravity.CENTER); 104 | //返回加载成功时的页面 105 | return tv; 106 | } 107 | 108 | ... 109 | } 110 | ``` 111 | 112 | - 第五步,实现抽象方法`initData()`,将数据请求部分的操作放在该方法中,便于`TpgView`获取到数据加载结果状态。但是加载结果必须要用父类的成员变量`mRltHandler`来操作... 113 | 114 | ```java 115 | @Override 116 | protected void initData() { 117 | //请求服务器数据 118 | getDataFromServer(); 119 | } 120 | 121 | @Override 122 | protected void initListener(){ 123 | //初始化一些事件监听 124 | } 125 | 126 | //模拟请求服务器数据过程 127 | private void getDataFromServer() { 128 | //用来产生一个随机的状态 129 | final Random random = new Random(); 130 | new Thread() { 131 | @Override 132 | public void run() { 133 | //模拟网络加载延迟 134 | SystemClock.sleep(3000); 135 | 136 | //数据加载结束后,需要手动刷新页面状态 137 | int temp = random.nextInt(3); 138 | switch (temp) { 139 | case 0: 140 | //如果加载成功 141 | tpgSuccess(); 142 | break; 143 | case 1: 144 | //加载失败 145 | tpgError(); 146 | break; 147 | case 2: 148 | //数据为空 149 | tpgEmpty(); 150 | break; 151 | default: 152 | break; 153 | } 154 | } 155 | }.start(); 156 | } 157 | ``` 158 | 159 | 以上就是简单用法的详细步骤 :joy::joy: 160 | 161 | ------ 162 | 163 | ### 3. 扩展 164 | 165 | - 自定义属性 166 | 167 | > 在布局文件中自行定义 168 | 169 | | 属性名 | 属性说明 | 默认值 | 170 | | :------------------------ | :------------------------------- | :---------------- | 171 | | `tab_height` | `Tab`栏高度 | `48dp` | 172 | | `tab_bg_color` | `Tab`栏的背景颜色 | 透明 | 173 | | `tab_text_normal_color` | 普通状态下选项卡字体颜色 | `#aaff4400` | 174 | | `tab_text_selected_color` | 选中状态下选项卡字体颜色 | `#ffff2200` | 175 | | `tab_indicator_color` | 选项指示条的颜色 | `#ffff2200` | 176 | | `tab_indicator_height` | 选项指示条的高度 | `3dp` | 177 | | `tab_mode` | `TabLayout`的`TabMode` | `MODE_SCROLLABLE` | 178 | | `tab_gravity` | `TabLayout`的`TabGravity` | `GRAVITY_FILL` | 179 | | `text_visible` | 是否显示`Tab`栏`TextView` | `GONE` | 180 | | `text_text` | `Tab`栏`TextView`内容 | —— | 181 | | `text_color` | `Tab`栏`TextView`字体颜色 | `#aaff4400` | 182 | | `text_size` | `Tab`栏`TextView`字体大小 | `14sp` | 183 | | `text_margin_left` | `Tab`栏`TextView`左侧边距 | `8dp` | 184 | | `text_margin_right` | `Tab`栏`TextView`右侧边距 | `8dp` | 185 | | `expand_visible` | 是否显示可扩展图标 | `GONE` | 186 | | `expand_icon` | 可扩展图标资源`id` | —— | 187 | | `tab_scroll_able` | 是否可滑动 | `true` | 188 | | `tab_background` | `tab`背景图资源`id` | —— | 189 | | `tab_divider_line_color` | 菜单栏与内容页面之间的分割线颜色 | 透明,不显示 | 190 | 191 | - 页面配置参数(上文提到过) 192 | 193 | > 框架已提供默认的加载中页面、错误页面和空数据页面,但是可能这些都不太适合你,你可以通过该参数配置自己想要的这些页面。该参数只在当前`TpgView`中有效。 194 | > 195 | > 注意:该参数必须在设置适配器前配置,通过适配器构造方法设置。 196 | 197 | ```java 198 | //页面配置,只在当前TpgView有效 199 | private PagerConfig mConfig; 200 | 201 | //配置页面参数 202 | mConfig = new PagerConfig(); 203 | //设置空页面的资源id和重试按钮资源id 204 | mConfig.setEmptyViewResId(R.layout.layout_view_empty, R.id.tv_retry); 205 | 206 | //设置适配器,传入页面配置参数 207 | mAdapter = new PagersAdapter(getSupportFragmentManager(), mConfig); 208 | tvContent.setAdapter(mAdapter); 209 | ``` 210 | 211 | - 单独页面的页面配置 212 | 213 | > 不仅可以配置`TpgView`范围的页面配置,也可以单独为某一个页面配置相关页面,只需要重写父类的相应方法,并返回页面即可。以配置某个页面的加载中页面为例。 214 | 215 | ```java 216 | @Override 217 | protected View getLoadingView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 218 | //只属于B页面的加载中页面 219 | return inflater.inflate(R.layout.layout_view_loading_b, container, false); 220 | } 221 | ``` 222 | 223 | 224 | - 重试加载 225 | 226 | > 框架提供的默认页面已经处理了重试加载功能,但是如果你是自定义的页面(通过以上两种方法),可能你也需要重试加载数据功能,比如“点击按钮重试”之类的功能。只需要在具体需要重试加载的地方调用方法`shouldLoadData()`即可。 227 | 228 | ```java 229 | retryBtn.setOnClickListener(new View.OnClickListener() { 230 | @Override 231 | public void onClick(View view) { 232 | //重试加载数据 233 | shouldLoadData(); 234 | } 235 | }); 236 | ``` 237 | 238 | - 对外事件接口 239 | 240 | > 已将`ViewPager`的页面滑动事件和右上角扩展按钮的点击事件提供对外接口 241 | 242 | ```java 243 | //扩展按钮点击事件 244 | tvContent.setOnExpandListener(new TpgView.OnExpandListener() { 245 | @Override 246 | public void onExpand(View view) { 247 | } 248 | }); 249 | 250 | //页面滑动事件 251 | tvContent.setOnPageChangedListener(new TpgView.OnPageChangedListener() { 252 | @Override 253 | public void onPageScrolled(int position, float positionOffset, int 254 | positionOffsetPixels) { 255 | } 256 | 257 | @Override 258 | public void onPageSelected(int position) { 259 | } 260 | 261 | @Override 262 | public void onPageScrollStateChanged(int state) { 263 | } 264 | }); 265 | ``` 266 | 267 | - 重新加载某页数据 268 | 269 | > 也许这个功能是没用的:flushed::flushed:为什么这么说呢?如果在某个页面中,你需要重新加载数据,可以直接调用你加载数据的方法即可,比如`getDataFromServer()`方法,根本不需要框架提供的重新加载数据方法。不过,如果想要在这些页面之外重新加载某个页面的数据的话(比如点击了右上角扩的展按钮之后,一般是要选一些查询条件,然后在重新查询当前页面的数据),就不太好办了:sob::sob:因为你需要调用到具体页面的`getDataFromServer()`方法,而你能想到的方法应该是把所有页面缓存到一个集合中,需要重新加载某页数据时,再取出相应页面,然后调用重新加载方法。当然,这也不失为一种好方法,不过,个人觉得框架中也应该提供默认的重新加载数据的方法,于是才有了该功能。 270 | > 271 | > 如何实现呢?? 272 | > 273 | > 只需要在具体页面中重写`reloadDate(Bundle args)`方法,实现重新加载数据的逻辑,然后在需要出发重新加载操作的地方调用以下方法即可。 274 | > 275 | > 重新加载某个页面的数据:`reloadDataForPager(int index, Bundle args)` 276 | > 277 | > ​ 参数:`index` 当前页面索引 278 | > 279 | > ​ `args` 重新加载时,可能需要的参数 280 | > 281 | > 重新加载当前页面数据:`reloadDataForCurrentPager(Bundle args)` 282 | > 283 | > ​ 参数: `args` 重新加载时,可能需要的参数 284 | 285 | ```java 286 | //在某个页面内时,重写父类方法即可 287 | @Override 288 | public void reloadDate(Bundle args) { 289 | String temp = args.getString("args"); 290 | ToastUtils.shortToast(temp + "页面重新加载数据"); 291 | 292 | //请求数据 293 | getDataFromServer(); 294 | } 295 | 296 | //在页面以外时,通过适配器调用重新加载数据方法 297 | if (null != mAdapter) { 298 | //通过适配器重新加载当前页面的数据 299 | Bundle args = new Bundle(); 300 | args.putString("args", TABS[tvContent.getCurrentPager()]); 301 | mAdapter.reloadDataForCurrentPager(args); 302 | } 303 | ``` 304 | 305 | ------ 306 | 307 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 30 22:19:51 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /imgs/download_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/imgs/download_qr.png -------------------------------------------------------------------------------- /imgs/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/imgs/screenshot.gif -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - source "$HOME/.sdkman/bin/sdkman-init.sh" 3 | - sdk update 4 | - sdk install java 20.0.1-open 5 | - sdk use java 20.0.1-open -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | maven { url 'https://jitpack.io' } 5 | maven { url "https://maven.aliyun.com/repository/public" } 6 | mavenCentral() 7 | google() 8 | } 9 | } 10 | 11 | dependencyResolutionManagement { 12 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 13 | repositories { 14 | mavenLocal() 15 | maven { url 'https://jitpack.io' } 16 | maven { url "https://maven.aliyun.com/repository/public" } 17 | maven { url "https://maven.aliyun.com/repository/gradle-plugin" } 18 | mavenCentral() 19 | google() 20 | gradlePluginPortal() 21 | } 22 | } 23 | 24 | 25 | rootProject.name = "TabPager" 26 | include ':app', ':tabnav' 27 | -------------------------------------------------------------------------------- /tabnav/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /tabnav/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'maven-publish' 4 | } 5 | 6 | group = 'com.github.yhyzgn.TabPager' 7 | 8 | android { 9 | namespace 'com.yhy.tabnav' 10 | compileSdk 33 11 | 12 | defaultConfig { 13 | minSdk 23 14 | targetSdk 33 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_17 28 | targetCompatibility JavaVersion.VERSION_17 29 | } 30 | 31 | publishing { 32 | singleVariant("release") 33 | } 34 | 35 | lint { 36 | abortOnError false 37 | checkReleaseBuilds false 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation 'androidx.appcompat:appcompat:1.6.1' 43 | api 'com.github.ybq:Android-SpinKit:1.4.0' 44 | api 'com.google.android.material:material:1.9.0' 45 | api 'com.github.yhyzgn.Badge:badge:1.0.5' 46 | 47 | annotationProcessor 'com.github.yhyzgn.Badge:badge-compiler:1.0.5' 48 | 49 | testImplementation 'junit:junit:4.13.2' 50 | androidTestImplementation('androidx.test.espresso:espresso-core:3.4.0-alpha05', { 51 | exclude group: 'com.android.support', module: 'support-annotations' 52 | }) 53 | } 54 | 55 | tasks.withType(JavaCompile) { 56 | options.encoding = "UTF-8" 57 | } 58 | 59 | task generateSourcesJar(type: Jar) { 60 | from android.sourceSets.main 61 | archiveClassifier = 'sources' 62 | } 63 | 64 | publishing { 65 | publications { 66 | release(MavenPublication) { 67 | groupId = "${group}" 68 | artifactId = "${project.name}" 69 | afterEvaluate { 70 | from components.release 71 | } 72 | } 73 | } 74 | // 发布的仓库配置 75 | repositories { 76 | mavenLocal() 77 | maven { 78 | url "https://jitpack.io" 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /tabnav/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 D:\android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /tabnav/src/androidTest/java/com/yhy/tabnav/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.yhy.tpg.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tabnav/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/adapter/NavAdapter.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.adapter; 2 | 3 | import com.yhy.tabnav.adapter.base.BasePagerAdapter; 4 | import com.yhy.tabnav.config.PagerConfig; 5 | import com.yhy.tabnav.entity.NavTab; 6 | import com.yhy.tabnav.widget.NavView; 7 | 8 | import java.util.List; 9 | 10 | import androidx.fragment.app.FragmentManager; 11 | 12 | /** 13 | * author : 颜洪毅 14 | * e-mail : yhyzgn@gmail.com 15 | * time : 2017-09-14 21:06 16 | * version: 1.0.0 17 | * desc : NavView适配器 18 | */ 19 | public abstract class NavAdapter extends BasePagerAdapter { 20 | // NavView底部子项模型集合 21 | private List mTabList; 22 | 23 | /** 24 | * 创建适配器 25 | * 26 | * @param fm FragmentManager 27 | * @param tabList NavView底部子项模型集合 28 | */ 29 | public NavAdapter(FragmentManager fm, List tabList) { 30 | this(fm, tabList, null); 31 | } 32 | 33 | /** 34 | * 创建适配器 35 | * 36 | * @param fm FragmentManager 37 | * @param tabList NavView底部子项模型集合 38 | * @param config 页面配置参数 39 | */ 40 | public NavAdapter(FragmentManager fm, List tabList, PagerConfig config) { 41 | super(fm, config); 42 | if (null == tabList) { 43 | throw new IllegalArgumentException("The argument tabList can not be null"); 44 | } 45 | mTabList = tabList; 46 | } 47 | 48 | /** 49 | * 获取页面数量 50 | * 51 | * @return 页面数量 52 | */ 53 | @Override 54 | public final int getCount() { 55 | return null == mTabList ? 0 : mTabList.size(); 56 | } 57 | 58 | /** 59 | * 获取页面标题 60 | * 61 | * @param position 页面索引 62 | * @return 页面标题 63 | */ 64 | @Override 65 | public final CharSequence getPageTitle(int position) { 66 | return mTabList.get(position).tabText; 67 | } 68 | 69 | /** 70 | * 获取每个选项图标 71 | * 72 | * @param position 页面索引 73 | * @return 选项图标 74 | */ 75 | public final int getTabIconId(int position) { 76 | return mTabList.get(position).tabIconId; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/adapter/TpgAdapter.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.adapter; 2 | 3 | import android.view.View; 4 | 5 | import androidx.fragment.app.FragmentManager; 6 | 7 | import com.yhy.tabnav.adapter.base.BasePagerAdapter; 8 | import com.yhy.tabnav.config.PagerConfig; 9 | import com.yhy.tabnav.widget.TpgView; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * author : 颜洪毅 15 | * e-mail : yhyzgn@gmail.com 16 | * time : 2017-09-14 21:06 17 | * version: 1.0.0 18 | * desc : TpgView适配器 19 | */ 20 | public abstract class TpgAdapter extends BasePagerAdapter { 21 | // Tab标题名称集合 22 | protected List mTabList; 23 | 24 | /** 25 | * 创建适配器 26 | * 27 | * @param fm FragmentManager 28 | * @param tabList Tab标题名称集合 29 | */ 30 | public TpgAdapter(FragmentManager fm, List tabList) { 31 | this(fm, tabList, null); 32 | } 33 | 34 | /** 35 | * 创建适配器 36 | * 37 | * @param fm FragmentManager 38 | * @param tabList Tab标题名称集合 39 | * @param config 页面配置参数 40 | */ 41 | public TpgAdapter(FragmentManager fm, List tabList, PagerConfig config) { 42 | super(fm, config); 43 | mTabList = tabList; 44 | } 45 | 46 | /** 47 | * 获取标题 48 | * 49 | * @param position 当前索引 50 | * @return 标题 51 | */ 52 | @Override 53 | public final CharSequence getPageTitle(int position) { 54 | return getTitle(position, mTabList.get(position)); 55 | } 56 | 57 | /** 58 | * 从子类中获取标题 59 | * 60 | * @param position 当前索引 61 | * @param data tab项对象 62 | * @return 标题 63 | */ 64 | public CharSequence getTitle(int position, T data) { 65 | return null; 66 | } 67 | 68 | /** 69 | * 获取自定义Tab 70 | * 71 | * @param position 当前索引 72 | * @param data tab项对象 73 | * @return Tab 74 | */ 75 | public View getCustomTabView(int position, T data) { 76 | return null; 77 | } 78 | 79 | /** 80 | * 获取Tab数据 81 | * 82 | * @param position 当前索引 83 | * @return tab数据 84 | */ 85 | public final T getTab(int position) { 86 | return mTabList.get(position); 87 | } 88 | 89 | /** 90 | * 获取页面数量 91 | * 92 | * @return 页面数量 93 | */ 94 | @Override 95 | public final int getCount() { 96 | return null == mTabList ? 0 : mTabList.size(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/adapter/base/BasePagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.adapter.base; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.yhy.tabnav.cache.PagerCache; 6 | import com.yhy.tabnav.config.PagerConfig; 7 | import com.yhy.tabnav.tpg.PagerFace; 8 | import com.yhy.tabnav.tpg.Tpg; 9 | 10 | import androidx.fragment.app.Fragment; 11 | import androidx.fragment.app.FragmentManager; 12 | import androidx.fragment.app.FragmentPagerAdapter; 13 | 14 | /** 15 | * author : 颜洪毅 16 | * e-mail : yhyzgn@gmail.com 17 | * time : 2017-09-14 21:06 18 | * version: 1.0.0 19 | * desc : 抽象适配器,实现一些基础方法,定义一些抽象方法让子类适配器实现 20 | */ 21 | public abstract class BasePagerAdapter extends FragmentPagerAdapter { 22 | private PagerConfig mConfig; 23 | private PagerCache mCache; 24 | private T view; 25 | 26 | /** 27 | * 创建适配器 28 | * 29 | * @param fm FragmentManager 30 | */ 31 | public BasePagerAdapter(FragmentManager fm) { 32 | this(fm, null); 33 | } 34 | 35 | /** 36 | * 创建适配器 37 | * 38 | * @param fm FragmentManager 39 | * @param config 页面配置参数 40 | */ 41 | public BasePagerAdapter(FragmentManager fm, PagerConfig config) { 42 | super(fm); 43 | mConfig = config; 44 | mCache = new PagerCache(); 45 | } 46 | 47 | public void bindTpgView(T view) { 48 | this.view = view; 49 | } 50 | 51 | /** 52 | * 重试加载数据,适用于之前加载错误或者为空,点击后的重试操作 53 | */ 54 | public void retryLoadDataForCurrentPager() { 55 | if (null != mCache && null != view) { 56 | //取得当前页面 57 | PagerFace pager = mCache.getPager(view.getCurrentPager()); 58 | if (null != pager) { 59 | //重新判断是否加载数据并加载 60 | pager.shouldLoadData(); 61 | } 62 | } 63 | } 64 | 65 | @Override 66 | public Fragment getItem(int position) { 67 | PagerFace pager = null; 68 | //先从缓存中获取页面 69 | if (null != mCache) { 70 | pager = mCache.getPager(position); 71 | //如果获取到页面,就直接返回 72 | if (null != pager) { 73 | return pager.getFragment(); 74 | } 75 | } 76 | //如果没获取到缓存页面的话,就从子类获取 77 | pager = getPager(position); 78 | //获取到当前页面的页面配置参数,并设置给具体页面 79 | pager.setPagerConfig(mConfig); 80 | //将页面缓存起来 81 | mCache.savePager(position, pager); 82 | return pager.getFragment(); 83 | } 84 | 85 | @Override 86 | public int getItemPosition(Object object) { 87 | //重写该方法并返回POSITION_NONE,达到在调用notifyDataSetChanged()方法时强制刷新ViewPager页面的效果 88 | return POSITION_NONE; 89 | } 90 | 91 | /** 92 | * 获取具体的页面 93 | * 94 | * @param position 页面索引 95 | * @return 一个页面 96 | */ 97 | public abstract PagerFace getPager(int position); 98 | 99 | /** 100 | * 获取页面的缓存 101 | * 102 | * @return 页面缓存 103 | */ 104 | public PagerCache getPagerCache() { 105 | return mCache; 106 | } 107 | 108 | /** 109 | * 重新加载某一页的数据 110 | * 111 | * @param index 页面索引 112 | * @param args 可能需要的参数 113 | */ 114 | public void reloadDataForPager(int index, Bundle args) { 115 | PagerFace pager = mCache.getPager(index); 116 | if (null != pager) { 117 | //发送加载中消息 118 | pager.tpgLoading(); 119 | //重新加载数据 120 | pager.reloadData(args); 121 | } 122 | } 123 | 124 | /** 125 | * 重新加载当前页面的数据 126 | * 127 | * @param args 可能需要的参数 128 | */ 129 | public void reloadDataForCurrentPager(Bundle args) { 130 | reloadDataForPager(view.getCurrentPager(), args); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/cache/PagerCache.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.cache; 2 | 3 | import android.util.SparseArray; 4 | 5 | import com.yhy.tabnav.tpg.PagerFace; 6 | 7 | /** 8 | * author : 颜洪毅 9 | * e-mail : yhyzgn@gmail.com 10 | * time : 2017-09-14 21:06 11 | * version: 1.0.0 12 | * desc : 页面缓存 13 | */ 14 | public class PagerCache { 15 | private SparseArray mCache; 16 | 17 | public PagerCache() { 18 | mCache = new SparseArray<>(); 19 | } 20 | 21 | /** 22 | * 保存一页 23 | * 24 | * @param index 页面索引 25 | * @param page 页面 26 | */ 27 | public void savePager(int index, PagerFace page) { 28 | if (null != mCache) { 29 | mCache.put(index, page); 30 | } 31 | } 32 | 33 | /** 34 | * 获取某一页 35 | * 36 | * @param index 页面索引 37 | * @return 页面 38 | */ 39 | public PagerFace getPager(int index) { 40 | if (null != mCache) { 41 | return mCache.get(index); 42 | } 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/config/PagerConfig.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.config; 2 | 3 | import com.yhy.tabnav.global.TpgConst; 4 | import com.yhy.tabnav.interceptor.EmptyInterceptor; 5 | import com.yhy.tabnav.interceptor.ErrorInterceptor; 6 | import com.yhy.tabnav.interceptor.LoadingInterceptor; 7 | import com.yhy.tabnav.interceptor.SuccessInterceptor; 8 | 9 | /** 10 | * author : 颜洪毅 11 | * e-mail : yhyzgn@gmail.com 12 | * time : 2017-09-14 21:06 13 | * version: 1.0.0 14 | * desc : 页面配置 15 | */ 16 | public class PagerConfig { 17 | private int mLoadingViewLayoutId; 18 | private int mErrorViewLayoutId; 19 | private int mErrorViewRetryResId; 20 | private int mEmptyViewLayoutId; 21 | private int mEmptyViewRetryResId; 22 | 23 | // 各种拦截器 24 | private LoadingInterceptor mLoadingInterceptor; 25 | private EmptyInterceptor mEmptyInterceptor; 26 | private ErrorInterceptor mErrorInterceptor; 27 | private SuccessInterceptor mSuccessInterceptor; 28 | 29 | /** 30 | * 创建页面配置对象 31 | */ 32 | public PagerConfig() { 33 | //默认值 34 | mLoadingViewLayoutId = TpgConst.PagerResIdDef.PAGER_NO_RES_ID; 35 | mErrorViewLayoutId = TpgConst.PagerResIdDef.PAGER_NO_RES_ID; 36 | mEmptyViewLayoutId = TpgConst.PagerResIdDef.PAGER_NO_RES_ID; 37 | mErrorViewRetryResId = TpgConst.PagerResIdDef.PAGER_NO_RES_ID; 38 | mEmptyViewRetryResId = TpgConst.PagerResIdDef.PAGER_NO_RES_ID; 39 | } 40 | 41 | /** 42 | * 设置加载中页面 43 | * 44 | * @param layoutId 页面资源id 45 | * @return 当前对象,便于链式编程 46 | */ 47 | public PagerConfig setLoadingViewLayoutId(int layoutId) { 48 | mLoadingViewLayoutId = layoutId; 49 | return this; 50 | } 51 | 52 | /** 53 | * 获取加载中页面资源id 54 | * 55 | * @return layoutId 56 | */ 57 | public int getLoadingViewLayoutId() { 58 | return mLoadingViewLayoutId; 59 | } 60 | 61 | /** 62 | * 设置错误页面 63 | * 64 | * @param layoutId 页面资源id 65 | * @param retryResId 错误页面的重试按钮资源id 66 | * @return 当前对象,便于链式编程 67 | */ 68 | public PagerConfig setErrorViewLayoutId(int layoutId, int retryResId) { 69 | mErrorViewLayoutId = layoutId; 70 | mErrorViewRetryResId = retryResId; 71 | return this; 72 | } 73 | 74 | /** 75 | * 获取错误页面资源id 76 | * 77 | * @return layoutId 78 | */ 79 | public int getErrorViewResId() { 80 | return mErrorViewLayoutId; 81 | } 82 | 83 | /** 84 | * 获取错误页面的重试按钮资源id 85 | * 86 | * @return 重试按钮资源id 87 | */ 88 | public int getErrorViewRetryResId() { 89 | return mErrorViewRetryResId; 90 | } 91 | 92 | /** 93 | * 设置空数据页面 94 | * 95 | * @param layoutId 页面资源id 96 | * @param retryResId 空数据页面的重试按钮资源id 97 | * @return 当前对象,便于链式编程 98 | */ 99 | public PagerConfig setEmptyViewLayoutId(int layoutId, int retryResId) { 100 | mEmptyViewLayoutId = layoutId; 101 | mEmptyViewRetryResId = retryResId; 102 | return this; 103 | } 104 | 105 | /** 106 | * 获取空数据页面资源id 107 | * 108 | * @return layoutId 109 | */ 110 | public int getEmptyViewResId() { 111 | return mEmptyViewLayoutId; 112 | } 113 | 114 | /** 115 | * 获取空数据页面的重试按钮资源id 116 | * 117 | * @return 重试按钮资源id 118 | */ 119 | public int getEmptyViewRetryResId() { 120 | return mEmptyViewRetryResId; 121 | } 122 | 123 | /** 124 | * 设置加载中拦截器 125 | * 126 | * @param interceptor 加载中拦截器 127 | * @return 当前对象,便于链式编程 128 | */ 129 | public PagerConfig setLoadingInterceptor(LoadingInterceptor interceptor) { 130 | mLoadingInterceptor = interceptor; 131 | return this; 132 | } 133 | 134 | /** 135 | * 获取加载中拦截器 136 | * 137 | * @return 加载中拦截器 138 | */ 139 | public LoadingInterceptor getLoadingInterceptor() { 140 | return mLoadingInterceptor; 141 | } 142 | 143 | /** 144 | * 设置空数据拦截器 145 | * 146 | * @param interceptor 空数据拦截器 147 | * @return 当前对象,便于链式编程 148 | */ 149 | public PagerConfig setEmptyInterceptor(EmptyInterceptor interceptor) { 150 | mEmptyInterceptor = interceptor; 151 | return this; 152 | } 153 | 154 | /** 155 | * 获取空数据拦截器 156 | * 157 | * @return 空数据拦截器 158 | */ 159 | public EmptyInterceptor getEmptyInterceptor() { 160 | return mEmptyInterceptor; 161 | } 162 | 163 | /** 164 | * 设置错误拦截器 165 | * 166 | * @param interceptor 错误拦截器 167 | * @return 当前对象,便于链式编程 168 | */ 169 | public PagerConfig setErrorInterceptor(ErrorInterceptor interceptor) { 170 | mErrorInterceptor = interceptor; 171 | return this; 172 | } 173 | 174 | /** 175 | * 获取错误拦截器 176 | * 177 | * @return 错误拦截器 178 | */ 179 | public ErrorInterceptor getErrorInterceptor() { 180 | return mErrorInterceptor; 181 | } 182 | 183 | /** 184 | * 设置成功拦截器 185 | * 186 | * @param interceptor 成功拦截器 187 | * @return 当前对象,便于链式编程 188 | */ 189 | public PagerConfig setSuccessInterceptor(SuccessInterceptor interceptor) { 190 | mSuccessInterceptor = interceptor; 191 | return this; 192 | } 193 | 194 | /** 195 | * 获取成功拦截器 196 | * 197 | * @return 成功拦截器 198 | */ 199 | public SuccessInterceptor getSuccessInterceptor() { 200 | return mSuccessInterceptor; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/dispatch/DispatchLoading.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.dispatch; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.os.Handler; 6 | import android.os.Message; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.widget.FrameLayout; 10 | 11 | import com.yhy.tabnav.handler.ResultHandler; 12 | import com.yhy.tabnav.global.TpgConst; 13 | import com.yhy.tabnav.interceptor.EmptyInterceptor; 14 | import com.yhy.tabnav.interceptor.ErrorInterceptor; 15 | import com.yhy.tabnav.interceptor.LoadingInterceptor; 16 | import com.yhy.tabnav.interceptor.SuccessInterceptor; 17 | 18 | /** 19 | * author : 颜洪毅 20 | * e-mail : yhyzgn@gmail.com 21 | * time : 2017-09-14 21:06 22 | * version: 1.0.0 23 | * desc : 用于控制分发页面,根据不同的状态确定该显示的页面 24 | */ 25 | public abstract class DispatchLoading extends FrameLayout { 26 | private static final String TAG = "DispatchLoading"; 27 | 28 | //用来更新UI的Handler(为了提高更新UI的安全性,这里就用Handler来更新UI) 29 | private Handler mHandler; 30 | 31 | //用来回调发送结果状态的ResultHandler对象 32 | private ResultHandler mResultHandler; 33 | 34 | //加载中页面 35 | private View mLoadingView; 36 | //加载错误页面 37 | private View mErrorView; 38 | //空数据页面 39 | private View mEmptyView; 40 | //成功页面 41 | private View mSuccessView; 42 | 43 | // 加载中拦截器 44 | private LoadingInterceptor mLoadingInterceptor; 45 | // 空数据拦截器 46 | private EmptyInterceptor mEmptyInterceptor; 47 | // 错误拦截器 48 | private ErrorInterceptor mErrorInterceptor; 49 | // 成功拦截器 50 | private SuccessInterceptor mSuccessInterceptor; 51 | 52 | //当前页面的状态 53 | private int mCurrentState; 54 | 55 | public DispatchLoading(Context context) { 56 | this(context, null); 57 | } 58 | 59 | public DispatchLoading(Context context, AttributeSet attrs) { 60 | this(context, attrs, 0); 61 | } 62 | 63 | public DispatchLoading(Context context, AttributeSet attrs, int defStyleAttr) { 64 | super(context, attrs, defStyleAttr); 65 | init(); 66 | } 67 | 68 | /** 69 | * 初始化 70 | */ 71 | @SuppressLint("HandlerLeak") 72 | private void init() { 73 | //初始化数据 74 | mHandler = new Handler() { 75 | @Override 76 | public void handleMessage(Message msg) { 77 | //只有要更新的状态不等于当前状态时,才有效 78 | if (null != msg && msg.what != mCurrentState) { 79 | //更新状态 80 | mCurrentState = msg.what; 81 | //更新UI 82 | updateUI(); 83 | } 84 | } 85 | }; 86 | 87 | //创建结果集Handler 88 | mResultHandler = new ResultHandler(mHandler); 89 | 90 | // 获取拦截器 91 | if (null == mLoadingInterceptor) { 92 | mLoadingInterceptor = getLoadingInterceptor(); 93 | } 94 | if (null == mEmptyInterceptor) { 95 | mEmptyInterceptor = getEmptyInterceptor(); 96 | } 97 | if (null == mErrorInterceptor) { 98 | mErrorInterceptor = getErrorInterceptor(); 99 | } 100 | if (null == mSuccessInterceptor) { 101 | mSuccessInterceptor = getSuccessInterceptor(); 102 | } 103 | 104 | //获取具体页面 105 | if (null == mLoadingView) { 106 | mLoadingView = getLoadingView(); 107 | addView(mLoadingView); 108 | mLoadingView.setVisibility(mLoadingInterceptor.processAhead() ? VISIBLE : GONE); 109 | } 110 | if (null == mErrorView) { 111 | mErrorView = getErrorView(); 112 | addView(mErrorView); 113 | mErrorView.setVisibility(GONE); 114 | } 115 | if (null == mEmptyView) { 116 | mEmptyView = getEmptyView(); 117 | addView(mEmptyView); 118 | mEmptyView.setVisibility(GONE); 119 | } 120 | if (null == mSuccessView) { 121 | mSuccessView = getSuccessView(); 122 | addView(mSuccessView); 123 | mSuccessView.setVisibility(GONE); 124 | } 125 | 126 | //当前状态为默认状态 127 | mCurrentState = TpgConst.LoadingStatus.STATE_DEFAULT; 128 | } 129 | 130 | /** 131 | * 获取加载中拦截器 132 | * 133 | * @return 加载中拦截器 134 | */ 135 | protected abstract LoadingInterceptor getLoadingInterceptor(); 136 | 137 | /** 138 | * 获取空数据拦截器 139 | * 140 | * @return 空数据拦截器 141 | */ 142 | protected abstract EmptyInterceptor getEmptyInterceptor(); 143 | 144 | /** 145 | * 获取错误拦截器 146 | * 147 | * @return 错误拦截器 148 | */ 149 | protected abstract ErrorInterceptor getErrorInterceptor(); 150 | 151 | /** 152 | * 获取成功拦截器 153 | * 154 | * @return 成功拦截器 155 | */ 156 | protected abstract SuccessInterceptor getSuccessInterceptor(); 157 | 158 | /** 159 | * 获取到成功页面 160 | * 161 | * @return 成功页面 162 | */ 163 | protected abstract View getSuccessView(); 164 | 165 | /** 166 | * 获取到加载中页面 167 | * 168 | * @return 加载中页面 169 | */ 170 | protected abstract View getLoadingView(); 171 | 172 | /** 173 | * 获取到加载错误页面 174 | * 175 | * @return 加载错误页面 176 | */ 177 | protected abstract View getErrorView(); 178 | 179 | /** 180 | * 获取到数据为空页面 181 | * 182 | * @return 数据为空页面 183 | */ 184 | protected abstract View getEmptyView(); 185 | 186 | /** 187 | * 初始化数据,用来回调具体页面的初始化数据方法 188 | */ 189 | protected abstract void initData(); 190 | 191 | /** 192 | * 判断是否应该加载数据,应该(当前状态不是成功)的话就加载 193 | */ 194 | public void shouldLoadData() { 195 | if (mCurrentState != TpgConst.LoadingStatus.STATE_SUCCESS && mCurrentState != TpgConst.LoadingStatus.STATE_LOADING) { 196 | //如果当前状态不是成功状态或者加载中状态,就把当前状态改为加载中状态,请求数据并更新UI 197 | mResultHandler.onLoading(); 198 | 199 | //回调页面中的加载数据方法 200 | initData(); 201 | } 202 | } 203 | 204 | /** 205 | * 更新UI 206 | */ 207 | private void updateUI() { 208 | // 加载中 209 | if (null != mLoadingView) { 210 | mLoadingView.setVisibility(mCurrentState == TpgConst.LoadingStatus.STATE_LOADING && mLoadingInterceptor.processAhead() ? VISIBLE : GONE); 211 | } 212 | if (null != mLoadingView) { 213 | mEmptyView.setVisibility(mCurrentState == TpgConst.LoadingStatus.STATE_EMPTY && mEmptyInterceptor.processAhead() ? VISIBLE : GONE); 214 | } 215 | if (null != mLoadingView) { 216 | mErrorView.setVisibility(mCurrentState == TpgConst.LoadingStatus.STATE_ERROR && mErrorInterceptor.processAhead() ? VISIBLE : GONE); 217 | } 218 | if (null != mLoadingView) { 219 | mSuccessView.setVisibility(mCurrentState == TpgConst.LoadingStatus.STATE_SUCCESS && mSuccessInterceptor.processAhead() ? VISIBLE : GONE); 220 | } 221 | 222 | // 加上拦截器后,改用以上方法切换页面 223 | // if (null != mLoadingView) { 224 | // mLoadingView.setVisibility(mCurrentState == TpgConst.LoadingStatus.STATE_LOADING ? VISIBLE : GONE); 225 | // } 226 | // if (null != mErrorView) { 227 | // mErrorView.setVisibility(mCurrentState == TpgConst.LoadingStatus.STATE_ERROR ? VISIBLE : GONE); 228 | // } 229 | // if (null != mEmptyView) { 230 | // mEmptyView.setVisibility(mCurrentState == TpgConst.LoadingStatus.STATE_EMPTY ? VISIBLE : GONE); 231 | // } 232 | // if (null != mSuccessView) { 233 | // mSuccessView.setVisibility(mCurrentState == TpgConst.LoadingStatus.STATE_SUCCESS ? VISIBLE : GONE); 234 | // } 235 | } 236 | 237 | /** 238 | * 获取用来回调发送结果状态的ResultHandler对象 239 | * 240 | * @return 用来回调发送结果状态的ResultHandler对象 241 | */ 242 | public ResultHandler getRltHandler() { 243 | return mResultHandler; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/entity/NavTab.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.entity; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2017-09-14 21:07 7 | * version: 1.0.0 8 | * desc : NavView每个子项的模型 9 | */ 10 | public class NavTab { 11 | public String tabText; 12 | public int tabIconId; 13 | 14 | /** 15 | * 构造函数 16 | * 17 | * @param tabText 子项文本 18 | * @param tabIconId 子项图标 19 | */ 20 | public NavTab(String tabText, int tabIconId) { 21 | this.tabText = tabText; 22 | this.tabIconId = tabIconId; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/global/TpgConst.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.global; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2017-09-14 21:07 7 | * version: 1.0.0 8 | * desc : 一些常量 9 | */ 10 | public interface TpgConst { 11 | 12 | /** 13 | * 页面加载状态常量 14 | */ 15 | interface LoadingStatus { 16 | //加载中... 17 | int STATE_LOADING = 100; 18 | //加载错误 19 | int STATE_ERROR = 101; 20 | //加载成功,但数据为空 21 | int STATE_EMPTY = 102; 22 | //加载成功,且数据有效 23 | int STATE_SUCCESS = 103; 24 | //默认状态 25 | int STATE_DEFAULT = 104; 26 | } 27 | 28 | /** 29 | * 页面布局资源id默认值 30 | */ 31 | interface PagerResIdDef { 32 | //没有设置默认资源ID时默认都为0 33 | int PAGER_NO_RES_ID = 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/handler/ResultHandler.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.handler; 2 | 3 | import android.os.Handler; 4 | 5 | import com.yhy.tabnav.global.TpgConst; 6 | 7 | /** 8 | * author : 颜洪毅 9 | * e-mail : yhyzgn@gmail.com 10 | * time : 2017-09-14 21:07 11 | * version: 1.0.0 12 | * desc : 用来发送页面消息的Handler 13 | */ 14 | public class ResultHandler { 15 | private final Handler mHandler; 16 | 17 | /** 18 | * 创建结果Handler对象 19 | * 20 | * @param handler 用来发送消息的Handler 21 | */ 22 | public ResultHandler(Handler handler) { 23 | mHandler = handler; 24 | } 25 | 26 | /** 27 | * 发送加载中消息 28 | */ 29 | public void onLoading() { 30 | mHandler.sendEmptyMessage(TpgConst.LoadingStatus.STATE_LOADING); 31 | } 32 | 33 | /** 34 | * 发送错误消息 35 | */ 36 | public void onError() { 37 | mHandler.sendEmptyMessage(TpgConst.LoadingStatus.STATE_ERROR); 38 | } 39 | 40 | /** 41 | * 发送空数据消息 42 | */ 43 | public void onEmpty() { 44 | mHandler.sendEmptyMessage(TpgConst.LoadingStatus.STATE_EMPTY); 45 | } 46 | 47 | /** 48 | * 发送成功消息 49 | */ 50 | public void onSuccess() { 51 | mHandler.sendEmptyMessage(TpgConst.LoadingStatus.STATE_SUCCESS); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/helper/PagerHelper.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.helper; 2 | 3 | import android.view.MotionEvent; 4 | 5 | import com.yhy.tabnav.tpg.Pager; 6 | 7 | /** 8 | * author : 颜洪毅 9 | * e-mail : yhyzgn@gmail.com 10 | * time : 2017-11-06 8:27 11 | * version: 1.0.0 12 | * desc : ViewPager辅助类 13 | */ 14 | public class PagerHelper { 15 | // ViewPager接口 16 | private Pager mPager; 17 | // 是否可滑动 18 | private boolean mScrollAble; 19 | 20 | /** 21 | * 构造函数 22 | * 23 | * @param pager 当前实现的Pager接口 24 | */ 25 | public PagerHelper(Pager pager) { 26 | mPager = pager; 27 | } 28 | 29 | /** 30 | * 设置是否可滑动,默认可滑动 31 | * 32 | * @param scrollAble 是否可滑动 33 | */ 34 | public void setScrollAble(boolean scrollAble) { 35 | mScrollAble = scrollAble; 36 | } 37 | 38 | /** 39 | * 触发拦截触摸事件 40 | * 41 | * @param ev 事件 42 | * @return 拦截结果 43 | */ 44 | public boolean onInterceptTouchEvent(MotionEvent ev) { 45 | // 处理缩放滑动异常 46 | try { 47 | //如果不允许滑动,就直接拦截事件 48 | if (!mScrollAble) { 49 | // 彻底解决滑动冲突(将事件都交于子View处理) 50 | return false; 51 | } 52 | return mPager.onSuperInterceptTouchEvent(ev); 53 | } catch (IllegalArgumentException ex) { 54 | ex.printStackTrace(); 55 | } 56 | return false; 57 | } 58 | 59 | /** 60 | * 设置禁止滑动 触发触摸事件 61 | * 62 | * @param ev 事件 63 | * @return 处理结果 64 | */ 65 | public boolean onTouchEvent(MotionEvent ev) { 66 | // 处理缩放滑动异常 67 | try { 68 | if (!mScrollAble) { 69 | return false;//禁止滑动 70 | } 71 | return mPager.onSuperTouchEvent(ev); 72 | } catch (IllegalArgumentException ex) { 73 | ex.printStackTrace(); 74 | } 75 | return false; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/helper/TpgHelper.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.helper; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.yhy.tabnav.R; 11 | import com.yhy.tabnav.config.PagerConfig; 12 | import com.yhy.tabnav.dispatch.DispatchLoading; 13 | import com.yhy.tabnav.global.TpgConst; 14 | import com.yhy.tabnav.handler.ResultHandler; 15 | import com.yhy.tabnav.interceptor.EmptyInterceptor; 16 | import com.yhy.tabnav.interceptor.ErrorInterceptor; 17 | import com.yhy.tabnav.interceptor.LoadingInterceptor; 18 | import com.yhy.tabnav.interceptor.SuccessInterceptor; 19 | import com.yhy.tabnav.tpg.PagerFace; 20 | import com.yhy.tabnav.utils.ViewUtils; 21 | 22 | /** 23 | * author : 颜洪毅 24 | * e-mail : yhyzgn@gmail.com 25 | * time : 2017-09-15 14:55 26 | * version: 1.0.0 27 | * desc : 用来配合PagerFace完成页面依附的辅助类 28 | */ 29 | public class TpgHelper { 30 | protected RT mRoot; 31 | //每个页面中分发页面的对象 32 | private DispatchLoading mDispatch; 33 | //页面配置 34 | private PagerConfig mConfig; 35 | //当前Activity对象 36 | public Activity mActivity; 37 | //记录页面显示或隐藏的状态 38 | private boolean mIsVisible; 39 | 40 | /** 41 | * 获取Activity 42 | * 43 | * @param ctx 上下文对象 44 | * @return 当前Activity对象 45 | */ 46 | public Activity getPagerActivity(Context ctx) { 47 | mActivity = null == ctx ? null : (Activity) ctx; 48 | return mActivity; 49 | } 50 | 51 | /** 52 | * 设置根页面 53 | * 54 | * @param root 根页面 55 | */ 56 | public void setRoot(RT root) { 57 | mRoot = root; 58 | } 59 | 60 | /** 61 | * 获取根页面 62 | * 63 | * @return 根页面 64 | */ 65 | public RT getRoot() { 66 | return mRoot; 67 | } 68 | 69 | /** 70 | * 设置页面配置参数 71 | * 72 | * @param config 页面配置参数 73 | */ 74 | public void setPagerConfig(PagerConfig config) { 75 | mConfig = config; 76 | } 77 | 78 | /** 79 | * 页面显示或隐藏状态改变时触发 80 | * 81 | * @param face 当前页面 82 | * @param isVisible 是否显示 83 | */ 84 | public void onPagerVisible(PagerFace face, boolean isVisible) { 85 | // 记录下当前状态(由于setUserVisibleHint方法再onCreateView之前执行,所以要实现懒加载的话,就再onCreateView方法中手动调用加载数据方法,并在setUserVisibleHint方法中判断是否显示) 86 | mIsVisible = isVisible; 87 | // 触发页面状态改变事件 88 | face.onPagerVisible(mIsVisible); 89 | } 90 | 91 | /** 92 | * 获取页面状态 93 | * 94 | * @return 页面状态 95 | */ 96 | public boolean getIsVisible() { 97 | return mIsVisible; 98 | } 99 | 100 | /** 101 | * 创建页面真正显示的View 102 | * 103 | * @param face 当前页面 104 | * @param inflater 布局映射器 105 | * @param container 容器 106 | * @param savedInstanceState 保存参数 107 | * @return 真正显示的View 108 | */ 109 | public View onCreatePagerView(final PagerFace face, final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { 110 | if (null == mDispatch) { 111 | //创建mDispatch对象,实现其抽象方法,并回调页面中相应的抽象方法 112 | mDispatch = new DispatchLoading(mActivity) { 113 | @Override 114 | protected LoadingInterceptor getLoadingInterceptor() { 115 | return face.getLoadingInterceptor(); 116 | } 117 | 118 | @Override 119 | protected EmptyInterceptor getEmptyInterceptor() { 120 | return face.getEmptyInterceptor(); 121 | } 122 | 123 | @Override 124 | protected ErrorInterceptor getErrorInterceptor() { 125 | return face.getErrorInterceptor(); 126 | } 127 | 128 | @Override 129 | protected SuccessInterceptor getSuccessInterceptor() { 130 | return face.getSuccessInterceptor(); 131 | } 132 | 133 | @Override 134 | public View getSuccessView() { 135 | return face.getSuccessView(inflater, container, savedInstanceState); 136 | } 137 | 138 | @Override 139 | public View getLoadingView() { 140 | return face.getLoadingView(inflater, container, savedInstanceState); 141 | } 142 | 143 | @Override 144 | public View getErrorView() { 145 | return face.getErrorView(inflater, container, savedInstanceState); 146 | } 147 | 148 | @Override 149 | public View getEmptyView() { 150 | return face.getEmptyView(inflater, container, savedInstanceState); 151 | } 152 | 153 | @Override 154 | public void initData() { 155 | // 初始化数据 156 | face.initData(); 157 | // 初始化事件一些事件监听 158 | face.initListener(); 159 | } 160 | }; 161 | } else { 162 | //由于一个View不能同时有两个parent,而当mDispatch不为空时说明当前页面(View)已经添加过给其他parent了, 163 | //所以这里需要把mDispatch从原来的parent中移除 164 | ViewUtils.removeFromParent(mDispatch); 165 | } 166 | return mDispatch; 167 | } 168 | 169 | /** 170 | * 获取加载中拦截器 171 | * 172 | * @return 加载中拦截器 173 | */ 174 | public LoadingInterceptor getLoadingInterceptor() { 175 | return null != mConfig && null != mConfig.getLoadingInterceptor() ? mConfig.getLoadingInterceptor() : new LoadingInterceptor() { 176 | @Override 177 | public boolean processAhead() { 178 | return true; 179 | } 180 | }; 181 | } 182 | 183 | /** 184 | * 获取空数据拦截器 185 | * 186 | * @return 空数据拦截器 187 | */ 188 | public EmptyInterceptor getEmptyInterceptor() { 189 | return null != mConfig && null != mConfig.getLoadingInterceptor() ? mConfig.getEmptyInterceptor() : new EmptyInterceptor() { 190 | @Override 191 | public boolean processAhead() { 192 | return true; 193 | } 194 | }; 195 | } 196 | 197 | /** 198 | * 获取错误拦截器 199 | * 200 | * @return 错误拦截器 201 | */ 202 | public ErrorInterceptor getErrorInterceptor() { 203 | return null != mConfig && null != mConfig.getLoadingInterceptor() ? mConfig.getErrorInterceptor() : new ErrorInterceptor() { 204 | @Override 205 | public boolean processAhead() { 206 | return true; 207 | } 208 | }; 209 | } 210 | 211 | /** 212 | * 获取成功拦截器 213 | * 214 | * @return 成功拦截器 215 | */ 216 | public SuccessInterceptor getSuccessInterceptor() { 217 | return null != mConfig && null != mConfig.getLoadingInterceptor() ? mConfig.getSuccessInterceptor() : new SuccessInterceptor() { 218 | @Override 219 | public boolean processAhead() { 220 | return true; 221 | } 222 | }; 223 | } 224 | 225 | /** 226 | * 获取[加载中]页面 227 | * 228 | * @param inflater 布局映射器 229 | * @param container 容器 230 | * @param savedInstanceState 保存参数 231 | * @return [加载中]页面 232 | */ 233 | public View getLoadingView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 234 | if (null != mConfig) { 235 | int loadingViewResId = mConfig.getLoadingViewLayoutId(); 236 | if (loadingViewResId > TpgConst.PagerResIdDef.PAGER_NO_RES_ID) { 237 | return inflater.inflate(loadingViewResId, container, false); 238 | } 239 | } 240 | return inflater.inflate(R.layout.layout_def_loading, container, false); 241 | } 242 | 243 | /** 244 | * 获取[空数据]页面 245 | * 246 | * @param face 当前页面 247 | * @param inflater 布局映射器 248 | * @param container 容器 249 | * @param savedInstanceState 保存参数 250 | * @return [空数据]页面 251 | */ 252 | public View getEmptyView(final PagerFace face, LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 253 | View emptyView = null; 254 | View retryView = null; 255 | 256 | //如果设置过页面参数,就以该参数为主 257 | if (null != mConfig) { 258 | //获取界面View 259 | int emptyViewResId = mConfig.getEmptyViewResId(); 260 | if (emptyViewResId > TpgConst.PagerResIdDef.PAGER_NO_RES_ID) { 261 | emptyView = inflater.inflate(emptyViewResId, container, false); 262 | } 263 | //获取重试按钮View 264 | int retryResId = mConfig.getEmptyViewRetryResId(); 265 | if (retryResId > TpgConst.PagerResIdDef.PAGER_NO_RES_ID && null != emptyView) { 266 | retryView = emptyView.findViewById(retryResId); 267 | } 268 | } 269 | 270 | //如果没有设置过页面参数,就使用默认的页面 271 | if (null == emptyView) { 272 | emptyView = inflater.inflate(R.layout.layout_def_empty, container, false); 273 | } 274 | if (null == retryView) { 275 | //默认将整个默认页面设置为重试View 276 | retryView = emptyView; 277 | } 278 | 279 | //设置重试加载事件 280 | retryView.setOnClickListener(new View.OnClickListener() { 281 | @Override 282 | public void onClick(View view) { 283 | face.shouldLoadData(); 284 | } 285 | }); 286 | return emptyView; 287 | } 288 | 289 | /** 290 | * 获取[错误]页面 291 | * 292 | * @param face 当前页面 293 | * @param inflater 布局映射器 294 | * @param container 容器 295 | * @param savedInstanceState 保存参数 296 | * @return [错误]页面 297 | */ 298 | public View getErrorView(final PagerFace face, LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 299 | View errorView = null; 300 | View retryView = null; 301 | 302 | //如果设置过页面参数,就以该参数为主 303 | if (null != mConfig) { 304 | //获取界面View 305 | int errorViewResId = mConfig.getErrorViewResId(); 306 | if (errorViewResId > TpgConst.PagerResIdDef.PAGER_NO_RES_ID) { 307 | errorView = inflater.inflate(errorViewResId, container, false); 308 | } 309 | //获取重试按钮View 310 | int retryResId = mConfig.getErrorViewRetryResId(); 311 | if (retryResId > TpgConst.PagerResIdDef.PAGER_NO_RES_ID && null != errorView) { 312 | retryView = errorView.findViewById(retryResId); 313 | } 314 | } 315 | 316 | //如果没有设置过页面参数,就使用默认的页面 317 | if (null == errorView) { 318 | errorView = inflater.inflate(R.layout.layout_def_error, container, false); 319 | } 320 | if (null == retryView) { 321 | //默认将整个默认页面设置为重试View 322 | retryView = errorView; 323 | } 324 | 325 | //设置重试加载事件 326 | retryView.setOnClickListener(new View.OnClickListener() { 327 | @Override 328 | public void onClick(View view) { 329 | face.shouldLoadData(); 330 | } 331 | }); 332 | return errorView; 333 | } 334 | 335 | /** 336 | * 请求数据 337 | */ 338 | public void shouldLoadData() { 339 | // 当页面初始化完成并且显示后才能往下执行 340 | if (null != mDispatch && mIsVisible) { 341 | mDispatch.shouldLoadData(); 342 | } 343 | } 344 | 345 | /** 346 | * 获取到用来发送页面切换通知消息的handler 347 | * 348 | * @return 用来发送消息的handler 349 | */ 350 | public ResultHandler getRltHandler() { 351 | return null != mDispatch ? mDispatch.getRltHandler() : null; 352 | } 353 | 354 | /** 355 | * 将页面切换为[加载中]状态 356 | */ 357 | public void onLoading() { 358 | getRltHandler().onLoading(); 359 | } 360 | 361 | /** 362 | * 将页面切换为[成功]状态 363 | */ 364 | public void onSuccess() { 365 | getRltHandler().onSuccess(); 366 | } 367 | 368 | /** 369 | * 将页面切换为[空数据]状态 370 | */ 371 | public void onEmpty() { 372 | getRltHandler().onEmpty(); 373 | } 374 | 375 | /** 376 | * 将页面切换为[错误]状态 377 | */ 378 | public void onError() { 379 | getRltHandler().onError(); 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/interceptor/EmptyInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.interceptor; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2019-03-09 23:41 7 | * version: 1.0.0 8 | * desc : 空数据拦截器 9 | */ 10 | public interface EmptyInterceptor { 11 | 12 | /** 13 | * 执行拦截器操作 14 | * 15 | * @return 是否继续切换到空数据页面 16 | */ 17 | boolean processAhead(); 18 | } 19 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/interceptor/ErrorInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.interceptor; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2019-03-09 23:41 7 | * version: 1.0.0 8 | * desc : 错误拦截器 9 | */ 10 | public interface ErrorInterceptor { 11 | 12 | /** 13 | * 执行拦截器操作 14 | * 15 | * @return 是否继续切换到错误月面 16 | */ 17 | boolean processAhead(); 18 | } 19 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/interceptor/LoadingInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.interceptor; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2019-03-09 23:11 7 | * version: 1.0.0 8 | * desc : 加载中拦截器 9 | */ 10 | public interface LoadingInterceptor { 11 | 12 | /** 13 | * 执行拦截器操作 14 | * 15 | * @return 是否继续切换到加载中页面 16 | */ 17 | boolean processAhead(); 18 | } 19 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/interceptor/SuccessInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.interceptor; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2019-03-09 23:42 7 | * version: 1.0.0 8 | * desc : 成功拦截器 9 | */ 10 | public interface SuccessInterceptor { 11 | 12 | /** 13 | * 执行拦截器操作 14 | * 15 | * @return 是否继续切换到成功页面 16 | */ 17 | boolean processAhead(); 18 | } 19 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/listener/OnPageChangedListener.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.listener; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2017-09-14 21:07 7 | * version: 1.0.0 8 | * desc : ViewPager页面切换事件监听器 9 | */ 10 | public interface OnPageChangedListener { 11 | 12 | /** 13 | * 当页面滑动时 14 | * 15 | * @param position 当前页面序号,只有翻页成功的情况下最后一次调用才会变为目标页面。 16 | * @param positionOffset 是当前页面滑动比例 17 | * @param positionOffsetPixels 是当前页面滑动像素,变化情况和positionOffset一致。 18 | */ 19 | void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); 20 | 21 | /** 22 | * 页面切换成功 23 | * 24 | * @param position 当前页面索引 25 | */ 26 | void onPageSelected(int position); 27 | 28 | /** 29 | * 手指操作屏幕的时候发生变化。有三个值:0(END),1(PRESS) , 2(UP)。 30 | * 当setCurrentItem翻页时,会执行这个方法两次,state值分别为2 , 0 。 31 | * 32 | * @param state 有三个值:0(END),1(PRESS) , 2(UP) 33 | */ 34 | void onPageScrollStateChanged(int state); 35 | } -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/pager/TpgFragment.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.pager; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.yhy.tabnav.config.PagerConfig; 11 | import com.yhy.tabnav.helper.TpgHelper; 12 | import com.yhy.tabnav.interceptor.EmptyInterceptor; 13 | import com.yhy.tabnav.interceptor.ErrorInterceptor; 14 | import com.yhy.tabnav.interceptor.LoadingInterceptor; 15 | import com.yhy.tabnav.interceptor.SuccessInterceptor; 16 | import com.yhy.tabnav.tpg.PagerFace; 17 | 18 | import androidx.annotation.Nullable; 19 | import androidx.fragment.app.Fragment; 20 | 21 | /** 22 | * author : 颜洪毅 23 | * e-mail : yhyzgn@gmail.com 24 | * time : 2017-09-14 21:08 25 | * version: 1.0.0 26 | * desc : 所有页面的父类,如果自定义的话请参照此处自行实现PagerFace接口 27 | */ 28 | public abstract class TpgFragment extends Fragment implements PagerFace { 29 | //当前Activity对象 30 | public Activity mActivity; 31 | //页面助手,用于创建各种View,比如错误页面等(必要)。便于自定义该页面时直接使用。 32 | public TpgHelper mHelper = new TpgHelper<>(); 33 | 34 | /** 35 | * Fragment生命周期方法--创建 36 | * 37 | * @param savedInstanceState 保存的参数 38 | */ 39 | @Override 40 | public void onCreate(@Nullable Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | // 获取Activity 43 | getPagerActivity(getActivity()); 44 | } 45 | 46 | /** 47 | * Fragment生命周期方法--依附到Activity 48 | * 49 | * @param context 上下文对象 50 | */ 51 | @Override 52 | public void onAttach(Context context) { 53 | super.onAttach(context); 54 | // 为了避免Activity为空,这里需要再次通过context获取Activity 55 | getPagerActivity(context); 56 | } 57 | 58 | /** 59 | * Fragment生命周期方法--View创建 60 | * 61 | * @param inflater 布局映射器 62 | * @param container 容器 63 | * @param savedInstanceState 保存的参数 64 | * @return 真正显示的View 65 | */ 66 | @Nullable 67 | @Override 68 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 69 | View view = onCreatePagerView(inflater, container, savedInstanceState); 70 | // 由于setUserVisibleHint方法再onCreateView之前执行,所以要实现懒加载的话,就再onCreateView方法中手动调用加载数据方法,并在setUserVisibleHint方法中判断是否显示 71 | shouldLoadData(); 72 | return view; 73 | } 74 | 75 | /** 76 | * Fragment的显示或者隐藏 77 | *

78 | * 必须在切换页面时手动调用 79 | * 这里的页面都用在ViewPager中,在FragmentPagerAdapter中已经主动触发 80 | * 81 | * @param isVisibleToUser 是否显示 82 | */ 83 | @Override 84 | public void setUserVisibleHint(boolean isVisibleToUser) { 85 | super.setUserVisibleHint(isVisibleToUser); 86 | 87 | // 记录下当前状态(由于setUserVisibleHint方法再onCreateView之前执行,所以要实现懒加载的话,就再onCreateView方法中手动调用加载数据方法,并在setUserVisibleHint方法中判断是否显示) 88 | mHelper.onPagerVisible(this, isVisibleToUser); 89 | } 90 | 91 | /** 92 | * PagerFace接口方法--适配器中获取Fragment使用 93 | * 94 | * @return 当前Fragment 95 | */ 96 | @Override 97 | public Fragment getFragment() { 98 | return this; 99 | } 100 | 101 | /** 102 | * PagerFace接口方法--设置根页面 103 | * 104 | * @param root 根页面 105 | */ 106 | @Override 107 | public void setRoot(RT root) { 108 | mHelper.setRoot(root); 109 | } 110 | 111 | /** 112 | * 获取根页面 113 | * 114 | * @return 根页面 115 | */ 116 | @Override 117 | public RT getRoot() { 118 | return mHelper.getRoot(); 119 | } 120 | 121 | /** 122 | * 设置Bundle参数 123 | * 124 | * @param params Bundle参数 125 | */ 126 | @Override 127 | public void setParams(Bundle params) { 128 | setArguments(params); 129 | } 130 | 131 | /** 132 | * 获取Bundle参数 133 | * 134 | * @return Bundle参数 135 | */ 136 | @Override 137 | public Bundle getParams() { 138 | return getArguments(); 139 | } 140 | 141 | /** 142 | * PagerFace接口方法--设置页面参数 143 | * 144 | * @param config 页面参数 145 | */ 146 | @Override 147 | public void setPagerConfig(PagerConfig config) { 148 | mHelper.setPagerConfig(config); 149 | } 150 | 151 | /** 152 | * PagerFace接口方法--获取当前Activity 153 | * 154 | * @param context 上下文对象 155 | */ 156 | @Override 157 | public void getPagerActivity(Context context) { 158 | if (null == mActivity) { 159 | mActivity = mHelper.getPagerActivity(context); 160 | } 161 | } 162 | 163 | /** 164 | * PagerFace接口方法--创建Fragment真正显示的View 165 | * 166 | * @param inflater 布局映射器 167 | * @param container 容器 168 | * @param savedInstanceState 保存的参数 169 | * @return 真正显示的View 170 | */ 171 | @Override 172 | public View onCreatePagerView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { 173 | return mHelper.onCreatePagerView(this, inflater, container, savedInstanceState); 174 | } 175 | 176 | /** 177 | * PagerFace接口方法--页面显示或隐藏时触发 178 | * 179 | * @param isVisible 是否显示 180 | */ 181 | @Override 182 | public void onPagerVisible(boolean isVisible) { 183 | //页面显示时自动调用shouldLoadData()方法加载数据 184 | if (isVisible) { 185 | shouldLoadData(); 186 | } 187 | } 188 | 189 | /** 190 | * PagerFace接口方法--获取加载中拦截器 191 | * 192 | * @return 加载中拦截器 193 | */ 194 | @Override 195 | public LoadingInterceptor getLoadingInterceptor() { 196 | return mHelper.getLoadingInterceptor(); 197 | } 198 | 199 | /** 200 | * PagerFace接口方法--获取空数据拦截器 201 | * 202 | * @return 空数据拦截器 203 | */ 204 | @Override 205 | public EmptyInterceptor getEmptyInterceptor() { 206 | return mHelper.getEmptyInterceptor(); 207 | } 208 | 209 | /** 210 | * PagerFace接口方法--获取错误拦截器 211 | * 212 | * @return 错误拦截器 213 | */ 214 | @Override 215 | public ErrorInterceptor getErrorInterceptor() { 216 | return mHelper.getErrorInterceptor(); 217 | } 218 | 219 | /** 220 | * PagerFace接口方法--获取成功拦截器 221 | * 222 | * @return 成功拦截器 223 | */ 224 | @Override 225 | public SuccessInterceptor getSuccessInterceptor() { 226 | return mHelper.getSuccessInterceptor(); 227 | } 228 | 229 | /** 230 | * PagerFace接口方法--获取加载中状态时的View 231 | * 232 | * @param inflater 布局映射器 233 | * @param container 容器 234 | * @param savedInstanceState 保存的参数 235 | * @return 加载中状态时的View 236 | */ 237 | @Override 238 | public View getLoadingView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 239 | return mHelper.getLoadingView(inflater, container, savedInstanceState); 240 | } 241 | 242 | /** 243 | * PagerFace接口方法--获取空数据状态时显示的View 244 | * 245 | * @param inflater 布局映射器 246 | * @param container 容器 247 | * @param savedInstanceState 保存的参数 248 | * @return 空数据状态时显示的View 249 | */ 250 | @Override 251 | public View getEmptyView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 252 | return mHelper.getEmptyView(this, inflater, container, savedInstanceState); 253 | } 254 | 255 | /** 256 | * PagerFace接口方法--获取错误状态时显示的View 257 | * 258 | * @param inflater 布局映射器 259 | * @param container 容器 260 | * @param savedInstanceState 保存的参数 261 | * @return 错误状态时显示的View 262 | */ 263 | @Override 264 | public View getErrorView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 265 | return mHelper.getErrorView(this, inflater, container, savedInstanceState); 266 | } 267 | 268 | /** 269 | * PagerFace接口方法--初始化事件 270 | */ 271 | @Override 272 | public void initListener() { 273 | } 274 | 275 | /** 276 | * PagerFace接口方法--主动加载数据 277 | */ 278 | @Override 279 | public void shouldLoadData() { 280 | mHelper.shouldLoadData(); 281 | } 282 | 283 | /** 284 | * PagerFace接口方法--重新重新加载数据 285 | * 286 | * @param args 携带的参数 287 | */ 288 | @Override 289 | public void reloadData(Bundle args) { 290 | } 291 | 292 | /** 293 | * PagerFace接口方法--将页面切换为加载中状态 294 | */ 295 | @Override 296 | public void tpgLoading() { 297 | mHelper.onLoading(); 298 | } 299 | 300 | /** 301 | * PagerFace接口方法--将页面切换为成功状态 302 | */ 303 | @Override 304 | public void tpgSuccess() { 305 | mHelper.onSuccess(); 306 | } 307 | 308 | /** 309 | * PagerFace接口方法--将页面切换为空数据状态 310 | */ 311 | @Override 312 | public void tpgEmpty() { 313 | mHelper.onEmpty(); 314 | } 315 | 316 | /** 317 | * PagerFace接口方法--将页面切换为错误状态 318 | */ 319 | @Override 320 | public void tpgError() { 321 | mHelper.onError(); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/tpg/Pager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.tpg; 2 | 3 | import android.view.MotionEvent; 4 | 5 | import androidx.viewpager.widget.ViewPager; 6 | 7 | /** 8 | * author : 颜洪毅 9 | * e-mail : yhyzgn@gmail.com 10 | * time : 2017-11-06 8:23 11 | * version: 1.0.0 12 | * desc : ViewPager接口 13 | */ 14 | public interface Pager { 15 | 16 | /** 17 | * 获取当前ViewPager 18 | * 19 | * @return 当前ViewPager 20 | */ 21 | ViewPager getViewPager(); 22 | 23 | /** 24 | * 控制页面是否可滑动 25 | * 26 | * @param scrollAble 是否可滑动 27 | */ 28 | void setScrollAble(boolean scrollAble); 29 | 30 | /** 31 | * 获取父类事件拦截器 32 | * 33 | * @param ev 事件 34 | * @return 拦截结果 35 | */ 36 | boolean onSuperInterceptTouchEvent(MotionEvent ev); 37 | 38 | /** 39 | * 获取父类事件处理器 40 | * 41 | * @param ev 事件 42 | * @return 处理结果 43 | */ 44 | boolean onSuperTouchEvent(MotionEvent ev); 45 | } 46 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/tpg/PagerFace.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.tpg; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.yhy.tabnav.config.PagerConfig; 10 | import com.yhy.tabnav.interceptor.EmptyInterceptor; 11 | import com.yhy.tabnav.interceptor.ErrorInterceptor; 12 | import com.yhy.tabnav.interceptor.LoadingInterceptor; 13 | import com.yhy.tabnav.interceptor.SuccessInterceptor; 14 | 15 | import androidx.fragment.app.Fragment; 16 | 17 | /** 18 | * author : 颜洪毅 19 | * e-mail : yhyzgn@gmail.com 20 | * time : 2017-09-14 21:11 21 | * version: 1.0.0 22 | * desc : 每个子页面必须实现的接口,请参照TpgFragment 23 | */ 24 | public interface PagerFace { 25 | /** 26 | * 获取实现该接口的Fragment 27 | *

28 | * 适配器中只使用Fragment,所以必须在这里获取到 29 | * 30 | * @return 实现该接口的Fragment 31 | */ 32 | Fragment getFragment(); 33 | 34 | /** 35 | * 设置根页面 36 | * 37 | * @param root 根页面 38 | */ 39 | void setRoot(RT root); 40 | 41 | /** 42 | * 获取根页面 43 | * 44 | * @return 根页面 45 | */ 46 | RT getRoot(); 47 | 48 | /** 49 | * 设置页面配置参数 50 | * 51 | * @param config 页面配置参数 52 | */ 53 | void setPagerConfig(PagerConfig config); 54 | 55 | /** 56 | * 获取到当前Fragment所依附的Activity 57 | * 58 | * @param context 上下文对象 59 | */ 60 | void getPagerActivity(Context context); 61 | 62 | /** 63 | * 设置Bundle参数 64 | * 65 | * @param params Bundle参数 66 | */ 67 | void setParams(Bundle params); 68 | 69 | /** 70 | * 获取Bundle参数 71 | * 72 | * @return Bundle参数 73 | */ 74 | Bundle getParams(); 75 | 76 | /** 77 | * 子类Fragment创建View时调用 78 | * 79 | * @param inflater 布局映射器 80 | * @param container 容器 81 | * @param savedInstanceState 保存的参数 82 | * @return 页面整个View 83 | */ 84 | View onCreatePagerView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); 85 | 86 | /** 87 | * 页面显示隐藏状态改变时调用 88 | * 89 | * @param isVisible 是否显示 90 | */ 91 | void onPagerVisible(boolean isVisible); 92 | 93 | /** 94 | * 获取加载中拦截器 95 | * 96 | * @return 加载中拦截器 97 | */ 98 | LoadingInterceptor getLoadingInterceptor(); 99 | 100 | /** 101 | * 获取空数据拦截器 102 | * 103 | * @return 空数据拦截器 104 | */ 105 | EmptyInterceptor getEmptyInterceptor(); 106 | 107 | /** 108 | * 获取错误拦截器 109 | * 110 | * @return 错误拦截器 111 | */ 112 | ErrorInterceptor getErrorInterceptor(); 113 | 114 | /** 115 | * 获取成功拦截器 116 | * 117 | * @return 成功拦截器 118 | */ 119 | SuccessInterceptor getSuccessInterceptor(); 120 | 121 | /** 122 | * 获取[记载中]显示的View 123 | * 124 | * @param inflater 布局映射器 125 | * @param container 容器 126 | * @param savedInstanceState 保存的参数 127 | * @return [记载中]显示的View 128 | */ 129 | View getLoadingView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); 130 | 131 | /** 132 | * 获取[空数据]显示的View 133 | * 134 | * @param inflater 布局映射器 135 | * @param container 容器 136 | * @param savedInstanceState 保存的参数 137 | * @return [空数据]显示的View 138 | */ 139 | View getEmptyView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); 140 | 141 | /** 142 | * 获取[错误]显示的View 143 | * 144 | * @param inflater 布局映射器 145 | * @param container 容器 146 | * @param savedInstanceState 保存的参数 147 | * @return [错误]显示的View 148 | */ 149 | View getErrorView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); 150 | 151 | /** 152 | * 获取[成功]显示的View 153 | * 154 | * @param inflater 布局映射器 155 | * @param container 容器 156 | * @param savedInstanceState 保存的参数 157 | * @return [成功]显示的View 158 | */ 159 | View getSuccessView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); 160 | 161 | /** 162 | * 初始化数据 163 | */ 164 | void initData(); 165 | 166 | /** 167 | * 初始化事件 168 | */ 169 | void initListener(); 170 | 171 | /** 172 | * 自动判断是否加载数据 173 | */ 174 | void shouldLoadData(); 175 | 176 | /** 177 | * 重新加载数据 178 | * 179 | * @param args 携带的参数 180 | */ 181 | void reloadData(Bundle args); 182 | 183 | /** 184 | * 将页面状态改为[加载中] 185 | */ 186 | void tpgLoading(); 187 | 188 | /** 189 | * 将页面状态改为[成功] 190 | */ 191 | void tpgSuccess(); 192 | 193 | /** 194 | * 将页面状态改为[空数据] 195 | */ 196 | void tpgEmpty(); 197 | 198 | /** 199 | * 将页面状态改为[错误] 200 | */ 201 | void tpgError(); 202 | } 203 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/tpg/TabBadge.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.tpg; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * author : 颜洪毅 7 | * e-mail : yhyzgn@gmail.com 8 | * time : 2017-09-17 12:10 9 | * version: 1.0.0 10 | * desc : 徽章控件相关的接口 11 | */ 12 | public interface TabBadge { 13 | 14 | /** 15 | * 显示圆点徽章 16 | * 17 | * @param index Tab的索引 18 | */ 19 | void showCirclePointBadge(int index); 20 | 21 | /** 22 | * 显示文字徽章 23 | * 24 | * @param index Tab的索引 25 | * @param badgeText 显示的文字 26 | */ 27 | void showTextBadge(int index, String badgeText); 28 | 29 | /** 30 | * 隐藏徽章 31 | * 32 | * @param index Tab的索引 33 | */ 34 | void dismissBadge(int index); 35 | 36 | /** 37 | * 显示图像徽章 38 | * 39 | * @param index Tab的索引 40 | * @param bitmap 图标 41 | */ 42 | void showDrawableBadge(int index, Bitmap bitmap); 43 | 44 | /** 45 | * 是否显示徽章 46 | * 47 | * @param index Tab的索引 48 | * @return 是否显示徽章 49 | */ 50 | boolean isShowBadge(int index); 51 | 52 | /** 53 | * 徽章消失的回调方法 54 | * 55 | * @param index Tab的索引 56 | * @param listener 回调事件 57 | */ 58 | void setOnDismissListener(int index, OnDismissBadgeListener listener); 59 | 60 | /** 61 | * 徽章消失事件回调接口 62 | */ 63 | interface OnDismissBadgeListener { 64 | void onDismiss(); 65 | } 66 | } -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/tpg/Tpg.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.tpg; 2 | 3 | /** 4 | * author : 颜洪毅 5 | * e-mail : yhyzgn@gmail.com 6 | * time : 2017-09-14 21:10 7 | * version: 1.0.0 8 | * desc : 整个框架的接口,在TpgView和NavView中实现 9 | */ 10 | public interface Tpg { 11 | 12 | /** 13 | * 设置当前页面 14 | * 15 | * @param index 页面索引 16 | */ 17 | void setCurrentPager(int index); 18 | 19 | /** 20 | * 获取当前页面 21 | * 22 | * @return 当前页面索引 23 | */ 24 | int getCurrentPager(); 25 | } 26 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/utils/DensityUtils.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.utils; 2 | 3 | import android.content.Context; 4 | import android.util.TypedValue; 5 | 6 | /** 7 | * author : 颜洪毅 8 | * e-mail : yhyzgn@gmail.com 9 | * time : 2017-09-14 21:08 10 | * version: 1.0.0 11 | * desc : 12 | */ 13 | public class DensityUtils { 14 | private DensityUtils() { 15 | /* cannot be instantiated */ 16 | throw new UnsupportedOperationException("cannot be instantiated"); 17 | } 18 | 19 | /** 20 | * dp转px 21 | * 22 | * @param context 上下文对象 23 | * @param dpVal dp值 24 | * @return px值 25 | */ 26 | public static int dp2px(Context context, float dpVal) { 27 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); 28 | } 29 | 30 | /** 31 | * sp转px 32 | * 33 | * @param context 上下文对象 34 | * @param spVal sp值 35 | * @return px值 36 | */ 37 | public static int sp2px(Context context, float spVal) { 38 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, context.getResources().getDisplayMetrics()); 39 | } 40 | 41 | /** 42 | * px转dp 43 | * 44 | * @param context 上下文对象 45 | * @param pxVal px值 46 | * @return dp值 47 | */ 48 | public static float px2dp(Context context, float pxVal) { 49 | final float scale = context.getResources().getDisplayMetrics().density; 50 | return (pxVal / scale); 51 | } 52 | 53 | /** 54 | * px转sp 55 | * 56 | * @param context 上下文对象 57 | * @param pxVal px值 58 | * @return sp值 59 | */ 60 | public static float px2sp(Context context, float pxVal) { 61 | return (pxVal / context.getResources().getDisplayMetrics().scaledDensity); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/utils/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.utils; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.view.ViewParent; 6 | 7 | /** 8 | * author : 颜洪毅 9 | * e-mail : yhyzgn@gmail.com 10 | * time : 2017-09-14 21:08 11 | * version: 1.0.0 12 | * desc : 13 | */ 14 | public class ViewUtils { 15 | 16 | private ViewUtils() { 17 | } 18 | 19 | /** 20 | * 从View的Parent中移除该View 21 | * 22 | * @param view 要移除的View对象 23 | */ 24 | public static void removeFromParent(View view) { 25 | if (null != view) { 26 | ViewParent parent = view.getParent(); 27 | if (null != parent && parent instanceof ViewGroup) { 28 | ((ViewGroup) parent).removeView(view); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/widget/NavView.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Color; 7 | import android.graphics.drawable.Drawable; 8 | import android.util.AttributeSet; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.FrameLayout; 13 | import android.widget.RadioButton; 14 | import android.widget.RadioGroup; 15 | import android.widget.RelativeLayout; 16 | 17 | import com.yhy.badge.Badge; 18 | import com.yhy.badge.BadgeRadioButton; 19 | import com.yhy.badge.OnDismissListener; 20 | import com.yhy.badge.annotation.BadgeViews; 21 | import com.yhy.tabnav.R; 22 | import com.yhy.tabnav.adapter.NavAdapter; 23 | import com.yhy.tabnav.cache.PagerCache; 24 | import com.yhy.tabnav.listener.OnPageChangedListener; 25 | import com.yhy.tabnav.tpg.Pager; 26 | import com.yhy.tabnav.tpg.TabBadge; 27 | import com.yhy.tabnav.tpg.Tpg; 28 | import com.yhy.tabnav.utils.DensityUtils; 29 | import com.yhy.tabnav.utils.ViewUtils; 30 | import com.yhy.tabnav.widget.pager.TpgViewPager; 31 | 32 | import androidx.annotation.IdRes; 33 | import androidx.viewpager.widget.ViewPager; 34 | 35 | /** 36 | * author : 颜洪毅 37 | * e-mail : yhyzgn@gmail.com 38 | * time : 2017-09-14 21:08 39 | * version: 1.0.0 40 | * desc : 用于底部导航栏布局页面 41 | */ 42 | @BadgeViews(RadioButton.class) 43 | public class NavView extends RelativeLayout implements Tpg, TabBadge { 44 | // ViewPager的显示区域 45 | private FrameLayout flContent; 46 | // 显示内容的ViewPager 47 | private ViewPager vpContent; 48 | // 分割线 49 | private View vDivider; 50 | // 导航栏 51 | private RadioGroup rgTabs; 52 | //页面缓存 53 | private PagerCache mCache; 54 | //页面切换事件 55 | private OnPageChangedListener mPageChangedListener; 56 | //导航栏高度,默认为48dp 57 | private int mNavHeight; 58 | //整个导航栏背景色,默认为#ffffff 59 | private int mNavBgColor; 60 | //整个导航栏背景图,默认为null 61 | private Drawable mNavBgImg; 62 | //导航栏默认字体颜色,默认为#000000 63 | private int mNavTextDefaultColor; 64 | //导航栏选中的字体颜色,默认为#000000 65 | private int mNavTextCheckedColor; 66 | //导航栏选中的背景色,默认为透明 67 | private int mNavBgCheckedColor; 68 | //导航栏选中的背景图,默认为null 69 | private Drawable mNavBgCheckedImg; 70 | //导航栏与页面之间的分割线颜色,默认为透明,此时分割线不显示 71 | private int mNavDividerLineColor; 72 | //是否可滑动,默认可滑动 73 | private boolean mScrollAble; 74 | // 徽章背景颜色,默认:#ffff2200 75 | private int mBadgeBgColor; 76 | // 徽章字体颜色,默认:#ffffffff 77 | private int mBadgeTextColor; 78 | // 徽章是否可拖拽,默认:false,不可拖拽 79 | private boolean mBadgeDragEnable; 80 | 81 | public NavView(Context context) { 82 | this(context, null); 83 | } 84 | 85 | public NavView(Context context, AttributeSet attrs) { 86 | this(context, attrs, 0); 87 | } 88 | 89 | public NavView(Context context, AttributeSet attrs, int defStyleAttr) { 90 | super(context, attrs, defStyleAttr); 91 | init(context, attrs); 92 | } 93 | 94 | /** 95 | * 初始化一些属性 96 | * 97 | * @param context 上下文对象 98 | * @param attrs 属性集 99 | */ 100 | private void init(Context context, AttributeSet attrs) { 101 | TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.NavView); 102 | mNavHeight = (int) ta.getDimensionPixelSize(R.styleable.NavView_nav_height, DensityUtils.dp2px(context, 48)); 103 | mNavBgColor = ta.getColor(R.styleable.NavView_nav_bg_color, Color.TRANSPARENT); 104 | mNavBgImg = ta.getDrawable(R.styleable.NavView_nav_bg_img); 105 | mNavTextDefaultColor = ta.getColor(R.styleable.NavView_nav_text_default_color, Color.BLACK); 106 | mNavTextCheckedColor = ta.getColor(R.styleable.NavView_nav_text_checked_color, Color.BLACK); 107 | mNavBgCheckedColor = ta.getColor(R.styleable.NavView_nav_bg_checked_color, Color.TRANSPARENT); 108 | mNavBgCheckedImg = ta.getDrawable(R.styleable.NavView_nav_bg_checked_img); 109 | mNavDividerLineColor = ta.getColor(R.styleable.NavView_nav_divider_line_color, Color.TRANSPARENT); 110 | mScrollAble = ta.getBoolean(R.styleable.NavView_nav_scroll_able, true); 111 | mBadgeBgColor = ta.getColor(R.styleable.NavView_nav_badge_bg_color, Color.parseColor("#ffff2200")); 112 | mBadgeTextColor = ta.getColor(R.styleable.NavView_nav_badge_text_color, Color.WHITE); 113 | mBadgeDragEnable = ta.getBoolean(R.styleable.NavView_nav_badge_drag_enable, false); 114 | 115 | ta.recycle(); 116 | } 117 | 118 | @Override 119 | protected void onFinishInflate() { 120 | super.onFinishInflate(); 121 | 122 | // 动态配置ViewPager 123 | if (getChildCount() == 1) { 124 | // 如果有一个子控件,就尝试把它当作ViewPager 125 | View child = getChildAt(0); 126 | if (child instanceof ViewPager && child instanceof Pager) { 127 | vpContent = (ViewPager) child; 128 | } else { 129 | throw new IllegalStateException("NavView must has 0 or 1 child that implement 'Pager' interface and exptend 'ViewPager' class at same time."); 130 | } 131 | } else { 132 | // 否则默认创建 133 | vpContent = new TpgViewPager(getContext()); 134 | } 135 | // 设置默认Id 136 | if (vpContent.getId() == View.NO_ID) { 137 | vpContent.setId(vpContent.hashCode()); 138 | } 139 | 140 | //获取控件 141 | View view = LayoutInflater.from(getContext()).inflate(R.layout.widget_nav, this); 142 | vDivider = view.findViewById(R.id.v_divider); 143 | rgTabs = view.findViewById(R.id.rg_tabs); 144 | flContent = view.findViewById(R.id.fl_content); 145 | 146 | // 将ViewPager添加到界面 147 | if (null != vpContent) { 148 | ViewUtils.removeFromParent(vpContent); 149 | flContent.addView(vpContent); 150 | } 151 | 152 | setNavHeight((int) DensityUtils.px2dp(getContext(), mNavHeight)); 153 | 154 | //设置整个导航栏的背景。如果同时设置了颜色和图片做背景,以图片为主 155 | rgTabs.setBackgroundColor(mNavBgColor); 156 | if (null != mNavBgImg) { 157 | rgTabs.setBackgroundDrawable(mNavBgImg); 158 | } 159 | 160 | if (mNavDividerLineColor == Color.TRANSPARENT) { 161 | //如果颜色透明,就隐藏分割线 162 | vDivider.setVisibility(View.GONE); 163 | } else { 164 | //否则就显示分割线,并设置相应颜色 165 | vDivider.setBackgroundColor(mNavDividerLineColor); 166 | vDivider.setVisibility(View.VISIBLE); 167 | } 168 | 169 | //设置是否可滑动 170 | setScrollAble(mScrollAble); 171 | } 172 | 173 | /** 174 | * 设置整个导航栏高度 175 | * 176 | * @param dpHeight 导航栏高度 177 | */ 178 | public void setNavHeight(int dpHeight) { 179 | mNavHeight = DensityUtils.dp2px(getContext(), dpHeight); 180 | ViewGroup.LayoutParams params = rgTabs.getLayoutParams(); 181 | params.height = mNavHeight; 182 | rgTabs.setLayoutParams(params); 183 | } 184 | 185 | /** 186 | * 设置是否可滑动,默认可滑动 187 | * 188 | * @param scrollAble 是否可滑动 189 | */ 190 | public void setScrollAble(boolean scrollAble) { 191 | mScrollAble = scrollAble; 192 | if (vpContent instanceof Pager) { 193 | ((Pager) vpContent).setScrollAble(mScrollAble); 194 | } else { 195 | throw new UnsupportedOperationException("Not support setScrollAble unless implement 'Pager' interface."); 196 | } 197 | } 198 | 199 | /** 200 | * 设置适配器 201 | * 202 | * @param adapter 适配器 203 | */ 204 | public void setAdapter(NavAdapter adapter) { 205 | //动态生成菜单 206 | int pageCount = adapter.getCount(); 207 | RadioGroup.LayoutParams params = new RadioGroup.LayoutParams(RadioGroup.LayoutParams.WRAP_CONTENT, RadioGroup.LayoutParams.MATCH_PARENT, 1); 208 | //先移除所有的菜单项 209 | rgTabs.removeAllViews(); 210 | for (int i = 0; i < pageCount; i++) { 211 | BadgeRadioButton tab = (BadgeRadioButton) LayoutInflater.from(getContext()).inflate(R.layout.view_nav_tab, null); 212 | tab.setLayoutParams(params); 213 | tab.setCompoundDrawablesWithIntrinsicBounds(null, getResources().getDrawable(adapter.getTabIconId(i)), null, null); 214 | tab.setText(adapter.getPageTitle(i)); 215 | tab.setTextColor(mNavTextDefaultColor); 216 | tab.setTag(i); 217 | tab.getBadgeViewHelper().setBadgeBgColorInt(mBadgeBgColor); 218 | tab.getBadgeViewHelper().setBadgeTextColorInt(mBadgeTextColor); 219 | tab.getBadgeViewHelper().setDragEnable(mBadgeDragEnable); 220 | 221 | rgTabs.addView(tab, i); 222 | } 223 | 224 | //从适配器获取页面缓存 225 | mCache = adapter.getPagerCache(); 226 | 227 | //设置ViewPager适配器 228 | vpContent.setAdapter(adapter); 229 | 230 | //设置默认选中的菜单选项 231 | ((RadioButton) rgTabs.getChildAt(vpContent.getCurrentItem())).setChecked(true); 232 | //设置选中项样式 233 | setTabStyle((RadioButton) rgTabs.getChildAt(vpContent.getCurrentItem()), true); 234 | 235 | //初始化事件 236 | initListener(); 237 | 238 | //绑定适配器与NavView,为了在适配器中能获取到NavView中的某些数据,比如当前页面 239 | adapter.bindTpgView(this); 240 | } 241 | 242 | private void initListener() { 243 | //页面切换事件 244 | vpContent.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 245 | private int mPreviousState; 246 | private int mCurrentStat; 247 | 248 | @Override 249 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 250 | if (null != mPageChangedListener) { 251 | mPageChangedListener.onPageScrolled(position, positionOffset, positionOffsetPixels); 252 | } 253 | } 254 | 255 | @Override 256 | public void onPageSelected(int position) { 257 | // rgTabs.check(rgTabs.getChildAt(position).getId()); 258 | // 上述方法会导致其onCheckedChanged回调方法多次执行,只能用一下方法来设置选中,使onCheckedChanged方法只执行一次 259 | ((RadioButton) rgTabs.getChildAt(position)).setChecked(true); 260 | 261 | if (null != mPageChangedListener) { 262 | mPageChangedListener.onPageSelected(position); 263 | } 264 | } 265 | 266 | @Override 267 | public void onPageScrollStateChanged(int state) { 268 | mPreviousState = mCurrentStat; 269 | mCurrentStat = state; 270 | 271 | if (null != mPageChangedListener) { 272 | mPageChangedListener.onPageScrollStateChanged(state); 273 | } 274 | } 275 | }); 276 | 277 | //RadioGroup选中事件 278 | rgTabs.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 279 | @Override 280 | public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) { 281 | //底部菜单点击改变时设置页面选中状态 282 | int currentIndex = (int) group.findViewById(checkedId).getTag(); 283 | 284 | for (int i = 0; i < rgTabs.getChildCount(); i++) { 285 | //设置相关值 286 | setTabStyle(((RadioButton) rgTabs.getChildAt(i)), i == currentIndex); 287 | } 288 | vpContent.setCurrentItem(currentIndex, true); 289 | } 290 | }); 291 | } 292 | 293 | /** 294 | * 设置页面切换监听器 295 | * 296 | * @param listener 页面切换监听器 297 | */ 298 | public void setOnPageChangedListener(OnPageChangedListener listener) { 299 | mPageChangedListener = listener; 300 | } 301 | 302 | /** 303 | * 设置当前页 304 | * 305 | * @param index 页面索引 306 | */ 307 | @Override 308 | public void setCurrentPager(int index) { 309 | //设置页面标识设为true 310 | rgTabs.check(rgTabs.getChildAt(index).getId()); 311 | } 312 | 313 | /** 314 | * 获取当前页面 315 | */ 316 | @Override 317 | public int getCurrentPager() { 318 | return vpContent.getCurrentItem(); 319 | } 320 | 321 | /** 322 | * 显示圆点徽章 323 | * 324 | * @param index Tab的索引 325 | */ 326 | @Override 327 | public void showCirclePointBadge(int index) { 328 | getTabByIndex(index).showCirclePointBadge(); 329 | } 330 | 331 | /** 332 | * 显示文本徽章 333 | * 334 | * @param index Tab的索引 335 | * @param badgeText 显示的文字 336 | */ 337 | @Override 338 | public void showTextBadge(int index, String badgeText) { 339 | getTabByIndex(index).showTextBadge(badgeText); 340 | } 341 | 342 | /** 343 | * 隐藏徽章 344 | * 345 | * @param index Tab的索引 346 | */ 347 | @Override 348 | public void dismissBadge(int index) { 349 | getTabByIndex(index).hiddenBadge(); 350 | } 351 | 352 | /** 353 | * 显示图片徽章 354 | * 355 | * @param index Tab的索引 356 | * @param bitmap 图标 357 | */ 358 | @Override 359 | public void showDrawableBadge(int index, Bitmap bitmap) { 360 | getTabByIndex(index).showDrawableBadge(bitmap); 361 | } 362 | 363 | /** 364 | * 徽章是否显示 365 | * 366 | * @param index Tab的索引 367 | * @return 是否显示 368 | */ 369 | @Override 370 | public boolean isShowBadge(int index) { 371 | return getTabByIndex(index).isShowBadge(); 372 | } 373 | 374 | /** 375 | * 设置徽章背景颜色 376 | *

377 | * 作用范围:全部徽章 378 | * 379 | * @param color 背景颜色 380 | */ 381 | public void setBadgeBgColor(int color) { 382 | setBadgeBgColor(-1, color); 383 | } 384 | 385 | /** 386 | * 设置徽章背景颜色 387 | *

388 | * 作用范围:指定索引的徽章 389 | * 390 | * @param index 徽章索引 391 | * @param color 背景颜色 392 | */ 393 | public void setBadgeBgColor(int index, int color) { 394 | BadgeRadioButton tab; 395 | if (index > -1) { 396 | tab = getTabByIndex(index); 397 | if (null != tab) { 398 | tab.getBadgeViewHelper().setBadgeBgColorInt(color); 399 | } 400 | } else { 401 | int count = rgTabs.getChildCount(); 402 | for (int i = 0; i < count; i++) { 403 | tab = getTabByIndex(i); 404 | if (null != tab) { 405 | tab.getBadgeViewHelper().setBadgeBgColorInt(color); 406 | } 407 | } 408 | } 409 | } 410 | 411 | /** 412 | * 设置徽章字体颜色 413 | *

414 | * 作用范围:全部徽章 415 | * 416 | * @param color 字体颜色 417 | */ 418 | public void setBadgeTextColor(int color) { 419 | setBadgeTextColor(-1, color); 420 | } 421 | 422 | /** 423 | * 设置徽章字体颜色 424 | *

425 | * 作用范围:指定索引的徽章 426 | * 427 | * @param index 徽章索引 428 | * @param color 字体颜色 429 | */ 430 | public void setBadgeTextColor(int index, int color) { 431 | BadgeRadioButton tab; 432 | if (index > -1) { 433 | tab = getTabByIndex(index); 434 | if (null != tab) { 435 | tab.getBadgeViewHelper().setBadgeTextColorInt(color); 436 | } 437 | } else { 438 | int count = rgTabs.getChildCount(); 439 | for (int i = 0; i < count; i++) { 440 | tab = getTabByIndex(i); 441 | if (null != tab) { 442 | tab.getBadgeViewHelper().setBadgeTextColorInt(color); 443 | } 444 | } 445 | } 446 | } 447 | 448 | /** 449 | * 设置徽章是否可滑动 450 | *

451 | * 作用范围:全部徽章 452 | * 453 | * @param enable 是否可滑动 454 | */ 455 | public void setBadgeDragEnable(boolean enable) { 456 | setBadgeDragEnable(-1, enable); 457 | } 458 | 459 | /** 460 | * 设置徽章是否可滑动 461 | *

462 | * 作用范围:指定索引的徽章 463 | * 464 | * @param index 徽章索引 465 | * @param enable 是否可滑动 466 | */ 467 | public void setBadgeDragEnable(int index, boolean enable) { 468 | BadgeRadioButton tab; 469 | if (index > -1) { 470 | tab = getTabByIndex(index); 471 | if (null != tab) { 472 | tab.getBadgeViewHelper().setDragEnable(enable); 473 | } 474 | } else { 475 | int count = rgTabs.getChildCount(); 476 | for (int i = 0; i < count; i++) { 477 | tab = getTabByIndex(i); 478 | if (null != tab) { 479 | tab.getBadgeViewHelper().setDragEnable(enable); 480 | } 481 | } 482 | } 483 | } 484 | 485 | /** 486 | * 设置徽章销毁时的回调事件 487 | * 488 | * @param index Tab的索引 489 | * @param listener 回调事件 490 | */ 491 | @Override 492 | public void setOnDismissListener(int index, final OnDismissBadgeListener listener) { 493 | getTabByIndex(index).setOnDismissListener(new OnDismissListener() { 494 | @Override 495 | public void onDismiss(Badge badge) { 496 | if (null != listener) { 497 | listener.onDismiss(); 498 | } 499 | } 500 | }); 501 | } 502 | 503 | /** 504 | * 获取当前ViewPager 505 | * 506 | * @return 当前ViewPager 507 | */ 508 | public ViewPager getViewPager() { 509 | return vpContent; 510 | } 511 | 512 | /** 513 | * 设置Tab样式 514 | * 515 | * @param rb Tab项 516 | * @param checked 是否选中 517 | */ 518 | private void setTabStyle(RadioButton rb, boolean checked) { 519 | if (checked) { 520 | rb.setTextColor(mNavTextCheckedColor); 521 | if (null == mNavBgCheckedImg) { 522 | rb.setBackgroundColor(mNavBgCheckedColor); 523 | } else { 524 | rb.setBackgroundDrawable(mNavBgCheckedImg); 525 | } 526 | } else { 527 | rb.setTextColor(mNavTextDefaultColor); 528 | rb.setBackgroundColor(Color.TRANSPARENT); 529 | rb.setBackgroundDrawable(null); 530 | } 531 | } 532 | 533 | /** 534 | * 按索引获取Tab 535 | * 536 | * @param index 索引 537 | * @return Tab项 538 | */ 539 | private BadgeRadioButton getTabByIndex(int index) { 540 | int count = rgTabs.getChildCount(); 541 | if (index < 0 || index > count) { 542 | throw new IllegalArgumentException("The argument index must between 0 and RadioGroup's childCount"); 543 | } 544 | return (BadgeRadioButton) rgTabs.getChildAt(index); 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/widget/pager/HighestViewPager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.widget.pager; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.view.View; 7 | 8 | import com.yhy.tabnav.helper.PagerHelper; 9 | import com.yhy.tabnav.tpg.Pager; 10 | 11 | import androidx.viewpager.widget.ViewPager; 12 | 13 | /** 14 | * author : 颜洪毅 15 | * e-mail : yhyzgn@gmail.com 16 | * time : 2017-11-06 9:50 17 | * version: 1.0.0 18 | * desc : ViewPager按最高方式显示 19 | */ 20 | public class HighestViewPager extends ViewPager implements Pager { 21 | 22 | private PagerHelper mHelper; 23 | 24 | public HighestViewPager(Context context) { 25 | this(context, null); 26 | } 27 | 28 | public HighestViewPager(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | mHelper = new PagerHelper(this); 31 | } 32 | 33 | @Override 34 | public ViewPager getViewPager() { 35 | return this; 36 | } 37 | 38 | @Override 39 | public void setScrollAble(boolean scrollAble) { 40 | mHelper.setScrollAble(scrollAble); 41 | } 42 | 43 | @Override 44 | public boolean onSuperInterceptTouchEvent(MotionEvent ev) { 45 | return super.onInterceptTouchEvent(ev); 46 | } 47 | 48 | @Override 49 | public boolean onSuperTouchEvent(MotionEvent ev) { 50 | return super.onTouchEvent(ev); 51 | } 52 | 53 | @Override 54 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 55 | int height = 0; 56 | for (int i = 0; i < getChildCount(); i++) { 57 | View child = getChildAt(i); 58 | child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 59 | int h = child.getMeasuredHeight(); 60 | if (h > height) height = h; 61 | } 62 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 63 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 64 | } 65 | 66 | private int measureHeight(int measureSpec, View view) { 67 | int result = 0; 68 | int specMode = MeasureSpec.getMode(measureSpec); 69 | int specSize = MeasureSpec.getSize(measureSpec); 70 | 71 | if (specMode == MeasureSpec.EXACTLY) { 72 | result = specSize; 73 | } else { 74 | if (view != null) { 75 | result = view.getMeasuredHeight(); 76 | } 77 | if (specMode == MeasureSpec.AT_MOST) { 78 | result = Math.min(result, specSize); 79 | } 80 | } 81 | return result; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tabnav/src/main/java/com/yhy/tabnav/widget/pager/TpgViewPager.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav.widget.pager; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | 7 | import com.yhy.tabnav.helper.PagerHelper; 8 | import com.yhy.tabnav.tpg.Pager; 9 | 10 | import androidx.viewpager.widget.ViewPager; 11 | 12 | /** 13 | * author : 颜洪毅 14 | * e-mail : yhyzgn@gmail.com 15 | * time : 2017-09-14 21:09 16 | * version: 1.0.0 17 | * desc : 可控制是否可滑动的ViewPager 18 | */ 19 | public class TpgViewPager extends ViewPager implements Pager { 20 | // ViewPager辅助实例 21 | private PagerHelper mHelper; 22 | 23 | public TpgViewPager(Context context) { 24 | this(context, null); 25 | } 26 | 27 | public TpgViewPager(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | mHelper = new PagerHelper(this); 30 | } 31 | 32 | /** 33 | * 获取当前ViewPager 34 | * 35 | * @return 当前ViewPager 36 | */ 37 | @Override 38 | public ViewPager getViewPager() { 39 | return this; 40 | } 41 | 42 | /** 43 | * 控制页面是否可滑动 44 | * 45 | * @param scrollAble 是否可滑动 46 | */ 47 | @Override 48 | public void setScrollAble(boolean scrollAble) { 49 | mHelper.setScrollAble(scrollAble); 50 | } 51 | 52 | /** 53 | * 获取父类事件拦截器 54 | * 55 | * @param ev 事件 56 | * @return 拦截结果 57 | */ 58 | @Override 59 | public boolean onSuperInterceptTouchEvent(MotionEvent ev) { 60 | return super.onInterceptTouchEvent(ev); 61 | } 62 | 63 | /** 64 | * 获取父类事件处理器 65 | * 66 | * @param ev 事件 67 | * @return 处理结果 68 | */ 69 | @Override 70 | public boolean onSuperTouchEvent(MotionEvent ev) { 71 | return super.onTouchEvent(ev); 72 | } 73 | 74 | /** 75 | * 触发拦截触摸事件 76 | */ 77 | @Override 78 | public boolean onInterceptTouchEvent(MotionEvent ev) { 79 | return mHelper.onInterceptTouchEvent(ev); 80 | } 81 | 82 | /** 83 | * 设置禁止滑动 触发触摸事件 84 | */ 85 | @Override 86 | public boolean onTouchEvent(MotionEvent ev) { 87 | return mHelper.onTouchEvent(ev); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tabnav/src/main/res/layout/layout_def_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /tabnav/src/main/res/layout/layout_def_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /tabnav/src/main/res/layout/layout_def_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /tabnav/src/main/res/layout/view_nav_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tabnav/src/main/res/layout/widget_nav.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 19 | 20 | 26 | 27 | 28 | 33 | 34 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tabnav/src/main/res/layout/widget_tpg.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 20 | 21 | 28 | 29 | 38 | 39 | 40 | 45 | 46 | 50 | -------------------------------------------------------------------------------- /tabnav/src/main/res/mipmap-xxhdpi/ic_expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhyzgn/TabPager/b136fda517abead5aec9a11509e3a4544ccf6def/tabnav/src/main/res/mipmap-xxhdpi/ic_expand.png -------------------------------------------------------------------------------- /tabnav/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /tabnav/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #fe4365 4 | 5 | #aaff4400 6 | #ffff2200 7 | #ffff2200 8 | #2f2459 9 | 10 | @android:color/transparent 11 | -------------------------------------------------------------------------------- /tabnav/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TabPager 3 | 4 | -------------------------------------------------------------------------------- /tabnav/src/test/java/com/yhy/tabnav/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yhy.tabnav; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } --------------------------------------------------------------------------------