├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── MarqueeTextView
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── zfdang
│ │ └── MarqueeTextView.kt
│ └── res
│ └── values
│ └── attrs.xml
├── README.md
├── app
├── .gitignore
├── billboard.keystore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── zfdang
│ │ └── mbb
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── zfdang
│ │ │ └── mbb
│ │ │ ├── BillboardActivity.java
│ │ │ └── Setting.java
│ └── res
│ │ ├── drawable-anydpi
│ │ └── ic_setting.xml
│ │ ├── drawable-hdpi
│ │ └── ic_setting.png
│ │ ├── drawable-mdpi
│ │ └── ic_setting.png
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xhdpi
│ │ └── ic_setting.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_setting.png
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ └── thumb.xml
│ │ ├── ic_launcher-web.png
│ │ ├── launcher.xcf
│ │ ├── layout
│ │ ├── activity_billboard.xml
│ │ └── setting_dialog.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-ldpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── playstore-banner.png
│ │ ├── playstore-icon.png
│ │ ├── values-en
│ │ └── strings.xml
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values-zh
│ │ └── strings.xml
│ │ ├── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ ├── data_extraction_rules.xml
│ │ └── locales_config.xml
│ └── test
│ └── java
│ └── com
│ └── zfdang
│ └── mbb
│ └── ExampleUnitTest.java
├── build.gradle
├── docs
├── CNAME
├── MBB.mp4
├── WechatIMG265.jpeg
├── WechatIMG266.jpeg
├── WechatIMG267.jpeg
├── WechatIMG268.jpeg
├── demo.gif
├── index.html
├── mbb-release-v2022.10.20.apk
└── privacy.html
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Build_MBB_APK
4 |
5 | # Controls when the action will run. Triggers the workflow on push or pull request
6 | # events but only for the master branch
7 | on:
8 | push:
9 | branches: [ main ]
10 | pull_request:
11 | branches: [ main ]
12 |
13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
14 | jobs:
15 | # This workflow contains a single job called "build"
16 | build:
17 | # The type of runner that the job will run on
18 | runs-on: ubuntu-latest
19 |
20 | # Steps represent a sequence of tasks that will be executed as part of the job
21 | steps:
22 |
23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
24 | - name: checkout codes
25 | uses: actions/checkout@v2
26 | with:
27 | fetch-depth: '0' # 0 indicates all history, this is needed for git revision
28 |
29 | - name: Set up JDK
30 | uses: actions/setup-java@v1
31 | with:
32 | java-version: '11'
33 |
34 | - name: Assemble Release APK
35 | run: ./gradlew assembleRelease --stacktrace
36 |
37 | - name: Upload APK to artifacts
38 | uses: actions/upload-artifact@v1
39 | with:
40 | name: MBB
41 | path: ./app/build/outputs/apk/
42 |
43 | - name: Create ZIPs
44 | working-directory: ./app/build/outputs/apk/
45 | run: |
46 | zip -r ./MBB.zip ./release
47 |
48 | - name: Get git revision
49 | id: get_git_revision
50 | run: |
51 | echo "::set-output name=tag_name::$(($(git rev-list HEAD --count) + 100))"
52 | echo "::set-output name=release_name::$(($(git rev-list HEAD --count) + 100))"
53 |
54 | - name: Create Release
55 | id: create_release
56 | uses: actions/create-release@v1
57 | env:
58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59 | with:
60 | tag_name: tag-${{ steps.get_git_revision.outputs.tag_name }}
61 | release_name: Release ${{ steps.get_git_revision.outputs.release_name }}
62 | draft: false
63 | prerelease: false
64 |
65 | - name: Upload Release APK
66 | id: upload-release-asset-images
67 | uses: actions/upload-release-asset@v1
68 | env:
69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70 | with:
71 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
72 | asset_path: ./app/build/outputs/apk/MBB.zip
73 | asset_name: MBB.zip
74 | asset_content_type: application/zip
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | /.idea/misc.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/MarqueeTextView/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/MarqueeTextView/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk myCompileSdkVersion
8 |
9 | defaultConfig {
10 | minSdk myMinSdkVersion
11 | targetSdk myTargetSdkVersion
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | kotlinOptions {
28 | jvmTarget = '1.8'
29 | }
30 | }
31 |
32 | dependencies {
33 |
34 | implementation 'androidx.core:core-ktx:1.7.0'
35 | implementation 'androidx.appcompat:appcompat:1.3.0'
36 | implementation 'com.google.android.material:material:1.4.0'
37 | testImplementation 'junit:junit:4.13.2'
38 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
39 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
40 | }
--------------------------------------------------------------------------------
/MarqueeTextView/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/MarqueeTextView/consumer-rules.pro
--------------------------------------------------------------------------------
/MarqueeTextView/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
--------------------------------------------------------------------------------
/MarqueeTextView/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/MarqueeTextView/src/main/java/com/zfdang/MarqueeTextView.kt:
--------------------------------------------------------------------------------
1 | package com.zfdang
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Color
6 | import android.graphics.Paint
7 | import android.os.Handler
8 | import android.os.Looper
9 | import android.os.Message
10 | import android.text.TextPaint
11 | import android.util.AttributeSet
12 | import android.view.View
13 | import androidx.annotation.ColorInt
14 | import androidx.annotation.FloatRange
15 | import androidx.annotation.Px
16 | import java.lang.ref.WeakReference
17 | import kotlin.math.abs
18 | import kotlin.math.ceil
19 |
20 | /**
21 | * 文本横向滚动,跑马灯效果
22 | * - 设置的内容不要太长,建议不要超过 [String] 最大长度的 1/5
23 | * @author JianXin
24 | * @date 2021-07-22 11:59
25 | */
26 | open class MarqueeTextView @JvmOverloads constructor(
27 | context: Context?,
28 | attrs: AttributeSet? = null,
29 | defStyleAttr: Int = 0
30 | ) : View(context, attrs, defStyleAttr) {
31 |
32 | companion object {
33 | const val TAG = "MarqueeTextView"
34 | const val BLANK = " "
35 | const val REPEAT_SINGLE = 1 //一次结束
36 | const val REPEAT_SINGLE_LOOP = 0 //单个循序
37 | const val REPEAT_FILL_LOOP = -1 // 填充后循环
38 | }
39 |
40 | /***
41 | * 滚动速度
42 | */
43 | var speed = 1f
44 | set(value) {
45 | if (value <= 0) {
46 | field = 0f
47 | stop()
48 | } else {
49 | field = value
50 | }
51 | }
52 |
53 | /**
54 | * 文本内容
55 | */
56 | var text = ""
57 | set(value) {
58 | if (value.isEmpty()) {
59 | return
60 | }
61 | field = value
62 | var targetContent = value.trim()
63 | if (isResetLocation) { // 控制重新设置文本内容的时候,是否初始化xLocation。
64 | xLocation = width * leftMarginPercentage
65 | }
66 |
67 | // 根据text之间的距离,补足空格
68 | val endingBlanks = getEndingBlanks()
69 | targetContent += endingBlanks
70 |
71 | // 根据模式计算宽度
72 | if (repeat == REPEAT_FILL_LOOP) {
73 | mFinalDrawText = ""
74 | //计算文本的宽度
75 | mSingleContentWidth = getTextWidth(targetContent)
76 | if (mSingleContentWidth > 0) {
77 | // 最大可见内容项数
78 | val maxVisibleCount = ceil(width / mSingleContentWidth.toDouble()).toInt() + 1
79 | repeat(maxVisibleCount) {
80 | mFinalDrawText += targetContent
81 | }
82 | }
83 | mContentWidth = getTextWidth(mFinalDrawText)
84 | } else {
85 | if (xLocation < 0 && repeat == REPEAT_SINGLE) {
86 | if (abs(xLocation) > mContentWidth) {
87 | xLocation = width * leftMarginPercentage
88 | }
89 | }
90 | mFinalDrawText = targetContent
91 | mContentWidth = getTextWidth(mFinalDrawText)
92 | mSingleContentWidth = mContentWidth
93 | }
94 | textHeight = getTextHeight()
95 | invalidate()
96 | }
97 |
98 | /**
99 | * 最终绘制显示的文本
100 | */
101 | private var mFinalDrawText: String = ""
102 |
103 | /**
104 | * 文字颜色
105 | */
106 | @ColorInt
107 | var textColor = Color.BLACK
108 | set(value) {
109 | if (value != field) {
110 | field = value
111 | textPaint.color = textColor
112 | invalidate()
113 | }
114 | }
115 |
116 | /**
117 | * 字体大小
118 | */
119 | @Px
120 | var textSize = 12f
121 | set(value) {
122 | if (value > 0 && value != field) {
123 | field = value
124 | textPaint.textSize = value
125 | // call setText to reset
126 | if (text.isNotEmpty()) {
127 | text = text
128 | }
129 | }
130 | }
131 |
132 | /**item间距,*/
133 | @Px
134 | var textItemDistance = 60f
135 | set(value) {
136 | if (field == value) {
137 | return
138 | }
139 | field = if (value < 0f) 0f else value
140 | // call setText to reset
141 | if (text.isNotEmpty()) {
142 | text = text
143 | }
144 | }
145 |
146 | /**
147 | * 滚动模式
148 | */
149 | var repeat = REPEAT_SINGLE_LOOP
150 | set(value) {
151 | if (value != field) {
152 | field = value
153 | resetInit = true
154 | // call setText to reset
155 | text = text
156 | }
157 | }
158 |
159 | /**
160 | * 开始的位置距离左边,0~1,0:最左边,1:最右边,0.5:中间。
161 | */
162 | @FloatRange(from = 0.0, to = 1.0)
163 | var leftMarginPercentage = 0.0f
164 | set(value) {
165 | if (value == field) {
166 | return
167 | }
168 | field = when {
169 | value < 0f -> 0f
170 | value > 1f -> 1f
171 | else -> value
172 | }
173 | }
174 |
175 | /**
176 | * 是否重置文本绘制的位置,默认为true
177 | */
178 | var isResetLocation = true
179 |
180 | private var xLocation = 0f // 文本的x坐标
181 |
182 | // 单个显示内容的宽度
183 | private var mSingleContentWidth: Float = 0f
184 |
185 | /** 最终绘制的内容的宽度 */
186 | private var mContentWidth = 0f
187 |
188 | /**是否继续滚动*/
189 | var isRolling = false
190 | private set
191 |
192 | /**画笔*/
193 | protected val textPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
194 |
195 | private var textHeight = 0f
196 | private var resetInit = true
197 |
198 | private val mHandler by lazy { MyHandler(this) }
199 |
200 | /**
201 | * 是否用户主动调用,默认 true
202 | */
203 | var isRollByUser = true
204 |
205 | init {
206 | initAttrs(attrs)
207 | initPaint()
208 | }
209 |
210 | override fun onDraw(canvas: Canvas) {
211 | super.onDraw(canvas)
212 | if (resetInit && text.isNotEmpty()) {
213 | textItemDistance = textItemDistance
214 | xLocation = width * leftMarginPercentage
215 | resetInit = false
216 | }
217 | val absLocation = abs(xLocation)
218 | when (repeat) {
219 | REPEAT_SINGLE -> if (mContentWidth < absLocation) {
220 | stop()
221 | }
222 | REPEAT_SINGLE_LOOP -> if (mContentWidth <= absLocation) {
223 | //一轮结束
224 | xLocation = width.toFloat()
225 | }
226 | REPEAT_FILL_LOOP -> if (xLocation < 0 && mSingleContentWidth <= absLocation) {
227 | xLocation = mSingleContentWidth - absLocation
228 | }
229 | else ->
230 | if (mContentWidth < absLocation) {
231 | //也就是说文字已经到头了
232 | stop()
233 | }
234 | }
235 | //绘制文本
236 | if (mFinalDrawText.isNotBlank()) {
237 | canvas.drawText(mFinalDrawText, xLocation, height / 2 + textHeight / 2, textPaint)
238 | }
239 | }
240 |
241 | override fun onAttachedToWindow() {
242 | super.onAttachedToWindow()
243 | if (!isRollByUser) {
244 | startInternal(true)
245 | }
246 | }
247 |
248 | override fun onDetachedFromWindow() {
249 | if (isRolling) {
250 | stopInternal(false)
251 | }
252 | super.onDetachedFromWindow()
253 | }
254 |
255 | override fun setVisibility(visibility: Int) {
256 | super.setVisibility(visibility)
257 | if (visibility != VISIBLE) {
258 | stopInternal(false)
259 | } else {
260 | if (!isRollByUser) {
261 | startInternal(false)
262 | }
263 | }
264 | }
265 |
266 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
267 | super.onSizeChanged(w, h, oldw, oldh)
268 | text = text
269 | }
270 |
271 | private fun initAttrs(attrs: AttributeSet?) {
272 | val a = context.obtainStyledAttributes(attrs, R.styleable.MarqueeTextView)
273 | textColor = a.getColor(R.styleable.MarqueeTextView_android_textColor, textColor)
274 | isResetLocation = a.getBoolean(R.styleable.MarqueeTextView_marqueeResetLocation, true)
275 | speed = a.getFloat(R.styleable.MarqueeTextView_marqueeSpeed, 1f)
276 | textSize = a.getDimension(R.styleable.MarqueeTextView_android_textSize, 12f)
277 | textItemDistance = a.getDimension(R.styleable.MarqueeTextView_marqueeItemDistance, 50f)
278 | leftMarginPercentage = a.getFloat(
279 | R.styleable.MarqueeTextView_marqueeLeftMarginPercentage,
280 | 0f
281 | )
282 | repeat = a.getInt(R.styleable.MarqueeTextView_marqueeRepeat, REPEAT_SINGLE_LOOP)
283 | text = a.getText(R.styleable.MarqueeTextView_android_text)?.toString() ?: ""
284 | a.recycle()
285 | }
286 |
287 | /**
288 | * 刻字机修改
289 | */
290 | private fun initPaint() {
291 | textPaint.apply {
292 | style = Paint.Style.FILL
293 | color = textColor
294 | textSize = textSize
295 | isAntiAlias = true
296 | density = 1 / resources.displayMetrics.density
297 | }
298 | }
299 |
300 | /**
301 | * 切换开始暂停
302 | */
303 | fun toggle() {
304 | if (isRolling) {
305 | stop()
306 | } else {
307 | start()
308 | }
309 | }
310 |
311 | /**
312 | * 继续滚动
313 | */
314 | fun start() {
315 | startInternal(true)
316 | }
317 |
318 | /**
319 | * [isRollByUser] 是否用户主动调用
320 | */
321 | protected fun startInternal(isRollByUser: Boolean) {
322 | this.isRollByUser = isRollByUser
323 | stop()
324 | if (text.isNotBlank()) {
325 | mHandler.sendEmptyMessage(MyHandler.WHAT_RUN)
326 | isRolling = true
327 | }
328 | }
329 |
330 | /**
331 | * 停止滚动
332 | */
333 | fun stop() {
334 | stopInternal(true)
335 | }
336 |
337 | /**
338 | * [isRollByUser] 是否用户主动调用
339 | */
340 | protected fun stopInternal(isRollByUser: Boolean) {
341 | this.isRollByUser = isRollByUser
342 | isRolling = false
343 | mHandler.removeMessages(MyHandler.WHAT_RUN)
344 | }
345 |
346 | /**
347 | * 计算出一个空格的宽度
348 | * @return
349 | */
350 | private fun getBlankWidth(): Float {
351 | return getTextWidth(BLANK)
352 | }
353 |
354 | private fun getTextWidth(text: String?): Float {
355 | if (text.isNullOrEmpty()) {
356 | return 0f
357 | }
358 | return textPaint.measureText(text)
359 | }
360 |
361 | /**
362 | * 文本高度
363 | */
364 | private fun getTextHeight(): Float {
365 | val fontMetrics = textPaint.fontMetrics
366 | return abs(fontMetrics.bottom - fontMetrics.top) / 2
367 | }
368 |
369 | private fun getEndingBlanks(): String {
370 | val oneBlankWidth = getBlankWidth() //空格的宽度
371 | var count = 1
372 | if (textItemDistance > 0 && oneBlankWidth != 0f) {
373 | count = (ceil(textItemDistance / oneBlankWidth).toInt()) //粗略计算空格数量
374 | }
375 | val builder = StringBuilder(count)
376 | for (i in 0..count) {
377 | builder.append(BLANK)//间隔字符串
378 | }
379 | return builder.toString()
380 | }
381 |
382 | /**
383 | * handler
384 | *
385 | * @author JianXin
386 | * @date 2021-07-22 11:43
387 | */
388 | private class MyHandler(view: MarqueeTextView) : Handler(Looper.getMainLooper()) {
389 |
390 | companion object {
391 | internal const val WHAT_RUN = 1001
392 | }
393 |
394 | private val mRef = WeakReference(view)
395 |
396 | override fun handleMessage(msg: Message) {
397 | super.handleMessage(msg)
398 | if (msg.what == WHAT_RUN) {
399 | mRef.get()?.apply {
400 | if (speed > 0) {
401 | xLocation -= speed
402 | invalidate()
403 | // 10 毫秒绘制一次
404 | sendEmptyMessageDelayed(WHAT_RUN, 10)
405 | }
406 | }
407 | }
408 | }
409 | }
410 |
411 | }
--------------------------------------------------------------------------------
/MarqueeTextView/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mobile-billboard
2 | turn your mobile phone to a led billboard
3 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/billboard.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/billboard.keystore
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | // http://www.race604.com/android-auto-version/
7 | // use git count as build number, as current date as build version
8 | def getDate() {
9 | def date = new Date()
10 | def formattedDate = date.format('yyyy.MM.dd')
11 | return formattedDate
12 | }
13 | def cmd = 'git rev-list HEAD --count'
14 | def gitVersion = cmd.execute().text.trim().toInteger() + 100
15 | def appVersion = getDate()
16 |
17 | android {
18 | compileSdk myCompileSdkVersion
19 |
20 | defaultConfig {
21 | applicationId "com.zfdang.mbb"
22 | minSdk myMinSdkVersion
23 | targetSdk myTargetSdkVersion
24 | versionCode gitVersion
25 | versionName appVersion
26 |
27 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
28 | }
29 |
30 | signingConfigs {
31 | release {
32 | storeFile file("billboard.keystore")
33 | storePassword "billboard"
34 | keyAlias "billboard"
35 | keyPassword "billboard"
36 | }
37 | }
38 |
39 | buildTypes {
40 | release {
41 | signingConfig signingConfigs.release
42 | minifyEnabled false
43 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
44 | }
45 | }
46 | compileOptions {
47 | sourceCompatibility JavaVersion.VERSION_1_8
48 | targetCompatibility JavaVersion.VERSION_1_8
49 | }
50 | buildFeatures {
51 | viewBinding true
52 | }
53 |
54 | // Add this block and enable/disable the parameters as follows
55 | bundle {
56 | density {
57 | // Different APKs are generated for devices with different screen densities; true by default.
58 | enableSplit true
59 | }
60 | abi {
61 | // Different APKs are generated for devices with different CPU architectures; true by default.
62 | enableSplit true
63 | }
64 | language {
65 | // This is disabled so that the App Bundle does NOT split the APK for each language.
66 | // We're gonna use the same APK for all languages.
67 | enableSplit true
68 | }
69 | }
70 |
71 | // http://www.sollyu.com/android-apk-studio-generated-automatically-appends-a-version-number/
72 | android.applicationVariants.all { variant ->
73 | variant.outputs.all { output ->
74 | if(buildType.name == "release") {
75 | output.outputFileName = new File("mbb" + "-" + buildType.name + "-v" + defaultConfig.versionName + ".apk" );
76 | } else {
77 | output.outputFileName = new File("mbb" + "-" + buildType.name + "-v" + defaultConfig.versionName + ".apk" );
78 | }
79 | }
80 | }
81 | }
82 |
83 | dependencies {
84 | implementation 'androidx.appcompat:appcompat:1.5.1'
85 | implementation 'com.google.android.material:material:1.6.1'
86 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
87 | implementation 'androidx.databinding:databinding-runtime:7.3.1'
88 | implementation 'com.github.skydoves:colorpickerview:2.2.4'
89 | implementation 'com.github.moisoni97:IndicatorSeekBar:3.0.0'
90 |
91 | implementation project(':MarqueeTextView')
92 |
93 | testImplementation 'junit:junit:4.13.2'
94 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
95 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
96 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/zfdang/mbb/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.zfdang.mbb;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.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.getInstrumentation().getTargetContext();
24 | assertEquals("com.zfdang.mbb", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
17 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zfdang/mbb/BillboardActivity.java:
--------------------------------------------------------------------------------
1 | package com.zfdang.mbb;
2 |
3 | import android.annotation.SuppressLint;
4 |
5 | import androidx.appcompat.app.ActionBar;
6 | import androidx.appcompat.app.AlertDialog;
7 | import androidx.appcompat.app.AppCompatActivity;
8 |
9 | import android.content.DialogInterface;
10 | import android.content.pm.ActivityInfo;
11 | import android.graphics.Color;
12 | import android.os.Build;
13 | import android.os.Bundle;
14 | import android.os.Handler;
15 | import android.os.Looper;
16 | import android.text.Editable;
17 | import android.text.TextUtils;
18 | import android.text.TextWatcher;
19 | import android.util.Log;
20 | import android.view.View;
21 | import android.view.Window;
22 | import android.view.WindowInsets;
23 | import android.view.WindowManager;
24 | import android.widget.CompoundButton;
25 | import android.widget.EditText;
26 | import android.widget.RadioButton;
27 | import android.widget.Toast;
28 |
29 | import com.google.android.material.floatingactionbutton.FloatingActionButton;
30 | import com.skydoves.colorpickerview.ColorEnvelope;
31 | import com.skydoves.colorpickerview.ColorPickerView;
32 | import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener;
33 | import com.skydoves.colorpickerview.sliders.BrightnessSlideBar;
34 | import com.warkiz.widget.IndicatorSeekBar;
35 | import com.warkiz.widget.OnSeekChangeListener;
36 | import com.warkiz.widget.SeekParams;
37 | import com.zfdang.MarqueeTextView;
38 | import com.zfdang.mbb.databinding.SettingDialogBinding;
39 | import com.zfdang.mbb.databinding.ActivityBillboardBinding;
40 |
41 | /**
42 | * An example full-screen activity that shows and hides the system UI (i.e.
43 | * status bar and navigation/system bar) with user interaction.
44 | */
45 | public class BillboardActivity extends AppCompatActivity {
46 | private static final String TAG = "BillboardActivity";
47 |
48 | private static final int UI_ANIMATION_DELAY = 300;
49 |
50 | private MarqueeTextView mMarqueeTextView;
51 |
52 | private final Handler mHideHandler = new Handler(Looper.myLooper());
53 |
54 | private ActivityBillboardBinding activityBinding;
55 |
56 | private FloatingActionButton btSetting;
57 | private Setting mSetting;
58 |
59 | @Override
60 | protected void onCreate(Bundle savedInstanceState) {
61 | super.onCreate(savedInstanceState);
62 |
63 | activityBinding = ActivityBillboardBinding.inflate(getLayoutInflater());
64 | setContentView(activityBinding.getRoot());
65 |
66 | mMarqueeTextView = activityBinding.marqueeTextview;
67 | btSetting = activityBinding.fabSetting;
68 |
69 | // show billboard in landscape mode
70 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
71 |
72 | // add floating button for config page
73 | btSetting.setOnClickListener(new View.OnClickListener() {
74 | @Override
75 | public void onClick(View view) {
76 | openSettingDialog();
77 | }
78 | });
79 |
80 | initSetting();
81 | updateMarqueeTextView();
82 | }
83 |
84 | private void initSetting() {
85 | mSetting = new Setting();
86 | mSetting.text = getString(R.string.defaut_marquee_text);
87 | }
88 |
89 | private void updateMarqueeTextView() {
90 | mMarqueeTextView.stop();
91 | mMarqueeTextView.setText(mSetting.text);
92 | mMarqueeTextView.setTextSize(mSetting.textSize);
93 | mMarqueeTextView.setSpeed(mSetting.speed);
94 | mMarqueeTextView.setTextColor(mSetting.textColor);
95 | mMarqueeTextView.setBackgroundColor(mSetting.bgColor);
96 | mMarqueeTextView.setRepeat(mSetting.loopMode);
97 | mMarqueeTextView.start();
98 | }
99 |
100 | private void setFullScreenMode(Window window) {
101 | //设置永不休眠模式
102 | window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
103 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
104 | //隐藏系统工具栏方式一
105 | window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
106 | //隐藏底部导航栏
107 | View decorView = window.getDecorView();
108 | if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) {
109 | decorView.setSystemUiVisibility(View.GONE);
110 | } else if (Build.VERSION.SDK_INT >= 19) {
111 | decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
112 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
113 | | View.SYSTEM_UI_FLAG_FULLSCREEN);
114 | }
115 | decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
116 | @Override
117 | public void onSystemUiVisibilityChange(int visibility) {
118 | View decorView = getWindow().getDecorView();
119 | int uiState = decorView.getSystemUiVisibility();
120 | if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) {
121 | if (uiState != View.GONE) decorView.setSystemUiVisibility(View.GONE);
122 | } else if (Build.VERSION.SDK_INT >= 19) {
123 | if (uiState != (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
124 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
125 | | View.SYSTEM_UI_FLAG_FULLSCREEN))
126 | decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
127 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
128 | | View.SYSTEM_UI_FLAG_FULLSCREEN);
129 | }
130 | }
131 | });
132 | }
133 |
134 | void openSettingDialog() {
135 | SettingDialogBinding dialogBinding = SettingDialogBinding.inflate(getLayoutInflater());
136 |
137 | // binding controls
138 | final EditText etText = dialogBinding.settingText;
139 | final RadioButton rbSingle = dialogBinding.radioButtonSingle;
140 | final RadioButton rbLoop = dialogBinding.radioButtonLoop;
141 | final RadioButton rbFillLoop = dialogBinding.radioButtonFillLoop;
142 | final IndicatorSeekBar sbTextSize = dialogBinding.seekBarTextSize;
143 | final IndicatorSeekBar sbSpeed = dialogBinding.seekBarSpeed;
144 | final ColorPickerView pickerTextColor = dialogBinding.colorPickerViewText;
145 | final BrightnessSlideBar brightnessSlideBarText = dialogBinding.slidebarText;
146 | pickerTextColor.attachBrightnessSlider(brightnessSlideBarText);
147 | final EditText etTextColor = dialogBinding.settingTextColorValue;
148 | final ColorPickerView pickerBgColor = dialogBinding.colorPickerViewBg;
149 | final BrightnessSlideBar brightnessSlideBarBg = dialogBinding.slidebarBg;
150 | pickerBgColor.attachBrightnessSlider(brightnessSlideBarBg);
151 | final EditText etBgColor = dialogBinding.settingBgColorValue;
152 |
153 | // init dialog values from setting
154 | etText.setText(mSetting.text);
155 | rbSingle.setChecked(mSetting.loopMode == Setting.LM_SINGLE);
156 | rbLoop.setChecked(mSetting.loopMode == Setting.LM_LOOP);
157 | rbFillLoop.setChecked(mSetting.loopMode == Setting.LM_FILL_LOOP);
158 | sbTextSize.setProgress(mSetting.textSize);
159 | sbSpeed.setProgress(mSetting.speed);
160 | pickerTextColor.setInitialColor(mSetting.textColor);
161 | etTextColor.setText(pickerTextColor.getColorEnvelope().getHexCode());
162 | pickerBgColor.setInitialColor(mSetting.bgColor);
163 | etBgColor.setText(pickerBgColor.getColorEnvelope().getHexCode());
164 |
165 | // add color change listeners for color picker
166 | pickerTextColor.setColorListener(new ColorEnvelopeListener() {
167 | @Override
168 | public void onColorSelected(ColorEnvelope envelope, boolean fromUser) {
169 | etTextColor.setText(envelope.getHexCode());
170 | mMarqueeTextView.setTextColor(envelope.getColor());
171 | }
172 | });
173 | pickerBgColor.setColorListener(new ColorEnvelopeListener() {
174 | @Override
175 | public void onColorSelected(ColorEnvelope envelope, boolean fromUser) {
176 | etBgColor.setText(envelope.getHexCode());
177 | mMarqueeTextView.setBackgroundColor(envelope.getColor());
178 | }
179 | });
180 |
181 | // add listeners for seekbar
182 | sbTextSize.setOnSeekChangeListener(new OnSeekChangeListener() {
183 | @Override
184 | public void onSeeking(SeekParams seekParams) {
185 |
186 | }
187 |
188 | @Override
189 | public void onStartTrackingTouch(IndicatorSeekBar seekBar) {
190 |
191 | }
192 |
193 | @Override
194 | public void onStopTrackingTouch(IndicatorSeekBar seekBar) {
195 | mMarqueeTextView.setTextSize(seekBar.getProgress());
196 | }
197 | });
198 | sbSpeed.setOnSeekChangeListener(new OnSeekChangeListener() {
199 | @Override
200 | public void onSeeking(SeekParams seekParams) {
201 |
202 | }
203 |
204 | @Override
205 | public void onStartTrackingTouch(IndicatorSeekBar seekBar) {
206 |
207 | }
208 |
209 | @Override
210 | public void onStopTrackingTouch(IndicatorSeekBar seekBar) {
211 | mMarqueeTextView.setSpeed(seekBar.getProgress());
212 | }
213 | });
214 |
215 | // set ratio button listeners
216 | rbSingle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
217 | @Override
218 | public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
219 | if(b) {
220 | mMarqueeTextView.setRepeat(Setting.LM_SINGLE);
221 | }
222 | }
223 | });
224 | rbLoop.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
225 | @Override
226 | public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
227 | if(b) {
228 | mMarqueeTextView.setRepeat(Setting.LM_LOOP);
229 | }
230 |
231 | }
232 | });
233 | rbFillLoop.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
234 | @Override
235 | public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
236 | if(b) {
237 | mMarqueeTextView.setRepeat(Setting.LM_FILL_LOOP);
238 | }
239 | }
240 | });
241 |
242 | etText.addTextChangedListener(new TextWatcher() {
243 | @Override
244 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
245 |
246 | }
247 |
248 | @Override
249 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
250 |
251 | }
252 |
253 | @Override
254 | public void afterTextChanged(Editable editable) {
255 | mMarqueeTextView.setText(editable.toString());
256 | }
257 | });
258 |
259 | AlertDialog dialog = new AlertDialog.Builder(this) // 使用android.support.v7.app.AlertDialog
260 | .setView(dialogBinding.getRoot()) // 设置布局
261 | .setCancelable(false) // 设置点击空白处不关闭
262 | .setPositiveButton(R.string.dialog_confirm, new DialogInterface.OnClickListener() {
263 | @Override
264 | public void onClick(DialogInterface dialog, int which) {
265 | String text = etText.getText().toString();
266 | if (TextUtils.isEmpty(text)) {
267 | // 判断输入的内容是否为空
268 | Toast.makeText(BillboardActivity.this, R.string.alert_content_empty, Toast.LENGTH_SHORT).show();
269 | } else {
270 | mSetting.text = text;
271 | }
272 |
273 | // save all settings
274 | mSetting.speed = sbSpeed.getProgress();
275 | mSetting.textSize = sbTextSize.getProgress();
276 | mSetting.textColor = pickerTextColor.getColor();
277 | mSetting.bgColor = pickerBgColor.getColor();
278 | if(rbFillLoop.isChecked()) {
279 | mSetting.loopMode = Setting.LM_FILL_LOOP;
280 | } else if(rbLoop.isChecked()) {
281 | mSetting.loopMode = Setting.LM_LOOP;
282 | } else if(rbSingle.isChecked()) {
283 | mSetting.loopMode = Setting.LM_SINGLE;
284 | }
285 |
286 | updateMarqueeTextView();
287 |
288 | // enter full screen again
289 | delayedSetFullScreen(100);
290 | }
291 | }) // 设置确定按钮,并设置监听事件})
292 | .setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
293 | @Override
294 | public void onClick(DialogInterface dialog, int which) {
295 | // reset all values by mSetting
296 | updateMarqueeTextView();
297 |
298 | // enter full screen again
299 | delayedSetFullScreen(100);
300 | }
301 | }) // 设置取消按钮,并设置监听事件
302 | .setNeutralButton(R.string.dialog_exit, new DialogInterface.OnClickListener() {
303 | public void onClick(DialogInterface dialog, int id) {
304 | finish();
305 | }
306 | }) // 设置退出对话框
307 | .create(); // 创建对话框
308 |
309 | final Window window = dialog.getWindow();
310 | setFullScreenMode(window);
311 | dialog.show();
312 | }
313 |
314 | @Override
315 | protected void onPostCreate(Bundle savedInstanceState) {
316 | super.onPostCreate(savedInstanceState);
317 |
318 | // Trigger the initial hide() shortly after the activity has been created
319 | delayedSetFullScreen(100);
320 | }
321 |
322 | /**
323 | * Schedules a call to hide() in delay milliseconds, canceling any
324 | * previously scheduled calls.
325 | */
326 | private void delayedSetFullScreen(int delayMillis) {
327 | // mHideRunnable --> mHidePart2Runnable
328 | mHideHandler.removeCallbacks(mHideRunnable);
329 | mHideHandler.postDelayed(mHideRunnable, delayMillis);
330 | }
331 |
332 | private final Runnable mHideRunnable = new Runnable() {
333 | @Override
334 | public void run() {
335 | // Hide UI first
336 | ActionBar actionBar = getSupportActionBar();
337 | if (actionBar != null) {
338 | actionBar.hide();
339 | }
340 | // Schedule a runnable to remove the status and navigation bar after a delay
341 | mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
342 | }
343 | };
344 |
345 | private final Runnable mHidePart2Runnable = new Runnable() {
346 | @SuppressLint("InlinedApi")
347 | @Override
348 | public void run() {
349 | // Delayed removal of status and navigation bar
350 | if (Build.VERSION.SDK_INT >= 30) {
351 | mMarqueeTextView.getWindowInsetsController().hide(
352 | WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
353 | } else {
354 | // Note that some of these constants are new as of API 16 (Jelly Bean)
355 | // and API 19 (KitKat). It is safe to use them, as they are inlined
356 | // at compile-time and do nothing on earlier devices.
357 | mMarqueeTextView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
358 | | View.SYSTEM_UI_FLAG_FULLSCREEN
359 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
360 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
361 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
362 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
363 | }
364 | }
365 | };
366 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/zfdang/mbb/Setting.java:
--------------------------------------------------------------------------------
1 | package com.zfdang.mbb;
2 |
3 | import android.graphics.Color;
4 |
5 | public class Setting {
6 |
7 | static public int LM_SINGLE = 1;
8 | static public int LM_LOOP = 0;
9 | static public int LM_FILL_LOOP = -1;
10 |
11 | public String text;
12 | public int loopMode;
13 | public int textSize;
14 | public int speed;
15 | public int textColor;
16 | public int bgColor;
17 |
18 | public Setting() {
19 | this.text = "";
20 | this.loopMode = LM_FILL_LOOP;
21 | this.textSize = 800;
22 | this.speed = 15;
23 | this.textColor = Color.valueOf(1,0,0).toArgb();
24 | this.bgColor = Color.valueOf(0,0,0).toArgb();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_setting.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/drawable-hdpi/ic_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/drawable-mdpi/ic_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/drawable-xhdpi/ic_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/drawable-xxhdpi/ic_setting.png
--------------------------------------------------------------------------------
/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/thumb.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/res/launcher.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/launcher.xcf
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_billboard.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
24 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/setting_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
17 |
18 |
22 |
23 |
28 |
29 |
36 |
37 |
38 |
43 |
44 |
48 |
49 |
55 |
56 |
57 |
62 |
63 |
71 |
72 |
73 |
78 |
79 |
84 |
85 |
92 |
93 |
94 |
95 |
100 |
101 |
104 |
105 |
117 |
118 |
119 |
120 |
125 |
126 |
131 |
132 |
139 |
140 |
141 |
142 |
147 |
148 |
151 |
152 |
164 |
165 |
166 |
167 |
168 |
169 |
174 |
175 |
181 |
182 |
187 |
188 |
193 |
194 |
205 |
206 |
207 |
208 |
209 |
212 |
213 |
218 |
219 |
223 |
224 |
231 |
232 |
233 |
234 |
235 |
240 |
241 |
247 |
248 |
253 |
254 |
259 |
260 |
270 |
271 |
272 |
273 |
276 |
277 |
282 |
283 |
287 |
288 |
295 |
296 |
297 |
298 |
299 |
300 |
305 |
306 |
311 |
312 |
318 |
319 |
320 |
324 |
325 |
329 |
330 |
336 |
337 |
342 |
343 |
348 |
349 |
350 |
351 |
356 |
357 |
358 |
359 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/mipmap-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/playstore-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/playstore-banner.png
--------------------------------------------------------------------------------
/app/src/main/res/playstore-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/app/src/main/res/playstore-icon.png
--------------------------------------------------------------------------------
/app/src/main/res/values-en/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Mobile Billboard
3 | Setting
4 | Setting
5 | Text to Display
6 | Loop Mode
7 | Single
8 | Loop
9 | Fill Loop
10 | Text Size
11 | Speed
12 | Text Color
13 | Background Color
14 | Confirm
15 | Cancel
16 | Exit
17 | Text cannot be empty...
18 | Rolling Text...
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 手机广告牌
3 | 配置
4 | 配置
5 | 显示内容
6 | 显示模式
7 | 单次显示
8 | 循环显示
9 | 连续循环显示
10 | 字体大小
11 | 滚动速度
12 | 字体颜色
13 | 背景颜色
14 | 确定
15 | 取消
16 | 退出
17 | 内容不能为空
18 | 滚动显示的文字...
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #FF039BE5
11 | #FF01579B
12 | #FF40C4FF
13 | #FF00B0FF
14 | #66000000
15 |
16 | #C6B4B4
17 | #FF0000
18 | #00FF00
19 | #007dff
20 | #FFFF00
21 | #FF00FF
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 手机广告牌
3 | 配置
4 | 配置
5 | 显示内容
6 | 显示模式
7 | 单次显示
8 | 循环显示
9 | 连续循环显示
10 | 字体大小
11 | 滚动速度
12 | 字体颜色
13 | 背景颜色
14 | 确定
15 | 取消
16 | 退出
17 | 内容不能为空
18 | 滚动显示的文字...
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
24 |
25 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/locales_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/test/java/com/zfdang/mbb/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.zfdang.mbb;
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 | buildscript {
3 | //THIS IS WHAT YOU ARE ADDING
4 | ext {
5 | myKotlinVersion = "1.7.0" //String
6 | myMinSdkVersion = 26 //Integer
7 | myTargetSdkVersion = 32
8 | myCompileSdkVersion = 33
9 | }
10 | }
11 |
12 |
13 | plugins {
14 | id 'com.android.application' version '7.2.2' apply false
15 | id 'com.android.library' version '7.2.2' apply false
16 | id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
17 | }
18 |
19 | task clean(type: Delete) {
20 | delete rootProject.buildDir
21 | }
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | mbb.zfdang.com
--------------------------------------------------------------------------------
/docs/MBB.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/docs/MBB.mp4
--------------------------------------------------------------------------------
/docs/WechatIMG265.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/docs/WechatIMG265.jpeg
--------------------------------------------------------------------------------
/docs/WechatIMG266.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/docs/WechatIMG266.jpeg
--------------------------------------------------------------------------------
/docs/WechatIMG267.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/docs/WechatIMG267.jpeg
--------------------------------------------------------------------------------
/docs/WechatIMG268.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/docs/WechatIMG268.jpeg
--------------------------------------------------------------------------------
/docs/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/docs/demo.gif
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
手机广告牌 Mobile Billboard
2 |
3 | 想让自己的手机屏幕变成一个LED广告牌?简单!
4 |
5 |
6 |
7 |
8 |
9 | 下载:
10 | mbb-release-v2022.10.20.apk
11 |
12 | Play Store
13 |
14 | Github Releases
15 |
16 | 使用说明:
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/docs/mbb-release-v2022.10.20.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/docs/mbb-release-v2022.10.20.apk
--------------------------------------------------------------------------------
/docs/privacy.html:
--------------------------------------------------------------------------------
1 | 手机广告牌 (Mobile Billboard)
2 |
3 | 本程序不收集任何个人信息.
4 |
5 | This application does not collect any personal information.
6 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zfdang/mobile-billboard/f63e22fa19adbeb6aafb6c83dca7f6e06b998396/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Oct 17 11:22:44 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | maven { url "https://jitpack.io" }
14 | }
15 | }
16 | rootProject.name = "Mobile Billboard"
17 | include ':app'
18 | include ':MarqueeTextView'
--------------------------------------------------------------------------------