├── .gitignore ├── .travis.yml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── about.html │ ├── java │ └── com │ │ └── sunfusheng │ │ └── marqueeview │ │ └── demo │ │ ├── AboutActivity.java │ │ ├── CommonFragment.java │ │ ├── MainActivity.java │ │ ├── RecyclerViewFragment.java │ │ ├── adapter │ │ ├── FragmentPagerItem.java │ │ └── FragmentPagerItemAdapter.java │ │ └── model │ │ └── CustomModel.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── drawable_list_item_selector.xml │ ├── ic_launcher_background.xml │ └── xml_oval_common_dark_bg.xml │ ├── font │ └── huawenxinwei.TTF │ ├── layout │ ├── activity_about.xml │ ├── activity_main.xml │ ├── divider_1px.xml │ ├── divider_20dp.xml │ ├── fragment_tab.xml │ ├── item_layout.xml │ └── layout_recyclerview.xml │ ├── menu │ └── menu_main.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 │ ├── ic_loudspeaker.png │ └── ic_red_loudspeaker.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_round.png │ ├── ic_loudspeaker.png │ └── ic_red_loudspeaker.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── marqueeview ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── sunfusheng │ │ └── marqueeview │ │ ├── IMarqueeItem.java │ │ ├── MarqueeView.java │ │ └── Utils.java │ └── res │ ├── anim │ ├── anim_bottom_in.xml │ ├── anim_bottom_out.xml │ ├── anim_left_in.xml │ ├── anim_left_out.xml │ ├── anim_right_in.xml │ ├── anim_right_out.xml │ ├── anim_top_in.xml │ └── anim_top_out.xml │ └── values │ ├── attrs.xml │ └── strings.xml ├── resources ├── MarqueeView.gif └── fir.im.png ├── settings.gradle └── signings ├── keystore.jks ├── keystore.jks.enc └── signing.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /build 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | sudo: true 4 | 5 | android: 6 | components: 7 | - tools 8 | - platform-tools 9 | - tools 10 | - build-tools-28.0.3 11 | - android-28 12 | - sys-img-armeabi-v7a-android-28 13 | - extra-android-support 14 | - extra-android-m2repository 15 | - extra-google-m2repository 16 | - add-on 17 | - extra 18 | licenses: 19 | - android-sdk-preview-license-52d11cd2 20 | - android-sdk-license-.+ 21 | - google-gdk-license-.+ 22 | 23 | before_cache: 24 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 25 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 26 | cache: 27 | directories: 28 | - "$HOME/.gradle/caches/" 29 | - "$HOME/.gradle/wrapper/" 30 | - "$HOME/.android/build-cache" 31 | 32 | before_install: 33 | - mkdir "$ANDROID_HOME/licenses" || true 34 | - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" 35 | - openssl aes-256-cbc -K $encrypted_98d2608e6a8a_key -iv $encrypted_98d2608e6a8a_iv 36 | -in signings/keystore.jks.enc -out signings/keystore.jks -d 37 | - gem install fir-cli 38 | 39 | before_script: 40 | - chmod +x gradlew 41 | 42 | script: 43 | - "./gradlew assembleRelease" 44 | 45 | deploy: 46 | provider: releases 47 | api_key: 48 | secure: BYgbTvgpuAcfbsRlU3zaGOddULelZdWYb2+7R7/2XnipLUd9MLp0QUyKSQ81LAlrvYOsH9UMP0DQu9OIzjcGsfP3Z9NDkgQjZZ6v3LNeiMcDlgCZy4oMAtk8EzvpvtzbXSGQ4T5RGgCy5d+dSQwpbTwhxo5vn6ad0MmZFxoZ8D+6DX52tMkgLdxDe+LWcGOowkXw88DTZKz5KqO0KcLsqowwL2/h0uNl3V1d72dEyxU7xSqNXctBua+DOyMVu9y0ZP0vu/tf98saTNCDyxSmZqLZ/Mq2iCdeX28C44m0+zKAds5UJFGxrcGJTbtW66visYkNrQuRrOoCHmyRZG3nGPiTKYNoxSZbQH00PJOlCrVSltQrhf4u/gHhB10KOgnK60OQRxj5ffMgGqJT0BTryiU4yqvw3KESElMWwdPni/VSZ0zEr9v5/WxhY2W9ZibJvbYDh5KjUP93B7CXw+9EjIW39OMmyTWV4KEX1ng5JXM0lykED+jKgyDeVUwyrZTxzOjxvh3oluv8KfnoHMggoUTQsLmYNp02gm+fS+eYLMMrp6Tu3SNtnm5lYuAav/pCr6+vQRwGC9NEWVHJhRc6IIVyjRjbXz/FwX5VtmVmgzWovL2aVDYWB+P5of80ymcD36GgsXO0ZdYGxTUky2+rQVza9yqniygjK0FPfcA/rfc= 49 | file: demo/release/MarqueeView.apk 50 | skip_cleanup: true 51 | on: 52 | repo: sunfusheng/MarqueeView 53 | tags: true 54 | all_branches: true 55 | 56 | after_deploy: 57 | - "./gradlew clean build bintrayUpload -PbintrayUser=sfsheng0322 -PbintrayKey=$BINTRAY_TOKEN -PdryRun=false" 58 | - fir p demo/release/MarqueeView.apk -T $FIR_TOKEN -c "`git cat-file tag $TRAVIS_TAG`" 59 | 60 | notifications: 61 | email: 62 | - sfsheng0322@126.com -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarqueeView [ ![MarqueeView](https://api.bintray.com/packages/sfsheng0322/maven/MarqueeView/images/download.svg) ](https://bintray.com/sfsheng0322/maven/MarqueeView/_latestVersion) 2 | 3 | 俗名:可垂直跑、可水平跑的跑马灯 4 | 学名:可垂直翻、可水平翻的翻页公告 5 | 6 | ### 效果图 7 | 8 | 9 | 10 | #### Gradle: 11 | 12 | compile 'com.sunfusheng:MarqueeView:' 13 | 14 | #### 属性 15 | 16 | | Attribute 属性 | Description 描述 | 17 | |:--- |:---| 18 | | mvAnimDuration | 一行文字动画执行时间 | 19 | | mvInterval | 两行文字翻页时间间隔 | 20 | | mvTextSize | 文字大小 | 21 | | mvTextColor | 文字颜色 | 22 | | mvGravity | 文字位置:left、center、right | 23 | | mvSingleLine | 单行设置 | 24 | | mvDirection | 动画滚动方向:bottom_to_top、top_to_bottom、right_to_left、left_to_right | 25 | | mvFont | 设置字体 | 26 | 27 | #### XML 28 | 29 | 40 | 41 | #### 设置字符串列表数据,或者设置自定义的Model数据类型 42 | 43 | MarqueeView marqueeView = (MarqueeView) findViewById(R.id.marqueeView); 44 | 45 | List messages = new ArrayList<>(); 46 | messages.add("1. 大家好,我是孙福生。"); 47 | messages.add("2. 欢迎大家关注我哦!"); 48 | messages.add("3. GitHub帐号:sunfusheng"); 49 | messages.add("4. 新浪微博:孙福生微博"); 50 | messages.add("5. 个人博客:sunfusheng.com"); 51 | messages.add("6. 微信公众号:孙福生"); 52 | marqueeView.startWithList(messages); 53 | 54 | // 或者设置自定义的Model数据类型 55 | public class CustomModel implements IMarqueeItem { 56 | @Override 57 | public CharSequence marqueeMessage() { 58 | return "..."; 59 | } 60 | } 61 | 62 | List messages = new ArrayList<>(); 63 | marqueeView.startWithList(messages); 64 | 65 | // 在代码里设置自己的动画 66 | marqueeView.startWithList(messages, R.anim.anim_bottom_in, R.anim.anim_top_out); 67 | 68 | #### 设置字符串数据 69 | 70 | String message = "心中有阳光,脚底有力量!心中有阳光,脚底有力量!心中有阳光,脚底有力量!"; 71 | marqueeView.startWithText(message); 72 | 73 | // 在代码里设置自己的动画 74 | marqueeView.startWithText(message, R.anim.anim_bottom_in, R.anim.anim_top_out); 75 | 76 | #### 设置事件监听 77 | 78 | marqueeView.setOnItemClickListener(new MarqueeView.OnItemClickListener() { 79 | @Override 80 | public void onItemClick(int position, TextView textView) { 81 | Toast.makeText(getApplicationContext(), String.valueOf(marqueeView1.getPosition()) + ". " + textView.getText(), Toast.LENGTH_SHORT).show(); 82 | } 83 | }); 84 | 85 | #### 重影问题可参考以下解决方案 86 | 87 | 在 Activity 或 Fragment 中 88 | 89 | @Override 90 | public void onStart() { 91 | super.onStart(); 92 | marqueeView.startFlipping(); 93 | } 94 | 95 | @Override 96 | public void onStop() { 97 | super.onStop(); 98 | marqueeView.stopFlipping(); 99 | } 100 | 101 | 在 ListView 或 RecyclerView 的 Adapter 中 102 | 103 | @Override 104 | public void onViewDetachedFromWindow(@NonNull ViewHolder holder) { 105 | super.onViewDetachedFromWindow(holder); 106 | holder.marqueeView.stopFlipping(); 107 | } 108 | 109 |
110 | 111 | ### 扫一扫[Fir.im](https://fir.im/MarqueeView)二维码下载APK 112 | 113 | 114 | 115 |
116 | 117 | ### 个人微信公众号 118 | 119 | 120 | 121 |
122 | 123 | ### 打点赏给作者加点油^_^ 124 | 125 | 126 | 127 |
128 | 129 | ### 关于我 130 | 131 | [GitHub: sunfusheng](https://github.com/sunfusheng) 132 | 133 | [个人邮箱: sfsheng0322@126.com](https://mail.126.com/) 134 | 135 | [个人博客: sunfusheng.com](http://sunfusheng.com/) 136 | 137 | [简书主页](http://www.jianshu.com/users/88509e7e2ed1/latest_articles) 138 | 139 | [新浪微博](http://weibo.com/u/3852192525) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | Properties signingProperties = new Properties() 4 | signingProperties.load(new FileInputStream(file("../signings/signing.properties"))) 5 | 6 | android { 7 | compileSdkVersion COMPILE_SDK_VERSION 8 | defaultConfig { 9 | applicationId "com.sunfusheng.marqueeview.demo" 10 | minSdkVersion MIN_SDK_VERSION 11 | targetSdkVersion TARGET_SDK_VERSION 12 | versionCode VERSION_CODE 13 | versionName VERSION_NAME 14 | multiDexEnabled true 15 | } 16 | 17 | compileOptions { 18 | sourceCompatibility JavaVersion.VERSION_1_8 19 | targetCompatibility JavaVersion.VERSION_1_8 20 | } 21 | 22 | signingConfigs { 23 | release { 24 | storeFile file(signingProperties['KEYSTORE_FILEPATH']) 25 | storePassword signingProperties['STORE_PASSWORD'] 26 | keyAlias signingProperties['KEY_ALIAS'] 27 | keyPassword signingProperties['KEY_PASSWORD'] 28 | } 29 | } 30 | 31 | buildTypes { 32 | debug { 33 | signingConfig signingConfigs.release 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | release { 37 | signingConfig signingConfigs.release 38 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 39 | applicationVariants.all { variant -> 40 | variant.outputs.all { 41 | outputFileName = "MarqueeView.apk" 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | def localSourceEnabled = true 49 | 50 | dependencies { 51 | implementation fileTree(include: ['*.jar'], dir: 'libs') 52 | implementation 'com.android.support:appcompat-v7:' + SUPPORT_LIBRARY_VERSION 53 | implementation 'com.android.support:design:' + SUPPORT_LIBRARY_VERSION 54 | implementation 'com.android.support:recyclerview-v7:' + SUPPORT_LIBRARY_VERSION 55 | implementation 'com.android.support:cardview-v7:' + SUPPORT_LIBRARY_VERSION 56 | 57 | implementation 'com.sunfusheng:FirUpdater:1.1.1' 58 | 59 | if (localSourceEnabled) { 60 | implementation project(':marqueeview') 61 | } else { 62 | implementation 'com.sunfusheng:MarqueeView:' + VERSION_NAME 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/sunfusheng/Android/Studio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/assets/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 155 | 关注我吧 156 | 157 | 158 | 159 |

关注我吧

160 | 161 |

项目开源地址:https://github.com/sunfusheng/MarqueeView

162 | 163 |

GitHub主页

164 | 165 |

简书主页

166 | 167 |

个人博客

168 | 169 |

新浪微博

170 | 171 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunfusheng/marqueeview/demo/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview.demo; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.text.TextUtils; 11 | import android.view.KeyEvent; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.webkit.WebSettings; 16 | import android.webkit.WebView; 17 | import android.webkit.WebViewClient; 18 | 19 | 20 | /** 21 | * @author by sunfusheng on 16/4/24. 22 | */ 23 | public class AboutActivity extends AppCompatActivity { 24 | 25 | private WebView webView; 26 | 27 | private WebSettings settings; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_about); 33 | 34 | initView(); 35 | } 36 | 37 | @SuppressLint("SetJavaScriptEnabled") 38 | private void initView() { 39 | webView = findViewById(R.id.webView); 40 | setTitle("关于(V" + getVersionName(this) + ")"); 41 | 42 | settings = webView.getSettings(); 43 | settings.setJavaScriptEnabled(true); //如果访问的页面中有Javascript,则WebView必须设置支持Javascript 44 | settings.setJavaScriptCanOpenWindowsAutomatically(true); 45 | settings.setSupportZoom(true); //支持缩放 46 | settings.setBuiltInZoomControls(true); //支持手势缩放 47 | settings.setDisplayZoomControls(false); //是否显示缩放按钮 48 | 49 | // >= 19(SDK4.4)启动硬件加速,否则启动软件加速 50 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 51 | webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 52 | settings.setLoadsImagesAutomatically(true); //支持自动加载图片 53 | } else { 54 | webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 55 | settings.setLoadsImagesAutomatically(false); 56 | } 57 | 58 | settings.setUseWideViewPort(true); //将图片调整到适合WebView的大小 59 | settings.setLoadWithOverviewMode(true); //自适应屏幕 60 | settings.setDomStorageEnabled(true); 61 | settings.setSaveFormData(true); 62 | settings.setSupportMultipleWindows(true); 63 | settings.setAppCacheEnabled(true); 64 | settings.setCacheMode(WebSettings.LOAD_DEFAULT); //优先使用缓存 65 | 66 | webView.setHorizontalScrollbarOverlay(true); 67 | webView.setHorizontalScrollBarEnabled(false); 68 | webView.setOverScrollMode(View.OVER_SCROLL_NEVER); // 取消WebView中滚动或拖动到顶部、底部时的阴影 69 | webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); // 取消滚动条白边效果 70 | webView.requestFocus(); 71 | 72 | webView.loadUrl("file:///android_asset/about.html"); 73 | webView.setWebViewClient(new WebViewClient() { 74 | @Override 75 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 76 | view.loadUrl(url); 77 | return true; 78 | } 79 | }); 80 | } 81 | 82 | // 获取当前应用的版本号 83 | public static String getVersionName(Context context) { 84 | try { 85 | PackageManager packageManager = context.getPackageManager(); 86 | PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); 87 | String version = packInfo.versionName; 88 | if (!TextUtils.isEmpty(version)) { 89 | return version; 90 | } 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } 94 | return ""; 95 | } 96 | 97 | 98 | @Override 99 | public boolean onCreateOptionsMenu(Menu menu) { 100 | return super.onCreateOptionsMenu(menu); 101 | } 102 | 103 | @Override 104 | public boolean onOptionsItemSelected(MenuItem item) { 105 | if (item.getItemId() == android.R.id.home) { 106 | finish(); 107 | return true; 108 | } 109 | return super.onOptionsItemSelected(item); 110 | } 111 | 112 | @Override 113 | public boolean onKeyDown(int keyCode, KeyEvent event) { 114 | if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) { 115 | webView.goBack(); 116 | return true; 117 | } 118 | return super.onKeyDown(keyCode, event); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunfusheng/marqueeview/demo/CommonFragment.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.content.res.ResourcesCompat; 7 | import android.text.SpannableString; 8 | import android.text.Spanned; 9 | import android.text.style.ForegroundColorSpan; 10 | import android.text.style.URLSpan; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.Toast; 15 | 16 | import com.sunfusheng.marqueeview.MarqueeView; 17 | import com.sunfusheng.marqueeview.demo.model.CustomModel; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * @author by sunfusheng on 2017/8/8. 24 | */ 25 | public class CommonFragment extends Fragment { 26 | 27 | private MarqueeView marqueeView; 28 | private MarqueeView marqueeView1; 29 | private MarqueeView marqueeView2; 30 | private MarqueeView marqueeView3; 31 | private MarqueeView marqueeView4; 32 | 33 | @Nullable 34 | @Override 35 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 36 | View view = inflater.inflate(R.layout.fragment_tab, container, false); 37 | marqueeView = view.findViewById(R.id.marqueeView); 38 | marqueeView1 = view.findViewById(R.id.marqueeView1); 39 | marqueeView2 = view.findViewById(R.id.marqueeView2); 40 | marqueeView3 = view.findViewById(R.id.marqueeView3); 41 | marqueeView4 = view.findViewById(R.id.marqueeView4); 42 | 43 | List list = new ArrayList<>(); 44 | SpannableString ss1 = new SpannableString("1、MarqueeView开源项目"); 45 | ss1.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.red)), 2, 13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 46 | list.add(ss1); 47 | SpannableString ss2 = new SpannableString("2、GitHub:sunfusheng"); 48 | ss2.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.blue)), 9, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 49 | list.add(ss2); 50 | SpannableString ss3 = new SpannableString("3、个人博客:sunfusheng.com"); 51 | ss3.setSpan(new URLSpan("http://sunfusheng.com/"), 7, 21, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 52 | list.add(ss3); 53 | list.add("4、新浪微博:@孙福生微博"); 54 | //set Custom font 55 | marqueeView.setTypeface(ResourcesCompat.getFont(getContext(), R.font.huawenxinwei)); 56 | 57 | marqueeView.startWithList(list); 58 | marqueeView.setOnItemClickListener((position, textView) -> Toast.makeText(getContext(), textView.getText() + "", Toast.LENGTH_SHORT).show()); 59 | 60 | marqueeView1.startWithText(getString(R.string.marquee_texts), R.anim.anim_top_in, R.anim.anim_bottom_out); 61 | marqueeView1.setOnItemClickListener((position, textView) -> Toast.makeText(getContext(), String.valueOf(position) + ". " + textView.getText(), Toast.LENGTH_SHORT).show()); 62 | 63 | marqueeView2.startWithText(getString(R.string.marquee_text)); 64 | 65 | marqueeView3.startWithText(getString(R.string.marquee_texts)); 66 | marqueeView3.setOnItemClickListener((position, textView) -> { 67 | CharSequence model = (CharSequence) marqueeView3.getMessages().get(position); 68 | Toast.makeText(getContext(), model, Toast.LENGTH_SHORT).show(); 69 | }); 70 | 71 | List models = new ArrayList<>(); 72 | models.add(new CustomModel(10000, "增加了新功能:", "设置自定义的Model数据类型")); 73 | models.add(new CustomModel(10001, "GitHub:sunfusheng", "新浪微博:@孙福生微博")); 74 | models.add(new CustomModel(10002, "MarqueeView开源项目", "个人博客:sunfusheng.com")); 75 | marqueeView4.startWithList(models); 76 | marqueeView4.setOnItemClickListener((position, textView) -> { 77 | CustomModel model = (CustomModel) marqueeView4.getMessages().get(position); 78 | Toast.makeText(getContext(), "ID:" + model.id, Toast.LENGTH_SHORT).show(); 79 | }); 80 | 81 | return view; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunfusheng/marqueeview/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview.demo; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.design.widget.TabLayout; 6 | import android.support.v4.view.ViewPager; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | 11 | import com.sunfusheng.FirUpdater; 12 | import com.sunfusheng.marqueeview.demo.adapter.FragmentPagerItemAdapter; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | TabLayout tabLayout = findViewById(R.id.tabLayout); 21 | ViewPager viewPager = findViewById(R.id.viewPager); 22 | 23 | FragmentPagerItemAdapter adapter = new FragmentPagerItemAdapter.Builder(this, getSupportFragmentManager()) 24 | .add("Common", new CommonFragment()) 25 | .add("RecyclerView", new RecyclerViewFragment()) 26 | .build(); 27 | viewPager.setAdapter(adapter); 28 | viewPager.setOffscreenPageLimit(1); 29 | tabLayout.setupWithViewPager(viewPager); 30 | 31 | new FirUpdater(this) 32 | .apiToken("3c57fb226edf7facf821501e4eba08d2") 33 | .appId("5cbff890ca87a80abed1b4d8") 34 | .checkVersion(); 35 | } 36 | 37 | @Override 38 | public boolean onCreateOptionsMenu(Menu menu) { 39 | getMenuInflater().inflate(R.menu.menu_main, menu); 40 | return true; 41 | } 42 | 43 | @Override 44 | public boolean onOptionsItemSelected(MenuItem item) { 45 | if (item.getItemId() == R.id.item_menu_app) { 46 | startActivity(new Intent(this, AboutActivity.class)); 47 | return true; 48 | } 49 | return super.onOptionsItemSelected(item); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunfusheng/marqueeview/demo/RecyclerViewFragment.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.TextView; 13 | 14 | import com.sunfusheng.marqueeview.MarqueeView; 15 | import com.sunfusheng.marqueeview.demo.R; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * @author sunfusheng on 2018/2/6. 22 | */ 23 | public class RecyclerViewFragment extends Fragment { 24 | 25 | public static class Item { 26 | public String title; 27 | public List list; 28 | public boolean showList; 29 | } 30 | 31 | @Nullable 32 | @Override 33 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 34 | return inflater.inflate(R.layout.layout_recyclerview, container, false); 35 | } 36 | 37 | @Override 38 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 39 | super.onViewCreated(view, savedInstanceState); 40 | 41 | List items = new ArrayList<>(); 42 | for (int i = 1; i <= 30; i++) { 43 | Item item = new Item(); 44 | item.title = "item" + i; 45 | if (i == 3 || i == 27) { 46 | List list = new ArrayList<>(); 47 | for (int j = 1; j <= 10; j++) { 48 | list.add("MarqueeView Item " + i + "-" + j); 49 | } 50 | item.list = list; 51 | item.showList = true; 52 | } 53 | items.add(item); 54 | } 55 | 56 | RecyclerView recyclerView = view.findViewById(R.id.recycleView); 57 | LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); 58 | recyclerView.setLayoutManager(layoutManager); 59 | RecyclerViewAdapter adapter = new RecyclerViewAdapter(items); 60 | recyclerView.setAdapter(adapter); 61 | 62 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 63 | @Override 64 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 65 | super.onScrollStateChanged(recyclerView, newState); 66 | } 67 | 68 | @Override 69 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 70 | super.onScrolled(recyclerView, dx, dy); 71 | } 72 | }); 73 | } 74 | 75 | class RecyclerViewAdapter extends RecyclerView.Adapter { 76 | private List list; 77 | 78 | RecyclerViewAdapter(List list) { 79 | this.list = list; 80 | } 81 | 82 | @NonNull 83 | @Override 84 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { 85 | View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_layout, viewGroup, false); 86 | return new ViewHolder(view); 87 | } 88 | 89 | @Override 90 | public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) { 91 | Item item = list.get(position); 92 | 93 | viewHolder.tvTitle.setText(item.title); 94 | viewHolder.tvTitle.setVisibility(item.showList ? View.GONE : View.VISIBLE); 95 | viewHolder.marqueeView.setVisibility(item.showList ? View.VISIBLE : View.GONE); 96 | if (item.showList) { 97 | viewHolder.marqueeView.startWithList(item.list); 98 | } 99 | } 100 | 101 | @Override 102 | public void onViewDetachedFromWindow(@NonNull ViewHolder holder) { 103 | super.onViewDetachedFromWindow(holder); 104 | if (holder.marqueeView != null) { 105 | holder.marqueeView.stopFlipping(); 106 | } 107 | } 108 | 109 | @Override 110 | public int getItemCount() { 111 | return list.size(); 112 | } 113 | 114 | class ViewHolder extends RecyclerView.ViewHolder { 115 | TextView tvTitle; 116 | MarqueeView marqueeView; 117 | 118 | ViewHolder(View view) { 119 | super(view); 120 | tvTitle = view.findViewById(R.id.tv_title); 121 | marqueeView = view.findViewById(R.id.marqueeView); 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunfusheng/marqueeview/demo/adapter/FragmentPagerItem.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview.demo.adapter; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.v4.app.Fragment; 7 | 8 | /** 9 | * @author by sunfusheng on 2017/7/20. 10 | */ 11 | public class FragmentPagerItem { 12 | 13 | private String title; 14 | private Fragment fragment; 15 | private Class clazz; 16 | private Bundle args; 17 | 18 | protected FragmentPagerItem(String title, @NonNull Fragment fragment) { 19 | this(title, fragment.getClass(), fragment.getArguments()); 20 | this.fragment = fragment; 21 | } 22 | 23 | protected FragmentPagerItem(String title, Class clazz, Bundle args) { 24 | this.title = title; 25 | this.clazz = clazz; 26 | this.args = args; 27 | } 28 | 29 | public static FragmentPagerItem create(String title, @NonNull Fragment fragment) { 30 | return new FragmentPagerItem(title, fragment); 31 | } 32 | 33 | public static FragmentPagerItem create(String title, Class clazz) { 34 | return create(title, clazz, null); 35 | } 36 | 37 | public static FragmentPagerItem create(String title, Class clazz, Bundle args) { 38 | return new FragmentPagerItem(title, clazz, args); 39 | } 40 | 41 | public String getPageTitle() { 42 | return title; 43 | } 44 | 45 | public Fragment getFragment() { 46 | return fragment; 47 | } 48 | 49 | public Fragment newInstance(Context context) { 50 | return Fragment.instantiate(context, clazz.getName(), args); 51 | } 52 | 53 | public Bundle getArgs() { 54 | return args; 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunfusheng/marqueeview/demo/adapter/FragmentPagerItemAdapter.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview.demo.adapter; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v4.app.FragmentPagerAdapter; 9 | import android.util.SparseArray; 10 | import android.view.ViewGroup; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * @author by sunfusheng on 2017/7/20. 17 | */ 18 | public class FragmentPagerItemAdapter extends FragmentPagerAdapter { 19 | 20 | private Context context; 21 | private List items; 22 | private SparseArray fragments = new SparseArray<>(); 23 | private OnInstantiateFragmentListener listener; 24 | 25 | private FragmentPagerItemAdapter(Context context, FragmentManager fragmentManager, List items) { 26 | super(fragmentManager); 27 | this.context = context; 28 | this.items = items; 29 | } 30 | 31 | @NonNull 32 | @Override 33 | public Object instantiateItem(ViewGroup container, int position) { 34 | Fragment fragment = (Fragment) super.instantiateItem(container, position); 35 | fragments.put(position, fragment); 36 | if (listener != null) { 37 | listener.onInstantiate(position, fragment, items.get(position).getArgs()); 38 | } 39 | return fragment; 40 | } 41 | 42 | @Override 43 | public Fragment getItem(int position) { 44 | return items.get(position).newInstance(context); 45 | } 46 | 47 | @Override 48 | public void destroyItem(ViewGroup container, int position, Object object) { 49 | super.destroyItem(container, position, object); 50 | fragments.remove(position); 51 | } 52 | 53 | @Override 54 | public int getCount() { 55 | return items.size(); 56 | } 57 | 58 | public Fragment getFragment(int position) { 59 | return fragments.get(position); 60 | } 61 | 62 | @Override 63 | public String getPageTitle(int position) { 64 | return items.get(position).getPageTitle(); 65 | } 66 | 67 | public void setOnInstantiateFragmentListener(OnInstantiateFragmentListener listener) { 68 | this.listener = listener; 69 | } 70 | 71 | public interface OnInstantiateFragmentListener { 72 | void onInstantiate(int position, Fragment fragment, Bundle args); 73 | } 74 | 75 | public static class Builder { 76 | 77 | private Context context; 78 | private FragmentManager fragmentManager; 79 | private List items = new ArrayList<>(); 80 | 81 | public Builder(Context context, FragmentManager fragmentManager) { 82 | this.context = context; 83 | this.fragmentManager = fragmentManager; 84 | } 85 | 86 | public Builder add(FragmentPagerItem item) { 87 | items.add(item); 88 | return this; 89 | } 90 | 91 | public Builder add(int resId, Fragment fragment) { 92 | return add(context.getString(resId), fragment); 93 | } 94 | 95 | public Builder add(int resId, Class clazz) { 96 | return add(context.getString(resId), clazz); 97 | } 98 | 99 | public Builder add(int resId, Class clazz, Bundle args) { 100 | return add(context.getString(resId), clazz, args); 101 | } 102 | 103 | public Builder add(String title, Fragment fragment) { 104 | return add(FragmentPagerItem.create(title, fragment)); 105 | } 106 | 107 | public Builder add(String title, Class clazz) { 108 | return add(FragmentPagerItem.create(title, clazz)); 109 | } 110 | 111 | public Builder add(String title, Class clazz, Bundle args) { 112 | return add(FragmentPagerItem.create(title, clazz, args)); 113 | } 114 | 115 | public FragmentPagerItemAdapter build() { 116 | return new FragmentPagerItemAdapter(context, fragmentManager, items); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunfusheng/marqueeview/demo/model/CustomModel.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview.demo.model; 2 | 3 | import com.sunfusheng.marqueeview.IMarqueeItem; 4 | 5 | /** 6 | * @author by sunfusheng on 2019-04-25 7 | */ 8 | public class CustomModel implements IMarqueeItem { 9 | 10 | public int id; 11 | public String title; 12 | public String content; 13 | 14 | public CustomModel(int id, String title, String content) { 15 | this.id = id; 16 | this.title = title; 17 | this.content = content; 18 | } 19 | 20 | @Override 21 | public CharSequence marqueeMessage() { 22 | return title + "\n" + content; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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/drawable_list_item_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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/drawable/xml_oval_common_dark_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/font/huawenxinwei.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/font/huawenxinwei.TTF -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/divider_1px.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/divider_20dp.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 29 | 30 | 36 | 37 | 45 | 46 | 47 | 55 | 56 | 62 | 63 | 71 | 72 | 73 | 80 | 81 | 91 | 92 | 93 | 100 | 101 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_loudspeaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xhdpi/ic_loudspeaker.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_red_loudspeaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xhdpi/ic_red_loudspeaker.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_loudspeaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xxhdpi/ic_loudspeaker.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_red_loudspeaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xxhdpi/ic_red_loudspeaker.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #C34A42 5 | #C34A42 6 | #C34A42 7 | 8 | #FFFFFF 9 | #FFFF00 10 | #FFD700 11 | #FFA500 12 | #C34A42 13 | #808080 14 | #008000 15 | #1B88EE 16 | #000000 17 | 18 | #00000000 19 | #4f000000 20 | #80ffffff 21 | 22 | #333333 23 | #9d9d9d 24 | 25 | #ebebeb 26 | #d6d6d6 27 | 28 | #ebebeb 29 | #d6d6d6 30 | 31 | #353C46 32 | #49505A 33 | #5D646E 34 | #717882 35 | #858C96 36 | #99A0AA 37 | #ADB4BE 38 | #C4C8D0 39 | #D8DCE4 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MarqueeView 3 | 心中有阳光,脚底有力量。 4 | 心中有阳光,脚底有力量。心中有阳光,脚底有力量。心中有阳光,脚底有力量。心中有阳光,脚底有力量。 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | google() 5 | jcenter() 6 | maven { url 'https://dl.bintray.com/sfsheng0322/maven' } 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.5.1' 11 | classpath 'com.novoda:bintray-release:0.9' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenCentral() 18 | google() 19 | jcenter() 20 | maven { url 'https://dl.bintray.com/sfsheng0322/maven' } 21 | } 22 | 23 | ext { 24 | COMPILE_SDK_VERSION = 28 25 | BUILD_TOOL_VERSION = '28.0.3' 26 | SUPPORT_LIBRARY_VERSION = '28.0.0' 27 | 28 | MIN_SDK_VERSION = 14 29 | TARGET_SDK_VERSION = 28 30 | VERSION_CODE = 14 31 | VERSION_NAME = '1.4.1' 32 | } 33 | } 34 | 35 | task clean(type: Delete) { 36 | delete rootProject.buildDir 37 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 24 11:09:12 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-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /marqueeview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | -------------------------------------------------------------------------------- /marqueeview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release' 3 | 4 | android { 5 | compileSdkVersion COMPILE_SDK_VERSION 6 | defaultConfig { 7 | minSdkVersion MIN_SDK_VERSION 8 | targetSdkVersion TARGET_SDK_VERSION 9 | versionCode VERSION_CODE 10 | versionName VERSION_NAME 11 | } 12 | } 13 | 14 | dependencies { 15 | implementation 'com.android.support:appcompat-v7:' + SUPPORT_LIBRARY_VERSION 16 | } 17 | 18 | publish { 19 | userOrg = 'sfsheng0322' 20 | groupId = 'com.sunfusheng' 21 | artifactId = 'MarqueeView' 22 | publishVersion = VERSION_NAME 23 | desc = 'A custom view for vertical or horizontal flip announcement.' 24 | website = 'https://github.com/sunfusheng/MarqueeView' 25 | } 26 | -------------------------------------------------------------------------------- /marqueeview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/sunfusheng/Android/Studio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /marqueeview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /marqueeview/src/main/java/com/sunfusheng/marqueeview/IMarqueeItem.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview; 2 | 3 | /** 4 | * @author by sunfusheng on 2019-04-25 5 | */ 6 | public interface IMarqueeItem { 7 | CharSequence marqueeMessage(); 8 | } 9 | -------------------------------------------------------------------------------- /marqueeview/src/main/java/com/sunfusheng/marqueeview/MarqueeView.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Typeface; 6 | import android.os.Build; 7 | import android.support.annotation.AnimRes; 8 | import android.support.annotation.FontRes; 9 | import android.support.v4.content.res.ResourcesCompat; 10 | import android.text.TextUtils; 11 | import android.util.AttributeSet; 12 | import android.view.Gravity; 13 | import android.view.View; 14 | import android.view.ViewTreeObserver; 15 | import android.view.animation.Animation; 16 | import android.view.animation.AnimationUtils; 17 | import android.widget.TextView; 18 | import android.widget.ViewFlipper; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * Created by sunfusheng on 16/5/31. 25 | */ 26 | public class MarqueeView extends ViewFlipper { 27 | 28 | private int interval = 3000; 29 | private boolean hasSetAnimDuration = false; 30 | private int animDuration = 1000; 31 | private int textSize = 14; 32 | private int textColor = 0xff000000; 33 | private boolean singleLine = false; 34 | 35 | private int gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; 36 | private static final int GRAVITY_LEFT = 0; 37 | private static final int GRAVITY_CENTER = 1; 38 | private static final int GRAVITY_RIGHT = 2; 39 | 40 | private int direction = DIRECTION_BOTTOM_TO_TOP; 41 | private static final int DIRECTION_BOTTOM_TO_TOP = 0; 42 | private static final int DIRECTION_TOP_TO_BOTTOM = 1; 43 | private static final int DIRECTION_RIGHT_TO_LEFT = 2; 44 | private static final int DIRECTION_LEFT_TO_RIGHT = 3; 45 | 46 | private Typeface typeface; 47 | 48 | @AnimRes 49 | private int inAnimResId = R.anim.anim_bottom_in; 50 | @AnimRes 51 | private int outAnimResId = R.anim.anim_top_out; 52 | 53 | private int position; 54 | private List messages = new ArrayList<>(); 55 | private OnItemClickListener onItemClickListener; 56 | 57 | public MarqueeView(Context context) { 58 | this(context, null); 59 | } 60 | 61 | public MarqueeView(Context context, AttributeSet attrs) { 62 | super(context, attrs); 63 | init(context, attrs, 0); 64 | } 65 | 66 | private void init(Context context, AttributeSet attrs, int defStyleAttr) { 67 | TypedArray typedArray = 68 | context.obtainStyledAttributes(attrs, R.styleable.MarqueeViewStyle, defStyleAttr, 0); 69 | 70 | interval = typedArray.getInteger(R.styleable.MarqueeViewStyle_mvInterval, interval); 71 | hasSetAnimDuration = typedArray.hasValue(R.styleable.MarqueeViewStyle_mvAnimDuration); 72 | animDuration = typedArray.getInteger(R.styleable.MarqueeViewStyle_mvAnimDuration, animDuration); 73 | singleLine = typedArray.getBoolean(R.styleable.MarqueeViewStyle_mvSingleLine, false); 74 | if (typedArray.hasValue(R.styleable.MarqueeViewStyle_mvTextSize)) { 75 | textSize = (int) typedArray.getDimension(R.styleable.MarqueeViewStyle_mvTextSize, textSize); 76 | textSize = Utils.px2sp(context, textSize); 77 | } 78 | textColor = typedArray.getColor(R.styleable.MarqueeViewStyle_mvTextColor, textColor); 79 | @FontRes int fontRes = typedArray.getResourceId(R.styleable.MarqueeViewStyle_mvFont, 0); 80 | if (fontRes != 0) { 81 | typeface = ResourcesCompat.getFont(context, fontRes); 82 | } 83 | int gravityType = typedArray.getInt(R.styleable.MarqueeViewStyle_mvGravity, GRAVITY_LEFT); 84 | switch (gravityType) { 85 | case GRAVITY_LEFT: 86 | gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; 87 | break; 88 | case GRAVITY_CENTER: 89 | gravity = Gravity.CENTER; 90 | break; 91 | case GRAVITY_RIGHT: 92 | gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; 93 | break; 94 | } 95 | 96 | if (typedArray.hasValue(R.styleable.MarqueeViewStyle_mvDirection)) { 97 | direction = typedArray.getInt(R.styleable.MarqueeViewStyle_mvDirection, direction); 98 | switch (direction) { 99 | case DIRECTION_BOTTOM_TO_TOP: 100 | inAnimResId = R.anim.anim_bottom_in; 101 | outAnimResId = R.anim.anim_top_out; 102 | break; 103 | case DIRECTION_TOP_TO_BOTTOM: 104 | inAnimResId = R.anim.anim_top_in; 105 | outAnimResId = R.anim.anim_bottom_out; 106 | break; 107 | case DIRECTION_RIGHT_TO_LEFT: 108 | inAnimResId = R.anim.anim_right_in; 109 | outAnimResId = R.anim.anim_left_out; 110 | break; 111 | case DIRECTION_LEFT_TO_RIGHT: 112 | inAnimResId = R.anim.anim_left_in; 113 | outAnimResId = R.anim.anim_right_out; 114 | break; 115 | } 116 | } else { 117 | inAnimResId = R.anim.anim_bottom_in; 118 | outAnimResId = R.anim.anim_top_out; 119 | } 120 | 121 | typedArray.recycle(); 122 | setFlipInterval(interval); 123 | } 124 | 125 | /** 126 | * 根据字符串,启动翻页公告 127 | * 128 | * @param message 字符串 129 | */ 130 | public void startWithText(String message) { 131 | startWithText(message, inAnimResId, outAnimResId); 132 | } 133 | 134 | /** 135 | * 根据字符串,启动翻页公告 136 | * 137 | * @param message 字符串 138 | * @param inAnimResId 进入动画的resID 139 | * @param outAnimResID 离开动画的resID 140 | */ 141 | public void startWithText(final String message, final @AnimRes int inAnimResId, 142 | final @AnimRes int outAnimResID) { 143 | if (TextUtils.isEmpty(message)) return; 144 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 145 | @Override 146 | public void onGlobalLayout() { 147 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 148 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 149 | } else { 150 | getViewTreeObserver().removeGlobalOnLayoutListener(this); 151 | } 152 | startWithFixedWidth(message, inAnimResId, outAnimResID); 153 | } 154 | }); 155 | } 156 | 157 | /** 158 | * 根据字符串和宽度,启动翻页公告 159 | * 160 | * @param message 字符串 161 | */ 162 | private void startWithFixedWidth(String message, @AnimRes int inAnimResId, 163 | @AnimRes int outAnimResID) { 164 | int messageLength = message.length(); 165 | int width = Utils.px2dip(getContext(), getWidth()); 166 | if (width == 0) { 167 | throw new RuntimeException("Please set the width of MarqueeView !"); 168 | } 169 | int limit = width / textSize; 170 | List list = new ArrayList(); 171 | 172 | if (messageLength <= limit) { 173 | list.add(message); 174 | } else { 175 | int size = messageLength / limit + (messageLength % limit != 0 ? 1 : 0); 176 | for (int i = 0; i < size; i++) { 177 | int startIndex = i * limit; 178 | int endIndex = ((i + 1) * limit >= messageLength ? messageLength : (i + 1) * limit); 179 | list.add(message.substring(startIndex, endIndex)); 180 | } 181 | } 182 | 183 | if (messages == null) { 184 | messages = new ArrayList<>(); 185 | } 186 | messages.clear(); 187 | messages.addAll(list); 188 | postStart(inAnimResId, outAnimResID); 189 | } 190 | 191 | /** 192 | * 根据字符串列表,启动翻页公告 193 | * 194 | * @param messages 字符串列表 195 | */ 196 | public void startWithList(List messages) { 197 | startWithList(messages, inAnimResId, outAnimResId); 198 | } 199 | 200 | /** 201 | * 根据字符串列表,启动翻页公告 202 | * 203 | * @param messages 字符串列表 204 | * @param inAnimResId 进入动画的resID 205 | * @param outAnimResID 离开动画的resID 206 | */ 207 | public void startWithList(List messages, @AnimRes int inAnimResId, @AnimRes int outAnimResID) { 208 | if (Utils.isEmpty(messages)) return; 209 | setMessages(messages); 210 | postStart(inAnimResId, outAnimResID); 211 | } 212 | 213 | private void postStart(final @AnimRes int inAnimResId, final @AnimRes int outAnimResID) { 214 | post(new Runnable() { 215 | @Override 216 | public void run() { 217 | start(inAnimResId, outAnimResID); 218 | } 219 | }); 220 | } 221 | 222 | private boolean isAnimStart = false; 223 | 224 | private void start(final @AnimRes int inAnimResId, final @AnimRes int outAnimResID) { 225 | removeAllViews(); 226 | clearAnimation(); 227 | // 检测数据源 228 | if (messages == null || messages.isEmpty()) { 229 | throw new RuntimeException("The messages cannot be empty!"); 230 | } 231 | position = 0; 232 | addView(createTextView(messages.get(position))); 233 | 234 | if (messages.size() > 1) { 235 | setInAndOutAnimation(inAnimResId, outAnimResID); 236 | startFlipping(); 237 | } 238 | 239 | if (getInAnimation() != null) { 240 | getInAnimation().setAnimationListener(new Animation.AnimationListener() { 241 | @Override 242 | public void onAnimationStart(Animation animation) { 243 | if (isAnimStart) { 244 | animation.cancel(); 245 | } 246 | isAnimStart = true; 247 | } 248 | 249 | @Override 250 | public void onAnimationEnd(Animation animation) { 251 | position++; 252 | if (position >= messages.size()) { 253 | position = 0; 254 | } 255 | View view = createTextView(messages.get(position)); 256 | if (view.getParent() == null) { 257 | addView(view); 258 | } 259 | isAnimStart = false; 260 | } 261 | 262 | @Override 263 | public void onAnimationRepeat(Animation animation) { 264 | } 265 | }); 266 | } 267 | } 268 | 269 | private TextView createTextView(T marqueeItem) { 270 | TextView textView = (TextView) getChildAt((getDisplayedChild() + 1) % 3); 271 | if (textView == null) { 272 | textView = new TextView(getContext()); 273 | textView.setGravity(gravity | Gravity.CENTER_VERTICAL); 274 | textView.setTextColor(textColor); 275 | textView.setTextSize(textSize); 276 | textView.setIncludeFontPadding(true); 277 | textView.setSingleLine(singleLine); 278 | if (singleLine) { 279 | textView.setMaxLines(1); 280 | textView.setEllipsize(TextUtils.TruncateAt.END); 281 | } 282 | if (typeface != null) { 283 | textView.setTypeface(typeface); 284 | } 285 | textView.setOnClickListener(new OnClickListener() { 286 | @Override 287 | public void onClick(View v) { 288 | if (onItemClickListener != null) { 289 | onItemClickListener.onItemClick(getPosition(), (TextView) v); 290 | } 291 | } 292 | }); 293 | } 294 | CharSequence message = ""; 295 | if (marqueeItem instanceof CharSequence) { 296 | message = (CharSequence) marqueeItem; 297 | } else if (marqueeItem instanceof IMarqueeItem) { 298 | message = ((IMarqueeItem) marqueeItem).marqueeMessage(); 299 | } 300 | textView.setText(message); 301 | textView.setTag(position); 302 | return textView; 303 | } 304 | 305 | public int getPosition() { 306 | return (int) getCurrentView().getTag(); 307 | } 308 | 309 | public List getMessages() { 310 | return messages; 311 | } 312 | 313 | public void setMessages(List messages) { 314 | this.messages = messages; 315 | } 316 | 317 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) { 318 | this.onItemClickListener = onItemClickListener; 319 | } 320 | 321 | public interface OnItemClickListener { 322 | void onItemClick(int position, TextView textView); 323 | } 324 | 325 | /** 326 | * 设置进入动画和离开动画 327 | * 328 | * @param inAnimResId 进入动画的resID 329 | * @param outAnimResID 离开动画的resID 330 | */ 331 | private void setInAndOutAnimation(@AnimRes int inAnimResId, @AnimRes int outAnimResID) { 332 | Animation inAnim = AnimationUtils.loadAnimation(getContext(), inAnimResId); 333 | if (hasSetAnimDuration) inAnim.setDuration(animDuration); 334 | setInAnimation(inAnim); 335 | 336 | Animation outAnim = AnimationUtils.loadAnimation(getContext(), outAnimResID); 337 | if (hasSetAnimDuration) outAnim.setDuration(animDuration); 338 | setOutAnimation(outAnim); 339 | } 340 | 341 | public void setTypeface(Typeface typeface) { 342 | this.typeface = typeface; 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /marqueeview/src/main/java/com/sunfusheng/marqueeview/Utils.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.marqueeview; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.util.DisplayMetrics; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by sunfusheng on 17/8/8. 11 | */ 12 | public class Utils { 13 | 14 | public static boolean notEmpty(List list) { 15 | return !isEmpty(list); 16 | } 17 | 18 | public static boolean isEmpty(List list) { 19 | if (list == null || list.size() == 0) { 20 | return true; 21 | } 22 | return false; 23 | } 24 | 25 | // 将px值转换为dip或dp值 26 | public static int px2dip(Context context, float pxValue) { 27 | final float scale = context.getResources().getDisplayMetrics().density; 28 | return (int) (pxValue / scale + 0.5f); 29 | } 30 | 31 | // 将dip或dp值转换为px值 32 | public static int dip2px(Context context, float dipValue) { 33 | final float scale = context.getResources().getDisplayMetrics().density; 34 | return (int) (dipValue * scale + 0.5f); 35 | } 36 | 37 | // 将px值转换为sp值 38 | public static int px2sp(Context context, float pxValue) { 39 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 40 | return (int) (pxValue / fontScale + 0.5f); 41 | } 42 | 43 | // 将sp值转换为px值 44 | public static int sp2px(Context context, float spValue) { 45 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 46 | return (int) (spValue * fontScale + 0.5f); 47 | } 48 | 49 | // 屏幕宽度(像素) 50 | public static int getWindowWidth(Activity context) { 51 | DisplayMetrics metric = new DisplayMetrics(); 52 | context.getWindowManager().getDefaultDisplay().getMetrics(metric); 53 | return metric.widthPixels; 54 | } 55 | 56 | // 屏幕高度(像素) 57 | public static int getWindowHeight(Activity context) { 58 | DisplayMetrics metric = new DisplayMetrics(); 59 | context.getWindowManager().getDefaultDisplay().getMetrics(metric); 60 | return metric.heightPixels; 61 | } 62 | 63 | // 根据Unicode编码判断中文汉字和符号 64 | private static boolean isChinese(char c) { 65 | Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); 66 | if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS 67 | || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B 68 | || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS 69 | || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) { 70 | return true; 71 | } 72 | return false; 73 | } 74 | 75 | // 判断中文汉字和符号 76 | public static boolean isChinese(String strName) { 77 | char[] ch = strName.toCharArray(); 78 | for (int i = 0; i < ch.length; i++) { 79 | char c = ch[i]; 80 | if (isChinese(c)) { 81 | return true; 82 | } 83 | } 84 | return false; 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /marqueeview/src/main/res/anim/anim_bottom_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/anim/anim_bottom_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/anim/anim_left_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/anim/anim_left_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/anim/anim_right_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/anim/anim_right_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/anim/anim_top_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/anim/anim_top_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /marqueeview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MarqueeView 3 | 4 | -------------------------------------------------------------------------------- /resources/MarqueeView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/resources/MarqueeView.gif -------------------------------------------------------------------------------- /resources/fir.im.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/resources/fir.im.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':marqueeview', 2 | ':app' -------------------------------------------------------------------------------- /signings/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/signings/keystore.jks -------------------------------------------------------------------------------- /signings/keystore.jks.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/MarqueeView/8dbe328af7d1bf7364f33698f710747630461ee2/signings/keystore.jks.enc -------------------------------------------------------------------------------- /signings/signing.properties: -------------------------------------------------------------------------------- 1 | KEYSTORE_FILEPATH=../signings/keystore.jks 2 | STORE_PASSWORD=sunfusheng 3 | KEY_ALIAS=sunfusheng 4 | KEY_PASSWORD=sunfusheng --------------------------------------------------------------------------------