├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── jingbin │ │ └── bybannerview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── me │ │ │ └── jingbin │ │ │ └── bybannerview │ │ │ ├── BannerItemBean.java │ │ │ ├── MainActivity.java │ │ │ ├── RecyclerViewBannerActivity.java │ │ │ └── TimeUtil.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xxhdpi │ │ └── image.jpg │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_rv.xml │ │ ├── banner_item.xml │ │ ├── header_banner.xml │ │ ├── item_banner_three.xml │ │ └── item_banner_two.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── me │ └── jingbin │ └── bybannerview │ └── ExampleUnitTest.java ├── build.gradle ├── bybanner ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── jingbin │ │ └── banner │ │ ├── ByBannerView.java │ │ ├── config │ │ ├── BannerConfig.java │ │ ├── BannerScroller.java │ │ ├── BannerViewPager.java │ │ ├── OnBannerClickListener.java │ │ ├── OnBannerFilterClickListener.java │ │ ├── ScaleRightTransformer.java │ │ └── WeakHandler.java │ │ └── holder │ │ ├── ByBannerViewHolder.java │ │ └── HolderCreator.java │ └── res │ ├── drawable │ ├── by_gray_radius.xml │ └── by_white_radius.xml │ ├── layout │ └── layout_bybanner.xml │ └── values │ ├── attr.xml │ └── ids.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sbannerview.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | .idea/ 45 | 46 | # Keystore files 47 | # Uncomment the following line if you do not want to check your keystore files in. 48 | #*.jks 49 | 50 | # External native build folder generated in Android Studio 2.2 and later 51 | .externalNativeBuild 52 | 53 | # Google Services (e.g. APIs or Firebase) 54 | google-services.json 55 | 56 | # Freeline 57 | freeline.py 58 | freeline/ 59 | freeline_project_description.json 60 | 61 | # fastlane 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | fastlane/readme.md 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 示例图 2 | 3 | ![](https://github.com/youlookwhat/SBannerView/blob/master/sbannerview.gif) 4 | 5 | [![](https://jitpack.io/v/youlookwhat/ByBannerView.svg)](https://jitpack.io/#youlookwhat/ByBannerView) 6 | 7 | 8 | ### 重大更新!! 9 | > 上个版本1.1.7,如果是左右留边的banner样式,首次进去会有黏在一起的情况,但是没有人反馈,所以我认为这个库基本除了我没人使用,哈哈。如果有人使用看这里就好。 10 | 11 | - 1.更改依赖库名:`com.github.youlookwhat:ByBannerView:1.4.0` 12 | - 2.xml引入更换:`` 13 | - 3.混淆修正:`-keep class me.jingbin.banner.** {*;}` 14 | - 4.如果默认是返回轮播,查看是否添加属性:`app:is_back_loop="true"` 15 | 16 | 17 | #### how to use 18 | Step 1. Add it in your root build.gradle at the end of repositories: 19 | 20 | ```java 21 | allprojects { 22 | repositories { 23 | ... 24 | maven { url 'https://jitpack.io' } 25 | } 26 | } 27 | ``` 28 | 29 | Step 2. Add the dependency 30 | 31 | ```java 32 | dependencies { 33 | implementation 'com.github.youlookwhat:ByBannerView:Tag' 34 | } 35 | ``` 36 | 37 | 38 | #### 功能 39 | - 1.默认滑动到最后一条时,往回轮播 40 | - 2.可设置右边距的轮播图,也可支持左右都有间距的轮播图 41 | - 3.支持正常轮播图,并往回轮播或者循环轮播 42 | - 4.防止重复点击,点击事件实现:`OnBannerFilterClickListener` 43 | 44 | #### 属性解释 45 | 46 | | 属性 | 类型 | 默认值 | 属性说明 | 47 | | ---- | ---- | ---- | --- | 48 | | delay_time | integer | 2000ms | 延迟多少毫秒开始滚动 | 49 | | scroll_time | integer | 800ms | 滚动一页需要多少毫秒| 50 | | is_auto_play | boolean | true | 是否自动滚动 | 51 | | is_loop | boolean | true | 是否无限滚动,false则滚动到最后一个时停止滚动 | 52 | | is_back_loop | boolean |false|**滑到到最后一个时,是否返回滑动**| 53 | | indicator_width | dimension | DisplayWidth / 80 | 指示器的宽度 | 54 | | indicator_height | dimension | DisplayWidth / 80 | 指示器的高度 | 55 | | indicator_margin | dimension | 10dp | 指示器距banner最底部的距离 | 56 | | indicator_padding | dimension | 5dp | 指示器之间的左右边距 | 57 | | indicator_drawable_selected | reference | gray_radius.xml | 选中的指示器样式 | 58 | | indicator_drawable_unselected | reference | white_radius.xml | 未选中的指示器样式 | 59 | | page_left_margin | dimension | 0 | banner距屏幕的左边距 | 60 | | page_right_margin | dimension | 0 | banner距屏幕的右边距 | 61 | 62 | 其他方法: 63 | 64 | - `setIndicatorGravity(int type)`: 设置指示器的位置 (BannerConfig.LEFT/CENTER/RIGHT),默认居中`CENTER` 65 | - `setBannerStyle(int bannerStyle)`: 设置指示器样式 (默认`BannerConfig.CIRCLE_INDICATOR`) 66 | - NOT_INDICATOR: 取消指示器 67 | - CIRCLE_INDICATOR: 自带的指示器 68 | - CUSTOM_INDICATOR: 手动设置的指示器,不规定指示器宽高,随指示器自身的宽高 69 | 70 | #### 使用示例 71 | ```xml 72 | 80 | ``` 81 | 82 | ```java 83 | banner.setPageRightMargin(dip2px(this, 59)) 84 | .setBannerAnimation(ScaleRightTransformer.class) 85 | .setOffscreenPageLimit(list.size()) 86 | .setDelayTime(3000) 87 | .setPages(list, new HolderCreator() { 88 | @Override 89 | public SBannerViewHolder createViewHolder() { 90 | return new CustomViewHolder(); 91 | } 92 | }) 93 | .start(); 94 | banner.setOnBannerClickListener(new OnBannerClickListener() { 95 | @Override 96 | public void onBannerClick(int position) { 97 | 98 | } 99 | }); 100 | 101 | 102 | class CustomViewHolder implements SBannerViewHolder { 103 | 104 | private TextView mTextView; 105 | 106 | @Override 107 | public View createView(Context context) { 108 | View view = LayoutInflater.from(context).inflate(R.layout.banner_item, null); 109 | mTextView = (TextView) view.findViewById(R.id.text); 110 | return view; 111 | } 112 | 113 | @Override 114 | public void onBind(Context context, int position, BannerItemBean data) { 115 | 116 | } 117 | } 118 | ``` 119 | 120 | #### 混淆 121 | ```java 122 | -keep class me.jingbin.banner.** {*;} 123 | ``` -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | defaultConfig { 6 | applicationId "me.jingbin.bybannerview" 7 | minSdkVersion 14 8 | targetSdkVersion 29 9 | versionCode 3 10 | versionName "1.4.1" 11 | multiDexEnabled true //Add this 12 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'androidx.appcompat:appcompat:1.2.0' 25 | implementation 'androidx.cardview:cardview:1.0.0' 26 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 27 | testImplementation 'junit:junit:4.13.2' 28 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 29 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 30 | implementation project(':bybanner') 31 | implementation 'com.github.youlookwhat:ByRecyclerView:1.3.6' 32 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 33 | implementation 'com.google.android.material:material:1.2.1' 34 | 35 | // implementation 'com.github.youlookwhat:SBannerView:1.4.0' 36 | } 37 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -keep class me.jingbin.banner.** {*;} 23 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/jingbin/bybannerview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.bybannerview; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("me.jingbin.bybannerview", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/me/jingbin/bybannerview/BannerItemBean.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.bybannerview; 2 | 3 | public class BannerItemBean { 4 | 5 | private String title; 6 | private long applyEndTime; 7 | 8 | public String getTitle() { 9 | return title; 10 | } 11 | 12 | public void setTitle(String title) { 13 | this.title = title; 14 | } 15 | 16 | public long getApplyEndTime() { 17 | return applyEndTime; 18 | } 19 | 20 | public void setApplyEndTime(long applyEndTime) { 21 | this.applyEndTime = applyEndTime; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/me/jingbin/bybannerview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.bybannerview; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.os.CountDownTimer; 7 | import android.util.Log; 8 | import android.util.SparseArray; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import androidx.appcompat.app.AppCompatActivity; 15 | import androidx.appcompat.widget.AppCompatButton; 16 | import androidx.viewpager.widget.ViewPager; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import me.jingbin.banner.ByBannerView; 22 | import me.jingbin.banner.config.OnBannerClickListener; 23 | import me.jingbin.banner.config.OnBannerFilterClickListener; 24 | import me.jingbin.banner.config.ScaleRightTransformer; 25 | import me.jingbin.banner.holder.ByBannerViewHolder; 26 | import me.jingbin.banner.holder.HolderCreator; 27 | 28 | /** 29 | * @author jingbin 30 | */ 31 | public class MainActivity extends AppCompatActivity { 32 | 33 | private ByBannerView banner; 34 | private ByBannerView banner2; 35 | private ByBannerView banner3; 36 | // 用于退出activity,避免countdown,造成资源浪费。 37 | private final SparseArray countDownMap = new SparseArray<>(); 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_main); 43 | banner = findViewById(R.id.banner); 44 | banner2 = findViewById(R.id.banner2); 45 | banner3 = findViewById(R.id.banner3); 46 | 47 | final List list = getList(4); 48 | setBannerView(list); 49 | setBanner2View(list); 50 | setBanner3View(list); 51 | } 52 | 53 | private void setBannerView(final List list) { 54 | banner.setPageRightMargin(dip2px(this, 59)) 55 | .setCanClickSideRoll(true)// 是否点击边缘会滚动,默认是 56 | // .setAutoPlay(true) 57 | // .setBannerStyle(BannerConfig.NOT_INDICATOR) 58 | .setBannerAnimation(ScaleRightTransformer.class) 59 | .setOffscreenPageLimit(list.size()) 60 | .setDelayTime(3000) 61 | .setPages(list, new HolderCreator() { 62 | @Override 63 | public ByBannerViewHolder createViewHolder() { 64 | return new CustomViewHolder(); 65 | } 66 | }) 67 | .start(); 68 | banner.setOnBannerClickListener(new OnBannerFilterClickListener() { 69 | @Override 70 | public void onSingleClick(int position) { 71 | // OnBannerFilterClickListener 防止重复点击 72 | if (banner.getCurrentItem() == position) { 73 | // 一屏多页时,如果点击的是当前的position则跳转 74 | MainActivity.this.startActivity(new Intent(banner.getContext(), RecyclerViewBannerActivity.class)); 75 | } 76 | } 77 | }); 78 | banner.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 79 | @Override 80 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 81 | 82 | } 83 | 84 | @Override 85 | public void onPageSelected(int position) { 86 | Log.e("onPageSelected", "position:" + position); 87 | } 88 | 89 | @Override 90 | public void onPageScrollStateChanged(int state) { 91 | 92 | } 93 | }); 94 | } 95 | 96 | private void setBanner2View(final List list) { 97 | banner2 98 | // .setAutoPlay(true) 99 | // .setBannerStyle(BannerConfig.NOT_INDICATOR) 100 | .setBannerAnimation(ScaleRightTransformer.class) 101 | .setOffscreenPageLimit(list.size()) 102 | .setDelayTime(3000) 103 | .setPages(list, new HolderCreator() { 104 | @Override 105 | public ByBannerViewHolder createViewHolder() { 106 | return new CustomViewHolder2(); 107 | } 108 | }) 109 | .start(); 110 | banner2.setOnBannerClickListener(new OnBannerClickListener() { 111 | @Override 112 | public void onBannerClick(int position) { 113 | Toast.makeText(getApplicationContext(), list.get(position).getTitle(), Toast.LENGTH_LONG).show(); 114 | } 115 | }); 116 | } 117 | 118 | 119 | private void setBanner3View(final List list) { 120 | banner3.setAutoPlay(true) 121 | .setOffscreenPageLimit(list.size()) 122 | .setDelayTime(3000) 123 | .setPages(list, new HolderCreator() { 124 | @Override 125 | public ByBannerViewHolder createViewHolder() { 126 | return new CustomViewHolder3(); 127 | } 128 | }) 129 | .start(); 130 | banner3.setOnBannerClickListener(new OnBannerClickListener() { 131 | @Override 132 | public void onBannerClick(int position) { 133 | Toast.makeText(getApplicationContext(), list.get(position).getTitle(), Toast.LENGTH_LONG).show(); 134 | } 135 | }); 136 | } 137 | 138 | class CustomViewHolder implements ByBannerViewHolder { 139 | 140 | private TextView mTextView; 141 | private TextView tvDay; 142 | private TextView tvHour; 143 | private TextView tvMin; 144 | private TextView tvMiao; 145 | private AppCompatButton btRefresh; 146 | CountDownTimer countDownTimer; 147 | 148 | @Override 149 | public View createView(Context context) { 150 | View view = LayoutInflater.from(context).inflate(R.layout.banner_item, null); 151 | mTextView = (TextView) view.findViewById(R.id.text); 152 | btRefresh = (AppCompatButton) view.findViewById(R.id.bt_refresh); 153 | tvDay = (TextView) view.findViewById(R.id.tv_day); 154 | tvHour = (TextView) view.findViewById(R.id.tv_hour); 155 | tvMin = (TextView) view.findViewById(R.id.tv_min); 156 | tvMiao = (TextView) view.findViewById(R.id.tv_miao); 157 | return view; 158 | } 159 | 160 | @Override 161 | public void onBind(Context context, int position, BannerItemBean data) { 162 | if (position == 3) { 163 | btRefresh.setText("刷新"); 164 | btRefresh.setOnClickListener(new View.OnClickListener() { 165 | @Override 166 | public void onClick(View v) { 167 | setBannerView(getList(2)); 168 | setBanner2View(getList(2)); 169 | } 170 | }); 171 | } else { 172 | btRefresh.setText("立即申请"); 173 | btRefresh.setOnClickListener(null); 174 | } 175 | // 数据绑定 176 | mTextView.setText(String.format("%d:%s", position, data.getTitle())); 177 | 178 | long applyEndTime = data.getApplyEndTime(); 179 | long currentTime = System.currentTimeMillis(); 180 | long end = (applyEndTime - currentTime / 1000); 181 | if (end > 0) { 182 | //将前一个缓存清除 183 | if (countDownTimer != null) { 184 | countDownTimer.cancel(); 185 | } 186 | countDownTimer = new CountDownTimer(end * 1000, 1000) { 187 | @Override 188 | public void onTick(long millisUntilFinished) { 189 | ArrayList time = TimeUtil.getCountTimeByLong(millisUntilFinished / 1000); 190 | if (time.size() == 4) { 191 | CustomViewHolder.this.tvDay.setText(time.get(0)); 192 | CustomViewHolder.this.tvHour.setText(time.get(1)); 193 | CustomViewHolder.this.tvMin.setText(time.get(2)); 194 | CustomViewHolder.this.tvMiao.setText(time.get(3)); 195 | } 196 | } 197 | 198 | @Override 199 | public void onFinish() { 200 | } 201 | }.start(); 202 | countDownMap.put(tvDay.hashCode(), countDownTimer); 203 | } else { 204 | tvDay.setText("0"); 205 | tvHour.setText("0"); 206 | tvMin.setText("0"); 207 | tvMiao.setText("0"); 208 | } 209 | } 210 | } 211 | 212 | static class CustomViewHolder2 implements ByBannerViewHolder { 213 | 214 | @Override 215 | public View createView(Context context) { 216 | return LayoutInflater.from(context).inflate(R.layout.item_banner_two, null); 217 | } 218 | 219 | @Override 220 | public void onBind(Context context, int position, BannerItemBean data) { 221 | } 222 | } 223 | 224 | static class CustomViewHolder3 implements ByBannerViewHolder { 225 | 226 | @Override 227 | public View createView(Context context) { 228 | return LayoutInflater.from(context).inflate(R.layout.item_banner_three, null); 229 | } 230 | 231 | @Override 232 | public void onBind(Context context, int position, BannerItemBean data) { 233 | } 234 | } 235 | 236 | /** 237 | * 清空资源 238 | */ 239 | public void cancelAllTimers() { 240 | if (countDownMap != null) { 241 | Log.e("TAG", "size : " + countDownMap.size()); 242 | for (int i = 0, length = countDownMap.size(); i < length; i++) { 243 | CountDownTimer cdt = countDownMap.get(countDownMap.keyAt(i)); 244 | if (cdt != null) { 245 | cdt.cancel(); 246 | } 247 | } 248 | } 249 | } 250 | 251 | /** 252 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素) 253 | * xml文件里的dp ---> 手机像素里的px 254 | */ 255 | public static int dip2px(Context context, float dpValue) { 256 | final float scale = context.getResources().getDisplayMetrics().density; 257 | return (int) (dpValue * scale + 0.5f); 258 | } 259 | 260 | public static List getList(int size) { 261 | List list = new ArrayList<>(); 262 | for (int i = 0; i < size; i++) { 263 | BannerItemBean itemBean = new BannerItemBean(); 264 | itemBean.setTitle("我是标题-" + i); 265 | long applyEndTime; 266 | if (i == 0) { 267 | applyEndTime = System.currentTimeMillis() / 1000 + 6000; 268 | } else if (i == 1) { 269 | applyEndTime = System.currentTimeMillis() / 1000 + 5000; 270 | } else { 271 | applyEndTime = System.currentTimeMillis() / 1000 + 4000; 272 | } 273 | itemBean.setApplyEndTime(applyEndTime); 274 | list.add(itemBean); 275 | } 276 | return list; 277 | } 278 | 279 | @Override 280 | protected void onResume() { 281 | super.onResume(); 282 | //开始轮播 283 | banner.startAutoPlay(); 284 | banner2.startAutoPlay(); 285 | banner3.startAutoPlay(); 286 | } 287 | 288 | @Override 289 | protected void onPause() { 290 | super.onPause(); 291 | //结束轮播 292 | banner.stopAutoPlay(); 293 | banner2.stopAutoPlay(); 294 | banner3.stopAutoPlay(); 295 | } 296 | 297 | @Override 298 | protected void onDestroy() { 299 | super.onDestroy(); 300 | banner.releaseBanner(); 301 | banner2.releaseBanner(); 302 | banner3.releaseBanner(); 303 | cancelAllTimers(); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /app/src/main/java/me/jingbin/bybannerview/RecyclerViewBannerActivity.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.bybannerview; 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.Toast; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import me.jingbin.library.ByRecyclerView; 15 | import me.jingbin.library.adapter.BaseByViewHolder; 16 | import me.jingbin.library.adapter.BaseRecyclerAdapter; 17 | import me.jingbin.banner.ByBannerView; 18 | import me.jingbin.banner.config.OnBannerClickListener; 19 | import me.jingbin.banner.config.ScaleRightTransformer; 20 | import me.jingbin.banner.holder.HolderCreator; 21 | import me.jingbin.banner.holder.ByBannerViewHolder; 22 | 23 | /** 24 | * @author jingbin 25 | */ 26 | public class RecyclerViewBannerActivity extends AppCompatActivity { 27 | 28 | private ByBannerView banner2; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_rv); 34 | 35 | ByRecyclerView recyclerView = findViewById(R.id.recyclerView); 36 | View inflate = LayoutInflater.from(this).inflate(R.layout.header_banner, (ViewGroup) recyclerView.getParent(), false); 37 | banner2 = inflate.findViewById(R.id.banner2); 38 | recyclerView.addHeaderView(inflate); 39 | ArrayList strings = new ArrayList<>(); 40 | for (int i = 0; i < 40; i++) { 41 | strings.add("item"); 42 | } 43 | recyclerView.setAdapter(new BaseRecyclerAdapter(R.layout.layout_by_default_item_skeleton, strings) { 44 | @Override 45 | protected void bindView(BaseByViewHolder baseByViewHolder, String s, int i) { 46 | 47 | } 48 | }); 49 | recyclerView.setOnItemClickListener(new ByRecyclerView.OnItemClickListener() { 50 | @Override 51 | public void onClick(View view, int i) { 52 | banner2.update(MainActivity.getList(4)); 53 | } 54 | }); 55 | setBanner2View(MainActivity.getList(5)); 56 | 57 | } 58 | 59 | private void setBanner2View(final List list) { 60 | banner2 61 | // .setAutoPlay(true) 62 | // .setBannerStyle(BannerConfig.NOT_INDICATOR) 63 | // .setBannerAnimation(ScaleRightTransformer.class) 64 | .setOffscreenPageLimit(list.size()) 65 | .setDelayTime(3000) 66 | .setPages(list, new HolderCreator() { 67 | @Override 68 | public ByBannerViewHolder createViewHolder() { 69 | return new MainActivity.CustomViewHolder2(); 70 | } 71 | }) 72 | .start(); 73 | banner2.setOnBannerClickListener(new OnBannerClickListener() { 74 | @Override 75 | public void onBannerClick(int position) { 76 | Toast.makeText(getApplicationContext(), list.get(position).getTitle(), Toast.LENGTH_LONG).show(); 77 | } 78 | }); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/me/jingbin/bybannerview/TimeUtil.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.bybannerview; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class TimeUtil { 6 | 7 | public static ArrayList getCountTimeByLong(long finishTime) { 8 | long day = 0, hour = 0, minute = 0, second = 0; 9 | 10 | day = finishTime / 60 / 60 / 24; 11 | hour = (finishTime - day * 60 * 60 * 24) / 60 / 60; 12 | minute = (finishTime - day * 60 * 60 * 24 - hour * 60 * 60) / 60; 13 | second = (finishTime - day * 60 * 60 * 24 - hour * 60 * 60 - minute * 60); 14 | if (day < 0) { 15 | day = 0; 16 | } 17 | if (hour < 0) { 18 | hour = 0; 19 | } 20 | if (minute < 0) { 21 | minute = 0; 22 | } 23 | if (second < 0) { 24 | second = 0; 25 | } 26 | 27 | ArrayList list = new ArrayList<>(); 28 | list.add(String.valueOf(day)); 29 | list.add(String.valueOf(hour)); 30 | list.add(String.valueOf(minute)); 31 | list.add(String.valueOf(second)); 32 | return list; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/drawable-xxhdpi/image.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | 24 | 25 | 26 | 36 | 37 | 38 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_rv.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/banner_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 20 | 21 | 29 | 30 | 41 | 42 | 51 | 52 | 60 | 61 | 69 | 70 | 76 | 77 | 85 | 86 | 92 | 93 | 101 | 102 | 108 | 109 | 117 | 118 | 124 | 125 | 126 | 127 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /app/src/main/res/layout/header_banner.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_banner_three.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_banner_two.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2483D9 4 | #2483D9 5 | #AC69FE 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ByBannerView 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/me/jingbin/bybannerview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.bybannerview; 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() { 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 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.4.2' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | maven { url 'https://jitpack.io' } 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } 27 | -------------------------------------------------------------------------------- /bybanner/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /bybanner/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | // JitPack Maven 3 | apply plugin: 'com.github.dcendents.android-maven' 4 | // Your Group 5 | group='com.github.youlookwhat' 6 | 7 | android { 8 | compileSdkVersion 29 9 | 10 | defaultConfig { 11 | minSdkVersion 14 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 17 | 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | 32 | implementation 'androidx.appcompat:appcompat:1.2.0' 33 | } 34 | -------------------------------------------------------------------------------- /bybanner/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /bybanner/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/ByBannerView.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.drawable.Drawable; 6 | import android.util.AttributeSet; 7 | import android.util.DisplayMetrics; 8 | import android.view.Gravity; 9 | import android.view.LayoutInflater; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.FrameLayout; 14 | import android.widget.ImageView; 15 | import android.widget.ImageView.ScaleType; 16 | import android.widget.LinearLayout; 17 | import android.widget.RelativeLayout; 18 | 19 | 20 | import androidx.viewpager.widget.PagerAdapter; 21 | import androidx.viewpager.widget.ViewPager; 22 | 23 | import java.lang.reflect.Field; 24 | import java.util.ArrayList; 25 | import java.util.LinkedList; 26 | import java.util.List; 27 | 28 | import me.jingbin.banner.config.BannerConfig; 29 | import me.jingbin.banner.config.BannerScroller; 30 | import me.jingbin.banner.config.BannerViewPager; 31 | import me.jingbin.banner.config.OnBannerClickListener; 32 | import me.jingbin.banner.config.WeakHandler; 33 | import me.jingbin.banner.holder.ByBannerViewHolder; 34 | import me.jingbin.banner.holder.HolderCreator; 35 | 36 | 37 | /** 38 | * @author jingbin 39 | * link: https://github.com/youlookwhat/ByBannerView 40 | */ 41 | public class ByBannerView extends FrameLayout implements ViewPager.OnPageChangeListener { 42 | 43 | // 单个指示器左右的间距 44 | private int mIndicatorPadding = BannerConfig.PADDING_SIZE; 45 | // 指示器距离底部的高度 46 | private int mIndicatorMargin = BannerConfig.MARGIN_BOTTOM; 47 | // 指示器距离左侧的宽度 48 | private int mIndicatorMarginLeft = 0; 49 | // 指示器距离右侧的宽度 50 | private int mIndicatorMarginRight = 0; 51 | // 单个指示器的宽度 52 | private int mIndicatorWidth; 53 | // 单个指示器的高度 54 | private int mIndicatorHeight; 55 | // 指示器显示样式:不显示/自带/自定义 56 | private int bannerStyle = BannerConfig.CIRCLE_INDICATOR; 57 | // 滚动间隔时间 58 | private int delayTime = BannerConfig.TIME; 59 | // ViewPager切换滑动速度 时间越大速度越慢 60 | private int scrollTime = BannerConfig.DURATION; 61 | // 是否自动循环滚动,默认true 62 | private boolean isAutoPlay = BannerConfig.IS_AUTO_PLAY; 63 | // ViewPager是否能手动滑动,默认true 64 | private boolean isScroll = BannerConfig.IS_SCROLL; 65 | // 是否循环播放,false则循环一轮后停止,默认true 66 | private boolean isLoop = BannerConfig.IS_LOOP; 67 | // 滑动到头后,是否返回滑动,默认false。返回滑动时page_left_margin无效 68 | private boolean isBackLoop = BannerConfig.IS_BACK_LOOP; 69 | // 指示器的默认宽高 70 | private final int indicatorSize; 71 | // 一屏多页模式是否可点击侧边切换,默认为true 72 | private boolean isCanClickSideRoll = true; 73 | private static final int NUM = 5000; 74 | 75 | private int count = 0; 76 | private int gravity = -1; 77 | private int widthPixels; 78 | private int lastPosition; 79 | private int currentItem; 80 | private int mPageLeftMargin; 81 | private int mPageRightMargin; 82 | private List mDatas; 83 | private List indicatorImages; 84 | private HolderCreator creator; 85 | private WeakHandler handler = null; 86 | 87 | private Context context; 88 | private LinearLayout indicator; 89 | private BannerViewPager viewPager; 90 | private BannerPagerAdapter adapter; 91 | private OnBannerClickListener listener; 92 | private Drawable mIndicatorSelectedDrawable; 93 | private Drawable mIndicatorUnselectedDrawable; 94 | private ViewPager.OnPageChangeListener mOnPageChangeListener; 95 | private int mIndicatorSelectedResId = R.drawable.by_gray_radius; 96 | private int mIndicatorUnselectedResId = R.drawable.by_white_radius; 97 | 98 | public ByBannerView(Context context) { 99 | this(context, null); 100 | } 101 | 102 | public ByBannerView(Context context, AttributeSet attrs) { 103 | this(context, attrs, 0); 104 | } 105 | 106 | public ByBannerView(Context context, AttributeSet attrs, int defStyle) { 107 | super(context, attrs, defStyle); 108 | this.context = context; 109 | mDatas = new ArrayList<>(); 110 | indicatorImages = new ArrayList<>(); 111 | DisplayMetrics dm = context.getResources().getDisplayMetrics(); 112 | widthPixels = dm.widthPixels; 113 | indicatorSize = dm.widthPixels / 80; 114 | initView(context, attrs); 115 | } 116 | 117 | private void initView(Context context, AttributeSet attrs) { 118 | handleTypedArray(context, attrs); 119 | View view = LayoutInflater.from(context).inflate(R.layout.layout_bybanner, this, true); 120 | viewPager = view.findViewById(R.id.bannerViewPager); 121 | indicator = view.findViewById(R.id.circleIndicator); 122 | setPageLeftRightMargin(mPageLeftMargin, mPageRightMargin); 123 | RelativeLayout.LayoutParams indicatorParam = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 124 | indicatorParam.bottomMargin = mIndicatorMargin; 125 | indicatorParam.leftMargin = mIndicatorMarginLeft - mIndicatorPadding; 126 | indicatorParam.rightMargin = mIndicatorMarginRight - mIndicatorPadding; 127 | indicator.setLayoutParams(indicatorParam); 128 | initViewPagerScroll(); 129 | } 130 | 131 | private void handleTypedArray(Context context, AttributeSet attrs) { 132 | if (attrs == null) { 133 | return; 134 | } 135 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ByBannerView); 136 | mIndicatorWidth = typedArray.getDimensionPixelSize(R.styleable.ByBannerView_indicator_width, indicatorSize); 137 | mIndicatorHeight = typedArray.getDimensionPixelSize(R.styleable.ByBannerView_indicator_height, indicatorSize); 138 | mIndicatorPadding = typedArray.getDimensionPixelSize(R.styleable.ByBannerView_indicator_padding, BannerConfig.PADDING_SIZE); 139 | mIndicatorMargin = typedArray.getDimensionPixelSize(R.styleable.ByBannerView_indicator_margin, BannerConfig.MARGIN_BOTTOM); 140 | mIndicatorMarginLeft = typedArray.getDimensionPixelSize(R.styleable.ByBannerView_indicator_margin_left, 0); 141 | mIndicatorMarginRight = typedArray.getDimensionPixelSize(R.styleable.ByBannerView_indicator_margin_right, 0); 142 | mIndicatorSelectedResId = typedArray.getResourceId(R.styleable.ByBannerView_indicator_drawable_selected, R.drawable.by_gray_radius); 143 | mIndicatorUnselectedResId = typedArray.getResourceId(R.styleable.ByBannerView_indicator_drawable_unselected, R.drawable.by_white_radius); 144 | delayTime = typedArray.getInt(R.styleable.ByBannerView_delay_time, BannerConfig.TIME); 145 | scrollTime = typedArray.getInt(R.styleable.ByBannerView_scroll_time, BannerConfig.DURATION); 146 | isAutoPlay = typedArray.getBoolean(R.styleable.ByBannerView_is_auto_play, BannerConfig.IS_AUTO_PLAY); 147 | isLoop = typedArray.getBoolean(R.styleable.ByBannerView_is_loop, BannerConfig.IS_LOOP); 148 | isBackLoop = typedArray.getBoolean(R.styleable.ByBannerView_is_back_loop, BannerConfig.IS_BACK_LOOP); 149 | mPageLeftMargin = typedArray.getDimensionPixelSize(R.styleable.ByBannerView_page_left_margin, 0); 150 | mPageRightMargin = typedArray.getDimensionPixelSize(R.styleable.ByBannerView_page_right_margin, 0); 151 | currentItem = isBackLoop ? 0 : -1; 152 | typedArray.recycle(); 153 | } 154 | 155 | private void initViewPagerScroll() { 156 | try { 157 | Field mField = ViewPager.class.getDeclaredField("mScroller"); 158 | mField.setAccessible(true); 159 | BannerScroller scroller = new BannerScroller(viewPager.getContext()); 160 | scroller.setDuration(scrollTime); 161 | mField.set(viewPager, scroller); 162 | } catch (Exception ignored) { 163 | 164 | } 165 | } 166 | 167 | /** 168 | * 设置一屏多页时,点击边缘是否可切换 169 | */ 170 | public ByBannerView setCanClickSideRoll(boolean canClickSideRoll) { 171 | isCanClickSideRoll = canClickSideRoll; 172 | return this; 173 | } 174 | 175 | /** 176 | * 一屏多页时的右边距 177 | * 178 | * @param pageRightMargin banner距屏幕的右边距 179 | */ 180 | public ByBannerView setPageRightMargin(int pageRightMargin) { 181 | this.mPageRightMargin = pageRightMargin; 182 | setPageLeftRightMargin(mPageLeftMargin, mPageRightMargin); 183 | return this; 184 | } 185 | 186 | /** 187 | * 一屏多页时的左边距 188 | * 189 | * @param pageLeftMargin banner距屏幕的左边距 190 | */ 191 | public ByBannerView setPageLeftMargin(int pageLeftMargin) { 192 | this.mPageLeftMargin = pageLeftMargin; 193 | setPageLeftRightMargin(mPageLeftMargin, mPageRightMargin); 194 | return this; 195 | } 196 | 197 | /** 198 | * 一屏多页时的左右边距 199 | * 200 | * @param pageLeftMargin banner距屏幕的左边距 201 | * @param pageRightMargin banner距屏幕的右边距 202 | */ 203 | public ByBannerView setPageLeftRightMargin(int pageLeftMargin, int pageRightMargin) { 204 | if (pageLeftMargin > 0 || pageRightMargin > 0) { 205 | this.mPageLeftMargin = pageLeftMargin; 206 | this.mPageRightMargin = pageRightMargin; 207 | viewPager.setClipChildren(false); 208 | viewPager.setClipToPadding(false); 209 | viewPager.setOffscreenPageLimit(2); 210 | viewPager.setPadding(mPageLeftMargin, 0, mPageRightMargin, 0); 211 | } 212 | return this; 213 | } 214 | 215 | 216 | public ByBannerView setAutoPlay(boolean isAutoPlay) { 217 | this.isAutoPlay = isAutoPlay; 218 | return this; 219 | } 220 | 221 | public ByBannerView setLoop(boolean isLoop) { 222 | this.isLoop = isLoop; 223 | return this; 224 | } 225 | 226 | public ByBannerView setDelayTime(int delayTime) { 227 | this.delayTime = delayTime; 228 | return this; 229 | } 230 | 231 | public ByBannerView setIndicatorGravity(int type) { 232 | switch (type) { 233 | case BannerConfig.LEFT: 234 | this.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; 235 | break; 236 | case BannerConfig.CENTER: 237 | this.gravity = Gravity.CENTER; 238 | break; 239 | case BannerConfig.RIGHT: 240 | this.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; 241 | break; 242 | default: 243 | break; 244 | } 245 | return this; 246 | } 247 | 248 | public ByBannerView setBannerAnimation(Class transformer) { 249 | try { 250 | viewPager.setHandleAttached(false); 251 | viewPager.setPageTransformer(true, transformer.newInstance()); 252 | } catch (Exception ignored) { 253 | 254 | } 255 | return this; 256 | } 257 | 258 | public ByBannerView setOffscreenPageLimit(int limit) { 259 | if (viewPager != null) { 260 | viewPager.setOffscreenPageLimit(limit); 261 | } 262 | return this; 263 | } 264 | 265 | public ByBannerView setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer) { 266 | viewPager.setHandleAttached(false); 267 | viewPager.setPageTransformer(reverseDrawingOrder, transformer); 268 | return this; 269 | } 270 | 271 | public ByBannerView setBannerStyle(int bannerStyle) { 272 | this.bannerStyle = bannerStyle; 273 | return this; 274 | } 275 | 276 | public ByBannerView setViewPagerIsScroll(boolean isScroll) { 277 | this.isScroll = isScroll; 278 | return this; 279 | } 280 | 281 | public ByBannerView setPages(List datas, HolderCreator creator) { 282 | this.mDatas = datas; 283 | this.creator = creator; 284 | this.count = datas.size(); 285 | return this; 286 | } 287 | 288 | public void update(List imageUrls) { 289 | if (mDatas != null) { 290 | mDatas.clear(); 291 | mDatas.addAll(imageUrls); 292 | count = mDatas.size(); 293 | } 294 | if (indicatorImages != null) { 295 | indicatorImages.clear(); 296 | } 297 | start(); 298 | } 299 | 300 | public void updateBannerStyle(int bannerStyle) { 301 | indicator.setVisibility(GONE); 302 | this.bannerStyle = bannerStyle; 303 | start(); 304 | } 305 | 306 | public ByBannerView start() { 307 | if (count > 0) { 308 | setStyleUI(); 309 | setImageList(); 310 | setData(); 311 | } 312 | return this; 313 | } 314 | 315 | public ByBannerView setIndicatorRes(int select, int unSelect) { 316 | if (select < 0) { 317 | throw new RuntimeException("[Banner] --> The select res is not exist"); 318 | } 319 | if (unSelect < 0) { 320 | throw new RuntimeException("[Banner] --> The unSelect res is not exist"); 321 | } 322 | 323 | mIndicatorSelectedResId = select; 324 | mIndicatorUnselectedResId = unSelect; 325 | return this; 326 | } 327 | 328 | public ByBannerView setIndicatorRes(Drawable select, Drawable unSelect) { 329 | if (select == null || unSelect == null) { 330 | throw new RuntimeException("[Banner] --> The Drawable res is null"); 331 | } 332 | 333 | mIndicatorSelectedDrawable = select; 334 | mIndicatorUnselectedDrawable = unSelect; 335 | return this; 336 | } 337 | 338 | private void setStyleUI() { 339 | int visibility = count > 1 ? View.VISIBLE : View.GONE; 340 | switch (bannerStyle) { 341 | case BannerConfig.CIRCLE_INDICATOR: 342 | indicator.setVisibility(visibility); 343 | break; 344 | case BannerConfig.CUSTOM_INDICATOR: 345 | indicator.setVisibility(visibility); 346 | break; 347 | default: 348 | break; 349 | } 350 | } 351 | 352 | private void setImageList() { 353 | if (bannerStyle == BannerConfig.CIRCLE_INDICATOR || bannerStyle == BannerConfig.CUSTOM_INDICATOR) { 354 | createIndicator(); 355 | } 356 | } 357 | 358 | public void setCurrentItem(int item) { 359 | if (viewPager == null) return; 360 | if (isLoop && !isBackLoop) { 361 | // 循环滚动,不是实际的position 362 | int position = NUM / 2 - ((NUM / 2) % count) + 1 + item; 363 | if (position < NUM) { 364 | viewPager.setCurrentItem(position); 365 | startAutoPlay(); 366 | } 367 | } else { 368 | viewPager.setCurrentItem(item); 369 | } 370 | } 371 | 372 | public void setCurrentItem(int item, boolean smoothScroll) { 373 | if (viewPager == null) return; 374 | if (isLoop && !isBackLoop) { 375 | // 循环滚动,不是实际的position 376 | int position = NUM / 2 - ((NUM / 2) % count) + 1 + item; 377 | if (position < NUM) { 378 | viewPager.setCurrentItem(position, smoothScroll); 379 | startAutoPlay(); 380 | } 381 | } else { 382 | viewPager.setCurrentItem(item, smoothScroll); 383 | } 384 | } 385 | 386 | /** 387 | * 一屏多页状态时,点击边缘位置,定位到指定position 388 | * 389 | * @param position 点击的position 390 | */ 391 | private void setClipCurrentItem(int position) { 392 | if (viewPager == null || mDatas == null) { 393 | return; 394 | } 395 | int currentItem = getCurrentItem(); 396 | int realCurrentItem = position; 397 | if (!isBackLoop) { 398 | realCurrentItem = toRealPosition(position); 399 | } 400 | if (realCurrentItem > mDatas.size() - 1 || currentItem == realCurrentItem) { 401 | return; 402 | } 403 | viewPager.setCurrentItem(position); 404 | if (isLoop && !isBackLoop) { 405 | startAutoPlay(); 406 | } 407 | } 408 | 409 | public ViewPager getViewPager() { 410 | return viewPager; 411 | } 412 | 413 | private void createIndicator() { 414 | indicatorImages.clear(); 415 | indicator.removeAllViews(); 416 | for (int i = 0; i < count; i++) { 417 | ImageView imageView = new ImageView(context); 418 | imageView.setScaleType(ScaleType.CENTER_CROP); 419 | if (i == 0) { 420 | if (mIndicatorSelectedDrawable != null) { 421 | imageView.setImageDrawable(mIndicatorSelectedDrawable); 422 | } else { 423 | imageView.setImageResource(mIndicatorSelectedResId); 424 | } 425 | } else { 426 | if (mIndicatorUnselectedDrawable != null) { 427 | imageView.setImageDrawable(mIndicatorUnselectedDrawable); 428 | } else { 429 | imageView.setImageResource(mIndicatorUnselectedResId); 430 | } 431 | } 432 | indicatorImages.add(imageView); 433 | if (bannerStyle == BannerConfig.CIRCLE_INDICATOR) { 434 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight); 435 | params.leftMargin = mIndicatorPadding; 436 | params.rightMargin = mIndicatorPadding; 437 | indicator.addView(imageView, params); 438 | } else if (bannerStyle == BannerConfig.CUSTOM_INDICATOR) { 439 | LinearLayout.LayoutParams customParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); 440 | customParams.leftMargin = mIndicatorPadding; 441 | customParams.rightMargin = mIndicatorPadding; 442 | indicator.addView(imageView, customParams); 443 | } 444 | } 445 | if (gravity != -1) { 446 | indicator.setGravity(gravity); 447 | } 448 | } 449 | 450 | private void setData() { 451 | if (isLoop && !isBackLoop) { 452 | // 循环滚动 且 不是返回滚动 453 | currentItem = NUM / 2 - ((NUM / 2) % count) + 1; 454 | lastPosition = 1; 455 | } else { 456 | currentItem = 0; 457 | lastPosition = 0; 458 | } 459 | if (adapter == null) { 460 | adapter = new BannerPagerAdapter(); 461 | viewPager.addOnPageChangeListener(this); 462 | } 463 | viewPager.setAdapter(adapter); 464 | viewPager.setCurrentItem(currentItem); 465 | viewPager.setOffscreenPageLimit(count); 466 | viewPager.setScrollable(isScroll && count > 1); 467 | startAutoPlay(); 468 | } 469 | 470 | public void startAutoPlay() { 471 | if (isAutoPlay && count > 1) { 472 | // 设置了自动轮播且数据大于1 473 | if (handler == null) { 474 | handler = new WeakHandler(); 475 | } 476 | handler.removeCallbacks(task); 477 | handler.postDelayed(task, delayTime); 478 | } 479 | } 480 | 481 | public void stopAutoPlay() { 482 | if (isAutoPlay && handler != null) { 483 | handler.removeCallbacks(task); 484 | } 485 | } 486 | 487 | // 是否向右滑动 488 | private boolean isSlipRight = true; 489 | 490 | private final Runnable task = new Runnable() { 491 | @Override 492 | public void run() { 493 | if (count > 1) { 494 | if (handler == null) { 495 | handler = new WeakHandler(); 496 | } 497 | if (isBackLoop) { 498 | // 下一个 499 | if (isSlipRight) { 500 | 501 | // > 最大值 502 | int pagerCurrentItem = viewPager.getCurrentItem(); 503 | if (pagerCurrentItem >= adapter.getCount()) { 504 | pagerCurrentItem = adapter.getCount() - 1; 505 | } 506 | 507 | // 2+1 508 | currentItem = pagerCurrentItem + 1; 509 | if (currentItem == adapter.getCount()) { 510 | isSlipRight = false; 511 | } 512 | } else { 513 | int pagerCurrentItem = viewPager.getCurrentItem(); 514 | if (pagerCurrentItem <= 1) { 515 | pagerCurrentItem = 1; 516 | } 517 | currentItem = pagerCurrentItem - 1; 518 | if (currentItem <= 0) { 519 | isSlipRight = true; 520 | } 521 | } 522 | 523 | if (isLoop) { 524 | // 最后一个 向前滑 525 | if (currentItem == adapter.getCount()) { 526 | viewPager.setCurrentItem(currentItem); 527 | handler.post(task); 528 | } else { 529 | viewPager.setCurrentItem(currentItem); 530 | handler.postDelayed(task, delayTime); 531 | } 532 | } else { 533 | if (currentItem >= adapter.getCount()) { 534 | stopAutoPlay(); 535 | } else { 536 | viewPager.setCurrentItem(currentItem); 537 | handler.postDelayed(task, delayTime); 538 | } 539 | } 540 | } else { 541 | currentItem = viewPager.getCurrentItem() + 1; 542 | if (isLoop) { 543 | if (currentItem == adapter.getCount() - 1) { 544 | currentItem = 0; 545 | viewPager.setCurrentItem(currentItem, false); 546 | handler.post(task); 547 | } else { 548 | viewPager.setCurrentItem(currentItem); 549 | handler.postDelayed(task, delayTime); 550 | } 551 | } else { 552 | if (currentItem >= adapter.getCount()) { 553 | stopAutoPlay(); 554 | } else { 555 | viewPager.setCurrentItem(currentItem); 556 | handler.postDelayed(task, delayTime); 557 | } 558 | } 559 | } 560 | } 561 | } 562 | }; 563 | 564 | @Override 565 | public boolean dispatchTouchEvent(MotionEvent ev) { 566 | if (!isAutoPlay) { 567 | return super.dispatchTouchEvent(ev); 568 | } 569 | switch (ev.getAction()) { 570 | case MotionEvent.ACTION_UP: 571 | case MotionEvent.ACTION_CANCEL: 572 | case MotionEvent.ACTION_OUTSIDE: 573 | startAutoPlay(); 574 | break; 575 | case MotionEvent.ACTION_DOWN: 576 | // 按下时 x坐标位置 577 | float touchX = ev.getRawX(); 578 | // 去除两边间隔的区域 579 | if (touchX >= mPageLeftMargin && touchX < widthPixels - mPageRightMargin) { 580 | stopAutoPlay(); 581 | } 582 | break; 583 | default: 584 | break; 585 | } 586 | return super.dispatchTouchEvent(ev); 587 | } 588 | 589 | private class BannerPagerAdapter extends PagerAdapter { 590 | 591 | private LinkedList mViewCache; 592 | 593 | BannerPagerAdapter() { 594 | this.mViewCache = new LinkedList<>(); 595 | } 596 | 597 | @Override 598 | public int getCount() { 599 | if (mDatas == null) { 600 | return 0; 601 | } 602 | if (mDatas.size() == 1) { 603 | return mDatas.size(); 604 | } else if (mDatas.size() < 1) { 605 | return 0; 606 | } else { 607 | if (isBackLoop) { 608 | // 返回播放 609 | return mDatas.size(); 610 | } else { 611 | // 循环播放 612 | if (isLoop) { 613 | return NUM; 614 | } else { 615 | return mDatas.size(); 616 | } 617 | } 618 | } 619 | } 620 | 621 | @Override 622 | public boolean isViewFromObject(View view, Object object) { 623 | return view == object; 624 | } 625 | 626 | @Override 627 | public Object instantiateItem(ViewGroup container, final int position) { 628 | if (creator == null) { 629 | throw new RuntimeException("[Banner] --> The layout is not specified,请指定 holder"); 630 | } 631 | ByBannerViewHolder holder; 632 | View view; 633 | if (mViewCache.size() == 0) { 634 | holder = creator.createViewHolder(); 635 | view = holder.createView(container.getContext()); 636 | view.setTag(holder); 637 | } else { 638 | view = mViewCache.removeFirst(); 639 | holder = (ByBannerViewHolder) view.getTag(); 640 | } 641 | 642 | // 设置点击事件,在onBind设置点击事件可将此点击事件覆盖 643 | view.setOnClickListener(new OnClickListener() { 644 | @Override 645 | public void onClick(View v) { 646 | if (listener != null) { 647 | if (isBackLoop) { 648 | listener.onBannerClick(position); 649 | } else { 650 | listener.onBannerClick(toRealPosition(position)); 651 | } 652 | } 653 | if (isClipChildrenMode() && isCanClickSideRoll) { 654 | // 一屏多页且点击的不是当前page,则滚动到对应的page 655 | setClipCurrentItem(position); 656 | } 657 | } 658 | }); 659 | 660 | if (mDatas != null && mDatas.size() > 0) { 661 | if (isBackLoop) { 662 | holder.onBind(container.getContext(), position, mDatas.get(position)); 663 | } else { 664 | holder.onBind(container.getContext(), toRealPosition(position), mDatas.get(toRealPosition(position))); 665 | } 666 | } 667 | container.addView(view); 668 | return view; 669 | } 670 | 671 | @Override 672 | public void destroyItem(ViewGroup container, int position, Object object) { 673 | container.removeView((View) object); 674 | this.mViewCache.add((View) object); 675 | } 676 | 677 | } 678 | 679 | @Override 680 | public void onPageScrollStateChanged(int state) { 681 | if (mOnPageChangeListener != null) { 682 | mOnPageChangeListener.onPageScrollStateChanged(state); 683 | } 684 | } 685 | 686 | @Override 687 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 688 | if (mOnPageChangeListener != null) { 689 | if (isBackLoop) { 690 | mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); 691 | } else { 692 | mOnPageChangeListener.onPageScrolled(toRealPosition(position), positionOffset, positionOffsetPixels); 693 | } 694 | } 695 | } 696 | 697 | public int getCurrentItem() { 698 | if (isBackLoop) { 699 | return viewPager.getCurrentItem(); 700 | } else { 701 | return toRealPosition(viewPager.getCurrentItem()); 702 | } 703 | } 704 | 705 | @Override 706 | public void onPageSelected(int position) { 707 | currentItem = position; 708 | if (mOnPageChangeListener != null) { 709 | if (isBackLoop) { 710 | mOnPageChangeListener.onPageSelected(position); 711 | } else { 712 | mOnPageChangeListener.onPageSelected(toRealPosition(position)); 713 | } 714 | } 715 | if (bannerStyle == BannerConfig.CIRCLE_INDICATOR || bannerStyle == BannerConfig.CUSTOM_INDICATOR) { 716 | if (isLoop) { 717 | if (isBackLoop) { 718 | // 返回播放 719 | if (mIndicatorSelectedDrawable != null && mIndicatorUnselectedDrawable != null) { 720 | // 未选择的图片 721 | indicatorImages.get(lastPosition).setImageDrawable(mIndicatorUnselectedDrawable); 722 | // 选择的图片 723 | indicatorImages.get(position).setImageDrawable(mIndicatorSelectedDrawable); 724 | } else { 725 | indicatorImages.get(lastPosition).setImageResource(mIndicatorUnselectedResId); 726 | indicatorImages.get(position).setImageResource(mIndicatorSelectedResId); 727 | } 728 | } else { 729 | if (mIndicatorSelectedDrawable != null && mIndicatorUnselectedDrawable != null) { 730 | indicatorImages.get((lastPosition - 1 + count) % count).setImageDrawable(mIndicatorUnselectedDrawable); 731 | indicatorImages.get((position - 1 + count) % count).setImageDrawable(mIndicatorSelectedDrawable); 732 | } else { 733 | indicatorImages.get((lastPosition - 1 + count) % count).setImageResource(mIndicatorUnselectedResId); 734 | indicatorImages.get((position - 1 + count) % count).setImageResource(mIndicatorSelectedResId); 735 | } 736 | } 737 | } else { 738 | if (isBackLoop) { 739 | // 返回播放 740 | if (mIndicatorSelectedDrawable != null && mIndicatorUnselectedDrawable != null) { 741 | indicatorImages.get(lastPosition).setImageDrawable(mIndicatorUnselectedDrawable); 742 | indicatorImages.get(position).setImageDrawable(mIndicatorSelectedDrawable); 743 | } else { 744 | indicatorImages.get(lastPosition).setImageResource(mIndicatorUnselectedResId); 745 | indicatorImages.get(position).setImageResource(mIndicatorSelectedResId); 746 | } 747 | } else { 748 | if (mIndicatorSelectedDrawable != null && mIndicatorUnselectedDrawable != null) { 749 | indicatorImages.get((lastPosition + count) % count).setImageDrawable(mIndicatorUnselectedDrawable); 750 | indicatorImages.get((toRealPosition(position) + count) % count).setImageDrawable(mIndicatorSelectedDrawable); 751 | } else { 752 | indicatorImages.get((lastPosition + count) % count).setImageResource(mIndicatorUnselectedResId); 753 | indicatorImages.get((toRealPosition(position) + count) % count).setImageResource(mIndicatorSelectedResId); 754 | } 755 | } 756 | } 757 | lastPosition = position; 758 | } 759 | } 760 | 761 | /** 762 | * 是否是一屏多页状态 763 | */ 764 | private boolean isClipChildrenMode() { 765 | return (mPageLeftMargin > 0 || mPageRightMargin > 0); 766 | } 767 | 768 | private int toRealPosition(int position) { 769 | //int realPosition = (position - 1) % count; 770 | int realPosition; 771 | if (isLoop) { 772 | realPosition = (position - 1 + count) % count; 773 | } else { 774 | realPosition = (position + count) % count; 775 | } 776 | if (realPosition < 0) { 777 | realPosition += count; 778 | } 779 | return realPosition; 780 | } 781 | 782 | public void setOnBannerClickListener(OnBannerClickListener listener) { 783 | this.listener = listener; 784 | } 785 | 786 | public void setOnPageChangeListener(ViewPager.OnPageChangeListener onPageChangeListener) { 787 | mOnPageChangeListener = onPageChangeListener; 788 | } 789 | 790 | public void releaseBanner() { 791 | if (handler != null) { 792 | handler.removeCallbacksAndMessages(null); 793 | handler = null; 794 | } 795 | if (mDatas != null) { 796 | mDatas.clear(); 797 | } 798 | if (indicatorImages != null) { 799 | indicatorImages.clear(); 800 | } 801 | } 802 | } 803 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/config/BannerConfig.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.config; 2 | 3 | public class BannerConfig { 4 | 5 | /** 6 | * indicator style 7 | * NOT_INDICATOR: 取消指示器 8 | * CIRCLE_INDICATOR: 自带的指示器 9 | * CUSTOM_INDICATOR: 手动设置的指示器 10 | */ 11 | public static final int NOT_INDICATOR = 0; 12 | public static final int CIRCLE_INDICATOR = 1; 13 | public static final int CUSTOM_INDICATOR = 2; 14 | 15 | /** 16 | * indicator gravity 17 | */ 18 | public static final int LEFT = 5; 19 | public static final int CENTER = 6; 20 | public static final int RIGHT = 7; 21 | 22 | /** 23 | * layout_jbanner 24 | * PADDING_SIZE: 指示器大小 25 | * MARGIN_BOTTOM: 指示器距底部的距离 26 | * TIME: 滚动时间间隔 27 | * DURATION: ViewPager切换滑动速度 时间越大速度越慢 28 | * IS_AUTO_PLAY: 是否自动循环 29 | * IS_SCROLL: ViewPager是否能手动滑动 30 | * IS_LOOP: 是否循环播放,false则循环一轮后停止 31 | * IS_BACK_LOOP: 滑到到最后一个时,是否返回滑动,false则循环播放 32 | */ 33 | public static final int PADDING_SIZE = 5; 34 | public static final int MARGIN_BOTTOM = 10; 35 | public static final int TIME = 2000; 36 | public static final int DURATION = 800; 37 | public static final boolean IS_AUTO_PLAY = true; 38 | public static final boolean IS_SCROLL = true; 39 | public static final boolean IS_LOOP = true; 40 | public static final boolean IS_BACK_LOOP = false; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/config/BannerScroller.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.config; 2 | 3 | import android.content.Context; 4 | import android.view.animation.Interpolator; 5 | import android.widget.Scroller; 6 | 7 | public class BannerScroller extends Scroller { 8 | 9 | private int mDuration = BannerConfig.DURATION; 10 | 11 | public BannerScroller(Context context) { 12 | super(context); 13 | } 14 | 15 | public BannerScroller(Context context, Interpolator interpolator) { 16 | super(context, interpolator); 17 | } 18 | 19 | public BannerScroller(Context context, Interpolator interpolator, boolean flywheel) { 20 | super(context, interpolator, flywheel); 21 | } 22 | 23 | @Override 24 | public void startScroll(int startX, int startY, int dx, int dy, int duration) { 25 | super.startScroll(startX, startY, dx, dy, mDuration); 26 | } 27 | 28 | @Override 29 | public void startScroll(int startX, int startY, int dx, int dy) { 30 | super.startScroll(startX, startY, dx, dy, mDuration); 31 | } 32 | 33 | public void setDuration(int time) { 34 | mDuration = time; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/config/BannerViewPager.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.config; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | 7 | import androidx.viewpager.widget.ViewPager; 8 | 9 | import java.lang.reflect.Field; 10 | 11 | public class BannerViewPager extends ViewPager { 12 | 13 | // 是否能手动滑动 14 | private boolean scrollable = true; 15 | private boolean mHandleAttach = true; 16 | 17 | public BannerViewPager(Context context) { 18 | super(context); 19 | } 20 | 21 | public BannerViewPager(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | @Override 26 | public boolean onTouchEvent(MotionEvent ev) { 27 | try { 28 | return this.scrollable && super.onTouchEvent(ev); 29 | } catch (IllegalArgumentException ex) { 30 | ex.printStackTrace(); 31 | } 32 | return false; 33 | } 34 | 35 | @Override 36 | public boolean onInterceptTouchEvent(MotionEvent ev) { 37 | try { 38 | return this.scrollable && super.onInterceptTouchEvent(ev); 39 | } catch (IllegalArgumentException ex) { 40 | ex.printStackTrace(); 41 | } 42 | return false; 43 | } 44 | 45 | public void setScrollable(boolean scrollable) { 46 | this.scrollable = scrollable; 47 | } 48 | 49 | @Override 50 | protected void onAttachedToWindow() { 51 | super.onAttachedToWindow(); 52 | // 解决完全隐藏ViewPager,再回来时,第一次滑动时没有动画效果,并且,经常出现view没有加载的情况的bug 53 | if (mHandleAttach) { 54 | try { 55 | Field mFirstLayout = ViewPager.class.getDeclaredField("mFirstLayout"); 56 | mFirstLayout.setAccessible(true); 57 | mFirstLayout.set(this, false); 58 | setCurrentItem(getCurrentItem()); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | } 64 | 65 | @Override 66 | protected void onDetachedFromWindow() { 67 | super.onDetachedFromWindow(); 68 | // 如果设置左右有间距的banner样式时,会初次进去会黏在一起的bug。 69 | // 这样会有另一个问题,在banner不可见刷新banner时左右间距会出现与异常 70 | // if (!mHandleAttach) { 71 | // mHandleAttach = true; 72 | // } 73 | } 74 | 75 | public void setHandleAttached(boolean handleAttach) { 76 | if (mHandleAttach != handleAttach) { 77 | mHandleAttach = handleAttach; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/config/OnBannerClickListener.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.config; 2 | 3 | public interface OnBannerClickListener { 4 | 5 | /** 6 | * 点击事件 7 | */ 8 | void onBannerClick(int position); 9 | } 10 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/config/OnBannerFilterClickListener.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.config; 2 | 3 | /** 4 | * banner防止重复点击 5 | * 6 | * @author jingbin 7 | */ 8 | public abstract class OnBannerFilterClickListener implements OnBannerClickListener { 9 | 10 | private long mLastClickTime = 0L; 11 | private long mTimeInterval = 1000L; 12 | 13 | public OnBannerFilterClickListener() { 14 | } 15 | 16 | public OnBannerFilterClickListener(long interval) { 17 | this.mTimeInterval = interval; 18 | } 19 | 20 | @Override 21 | public void onBannerClick(int position) { 22 | long nowTime = System.currentTimeMillis(); 23 | if (nowTime - this.mLastClickTime > this.mTimeInterval) { 24 | this.mLastClickTime = nowTime; 25 | this.onSingleClick(position); 26 | } 27 | } 28 | 29 | protected abstract void onSingleClick(int position); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/config/ScaleRightTransformer.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.config; 2 | 3 | import android.view.View; 4 | 5 | import androidx.viewpager.widget.ViewPager; 6 | 7 | public class ScaleRightTransformer implements ViewPager.PageTransformer { 8 | 9 | private ViewPager viewPager; 10 | private static final float SCALE_X = 0.08f; 11 | 12 | @Override 13 | public void transformPage(View view, float position) { 14 | if (viewPager == null) { 15 | viewPager = (ViewPager) view.getParent(); 16 | } 17 | int leftInScreen = view.getLeft() - viewPager.getScrollX(); 18 | int centerXInViewPager = leftInScreen + view.getMeasuredWidth() / 2; 19 | int offsetX = centerXInViewPager - viewPager.getMeasuredWidth() / 2; 20 | float offsetRate = (float) offsetX * SCALE_X / viewPager.getMeasuredWidth(); 21 | float scaleFactor = 1 - Math.abs(offsetRate); 22 | if (scaleFactor > 0) { 23 | view.setScaleX(scaleFactor); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/config/WeakHandler.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.config; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.os.Message; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | import androidx.annotation.VisibleForTesting; 10 | 11 | import java.lang.ref.WeakReference; 12 | import java.util.concurrent.locks.Lock; 13 | import java.util.concurrent.locks.ReentrantLock; 14 | 15 | public class WeakHandler { 16 | 17 | private final Handler.Callback mCallback; // hard reference to Callback. We need to keep callback in memory 18 | private final ExecHandler mExec; 19 | private Lock mLock = new ReentrantLock(); 20 | @SuppressWarnings("ConstantConditions") 21 | @VisibleForTesting 22 | final ChainedRef mRunnables = new ChainedRef(mLock, null); 23 | 24 | /** 25 | * Default constructor associates this handler with the {@link Looper} for the 26 | * current thread. 27 | *

28 | * If this thread does not have a looper, this handler won't be able to receive messages 29 | * so an exception is thrown. 30 | */ 31 | public WeakHandler() { 32 | mCallback = null; 33 | mExec = new ExecHandler(); 34 | } 35 | 36 | /** 37 | * Constructor associates this handler with the {@link Looper} for the 38 | * current thread and takes a callback interface in which you can handle 39 | * messages. 40 | *

41 | * If this thread does not have a looper, this handler won't be able to receive messages 42 | * so an exception is thrown. 43 | * 44 | * @param callback The callback interface in which to handle messages, or null. 45 | */ 46 | public WeakHandler(@Nullable Handler.Callback callback) { 47 | mCallback = callback; // Hard referencing body 48 | mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler 49 | } 50 | 51 | /** 52 | * Use the provided {@link Looper} instead of the default one. 53 | * 54 | * @param looper The looper, must not be null. 55 | */ 56 | public WeakHandler(@NonNull Looper looper) { 57 | mCallback = null; 58 | mExec = new ExecHandler(looper); 59 | } 60 | 61 | /** 62 | * Use the provided {@link Looper} instead of the default one and take a callback 63 | * interface in which to handle messages. 64 | * 65 | * @param looper The looper, must not be null. 66 | * @param callback The callback interface in which to handle messages, or null. 67 | */ 68 | public WeakHandler(@NonNull Looper looper, @NonNull Handler.Callback callback) { 69 | mCallback = callback; 70 | mExec = new ExecHandler(looper, new WeakReference<>(callback)); 71 | } 72 | 73 | /** 74 | * Causes the Runnable r to be added to the message queue. 75 | * The runnable will be run on the thread to which this handler is 76 | * attached. 77 | * 78 | * @param r The Runnable that will be executed. 79 | * @return Returns true if the Runnable was successfully placed in to the 80 | * message queue. Returns false on failure, usually because the 81 | * looper processing the message queue is exiting. 82 | */ 83 | public final boolean post(@NonNull Runnable r) { 84 | return mExec.post(wrapRunnable(r)); 85 | } 86 | 87 | /** 88 | * Causes the Runnable r to be added to the message queue, to be run 89 | * at a specific time given by uptimeMillis. 90 | * The time-base is {@link android.os.SystemClock#uptimeMillis}. 91 | * The runnable will be run on the thread to which this handler is attached. 92 | * 93 | * @param r The Runnable that will be executed. 94 | * @param uptimeMillis The absolute time at which the callback should run, 95 | * using the {@link android.os.SystemClock#uptimeMillis} time-base. 96 | * @return Returns true if the Runnable was successfully placed in to the 97 | * message queue. Returns false on failure, usually because the 98 | * looper processing the message queue is exiting. Note that a 99 | * result of true does not mean the Runnable will be processed -- if 100 | * the looper is quit before the delivery time of the message 101 | * occurs then the message will be dropped. 102 | */ 103 | public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) { 104 | return mExec.postAtTime(wrapRunnable(r), uptimeMillis); 105 | } 106 | 107 | /** 108 | * Causes the Runnable r to be added to the message queue, to be run 109 | * at a specific time given by uptimeMillis. 110 | * The time-base is {@link android.os.SystemClock#uptimeMillis}. 111 | * The runnable will be run on the thread to which this handler is attached. 112 | * 113 | * @param r The Runnable that will be executed. 114 | * @param uptimeMillis The absolute time at which the callback should run, 115 | * using the {@link android.os.SystemClock#uptimeMillis} time-base. 116 | * @return Returns true if the Runnable was successfully placed in to the 117 | * message queue. Returns false on failure, usually because the 118 | * looper processing the message queue is exiting. Note that a 119 | * result of true does not mean the Runnable will be processed -- if 120 | * the looper is quit before the delivery time of the message 121 | * occurs then the message will be dropped. 122 | * @see android.os.SystemClock#uptimeMillis 123 | */ 124 | public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) { 125 | return mExec.postAtTime(wrapRunnable(r), token, uptimeMillis); 126 | } 127 | 128 | /** 129 | * Causes the Runnable r to be added to the message queue, to be run 130 | * after the specified amount of time elapses. 131 | * The runnable will be run on the thread to which this handler 132 | * is attached. 133 | * 134 | * @param r The Runnable that will be executed. 135 | * @param delayMillis The delay (in milliseconds) until the Runnable 136 | * will be executed. 137 | * @return Returns true if the Runnable was successfully placed in to the 138 | * message queue. Returns false on failure, usually because the 139 | * looper processing the message queue is exiting. Note that a 140 | * result of true does not mean the Runnable will be processed -- 141 | * if the looper is quit before the delivery time of the message 142 | * occurs then the message will be dropped. 143 | */ 144 | public final boolean postDelayed(Runnable r, long delayMillis) { 145 | return mExec.postDelayed(wrapRunnable(r), delayMillis); 146 | } 147 | 148 | /** 149 | * Posts a message to an object that implements Runnable. 150 | * Causes the Runnable r to executed on the next iteration through the 151 | * message queue. The runnable will be run on the thread to which this 152 | * handler is attached. 153 | * This method is only for use in very special circumstances -- it 154 | * can easily starve the message queue, cause ordering problems, or have 155 | * other unexpected side-effects. 156 | * 157 | * @param r The Runnable that will be executed. 158 | * @return Returns true if the message was successfully placed in to the 159 | * message queue. Returns false on failure, usually because the 160 | * looper processing the message queue is exiting. 161 | */ 162 | public final boolean postAtFrontOfQueue(Runnable r) { 163 | return mExec.postAtFrontOfQueue(wrapRunnable(r)); 164 | } 165 | 166 | /** 167 | * Remove any pending posts of Runnable r that are in the message queue. 168 | */ 169 | public final void removeCallbacks(Runnable r) { 170 | final WeakRunnable runnable = mRunnables.remove(r); 171 | if (runnable != null) { 172 | mExec.removeCallbacks(runnable); 173 | } 174 | } 175 | 176 | /** 177 | * Remove any pending posts of Runnable r with Object 178 | * token that are in the message queue. If token is null, 179 | * all callbacks will be removed. 180 | */ 181 | public final void removeCallbacks(Runnable r, Object token) { 182 | final WeakRunnable runnable = mRunnables.remove(r); 183 | if (runnable != null) { 184 | mExec.removeCallbacks(runnable, token); 185 | } 186 | } 187 | 188 | /** 189 | * Pushes a message onto the end of the message queue after all pending messages 190 | * before the current time. It will be received in callback, 191 | * in the thread attached to this handler. 192 | * 193 | * @return Returns true if the message was successfully placed in to the 194 | * message queue. Returns false on failure, usually because the 195 | * looper processing the message queue is exiting. 196 | */ 197 | public final boolean sendMessage(Message msg) { 198 | return mExec.sendMessage(msg); 199 | } 200 | 201 | /** 202 | * Sends a Message containing only the what value. 203 | * 204 | * @return Returns true if the message was successfully placed in to the 205 | * message queue. Returns false on failure, usually because the 206 | * looper processing the message queue is exiting. 207 | */ 208 | public final boolean sendEmptyMessage(int what) { 209 | return mExec.sendEmptyMessage(what); 210 | } 211 | 212 | /** 213 | * Sends a Message containing only the what value, to be delivered 214 | * after the specified amount of time elapses. 215 | * 216 | * @return Returns true if the message was successfully placed in to the 217 | * message queue. Returns false on failure, usually because the 218 | * looper processing the message queue is exiting. 219 | * @see #sendMessageDelayed(Message, long) 220 | */ 221 | public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { 222 | return mExec.sendEmptyMessageDelayed(what, delayMillis); 223 | } 224 | 225 | /** 226 | * Sends a Message containing only the what value, to be delivered 227 | * at a specific time. 228 | * 229 | * @return Returns true if the message was successfully placed in to the 230 | * message queue. Returns false on failure, usually because the 231 | * looper processing the message queue is exiting. 232 | * @see #sendMessageAtTime(Message, long) 233 | */ 234 | public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { 235 | return mExec.sendEmptyMessageAtTime(what, uptimeMillis); 236 | } 237 | 238 | /** 239 | * Enqueue a message into the message queue after all pending messages 240 | * before (current time + delayMillis). You will receive it in 241 | * callback, in the thread attached to this handler. 242 | * 243 | * @return Returns true if the message was successfully placed in to the 244 | * message queue. Returns false on failure, usually because the 245 | * looper processing the message queue is exiting. Note that a 246 | * result of true does not mean the message will be processed -- if 247 | * the looper is quit before the delivery time of the message 248 | * occurs then the message will be dropped. 249 | */ 250 | public final boolean sendMessageDelayed(Message msg, long delayMillis) { 251 | return mExec.sendMessageDelayed(msg, delayMillis); 252 | } 253 | 254 | /** 255 | * Enqueue a message into the message queue after all pending messages 256 | * before the absolute time (in milliseconds) uptimeMillis. 257 | * The time-base is {@link android.os.SystemClock#uptimeMillis}. 258 | * You will receive it in callback, in the thread attached 259 | * to this handler. 260 | * 261 | * @param uptimeMillis The absolute time at which the message should be 262 | * delivered, using the 263 | * {@link android.os.SystemClock#uptimeMillis} time-base. 264 | * @return Returns true if the message was successfully placed in to the 265 | * message queue. Returns false on failure, usually because the 266 | * looper processing the message queue is exiting. Note that a 267 | * result of true does not mean the message will be processed -- if 268 | * the looper is quit before the delivery time of the message 269 | * occurs then the message will be dropped. 270 | */ 271 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 272 | return mExec.sendMessageAtTime(msg, uptimeMillis); 273 | } 274 | 275 | /** 276 | * Enqueue a message at the front of the message queue, to be processed on 277 | * the next iteration of the message loop. You will receive it in 278 | * callback, in the thread attached to this handler. 279 | * This method is only for use in very special circumstances -- it 280 | * can easily starve the message queue, cause ordering problems, or have 281 | * other unexpected side-effects. 282 | * 283 | * @return Returns true if the message was successfully placed in to the 284 | * message queue. Returns false on failure, usually because the 285 | * looper processing the message queue is exiting. 286 | */ 287 | public final boolean sendMessageAtFrontOfQueue(Message msg) { 288 | return mExec.sendMessageAtFrontOfQueue(msg); 289 | } 290 | 291 | /** 292 | * Remove any pending posts of messages with code 'what' that are in the 293 | * message queue. 294 | */ 295 | public final void removeMessages(int what) { 296 | mExec.removeMessages(what); 297 | } 298 | 299 | /** 300 | * Remove any pending posts of messages with code 'what' and whose obj is 301 | * 'object' that are in the message queue. If object is null, 302 | * all messages will be removed. 303 | */ 304 | public final void removeMessages(int what, Object object) { 305 | mExec.removeMessages(what, object); 306 | } 307 | 308 | /** 309 | * Remove any pending posts of callbacks and sent messages whose 310 | * obj is token. If token is null, 311 | * all callbacks and messages will be removed. 312 | */ 313 | public final void removeCallbacksAndMessages(Object token) { 314 | mExec.removeCallbacksAndMessages(token); 315 | } 316 | 317 | /** 318 | * Check if there are any pending posts of messages with code 'what' in 319 | * the message queue. 320 | */ 321 | public final boolean hasMessages(int what) { 322 | return mExec.hasMessages(what); 323 | } 324 | 325 | /** 326 | * Check if there are any pending posts of messages with code 'what' and 327 | * whose obj is 'object' in the message queue. 328 | */ 329 | public final boolean hasMessages(int what, Object object) { 330 | return mExec.hasMessages(what, object); 331 | } 332 | 333 | public final Looper getLooper() { 334 | return mExec.getLooper(); 335 | } 336 | 337 | private WeakRunnable wrapRunnable(@NonNull Runnable r) { 338 | //noinspection ConstantConditions 339 | if (r == null) { 340 | throw new NullPointerException("Runnable can't be null"); 341 | } 342 | final ChainedRef hardRef = new ChainedRef(mLock, r); 343 | mRunnables.insertAfter(hardRef); 344 | return hardRef.wrapper; 345 | } 346 | 347 | private static class ExecHandler extends Handler { 348 | private final WeakReference mCallback; 349 | 350 | ExecHandler() { 351 | mCallback = null; 352 | } 353 | 354 | ExecHandler(WeakReference callback) { 355 | mCallback = callback; 356 | } 357 | 358 | ExecHandler(Looper looper) { 359 | super(looper); 360 | mCallback = null; 361 | } 362 | 363 | ExecHandler(Looper looper, WeakReference callback) { 364 | super(looper); 365 | mCallback = callback; 366 | } 367 | 368 | @Override 369 | public void handleMessage(@NonNull Message msg) { 370 | if (mCallback == null) { 371 | return; 372 | } 373 | final Callback callback = mCallback.get(); 374 | if (callback == null) { // Already disposed 375 | return; 376 | } 377 | callback.handleMessage(msg); 378 | } 379 | } 380 | 381 | static class WeakRunnable implements Runnable { 382 | private final WeakReference mDelegate; 383 | private final WeakReference mReference; 384 | 385 | WeakRunnable(WeakReference delegate, WeakReference reference) { 386 | mDelegate = delegate; 387 | mReference = reference; 388 | } 389 | 390 | @Override 391 | public void run() { 392 | final Runnable delegate = mDelegate.get(); 393 | final ChainedRef reference = mReference.get(); 394 | if (reference != null) { 395 | reference.remove(); 396 | } 397 | if (delegate != null) { 398 | delegate.run(); 399 | } 400 | } 401 | } 402 | 403 | static class ChainedRef { 404 | @Nullable 405 | ChainedRef next; 406 | @Nullable 407 | ChainedRef prev; 408 | @NonNull 409 | final Runnable runnable; 410 | @NonNull 411 | final WeakRunnable wrapper; 412 | 413 | @NonNull 414 | Lock lock; 415 | 416 | public ChainedRef(@NonNull Lock lock, @NonNull Runnable r) { 417 | this.runnable = r; 418 | this.lock = lock; 419 | this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this)); 420 | } 421 | 422 | public WeakRunnable remove() { 423 | lock.lock(); 424 | try { 425 | if (prev != null) { 426 | prev.next = next; 427 | } 428 | if (next != null) { 429 | next.prev = prev; 430 | } 431 | prev = null; 432 | next = null; 433 | } finally { 434 | lock.unlock(); 435 | } 436 | return wrapper; 437 | } 438 | 439 | public void insertAfter(@NonNull ChainedRef candidate) { 440 | lock.lock(); 441 | try { 442 | if (this.next != null) { 443 | this.next.prev = candidate; 444 | } 445 | 446 | candidate.next = this.next; 447 | this.next = candidate; 448 | candidate.prev = this; 449 | } finally { 450 | lock.unlock(); 451 | } 452 | } 453 | 454 | @Nullable 455 | public WeakRunnable remove(Runnable obj) { 456 | lock.lock(); 457 | try { 458 | ChainedRef curr = this.next; // Skipping head 459 | while (curr != null) { 460 | if (curr.runnable == obj) { // We do comparison exactly how Handler does inside 461 | return curr.remove(); 462 | } 463 | curr = curr.next; 464 | } 465 | } finally { 466 | lock.unlock(); 467 | } 468 | return null; 469 | } 470 | } 471 | } -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/holder/ByBannerViewHolder.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.holder; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | public interface ByBannerViewHolder { 7 | 8 | /** 9 | * 创建View 10 | */ 11 | View createView(Context context); 12 | 13 | /** 14 | * 绑定数据 15 | */ 16 | void onBind(Context context, int position, T data); 17 | } 18 | -------------------------------------------------------------------------------- /bybanner/src/main/java/me/jingbin/banner/holder/HolderCreator.java: -------------------------------------------------------------------------------- 1 | package me.jingbin.banner.holder; 2 | 3 | /** 4 | * Created by songwenchao 5 | * on 2018/5/16 0016. 6 | *

7 | * 类名 8 | * 需要 -- 9 | * 可以 -- 10 | */ 11 | public interface HolderCreator { 12 | 13 | /** 14 | * 创建ViewHolder 15 | */ 16 | VH createViewHolder(); 17 | } 18 | -------------------------------------------------------------------------------- /bybanner/src/main/res/drawable/by_gray_radius.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /bybanner/src/main/res/drawable/by_white_radius.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /bybanner/src/main/res/layout/layout_bybanner.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 18 | 19 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /bybanner/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /bybanner/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Mar 01 16:09:04 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /sbannerview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youlookwhat/ByBannerView/2eb949d5f419c106b08bbb462a40dedd0fc6e2dc/sbannerview.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':bybanner' 2 | --------------------------------------------------------------------------------