新增礼物墙相关内容
This commit is contained in:
parent
9c38f40098
commit
845b217c4d
@ -334,4 +334,7 @@
|
||||
<string name="home_function_name_fine_sticker">Exquisite sticker</string>
|
||||
<string name="dialog_reset">Reset</string>
|
||||
<string name="menu_diy">Custom</string>
|
||||
|
||||
<string name="toast_not_detect_face">No face tracking</string>
|
||||
<string name="toast_not_detect_face_or_body">No face or body tracking</string>
|
||||
</resources>
|
||||
|
2
SVGAlibrary/.gitignore
vendored
Normal file
2
SVGAlibrary/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/build
|
||||
*.iml
|
37
SVGAlibrary/build.gradle
Normal file
37
SVGAlibrary/build.gradle
Normal file
@ -0,0 +1,37 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
}
|
||||
compileOptions {
|
||||
kotlinOptions.freeCompilerArgs += ['-module-name', "com.opensource.svgaplayer"]
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'META-INF/ASL2.0'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/NOTICE.txt'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/MANIFEST.MF'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation 'com.squareup.wire:wire-runtime:4.4.1'
|
||||
}
|
17
SVGAlibrary/proguard-rules.pro
vendored
Normal file
17
SVGAlibrary/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/PonyCui_Home/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
8
SVGAlibrary/src/main/AndroidManifest.xml
Normal file
8
SVGAlibrary/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.opensource.svgaplayer">
|
||||
|
||||
<application android:allowBackup="true" android:label="@string/app_name">
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,9 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
/**
|
||||
* Created by miaojun on 2019/6/21.
|
||||
* mail:1290846731@qq.com
|
||||
*/
|
||||
interface IClickAreaListener{
|
||||
fun onResponseArea(key : String,x0 : Int, y0 : Int, x1 : Int, y1 : Int)
|
||||
}
|
119
SVGAlibrary/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Normal file
119
SVGAlibrary/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Normal file
@ -0,0 +1,119 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
import android.content.Context
|
||||
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* SVGA 缓存管理
|
||||
*/
|
||||
object SVGACache {
|
||||
enum class Type {
|
||||
DEFAULT,
|
||||
FILE
|
||||
}
|
||||
|
||||
private const val TAG = "SVGACache"
|
||||
private var type: Type = Type.DEFAULT
|
||||
private var cacheDir: String = "/"
|
||||
get() {
|
||||
if (field != "/") {
|
||||
val dir = File(field)
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs()
|
||||
}
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
|
||||
fun onCreate(context: Context?) {
|
||||
onCreate(context, Type.DEFAULT)
|
||||
}
|
||||
|
||||
fun onCreate(context: Context?, type: Type) {
|
||||
if (isInitialized()) return
|
||||
context ?: return
|
||||
cacheDir = "${context.cacheDir.absolutePath}/svga/"
|
||||
File(cacheDir).takeIf { !it.exists() }?.mkdirs()
|
||||
this.type = type
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
*/
|
||||
fun clearCache() {
|
||||
if (!isInitialized()) {
|
||||
LogUtils.error(TAG, "SVGACache is not init!")
|
||||
return
|
||||
}
|
||||
SVGAParser.threadPoolExecutor.execute {
|
||||
clearDir(cacheDir)
|
||||
LogUtils.info(TAG, "Clear svga cache done!")
|
||||
}
|
||||
}
|
||||
|
||||
// 清除目录下的所有文件
|
||||
internal fun clearDir(path: String) {
|
||||
try {
|
||||
val dir = File(path)
|
||||
dir.takeIf { it.exists() }?.let { parentDir ->
|
||||
parentDir.listFiles()?.forEach { file ->
|
||||
if (!file.exists()) {
|
||||
return@forEach
|
||||
}
|
||||
if (file.isDirectory) {
|
||||
clearDir(file.absolutePath)
|
||||
}
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.error(TAG, "Clear svga cache path: $path fail", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun isInitialized(): Boolean {
|
||||
return "/" != cacheDir && File(cacheDir).exists()
|
||||
}
|
||||
|
||||
fun isDefaultCache(): Boolean = type == Type.DEFAULT
|
||||
|
||||
fun isCached(cacheKey: String): Boolean {
|
||||
return if (isDefaultCache()) {
|
||||
buildCacheDir(cacheKey)
|
||||
} else {
|
||||
buildSvgaFile(
|
||||
cacheKey
|
||||
)
|
||||
}.exists()
|
||||
}
|
||||
|
||||
fun buildCacheKey(str: String): String {
|
||||
val messageDigest = MessageDigest.getInstance("MD5")
|
||||
messageDigest.update(str.toByteArray(charset("UTF-8")))
|
||||
val digest = messageDigest.digest()
|
||||
var sb = ""
|
||||
for (b in digest) {
|
||||
sb += String.format("%02x", b)
|
||||
}
|
||||
return sb
|
||||
}
|
||||
|
||||
fun buildCacheKey(url: URL): String = buildCacheKey(url.toString())
|
||||
|
||||
fun buildCacheDir(cacheKey: String): File {
|
||||
return File("$cacheDir$cacheKey/")
|
||||
}
|
||||
|
||||
fun buildSvgaFile(cacheKey: String): File {
|
||||
return File("$cacheDir$cacheKey.svga")
|
||||
}
|
||||
|
||||
fun buildAudioFile(audio: String): File {
|
||||
return File("$cacheDir$audio.mp3")
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2017/3/30.
|
||||
*/
|
||||
interface SVGACallback {
|
||||
|
||||
fun onPause()
|
||||
fun onFinished()
|
||||
fun onRepeat()
|
||||
fun onStep(frame: Int, percentage: Double)
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
/**
|
||||
* Created by miaojun on 2019/6/21.
|
||||
* mail:1290846731@qq.com
|
||||
*/
|
||||
interface SVGAClickAreaListener{
|
||||
fun onClick(clickKey : String)
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.ImageView
|
||||
import com.opensource.svgaplayer.drawer.SVGACanvasDrawer
|
||||
|
||||
class SVGADrawable(val videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity): Drawable() {
|
||||
|
||||
constructor(videoItem: SVGAVideoEntity): this(videoItem, SVGADynamicEntity())
|
||||
|
||||
var cleared = true
|
||||
internal set (value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
var currentFrame = 0
|
||||
internal set (value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
var scaleType: ImageView.ScaleType = ImageView.ScaleType.MATRIX
|
||||
|
||||
private val drawer = SVGACanvasDrawer(videoItem, dynamicItem)
|
||||
|
||||
override fun draw(canvas: Canvas?) {
|
||||
if (cleared) {
|
||||
return
|
||||
}
|
||||
canvas?.let {
|
||||
drawer.drawFrame(it,currentFrame, scaleType)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int {
|
||||
return PixelFormat.TRANSPARENT
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
|
||||
}
|
||||
|
||||
fun resume() {
|
||||
videoItem.audioList.forEach { audio ->
|
||||
audio.playID?.let {
|
||||
if (SVGASoundManager.isInit()){
|
||||
SVGASoundManager.resume(it)
|
||||
}else{
|
||||
videoItem.soundPool?.resume(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
videoItem.audioList.forEach { audio ->
|
||||
audio.playID?.let {
|
||||
if (SVGASoundManager.isInit()){
|
||||
SVGASoundManager.pause(it)
|
||||
}else{
|
||||
videoItem.soundPool?.pause(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
videoItem.audioList.forEach { audio ->
|
||||
audio.playID?.let {
|
||||
if (SVGASoundManager.isInit()){
|
||||
SVGASoundManager.stop(it)
|
||||
}else{
|
||||
videoItem.soundPool?.stop(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
videoItem.audioList.forEach { audio ->
|
||||
audio.playID?.let {
|
||||
if (SVGASoundManager.isInit()){
|
||||
SVGASoundManager.stop(it)
|
||||
}else{
|
||||
videoItem.soundPool?.stop(it)
|
||||
}
|
||||
}
|
||||
audio.playID = null
|
||||
}
|
||||
videoItem.clear()
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.text.BoringLayout
|
||||
import android.text.StaticLayout
|
||||
import android.text.TextPaint
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2017/3/30.
|
||||
*/
|
||||
class SVGADynamicEntity {
|
||||
|
||||
internal var dynamicHidden: HashMap<String, Boolean> = hashMapOf()
|
||||
|
||||
internal var dynamicImage: HashMap<String, Bitmap> = hashMapOf()
|
||||
|
||||
internal var dynamicText: HashMap<String, String> = hashMapOf()
|
||||
|
||||
internal var dynamicTextPaint: HashMap<String, TextPaint> = hashMapOf()
|
||||
|
||||
internal var dynamicStaticLayoutText: HashMap<String, StaticLayout> = hashMapOf()
|
||||
|
||||
internal var dynamicBoringLayoutText: HashMap<String, BoringLayout> = hashMapOf()
|
||||
|
||||
internal var dynamicDrawer: HashMap<String, (canvas: Canvas, frameIndex: Int) -> Boolean> = hashMapOf()
|
||||
|
||||
//点击事件回调map
|
||||
internal var mClickMap : HashMap<String, IntArray> = hashMapOf()
|
||||
internal var dynamicIClickArea: HashMap<String, IClickAreaListener> = hashMapOf()
|
||||
|
||||
internal var dynamicDrawerSized: HashMap<String, (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean> = hashMapOf()
|
||||
|
||||
|
||||
internal var isTextDirty = false
|
||||
|
||||
fun setHidden(value: Boolean, forKey: String) {
|
||||
this.dynamicHidden.put(forKey, value)
|
||||
}
|
||||
|
||||
fun setDynamicImage(bitmap: Bitmap, forKey: String) {
|
||||
this.dynamicImage.put(forKey, bitmap)
|
||||
}
|
||||
|
||||
fun setDynamicImage(url: String, forKey: String) {
|
||||
val handler = android.os.Handler()
|
||||
SVGAParser.threadPoolExecutor.execute {
|
||||
(URL(url).openConnection() as? HttpURLConnection)?.let {
|
||||
try {
|
||||
it.connectTimeout = 20 * 1000
|
||||
it.requestMethod = "GET"
|
||||
it.connect()
|
||||
it.inputStream.use { stream ->
|
||||
BitmapFactory.decodeStream(stream)?.let {
|
||||
handler.post { setDynamicImage(it, forKey) }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
it.disconnect()
|
||||
} catch (disconnectException: Throwable) {
|
||||
// ignored here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setDynamicText(text: String, textPaint: TextPaint, forKey: String) {
|
||||
this.isTextDirty = true
|
||||
this.dynamicText.put(forKey, text)
|
||||
this.dynamicTextPaint.put(forKey, textPaint)
|
||||
}
|
||||
|
||||
fun setDynamicText(layoutText: StaticLayout, forKey: String) {
|
||||
this.isTextDirty = true
|
||||
this.dynamicStaticLayoutText.put(forKey, layoutText)
|
||||
}
|
||||
|
||||
fun setDynamicText(layoutText: BoringLayout, forKey: String) {
|
||||
this.isTextDirty = true
|
||||
BoringLayout.isBoring(layoutText.text,layoutText.paint)?.let {
|
||||
this.dynamicBoringLayoutText.put(forKey,layoutText)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDynamicDrawer(drawer: (canvas: Canvas, frameIndex: Int) -> Boolean, forKey: String) {
|
||||
this.dynamicDrawer.put(forKey, drawer)
|
||||
}
|
||||
|
||||
fun setClickArea(clickKey: List<String>) {
|
||||
for(itemKey in clickKey){
|
||||
dynamicIClickArea.put(itemKey,object : IClickAreaListener {
|
||||
override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) {
|
||||
mClickMap.let {
|
||||
if(it.get(key) == null){
|
||||
it.put(key, intArrayOf(x0,y0,x1,y1))
|
||||
}else{
|
||||
it.get(key)?.let {
|
||||
it[0] = x0
|
||||
it[1] = y0
|
||||
it[2] = x1
|
||||
it[3] = y1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun setClickArea(clickKey: String) {
|
||||
dynamicIClickArea.put(clickKey, object : IClickAreaListener {
|
||||
override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) {
|
||||
mClickMap.let {
|
||||
if (it.get(key) == null) {
|
||||
it.put(key, intArrayOf(x0, y0, x1, y1))
|
||||
} else {
|
||||
it.get(key)?.let {
|
||||
it[0] = x0
|
||||
it[1] = y0
|
||||
it[2] = x1
|
||||
it[3] = y1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun setDynamicDrawerSized(drawer: (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean, forKey: String) {
|
||||
this.dynamicDrawerSized.put(forKey, drawer)
|
||||
}
|
||||
|
||||
fun clearDynamicObjects() {
|
||||
this.isTextDirty = true
|
||||
this.dynamicHidden.clear()
|
||||
this.dynamicImage.clear()
|
||||
this.dynamicText.clear()
|
||||
this.dynamicTextPaint.clear()
|
||||
this.dynamicStaticLayoutText.clear()
|
||||
this.dynamicBoringLayoutText.clear()
|
||||
this.dynamicDrawer.clear()
|
||||
this.dynamicIClickArea.clear()
|
||||
this.mClickMap.clear()
|
||||
this.dynamicDrawerSized.clear()
|
||||
}
|
||||
}
|
@ -0,0 +1,329 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.ImageView
|
||||
import com.opensource.svgaplayer.utils.SVGARange
|
||||
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Created by PonyCui on 2017/3/29.
|
||||
*/
|
||||
open class SVGAImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ImageView(context, attrs, defStyleAttr) {
|
||||
|
||||
private val TAG = "SVGAImageView"
|
||||
|
||||
enum class FillMode {
|
||||
Backward,
|
||||
Forward,
|
||||
Clear,
|
||||
}
|
||||
|
||||
var isAnimating = false
|
||||
private set
|
||||
|
||||
var loops = 0
|
||||
|
||||
@Deprecated(
|
||||
"It is recommended to use clearAfterDetached, or manually call to SVGAVideoEntity#clear." +
|
||||
"If you just consider cleaning up the canvas after playing, you can use FillMode#Clear.",
|
||||
level = DeprecationLevel.WARNING
|
||||
)
|
||||
var clearsAfterStop = false
|
||||
var clearsAfterDetached = false
|
||||
var fillMode: FillMode = FillMode.Forward
|
||||
var callback: SVGACallback? = null
|
||||
|
||||
private var mAnimator: ValueAnimator? = null
|
||||
private var mItemClickAreaListener: SVGAClickAreaListener? = null
|
||||
private var mAntiAlias = true
|
||||
private var mAutoPlay = true
|
||||
private val mAnimatorListener = AnimatorListener(this)
|
||||
private val mAnimatorUpdateListener = AnimatorUpdateListener(this)
|
||||
private var mStartFrame = 0
|
||||
private var mEndFrame = 0
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||
}
|
||||
attrs?.let { loadAttrs(it) }
|
||||
}
|
||||
|
||||
private fun loadAttrs(attrs: AttributeSet) {
|
||||
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.SVGAImageView, 0, 0)
|
||||
loops = typedArray.getInt(R.styleable.SVGAImageView_loopCount, 0)
|
||||
clearsAfterStop = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterStop, false)
|
||||
clearsAfterDetached = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterDetached, false)
|
||||
mAntiAlias = typedArray.getBoolean(R.styleable.SVGAImageView_antiAlias, true)
|
||||
mAutoPlay = typedArray.getBoolean(R.styleable.SVGAImageView_autoPlay, true)
|
||||
typedArray.getString(R.styleable.SVGAImageView_fillMode)?.let {
|
||||
when (it) {
|
||||
"0" -> {
|
||||
fillMode = FillMode.Backward
|
||||
}
|
||||
"1" -> {
|
||||
fillMode = FillMode.Forward
|
||||
}
|
||||
"2" -> {
|
||||
fillMode = FillMode.Clear
|
||||
}
|
||||
}
|
||||
}
|
||||
typedArray.getString(R.styleable.SVGAImageView_source)?.let {
|
||||
parserSource(it)
|
||||
}
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
private fun parserSource(source: String) {
|
||||
val refImgView = WeakReference<SVGAImageView>(this)
|
||||
val parser = SVGAParser(context)
|
||||
if (source.startsWith("http://") || source.startsWith("https://")) {
|
||||
parser.decodeFromURL(URL(source), createParseCompletion(refImgView))
|
||||
} else {
|
||||
parser.decodeFromAssets(source, createParseCompletion(refImgView))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createParseCompletion(ref: WeakReference<SVGAImageView>): SVGAParser.ParseCompletion {
|
||||
return object : SVGAParser.ParseCompletion {
|
||||
override fun onComplete(videoItem: SVGAVideoEntity) {
|
||||
ref.get()?.startAnimation(videoItem)
|
||||
}
|
||||
|
||||
override fun onError() {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAnimation(videoItem: SVGAVideoEntity) {
|
||||
this@SVGAImageView.post {
|
||||
videoItem.antiAlias = mAntiAlias
|
||||
setVideoItem(videoItem)
|
||||
getSVGADrawable()?.scaleType = scaleType
|
||||
if (mAutoPlay) {
|
||||
startAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startAnimation() {
|
||||
startAnimation(null, false)
|
||||
}
|
||||
|
||||
fun startAnimation(range: SVGARange?, reverse: Boolean = false) {
|
||||
stopAnimation(false)
|
||||
play(range, reverse)
|
||||
}
|
||||
|
||||
private fun play(range: SVGARange?, reverse: Boolean) {
|
||||
LogUtils.info(TAG, "================ start animation ================")
|
||||
val drawable = getSVGADrawable() ?: return
|
||||
setupDrawable()
|
||||
mStartFrame = Math.max(0, range?.location ?: 0)
|
||||
val videoItem = drawable.videoItem
|
||||
mEndFrame = Math.min(videoItem.frames - 1, ((range?.location ?: 0) + (range?.length ?: Int.MAX_VALUE) - 1))
|
||||
val animator = ValueAnimator.ofInt(mStartFrame, mEndFrame)
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.duration = ((mEndFrame - mStartFrame + 1) * (1000 / videoItem.FPS) / generateScale()).toLong()
|
||||
animator.repeatCount = if (loops <= 0) 99999 else loops - 1
|
||||
animator.addUpdateListener(mAnimatorUpdateListener)
|
||||
animator.addListener(mAnimatorListener)
|
||||
if (reverse) {
|
||||
animator.reverse()
|
||||
} else {
|
||||
animator.start()
|
||||
}
|
||||
mAnimator = animator
|
||||
}
|
||||
|
||||
private fun setupDrawable() {
|
||||
val drawable = getSVGADrawable() ?: return
|
||||
drawable.cleared = false
|
||||
drawable.scaleType = scaleType
|
||||
}
|
||||
|
||||
private fun getSVGADrawable(): SVGADrawable? {
|
||||
return drawable as? SVGADrawable
|
||||
}
|
||||
|
||||
@Suppress("UNNECESSARY_SAFE_CALL")
|
||||
private fun generateScale(): Double {
|
||||
var scale = 1.0
|
||||
try {
|
||||
val animatorClass = Class.forName("android.animation.ValueAnimator") ?: return scale
|
||||
val getMethod = animatorClass.getDeclaredMethod("getDurationScale") ?: return scale
|
||||
scale = (getMethod.invoke(animatorClass) as Float).toDouble()
|
||||
if (scale == 0.0) {
|
||||
val setMethod = animatorClass.getDeclaredMethod("setDurationScale",Float::class.java) ?: return scale
|
||||
setMethod.isAccessible = true
|
||||
setMethod.invoke(animatorClass,1.0f)
|
||||
scale = 1.0
|
||||
LogUtils.info(TAG,
|
||||
"The animation duration scale has been reset to" +
|
||||
" 1.0x, because you closed it on developer options.")
|
||||
}
|
||||
} catch (ignore: Exception) {
|
||||
ignore.printStackTrace()
|
||||
}
|
||||
return scale
|
||||
}
|
||||
|
||||
private fun onAnimatorUpdate(animator: ValueAnimator?) {
|
||||
val drawable = getSVGADrawable() ?: return
|
||||
drawable.currentFrame = animator?.animatedValue as Int
|
||||
val percentage = (drawable.currentFrame + 1).toDouble() / drawable.videoItem.frames.toDouble()
|
||||
callback?.onStep(drawable.currentFrame, percentage)
|
||||
}
|
||||
|
||||
private fun onAnimationEnd(animation: Animator?) {
|
||||
isAnimating = false
|
||||
stopAnimation()
|
||||
val drawable = getSVGADrawable()
|
||||
if (drawable != null) {
|
||||
when (fillMode) {
|
||||
FillMode.Backward -> {
|
||||
drawable.currentFrame = mStartFrame
|
||||
}
|
||||
FillMode.Forward -> {
|
||||
drawable.currentFrame = mEndFrame
|
||||
}
|
||||
FillMode.Clear -> {
|
||||
drawable.cleared = true
|
||||
}
|
||||
}
|
||||
}
|
||||
callback?.onFinished()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
getSVGADrawable()?.cleared = true
|
||||
getSVGADrawable()?.clear()
|
||||
// 清除对 drawable 的引用
|
||||
setImageDrawable(null)
|
||||
}
|
||||
|
||||
fun pauseAnimation() {
|
||||
stopAnimation(false)
|
||||
callback?.onPause()
|
||||
}
|
||||
|
||||
fun stopAnimation() {
|
||||
stopAnimation(clear = clearsAfterStop)
|
||||
}
|
||||
|
||||
fun stopAnimation(clear: Boolean) {
|
||||
mAnimator?.cancel()
|
||||
mAnimator?.removeAllListeners()
|
||||
mAnimator?.removeAllUpdateListeners()
|
||||
getSVGADrawable()?.stop()
|
||||
getSVGADrawable()?.cleared = clear
|
||||
}
|
||||
|
||||
fun setVideoItem(videoItem: SVGAVideoEntity?) {
|
||||
setVideoItem(videoItem, SVGADynamicEntity())
|
||||
}
|
||||
|
||||
fun setVideoItem(videoItem: SVGAVideoEntity?, dynamicItem: SVGADynamicEntity?) {
|
||||
if (videoItem == null) {
|
||||
setImageDrawable(null)
|
||||
} else {
|
||||
val drawable = SVGADrawable(videoItem, dynamicItem ?: SVGADynamicEntity())
|
||||
drawable.cleared = true
|
||||
setImageDrawable(drawable)
|
||||
}
|
||||
}
|
||||
|
||||
fun stepToFrame(frame: Int, andPlay: Boolean) {
|
||||
pauseAnimation()
|
||||
val drawable = getSVGADrawable() ?: return
|
||||
drawable.currentFrame = frame
|
||||
if (andPlay) {
|
||||
startAnimation()
|
||||
mAnimator?.let {
|
||||
it.currentPlayTime = (Math.max(0.0f, Math.min(1.0f, (frame.toFloat() / drawable.videoItem.frames.toFloat()))) * it.duration).toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stepToPercentage(percentage: Double, andPlay: Boolean) {
|
||||
val drawable = drawable as? SVGADrawable ?: return
|
||||
var frame = (drawable.videoItem.frames * percentage).toInt()
|
||||
if (frame >= drawable.videoItem.frames && frame > 0) {
|
||||
frame = drawable.videoItem.frames - 1
|
||||
}
|
||||
stepToFrame(frame, andPlay)
|
||||
}
|
||||
|
||||
fun setOnAnimKeyClickListener(clickListener : SVGAClickAreaListener){
|
||||
mItemClickAreaListener = clickListener
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
if (event?.action != MotionEvent.ACTION_DOWN) {
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
val drawable = getSVGADrawable() ?: return super.onTouchEvent(event)
|
||||
for ((key, value) in drawable.dynamicItem.mClickMap) {
|
||||
if (event.x >= value[0] && event.x <= value[2] && event.y >= value[1] && event.y <= value[3]) {
|
||||
mItemClickAreaListener?.let {
|
||||
it.onClick(key)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
stopAnimation(clearsAfterDetached)
|
||||
if (clearsAfterDetached) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
private class AnimatorListener(view: SVGAImageView) : Animator.AnimatorListener {
|
||||
private val weakReference = WeakReference<SVGAImageView>(view)
|
||||
|
||||
override fun onAnimationRepeat(animation: Animator?) {
|
||||
weakReference.get()?.callback?.onRepeat()
|
||||
}
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
weakReference.get()?.onAnimationEnd(animation)
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) {
|
||||
weakReference.get()?.isAnimating = false
|
||||
}
|
||||
|
||||
override fun onAnimationStart(animation: Animator?) {
|
||||
weakReference.get()?.isAnimating = true
|
||||
}
|
||||
} // end of AnimatorListener
|
||||
|
||||
|
||||
private class AnimatorUpdateListener(view: SVGAImageView) : ValueAnimator.AnimatorUpdateListener {
|
||||
private val weakReference = WeakReference<SVGAImageView>(view)
|
||||
|
||||
override fun onAnimationUpdate(animation: ValueAnimator?) {
|
||||
weakReference.get()?.onAnimatorUpdate(animation)
|
||||
}
|
||||
} // end of AnimatorUpdateListener
|
||||
}
|
@ -0,0 +1,565 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
import android.content.Context
|
||||
import android.net.http.HttpResponseCache
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.opensource.svgaplayer.proto.MovieEntity
|
||||
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||
import org.json.JSONObject
|
||||
import java.io.*
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.zip.Inflater
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
/**
|
||||
* Created by PonyCui 16/6/18.
|
||||
*/
|
||||
private var fileLock: Int = 0
|
||||
private var isUnzipping = false
|
||||
|
||||
class SVGAParser(context: Context?) {
|
||||
private var mContext = context?.applicationContext
|
||||
|
||||
init {
|
||||
SVGACache.onCreate(context)
|
||||
}
|
||||
|
||||
@Volatile
|
||||
private var mFrameWidth: Int = 0
|
||||
|
||||
@Volatile
|
||||
private var mFrameHeight: Int = 0
|
||||
|
||||
interface ParseCompletion {
|
||||
fun onComplete(videoItem: SVGAVideoEntity)
|
||||
fun onError()
|
||||
}
|
||||
|
||||
interface PlayCallback{
|
||||
fun onPlay(file: List<File>)
|
||||
}
|
||||
|
||||
open class FileDownloader {
|
||||
|
||||
var noCache = false
|
||||
|
||||
open fun resume(url: URL, complete: (inputStream: InputStream) -> Unit, failure: (e: Exception) -> Unit): () -> Unit {
|
||||
var cancelled = false
|
||||
val cancelBlock = {
|
||||
cancelled = true
|
||||
}
|
||||
threadPoolExecutor.execute {
|
||||
try {
|
||||
LogUtils.info(TAG, "================ svga file download start ================")
|
||||
if (HttpResponseCache.getInstalled() == null && !noCache) {
|
||||
LogUtils.error(TAG, "SVGAParser can not handle cache before install HttpResponseCache. see https://github.com/yyued/SVGAPlayer-Android#cache")
|
||||
LogUtils.error(TAG, "在配置 HttpResponseCache 前 SVGAParser 无法缓存. 查看 https://github.com/yyued/SVGAPlayer-Android#cache ")
|
||||
}
|
||||
(url.openConnection() as? HttpURLConnection)?.let {
|
||||
it.connectTimeout = 20 * 1000
|
||||
it.requestMethod = "GET"
|
||||
it.setRequestProperty("Connection", "close")
|
||||
it.connect()
|
||||
it.inputStream.use { inputStream ->
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
val buffer = ByteArray(4096)
|
||||
var count: Int
|
||||
while (true) {
|
||||
if (cancelled) {
|
||||
LogUtils.warn(TAG, "================ svga file download canceled ================")
|
||||
break
|
||||
}
|
||||
count = inputStream.read(buffer, 0, 4096)
|
||||
if (count == -1) {
|
||||
break
|
||||
}
|
||||
outputStream.write(buffer, 0, count)
|
||||
}
|
||||
if (cancelled) {
|
||||
LogUtils.warn(TAG, "================ svga file download canceled ================")
|
||||
return@execute
|
||||
}
|
||||
ByteArrayInputStream(outputStream.toByteArray()).use {
|
||||
LogUtils.info(TAG, "================ svga file download complete ================")
|
||||
complete(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.error(TAG, "================ svga file download fail ================")
|
||||
LogUtils.error(TAG, "error: ${e.message}")
|
||||
e.printStackTrace()
|
||||
failure(e)
|
||||
}
|
||||
}
|
||||
return cancelBlock
|
||||
}
|
||||
}
|
||||
|
||||
var fileDownloader = FileDownloader()
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SVGAParser"
|
||||
|
||||
private val threadNum = AtomicInteger(0)
|
||||
private var mShareParser = SVGAParser(null)
|
||||
|
||||
internal var threadPoolExecutor = Executors.newCachedThreadPool { r ->
|
||||
Thread(r, "SVGAParser-Thread-${threadNum.getAndIncrement()}")
|
||||
}
|
||||
|
||||
fun setThreadPoolExecutor(executor: ThreadPoolExecutor) {
|
||||
threadPoolExecutor = executor
|
||||
}
|
||||
|
||||
fun shareParser(): SVGAParser {
|
||||
return mShareParser
|
||||
}
|
||||
}
|
||||
|
||||
fun init(context: Context) {
|
||||
mContext = context.applicationContext
|
||||
SVGACache.onCreate(mContext)
|
||||
}
|
||||
|
||||
fun setFrameSize(frameWidth: Int, frameHeight: Int) {
|
||||
mFrameWidth = frameWidth
|
||||
mFrameHeight = frameHeight
|
||||
}
|
||||
|
||||
fun decodeFromAssets(
|
||||
name: String,
|
||||
callback: ParseCompletion?,
|
||||
playCallback: PlayCallback? = null
|
||||
) {
|
||||
if (mContext == null) {
|
||||
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
|
||||
return
|
||||
}
|
||||
LogUtils.info(TAG, "================ decode $name from assets ================")
|
||||
threadPoolExecutor.execute {
|
||||
try {
|
||||
mContext?.assets?.open(name)?.let {
|
||||
this.decodeFromInputStream(
|
||||
it,
|
||||
SVGACache.buildCacheKey("file:///assets/$name"),
|
||||
callback,
|
||||
true,
|
||||
playCallback,
|
||||
alias = name
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
this.invokeErrorCallback(e, callback, name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun decodeFromURL(
|
||||
url: URL,
|
||||
callback: ParseCompletion?,
|
||||
playCallback: PlayCallback? = null
|
||||
): (() -> Unit)? {
|
||||
if (mContext == null) {
|
||||
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
|
||||
return null
|
||||
}
|
||||
val urlPath = url.toString()
|
||||
LogUtils.info(TAG, "================ decode from url: $urlPath ================")
|
||||
val cacheKey = SVGACache.buildCacheKey(url);
|
||||
return if (SVGACache.isCached(cacheKey)) {
|
||||
LogUtils.info(TAG, "this url cached")
|
||||
threadPoolExecutor.execute {
|
||||
if (SVGACache.isDefaultCache()) {
|
||||
this.decodeFromCacheKey(cacheKey, callback, alias = urlPath)
|
||||
} else {
|
||||
this.decodeFromSVGAFileCacheKey(cacheKey, callback, playCallback, alias = urlPath)
|
||||
}
|
||||
}
|
||||
return null
|
||||
} else {
|
||||
LogUtils.info(TAG, "no cached, prepare to download")
|
||||
fileDownloader.resume(url, {
|
||||
this.decodeFromInputStream(
|
||||
it,
|
||||
cacheKey,
|
||||
callback,
|
||||
false,
|
||||
playCallback,
|
||||
alias = urlPath
|
||||
)
|
||||
}, {
|
||||
LogUtils.error(
|
||||
TAG,
|
||||
"================ svga file: $url download fail ================"
|
||||
)
|
||||
this.invokeErrorCallback(it, callback, alias = urlPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取解析本地缓存的 svga 文件.
|
||||
*/
|
||||
fun decodeFromSVGAFileCacheKey(
|
||||
cacheKey: String,
|
||||
callback: ParseCompletion?,
|
||||
playCallback: PlayCallback?,
|
||||
alias: String? = null
|
||||
) {
|
||||
threadPoolExecutor.execute {
|
||||
try {
|
||||
LogUtils.info(TAG, "================ decode $alias from svga cachel file to entity ================")
|
||||
FileInputStream(SVGACache.buildSvgaFile(cacheKey)).use { inputStream ->
|
||||
readAsBytes(inputStream)?.let { bytes ->
|
||||
if (isZipFile(bytes)) {
|
||||
this.decodeFromCacheKey(cacheKey, callback, alias)
|
||||
} else {
|
||||
LogUtils.info(TAG, "inflate start")
|
||||
inflate(bytes)?.let {
|
||||
LogUtils.info(TAG, "inflate complete")
|
||||
val videoItem = SVGAVideoEntity(
|
||||
MovieEntity.ADAPTER.decode(it),
|
||||
File(cacheKey),
|
||||
mFrameWidth,
|
||||
mFrameHeight
|
||||
)
|
||||
LogUtils.info(TAG, "SVGAVideoEntity prepare start")
|
||||
videoItem.prepare({
|
||||
LogUtils.info(TAG, "SVGAVideoEntity prepare success")
|
||||
this.invokeCompleteCallback(videoItem, callback, alias)
|
||||
},playCallback)
|
||||
|
||||
} ?: this.invokeErrorCallback(
|
||||
Exception("inflate(bytes) cause exception"),
|
||||
callback,
|
||||
alias
|
||||
)
|
||||
}
|
||||
} ?: this.invokeErrorCallback(
|
||||
Exception("readAsBytes(inputStream) cause exception"),
|
||||
callback,
|
||||
alias
|
||||
)
|
||||
}
|
||||
} catch (e: java.lang.Exception) {
|
||||
this.invokeErrorCallback(e, callback, alias)
|
||||
} finally {
|
||||
LogUtils.info(TAG, "================ decode $alias from svga cachel file to entity end ================")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeFromInputStream(
|
||||
inputStream: InputStream,
|
||||
cacheKey: String,
|
||||
callback: ParseCompletion?,
|
||||
closeInputStream: Boolean = false,
|
||||
playCallback: PlayCallback? = null,
|
||||
alias: String? = null
|
||||
) {
|
||||
if (mContext == null) {
|
||||
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
|
||||
return
|
||||
}
|
||||
LogUtils.info(TAG, "================ decode $alias from input stream ================")
|
||||
threadPoolExecutor.execute {
|
||||
try {
|
||||
readAsBytes(inputStream)?.let { bytes ->
|
||||
if (isZipFile(bytes)) {
|
||||
LogUtils.info(TAG, "decode from zip file")
|
||||
if (!SVGACache.buildCacheDir(cacheKey).exists() || isUnzipping) {
|
||||
synchronized(fileLock) {
|
||||
if (!SVGACache.buildCacheDir(cacheKey).exists()) {
|
||||
isUnzipping = true
|
||||
LogUtils.info(TAG, "no cached, prepare to unzip")
|
||||
ByteArrayInputStream(bytes).use {
|
||||
unzip(it, cacheKey)
|
||||
isUnzipping = false
|
||||
LogUtils.info(TAG, "unzip success")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.decodeFromCacheKey(cacheKey, callback, alias)
|
||||
} else {
|
||||
if (!SVGACache.isDefaultCache()) {
|
||||
// 如果 SVGACache 设置类型为 FILE
|
||||
threadPoolExecutor.execute {
|
||||
SVGACache.buildSvgaFile(cacheKey).let { cacheFile ->
|
||||
try {
|
||||
cacheFile.takeIf { !it.exists() }?.createNewFile()
|
||||
FileOutputStream(cacheFile).write(bytes)
|
||||
} catch (e: Exception) {
|
||||
LogUtils.error(TAG, "create cache file fail.", e)
|
||||
cacheFile.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LogUtils.info(TAG, "inflate start")
|
||||
inflate(bytes)?.let {
|
||||
LogUtils.info(TAG, "inflate complete")
|
||||
val videoItem = SVGAVideoEntity(
|
||||
MovieEntity.ADAPTER.decode(it),
|
||||
File(cacheKey),
|
||||
mFrameWidth,
|
||||
mFrameHeight
|
||||
)
|
||||
LogUtils.info(TAG, "SVGAVideoEntity prepare start")
|
||||
videoItem.prepare({
|
||||
LogUtils.info(TAG, "SVGAVideoEntity prepare success")
|
||||
this.invokeCompleteCallback(videoItem, callback, alias)
|
||||
},playCallback)
|
||||
|
||||
} ?: this.invokeErrorCallback(
|
||||
Exception("inflate(bytes) cause exception"),
|
||||
callback,
|
||||
alias
|
||||
)
|
||||
}
|
||||
} ?: this.invokeErrorCallback(
|
||||
Exception("readAsBytes(inputStream) cause exception"),
|
||||
callback,
|
||||
alias
|
||||
)
|
||||
} catch (e: java.lang.Exception) {
|
||||
this.invokeErrorCallback(e, callback, alias)
|
||||
} finally {
|
||||
if (closeInputStream) {
|
||||
inputStream.close()
|
||||
}
|
||||
LogUtils.info(TAG, "================ decode $alias from input stream end ================")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated from 2.4.0
|
||||
*/
|
||||
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromAssets(assetsName, callback)"))
|
||||
fun parse(assetsName: String, callback: ParseCompletion?) {
|
||||
this.decodeFromAssets(assetsName, callback,null)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated from 2.4.0
|
||||
*/
|
||||
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromURL(url, callback)"))
|
||||
fun parse(url: URL, callback: ParseCompletion?) {
|
||||
this.decodeFromURL(url, callback,null)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated from 2.4.0
|
||||
*/
|
||||
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromInputStream(inputStream, cacheKey, callback, closeInputStream)"))
|
||||
fun parse(
|
||||
inputStream: InputStream,
|
||||
cacheKey: String,
|
||||
callback: ParseCompletion?,
|
||||
closeInputStream: Boolean = false
|
||||
) {
|
||||
this.decodeFromInputStream(inputStream, cacheKey, callback, closeInputStream,null)
|
||||
}
|
||||
|
||||
private fun invokeCompleteCallback(
|
||||
videoItem: SVGAVideoEntity,
|
||||
callback: ParseCompletion?,
|
||||
alias: String?
|
||||
) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
LogUtils.info(TAG, "================ $alias parser complete ================")
|
||||
callback?.onComplete(videoItem)
|
||||
}
|
||||
}
|
||||
|
||||
private fun invokeErrorCallback(
|
||||
e: Exception,
|
||||
callback: ParseCompletion?,
|
||||
alias: String?
|
||||
) {
|
||||
e.printStackTrace()
|
||||
LogUtils.error(TAG, "================ $alias parser error ================")
|
||||
LogUtils.error(TAG, "$alias parse error", e)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
callback?.onError()
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeFromCacheKey(
|
||||
cacheKey: String,
|
||||
callback: ParseCompletion?,
|
||||
alias: String?
|
||||
) {
|
||||
LogUtils.info(TAG, "================ decode $alias from cache ================")
|
||||
LogUtils.debug(TAG, "decodeFromCacheKey called with cacheKey : $cacheKey")
|
||||
if (mContext == null) {
|
||||
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
|
||||
return
|
||||
}
|
||||
try {
|
||||
val cacheDir = SVGACache.buildCacheDir(cacheKey)
|
||||
File(cacheDir, "movie.binary").takeIf { it.isFile }?.let { binaryFile ->
|
||||
try {
|
||||
LogUtils.info(TAG, "binary change to entity")
|
||||
FileInputStream(binaryFile).use {
|
||||
LogUtils.info(TAG, "binary change to entity success")
|
||||
this.invokeCompleteCallback(
|
||||
SVGAVideoEntity(
|
||||
MovieEntity.ADAPTER.decode(it),
|
||||
cacheDir,
|
||||
mFrameWidth,
|
||||
mFrameHeight
|
||||
),
|
||||
callback,
|
||||
alias
|
||||
)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
LogUtils.error(TAG, "binary change to entity fail", e)
|
||||
cacheDir.delete()
|
||||
binaryFile.delete()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
File(cacheDir, "movie.spec").takeIf { it.isFile }?.let { jsonFile ->
|
||||
try {
|
||||
LogUtils.info(TAG, "spec change to entity")
|
||||
FileInputStream(jsonFile).use { fileInputStream ->
|
||||
ByteArrayOutputStream().use { byteArrayOutputStream ->
|
||||
val buffer = ByteArray(2048)
|
||||
while (true) {
|
||||
val size = fileInputStream.read(buffer, 0, buffer.size)
|
||||
if (size == -1) {
|
||||
break
|
||||
}
|
||||
byteArrayOutputStream.write(buffer, 0, size)
|
||||
}
|
||||
byteArrayOutputStream.toString().let {
|
||||
JSONObject(it).let {
|
||||
LogUtils.info(TAG, "spec change to entity success")
|
||||
this.invokeCompleteCallback(
|
||||
SVGAVideoEntity(
|
||||
it,
|
||||
cacheDir,
|
||||
mFrameWidth,
|
||||
mFrameHeight
|
||||
),
|
||||
callback,
|
||||
alias
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.error(TAG, "$alias movie.spec change to entity fail", e)
|
||||
cacheDir.delete()
|
||||
jsonFile.delete()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
this.invokeErrorCallback(e, callback, alias)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readAsBytes(inputStream: InputStream): ByteArray? {
|
||||
ByteArrayOutputStream().use { byteArrayOutputStream ->
|
||||
val byteArray = ByteArray(2048)
|
||||
while (true) {
|
||||
val count = inputStream.read(byteArray, 0, 2048)
|
||||
if (count <= 0) {
|
||||
break
|
||||
} else {
|
||||
byteArrayOutputStream.write(byteArray, 0, count)
|
||||
}
|
||||
}
|
||||
return byteArrayOutputStream.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflate(byteArray: ByteArray): ByteArray? {
|
||||
val inflater = Inflater()
|
||||
inflater.setInput(byteArray, 0, byteArray.size)
|
||||
val inflatedBytes = ByteArray(2048)
|
||||
ByteArrayOutputStream().use { inflatedOutputStream ->
|
||||
while (true) {
|
||||
val count = inflater.inflate(inflatedBytes, 0, 2048)
|
||||
if (count <= 0) {
|
||||
break
|
||||
} else {
|
||||
inflatedOutputStream.write(inflatedBytes, 0, count)
|
||||
}
|
||||
}
|
||||
inflater.end()
|
||||
return inflatedOutputStream.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
// 是否是 zip 文件
|
||||
private fun isZipFile(bytes: ByteArray): Boolean {
|
||||
return bytes.size > 4 && bytes[0].toInt() == 80 && bytes[1].toInt() == 75 && bytes[2].toInt() == 3 && bytes[3].toInt() == 4
|
||||
}
|
||||
|
||||
// 解压
|
||||
private fun unzip(inputStream: InputStream, cacheKey: String) {
|
||||
LogUtils.info(TAG, "================ unzip prepare ================")
|
||||
val cacheDir = SVGACache.buildCacheDir(cacheKey)
|
||||
cacheDir.mkdirs()
|
||||
try {
|
||||
BufferedInputStream(inputStream).use {
|
||||
ZipInputStream(it).use { zipInputStream ->
|
||||
while (true) {
|
||||
val zipItem = zipInputStream.nextEntry ?: break
|
||||
if (zipItem.name.contains("../")) {
|
||||
// 解压路径存在路径穿越问题,直接过滤
|
||||
continue
|
||||
}
|
||||
if (zipItem.name.contains("/")) {
|
||||
continue
|
||||
}
|
||||
val file = File(cacheDir, zipItem.name)
|
||||
ensureUnzipSafety(file, cacheDir.absolutePath)
|
||||
FileOutputStream(file).use { fileOutputStream ->
|
||||
val buff = ByteArray(2048)
|
||||
while (true) {
|
||||
val readBytes = zipInputStream.read(buff)
|
||||
if (readBytes <= 0) {
|
||||
break
|
||||
}
|
||||
fileOutputStream.write(buff, 0, readBytes)
|
||||
}
|
||||
}
|
||||
LogUtils.error(TAG, "================ unzip complete ================")
|
||||
zipInputStream.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.error(TAG, "================ unzip error ================")
|
||||
LogUtils.error(TAG, "error", e)
|
||||
SVGACache.clearDir(cacheDir.absolutePath)
|
||||
cacheDir.delete()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 zip 路径穿透
|
||||
private fun ensureUnzipSafety(outputFile: File, dstDirPath: String) {
|
||||
val dstDirCanonicalPath = File(dstDirPath).canonicalPath
|
||||
val outputFileCanonicalPath = outputFile.canonicalPath
|
||||
if (!outputFileCanonicalPath.startsWith(dstDirCanonicalPath)) {
|
||||
throw IOException("Found Zip Path Traversal Vulnerability with $dstDirCanonicalPath")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2017/3/30.
|
||||
* @deprecated from 2.4.0
|
||||
*/
|
||||
@Deprecated("This class has been deprecated from 2.4.0. We don't recommend you to use it.")
|
||||
class SVGAPlayer: SVGAImageView {
|
||||
|
||||
constructor(context: Context) : super(context) {}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
|
||||
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
/**
|
||||
* @author Devin
|
||||
*
|
||||
* Created on 2/24/21.
|
||||
*/
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioManager
|
||||
import android.media.SoundPool
|
||||
import android.os.Build
|
||||
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||
import java.io.FileDescriptor
|
||||
|
||||
/**
|
||||
* Author : llk
|
||||
* Time : 2020/10/24
|
||||
* Description : svga 音频加载管理类
|
||||
* 将 SoundPool 抽取到单例里边,规避 load 资源之后不回调 onLoadComplete 的问题。
|
||||
*
|
||||
* 需要对 SVGASoundManager 进行初始化
|
||||
*
|
||||
* 相关文章:Android SoundPool 崩溃问题研究
|
||||
* https://zhuanlan.zhihu.com/p/29985198
|
||||
*/
|
||||
object SVGASoundManager {
|
||||
|
||||
private val TAG = SVGASoundManager::class.java.simpleName
|
||||
|
||||
private var soundPool: SoundPool? = null
|
||||
|
||||
private val soundCallBackMap: MutableMap<Int, SVGASoundCallBack> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* 音量设置,范围在 [0, 1] 之间
|
||||
*/
|
||||
private var volume: Float = 1f
|
||||
|
||||
/**
|
||||
* 音频回调
|
||||
*/
|
||||
internal interface SVGASoundCallBack {
|
||||
|
||||
// 音量发生变化
|
||||
fun onVolumeChange(value: Float)
|
||||
|
||||
// 音频加载完成
|
||||
fun onComplete()
|
||||
}
|
||||
|
||||
fun init() {
|
||||
init(20)
|
||||
}
|
||||
|
||||
fun init(maxStreams: Int) {
|
||||
LogUtils.debug(TAG, "**************** init **************** $maxStreams")
|
||||
if (soundPool != null) {
|
||||
return
|
||||
}
|
||||
soundPool = getSoundPool(maxStreams)
|
||||
soundPool?.setOnLoadCompleteListener { _, soundId, status ->
|
||||
LogUtils.debug(TAG, "SoundPool onLoadComplete soundId=$soundId status=$status")
|
||||
if (status == 0) { //加载该声音成功
|
||||
if (soundCallBackMap.containsKey(soundId)) {
|
||||
soundCallBackMap[soundId]?.onComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
LogUtils.debug(TAG, "**************** release ****************")
|
||||
if (soundCallBackMap.isNotEmpty()) {
|
||||
soundCallBackMap.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前播放实体,设置音量
|
||||
*
|
||||
* @param volume 范围在 [0, 1]
|
||||
* @param entity 根据需要控制对应 entity 音量大小,若为空则控制所有正在播放的音频音量
|
||||
*/
|
||||
fun setVolume(volume: Float, entity: SVGAVideoEntity? = null) {
|
||||
if (!checkInit()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (volume < 0f || volume > 1f) {
|
||||
LogUtils.error(TAG, "The volume level is in the range of 0 to 1 ")
|
||||
return
|
||||
}
|
||||
|
||||
if (entity == null) {
|
||||
this.volume = volume
|
||||
val iterator = soundCallBackMap.entries.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val e = iterator.next()
|
||||
e.value.onVolumeChange(volume)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val soundPool = soundPool ?: return
|
||||
|
||||
entity.audioList.forEach { audio ->
|
||||
val streamId = audio.playID ?: return
|
||||
soundPool.setVolume(streamId, volume, volume)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否初始化
|
||||
* 如果没有初始化,就使用原来SvgaPlayer库的音频加载逻辑。
|
||||
* @return true 则已初始化, 否则为 false
|
||||
*/
|
||||
internal fun isInit(): Boolean {
|
||||
return soundPool != null
|
||||
}
|
||||
|
||||
private fun checkInit(): Boolean {
|
||||
val isInit = isInit()
|
||||
if (!isInit) {
|
||||
LogUtils.error(TAG, "soundPool is null, you need call init() !!!")
|
||||
}
|
||||
return isInit
|
||||
}
|
||||
|
||||
private fun getSoundPool(maxStreams: Int) = if (Build.VERSION.SDK_INT >= 21) {
|
||||
val attributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
SoundPool.Builder().setAudioAttributes(attributes)
|
||||
.setMaxStreams(maxStreams)
|
||||
.build()
|
||||
} else {
|
||||
SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0)
|
||||
}
|
||||
|
||||
internal fun load(callBack: SVGASoundCallBack?,
|
||||
fd: FileDescriptor?,
|
||||
offset: Long,
|
||||
length: Long,
|
||||
priority: Int): Int {
|
||||
if (!checkInit()) return -1
|
||||
|
||||
val soundId = soundPool!!.load(fd, offset, length, priority)
|
||||
|
||||
LogUtils.debug(TAG, "load soundId=$soundId callBack=$callBack")
|
||||
|
||||
if (callBack != null && !soundCallBackMap.containsKey(soundId)) {
|
||||
soundCallBackMap[soundId] = callBack
|
||||
}
|
||||
return soundId
|
||||
}
|
||||
|
||||
internal fun unload(soundId: Int) {
|
||||
if (!checkInit()) return
|
||||
|
||||
LogUtils.debug(TAG, "unload soundId=$soundId")
|
||||
|
||||
soundPool!!.unload(soundId)
|
||||
|
||||
soundCallBackMap.remove(soundId)
|
||||
}
|
||||
|
||||
internal fun play(soundId: Int): Int {
|
||||
if (!checkInit()) return -1
|
||||
|
||||
LogUtils.debug(TAG, "play soundId=$soundId")
|
||||
return soundPool!!.play(soundId, volume, volume, 1, 0, 1.0f)
|
||||
}
|
||||
|
||||
internal fun stop(soundId: Int) {
|
||||
if (!checkInit()) return
|
||||
|
||||
LogUtils.debug(TAG, "stop soundId=$soundId")
|
||||
soundPool!!.stop(soundId)
|
||||
}
|
||||
|
||||
internal fun resume(soundId: Int) {
|
||||
if (!checkInit()) return
|
||||
|
||||
LogUtils.debug(TAG, "stop soundId=$soundId")
|
||||
soundPool!!.resume(soundId)
|
||||
}
|
||||
|
||||
internal fun pause(soundId: Int) {
|
||||
if (!checkInit()) return
|
||||
|
||||
LogUtils.debug(TAG, "pause soundId=$soundId")
|
||||
soundPool!!.pause(soundId)
|
||||
}
|
||||
}
|
@ -0,0 +1,347 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioManager
|
||||
import android.media.SoundPool
|
||||
import android.os.Build
|
||||
import com.opensource.svgaplayer.bitmap.SVGABitmapByteArrayDecoder
|
||||
import com.opensource.svgaplayer.bitmap.SVGABitmapFileDecoder
|
||||
import com.opensource.svgaplayer.entities.SVGAAudioEntity
|
||||
import com.opensource.svgaplayer.entities.SVGAVideoSpriteEntity
|
||||
import com.opensource.svgaplayer.proto.AudioEntity
|
||||
import com.opensource.svgaplayer.proto.MovieEntity
|
||||
import com.opensource.svgaplayer.proto.MovieParams
|
||||
import com.opensource.svgaplayer.utils.SVGARect
|
||||
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Created by PonyCui on 16/6/18.
|
||||
*/
|
||||
class SVGAVideoEntity {
|
||||
|
||||
private val TAG = "SVGAVideoEntity"
|
||||
|
||||
var antiAlias = true
|
||||
var movieItem: MovieEntity? = null
|
||||
|
||||
var videoSize = SVGARect(0.0, 0.0, 0.0, 0.0)
|
||||
private set
|
||||
|
||||
var FPS = 15
|
||||
private set
|
||||
|
||||
var frames: Int = 0
|
||||
private set
|
||||
|
||||
internal var spriteList: List<SVGAVideoSpriteEntity> = emptyList()
|
||||
internal var audioList: List<SVGAAudioEntity> = emptyList()
|
||||
internal var soundPool: SoundPool? = null
|
||||
private var soundCallback: SVGASoundManager.SVGASoundCallBack? = null
|
||||
internal var imageMap = HashMap<String, Bitmap>()
|
||||
private var mCacheDir: File
|
||||
private var mFrameHeight = 0
|
||||
private var mFrameWidth = 0
|
||||
private var mPlayCallback: SVGAParser.PlayCallback?=null
|
||||
private lateinit var mCallback: () -> Unit
|
||||
|
||||
constructor(json: JSONObject, cacheDir: File) : this(json, cacheDir, 0, 0)
|
||||
|
||||
constructor(json: JSONObject, cacheDir: File, frameWidth: Int, frameHeight: Int) {
|
||||
mFrameWidth = frameWidth
|
||||
mFrameHeight = frameHeight
|
||||
mCacheDir = cacheDir
|
||||
val movieJsonObject = json.optJSONObject("movie") ?: return
|
||||
setupByJson(movieJsonObject)
|
||||
try {
|
||||
parserImages(json)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} catch (e: OutOfMemoryError) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
resetSprites(json)
|
||||
}
|
||||
|
||||
private fun setupByJson(movieObject: JSONObject) {
|
||||
movieObject.optJSONObject("viewBox")?.let { viewBoxObject ->
|
||||
val width = viewBoxObject.optDouble("width", 0.0)
|
||||
val height = viewBoxObject.optDouble("height", 0.0)
|
||||
videoSize = SVGARect(0.0, 0.0, width, height)
|
||||
}
|
||||
FPS = movieObject.optInt("fps", 20)
|
||||
frames = movieObject.optInt("frames", 0)
|
||||
}
|
||||
|
||||
constructor(entity: MovieEntity, cacheDir: File) : this(entity, cacheDir, 0, 0)
|
||||
|
||||
constructor(entity: MovieEntity, cacheDir: File, frameWidth: Int, frameHeight: Int) {
|
||||
this.mFrameWidth = frameWidth
|
||||
this.mFrameHeight = frameHeight
|
||||
this.mCacheDir = cacheDir
|
||||
this.movieItem = entity
|
||||
entity.params?.let(this::setupByMovie)
|
||||
try {
|
||||
parserImages(entity)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} catch (e: OutOfMemoryError) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
resetSprites(entity)
|
||||
}
|
||||
|
||||
private fun setupByMovie(movieParams: MovieParams) {
|
||||
val width = (movieParams.viewBoxWidth ?: 0.0f).toDouble()
|
||||
val height = (movieParams.viewBoxHeight ?: 0.0f).toDouble()
|
||||
videoSize = SVGARect(0.0, 0.0, width, height)
|
||||
FPS = movieParams.fps ?: 20
|
||||
frames = movieParams.frames ?: 0
|
||||
}
|
||||
|
||||
internal fun prepare(callback: () -> Unit, playCallback: SVGAParser.PlayCallback?) {
|
||||
mCallback = callback
|
||||
mPlayCallback = playCallback
|
||||
if (movieItem == null) {
|
||||
mCallback()
|
||||
} else {
|
||||
setupAudios(movieItem!!) {
|
||||
mCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parserImages(json: JSONObject) {
|
||||
val imgJson = json.optJSONObject("images") ?: return
|
||||
imgJson.keys().forEach { imgKey ->
|
||||
val filePath = generateBitmapFilePath(imgJson[imgKey].toString(), imgKey)
|
||||
if (filePath.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val bitmapKey = imgKey.replace(".matte", "")
|
||||
val bitmap = createBitmap(filePath)
|
||||
if (bitmap != null) {
|
||||
imageMap[bitmapKey] = bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateBitmapFilePath(imgName: String, imgKey: String): String {
|
||||
val path = mCacheDir.absolutePath + "/" + imgName
|
||||
val path1 = "$path.png"
|
||||
val path2 = mCacheDir.absolutePath + "/" + imgKey + ".png"
|
||||
|
||||
return when {
|
||||
File(path).exists() -> path
|
||||
File(path1).exists() -> path1
|
||||
File(path2).exists() -> path2
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBitmap(filePath: String): Bitmap? {
|
||||
return SVGABitmapFileDecoder.decodeBitmapFrom(filePath, mFrameWidth, mFrameHeight)
|
||||
}
|
||||
|
||||
private fun parserImages(obj: MovieEntity) {
|
||||
obj.images?.entries?.forEach { entry ->
|
||||
val byteArray = entry.value.toByteArray()
|
||||
if (byteArray.count() < 4) {
|
||||
return@forEach
|
||||
}
|
||||
val fileTag = byteArray.slice(IntRange(0, 3))
|
||||
if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
|
||||
return@forEach
|
||||
}
|
||||
val filePath = generateBitmapFilePath(entry.value.utf8(), entry.key)
|
||||
createBitmap(byteArray, filePath)?.let { bitmap ->
|
||||
imageMap[entry.key] = bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBitmap(byteArray: ByteArray, filePath: String): Bitmap? {
|
||||
val bitmap = SVGABitmapByteArrayDecoder.decodeBitmapFrom(byteArray, mFrameWidth, mFrameHeight)
|
||||
return bitmap ?: createBitmap(filePath)
|
||||
}
|
||||
|
||||
private fun resetSprites(json: JSONObject) {
|
||||
val mutableList: MutableList<SVGAVideoSpriteEntity> = mutableListOf()
|
||||
json.optJSONArray("sprites")?.let { item ->
|
||||
for (i in 0 until item.length()) {
|
||||
item.optJSONObject(i)?.let { entryJson ->
|
||||
mutableList.add(SVGAVideoSpriteEntity(entryJson))
|
||||
}
|
||||
}
|
||||
}
|
||||
spriteList = mutableList.toList()
|
||||
}
|
||||
|
||||
private fun resetSprites(entity: MovieEntity) {
|
||||
spriteList = entity.sprites?.map {
|
||||
return@map SVGAVideoSpriteEntity(it)
|
||||
} ?: listOf()
|
||||
}
|
||||
|
||||
private fun setupAudios(entity: MovieEntity, completionBlock: () -> Unit) {
|
||||
if (entity.audios == null || entity.audios.isEmpty()) {
|
||||
run(completionBlock)
|
||||
return
|
||||
}
|
||||
setupSoundPool(entity, completionBlock)
|
||||
val audiosFileMap = generateAudioFileMap(entity)
|
||||
//repair when audioEntity error can not callback
|
||||
//如果audiosFileMap为空 soundPool?.load 不会走 导致 setOnLoadCompleteListener 不会回调 导致外层prepare不回调卡住
|
||||
if (audiosFileMap.size == 0) {
|
||||
run(completionBlock)
|
||||
return
|
||||
}
|
||||
this.audioList = entity.audios.map { audio ->
|
||||
return@map createSvgaAudioEntity(audio, audiosFileMap)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSvgaAudioEntity(audio: AudioEntity, audiosFileMap: HashMap<String, File>): SVGAAudioEntity {
|
||||
val item = SVGAAudioEntity(audio)
|
||||
val startTime = (audio.startTime ?: 0).toDouble()
|
||||
val totalTime = (audio.totalTime ?: 0).toDouble()
|
||||
if (totalTime.toInt() == 0) {
|
||||
// 除数不能为 0
|
||||
return item
|
||||
}
|
||||
// 直接回调文件,后续播放都不走
|
||||
mPlayCallback?.let {
|
||||
val fileList: MutableList<File> = ArrayList()
|
||||
audiosFileMap.forEach { entity ->
|
||||
fileList.add(entity.value)
|
||||
}
|
||||
it.onPlay(fileList)
|
||||
mCallback()
|
||||
return item
|
||||
}
|
||||
|
||||
audiosFileMap[audio.audioKey]?.let { file ->
|
||||
FileInputStream(file).use {
|
||||
val length = it.available().toDouble()
|
||||
val offset = ((startTime / totalTime) * length).toLong()
|
||||
if (SVGASoundManager.isInit()) {
|
||||
item.soundID = SVGASoundManager.load(soundCallback,
|
||||
it.fd,
|
||||
offset,
|
||||
length.toLong(),
|
||||
1)
|
||||
} else {
|
||||
item.soundID = soundPool?.load(it.fd, offset, length.toLong(), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
private fun generateAudioFile(audioCache: File, value: ByteArray): File {
|
||||
audioCache.createNewFile()
|
||||
FileOutputStream(audioCache).write(value)
|
||||
return audioCache
|
||||
}
|
||||
|
||||
private fun generateAudioFileMap(entity: MovieEntity): HashMap<String, File> {
|
||||
val audiosDataMap = generateAudioMap(entity)
|
||||
val audiosFileMap = HashMap<String, File>()
|
||||
if (audiosDataMap.count() > 0) {
|
||||
audiosDataMap.forEach {
|
||||
val audioCache = SVGACache.buildAudioFile(it.key)
|
||||
audiosFileMap[it.key] =
|
||||
audioCache.takeIf { file -> file.exists() } ?: generateAudioFile(
|
||||
audioCache,
|
||||
it.value
|
||||
)
|
||||
}
|
||||
}
|
||||
return audiosFileMap
|
||||
}
|
||||
|
||||
private fun generateAudioMap(entity: MovieEntity): HashMap<String, ByteArray> {
|
||||
val audiosDataMap = HashMap<String, ByteArray>()
|
||||
entity.images?.entries?.forEach {
|
||||
val imageKey = it.key
|
||||
val byteArray = it.value.toByteArray()
|
||||
if (byteArray.count() < 4) {
|
||||
return@forEach
|
||||
}
|
||||
val fileTag = byteArray.slice(IntRange(0, 3))
|
||||
if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
|
||||
audiosDataMap[imageKey] = byteArray
|
||||
}else if(fileTag[0].toInt() == -1 && fileTag[1].toInt() == -5 && fileTag[2].toInt() == -108){
|
||||
audiosDataMap[imageKey] = byteArray
|
||||
}
|
||||
}
|
||||
return audiosDataMap
|
||||
}
|
||||
|
||||
private fun setupSoundPool(entity: MovieEntity, completionBlock: () -> Unit) {
|
||||
var soundLoaded = 0
|
||||
if (SVGASoundManager.isInit()) {
|
||||
soundCallback = object : SVGASoundManager.SVGASoundCallBack {
|
||||
override fun onVolumeChange(value: Float) {
|
||||
SVGASoundManager.setVolume(value, this@SVGAVideoEntity)
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
soundLoaded++
|
||||
if (soundLoaded >= entity.audios.count()) {
|
||||
completionBlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
soundPool = generateSoundPool(entity)
|
||||
LogUtils.info("SVGAParser", "pool_start")
|
||||
soundPool?.setOnLoadCompleteListener { _, _, _ ->
|
||||
LogUtils.info("SVGAParser", "pool_complete")
|
||||
soundLoaded++
|
||||
if (soundLoaded >= entity.audios.count()) {
|
||||
completionBlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateSoundPool(entity: MovieEntity): SoundPool? {
|
||||
return try {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
val attributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
SoundPool.Builder().setAudioAttributes(attributes)
|
||||
.setMaxStreams(12.coerceAtMost(entity.audios.count()))
|
||||
.build()
|
||||
} else {
|
||||
SoundPool(12.coerceAtMost(entity.audios.count()), AudioManager.STREAM_MUSIC, 0)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.error(TAG, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
if (SVGASoundManager.isInit()) {
|
||||
this.audioList.forEach {
|
||||
it.soundID?.let { id -> SVGASoundManager.unload(id) }
|
||||
}
|
||||
soundCallback = null
|
||||
}
|
||||
soundPool?.release()
|
||||
soundPool = null
|
||||
audioList = emptyList()
|
||||
spriteList = emptyList()
|
||||
imageMap.clear()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package com.opensource.svgaplayer.bitmap
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
|
||||
/**
|
||||
*
|
||||
* Create by im_dsd 2020/7/7 17:59
|
||||
*/
|
||||
internal object BitmapSampleSizeCalculator {
|
||||
|
||||
fun calculate(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
||||
// Raw height and width of image
|
||||
val (height: Int, width: Int) = options.run { outHeight to outWidth }
|
||||
var inSampleSize = 1
|
||||
|
||||
if (reqHeight <= 0 || reqWidth <= 0) {
|
||||
return inSampleSize
|
||||
}
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
|
||||
val halfHeight: Int = height / 2
|
||||
val halfWidth: Int = width / 2
|
||||
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||
// height and width larger than the requested height and width.
|
||||
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
||||
inSampleSize *= 2
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.opensource.svgaplayer.bitmap
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
|
||||
/**
|
||||
* 通过字节码解码 Bitmap
|
||||
*
|
||||
* Create by im_dsd 2020/7/7 17:50
|
||||
*/
|
||||
internal object SVGABitmapByteArrayDecoder : SVGABitmapDecoder<ByteArray>() {
|
||||
|
||||
override fun onDecode(data: ByteArray, ops: BitmapFactory.Options): Bitmap? {
|
||||
return BitmapFactory.decodeByteArray(data, 0, data.count(), ops)
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.opensource.svgaplayer.bitmap
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
|
||||
/**
|
||||
* Bitmap 解码器
|
||||
*
|
||||
* <T> 需要加载的数据类型
|
||||
*
|
||||
* Create by im_dsd 2020/7/7 17:39
|
||||
*/
|
||||
internal abstract class SVGABitmapDecoder<T> {
|
||||
|
||||
fun decodeBitmapFrom(data: T, reqWidth: Int, reqHeight: Int): Bitmap? {
|
||||
return BitmapFactory.Options().run {
|
||||
// 如果期望的宽高是合法的, 则开启检测尺寸模式
|
||||
inJustDecodeBounds = (reqWidth > 0 && reqHeight > 0)
|
||||
inPreferredConfig = Bitmap.Config.RGB_565
|
||||
|
||||
val bitmap = onDecode(data, this)
|
||||
if (!inJustDecodeBounds) {
|
||||
return bitmap
|
||||
}
|
||||
|
||||
// Calculate inSampleSize
|
||||
inSampleSize = BitmapSampleSizeCalculator.calculate(this, reqWidth, reqHeight)
|
||||
// Decode bitmap with inSampleSize set
|
||||
inJustDecodeBounds = false
|
||||
onDecode(data, this)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onDecode(data: T, ops: BitmapFactory.Options): Bitmap?
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.opensource.svgaplayer.bitmap
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
|
||||
/**
|
||||
* 通过文件解码 Bitmap
|
||||
*
|
||||
* Create by im_dsd 2020/7/7 17:50
|
||||
*/
|
||||
internal object SVGABitmapFileDecoder : SVGABitmapDecoder<String>() {
|
||||
|
||||
override fun onDecode(data: String, ops: BitmapFactory.Options): Bitmap? {
|
||||
return BitmapFactory.decodeFile(data, ops)
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.opensource.svgaplayer.drawer
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.widget.ImageView
|
||||
import com.opensource.svgaplayer.SVGAVideoEntity
|
||||
import com.opensource.svgaplayer.entities.SVGAVideoSpriteFrameEntity
|
||||
import com.opensource.svgaplayer.utils.Pools
|
||||
import com.opensource.svgaplayer.utils.SVGAScaleInfo
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2017/3/29.
|
||||
*/
|
||||
|
||||
open internal class SGVADrawer(val videoItem: SVGAVideoEntity) {
|
||||
|
||||
val scaleInfo = SVGAScaleInfo()
|
||||
|
||||
private val spritePool = Pools.SimplePool<SVGADrawerSprite>(max(1, videoItem.spriteList.size))
|
||||
|
||||
inner class SVGADrawerSprite(var _matteKey: String? = null, var _imageKey: String? = null, var _frameEntity: SVGAVideoSpriteFrameEntity? = null) {
|
||||
val matteKey get() = _matteKey
|
||||
val imageKey get() = _imageKey
|
||||
val frameEntity get() = _frameEntity!!
|
||||
}
|
||||
|
||||
internal fun requestFrameSprites(frameIndex: Int): List<SVGADrawerSprite> {
|
||||
return videoItem.spriteList.mapNotNull {
|
||||
if (frameIndex >= 0 && frameIndex < it.frames.size) {
|
||||
it.imageKey?.let { imageKey ->
|
||||
if (!imageKey.endsWith(".matte") && it.frames[frameIndex].alpha <= 0.0) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
return@mapNotNull (spritePool.acquire() ?: SVGADrawerSprite()).apply {
|
||||
_matteKey = it.matteKey
|
||||
_imageKey = it.imageKey
|
||||
_frameEntity = it.frames[frameIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
return@mapNotNull null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun releaseFrameSprites(sprites: List<SVGADrawerSprite>) {
|
||||
sprites.forEach { spritePool.release(it) }
|
||||
}
|
||||
|
||||
open fun drawFrame(canvas : Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
|
||||
scaleInfo.performScaleType(canvas.width.toFloat(),canvas.height.toFloat(), videoItem.videoSize.width.toFloat(), videoItem.videoSize.height.toFloat(), scaleType)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,559 @@
|
||||
package com.opensource.svgaplayer.drawer
|
||||
|
||||
import android.graphics.*
|
||||
import android.os.Build
|
||||
import android.text.StaticLayout
|
||||
import android.text.TextUtils
|
||||
import android.widget.ImageView
|
||||
import com.opensource.svgaplayer.SVGADynamicEntity
|
||||
import com.opensource.svgaplayer.SVGASoundManager
|
||||
import com.opensource.svgaplayer.SVGAVideoEntity
|
||||
import com.opensource.svgaplayer.entities.SVGAVideoShapeEntity
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2017/3/29.
|
||||
*/
|
||||
|
||||
internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity) : SGVADrawer(videoItem) {
|
||||
|
||||
private val sharedValues = ShareValues()
|
||||
private val drawTextCache: HashMap<String, Bitmap> = hashMapOf()
|
||||
private val pathCache = PathCache()
|
||||
|
||||
private var beginIndexList: Array<Boolean>? = null
|
||||
private var endIndexList: Array<Boolean>? = null
|
||||
|
||||
override fun drawFrame(canvas: Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
|
||||
super.drawFrame(canvas, frameIndex, scaleType)
|
||||
playAudio(frameIndex)
|
||||
this.pathCache.onSizeChanged(canvas)
|
||||
val sprites = requestFrameSprites(frameIndex)
|
||||
// Filter null sprites
|
||||
if (sprites.count() <= 0) return
|
||||
val matteSprites = mutableMapOf<String, SVGADrawerSprite>()
|
||||
var saveID = -1
|
||||
beginIndexList = null
|
||||
endIndexList = null
|
||||
|
||||
// Filter no matte layer
|
||||
var hasMatteLayer = false
|
||||
sprites.get(0).imageKey?.let {
|
||||
if (it.endsWith(".matte")) {
|
||||
hasMatteLayer = true
|
||||
}
|
||||
}
|
||||
sprites.forEachIndexed { index, svgaDrawerSprite ->
|
||||
|
||||
// Save matte sprite
|
||||
svgaDrawerSprite.imageKey?.let {
|
||||
/// No matte layer included or VERSION Unsopport matte
|
||||
if (!hasMatteLayer || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Normal sprite
|
||||
drawSprite(svgaDrawerSprite, canvas, frameIndex)
|
||||
// Continue
|
||||
return@forEachIndexed
|
||||
}
|
||||
/// Cache matte sprite
|
||||
if (it.endsWith(".matte")) {
|
||||
matteSprites.put(it, svgaDrawerSprite)
|
||||
// Continue
|
||||
return@forEachIndexed
|
||||
}
|
||||
}
|
||||
/// Is matte begin
|
||||
if (isMatteBegin(index, sprites)) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
saveID = canvas.saveLayer(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), null)
|
||||
} else {
|
||||
canvas.save()
|
||||
}
|
||||
}
|
||||
/// Normal matte
|
||||
drawSprite(svgaDrawerSprite, canvas, frameIndex)
|
||||
|
||||
/// Is matte end
|
||||
if (isMatteEnd(index, sprites)) {
|
||||
matteSprites.get(svgaDrawerSprite.matteKey)?.let {
|
||||
drawSprite(it, this.sharedValues.shareMatteCanvas(canvas.width, canvas.height), frameIndex)
|
||||
canvas.drawBitmap(this.sharedValues.sharedMatteBitmap(), 0f, 0f, this.sharedValues.shareMattePaint())
|
||||
if (saveID != -1) {
|
||||
canvas.restoreToCount(saveID)
|
||||
} else {
|
||||
canvas.restore()
|
||||
}
|
||||
// Continue
|
||||
return@forEachIndexed
|
||||
}
|
||||
}
|
||||
}
|
||||
releaseFrameSprites(sprites)
|
||||
}
|
||||
|
||||
private fun isMatteBegin(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
|
||||
if (beginIndexList == null) {
|
||||
val boolArray = Array(sprites.count()) { false }
|
||||
sprites.forEachIndexed { index, svgaDrawerSprite ->
|
||||
svgaDrawerSprite.imageKey?.let {
|
||||
/// Filter matte sprite
|
||||
if (it.endsWith(".matte")) {
|
||||
// Continue
|
||||
return@forEachIndexed
|
||||
}
|
||||
}
|
||||
svgaDrawerSprite.matteKey?.let {
|
||||
if (it.length > 0) {
|
||||
sprites.get(index - 1)?.let { lastSprite ->
|
||||
if (lastSprite.matteKey.isNullOrEmpty()) {
|
||||
boolArray[index] = true
|
||||
} else {
|
||||
if (lastSprite.matteKey != svgaDrawerSprite.matteKey) {
|
||||
boolArray[index] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
beginIndexList = boolArray
|
||||
}
|
||||
return beginIndexList?.get(spriteIndex) ?: false
|
||||
}
|
||||
|
||||
private fun isMatteEnd(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
|
||||
if (endIndexList == null) {
|
||||
val boolArray = Array(sprites.count()) { false }
|
||||
sprites.forEachIndexed { index, svgaDrawerSprite ->
|
||||
svgaDrawerSprite.imageKey?.let {
|
||||
/// Filter matte sprite
|
||||
if (it.endsWith(".matte")) {
|
||||
// Continue
|
||||
return@forEachIndexed
|
||||
}
|
||||
}
|
||||
svgaDrawerSprite.matteKey?.let {
|
||||
if (it.length > 0) {
|
||||
// Last one
|
||||
if (index == sprites.count() - 1) {
|
||||
boolArray[index] = true
|
||||
} else {
|
||||
sprites.get(index + 1)?.let { nextSprite ->
|
||||
if (nextSprite.matteKey.isNullOrEmpty()) {
|
||||
boolArray[index] = true
|
||||
} else {
|
||||
if (nextSprite.matteKey != svgaDrawerSprite.matteKey) {
|
||||
boolArray[index] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
endIndexList = boolArray
|
||||
}
|
||||
return endIndexList?.get(spriteIndex) ?: false
|
||||
}
|
||||
|
||||
private fun playAudio(frameIndex: Int) {
|
||||
this.videoItem.audioList.forEach { audio ->
|
||||
if (audio.startFrame == frameIndex) {
|
||||
if (SVGASoundManager.isInit()) {
|
||||
audio.soundID?.let { soundID ->
|
||||
audio.playID = SVGASoundManager.play(soundID)
|
||||
}
|
||||
} else {
|
||||
this.videoItem.soundPool?.let { soundPool ->
|
||||
audio.soundID?.let { soundID ->
|
||||
audio.playID = soundPool.play(soundID, 1.0f, 1.0f, 1, 0, 1.0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (audio.endFrame <= frameIndex) {
|
||||
audio.playID?.let {
|
||||
if (SVGASoundManager.isInit()) {
|
||||
SVGASoundManager.stop(it)
|
||||
} else {
|
||||
this.videoItem.soundPool?.stop(it)
|
||||
}
|
||||
}
|
||||
audio.playID = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareFrameMatrix(transform: Matrix): Matrix {
|
||||
val matrix = this.sharedValues.sharedMatrix()
|
||||
matrix.postScale(scaleInfo.scaleFx, scaleInfo.scaleFy)
|
||||
matrix.postTranslate(scaleInfo.tranFx, scaleInfo.tranFy)
|
||||
matrix.preConcat(transform)
|
||||
return matrix
|
||||
}
|
||||
|
||||
private fun drawSprite(sprite: SVGADrawerSprite, canvas: Canvas, frameIndex: Int) {
|
||||
drawImage(sprite, canvas)
|
||||
drawShape(sprite, canvas)
|
||||
drawDynamic(sprite, canvas, frameIndex)
|
||||
}
|
||||
|
||||
private fun drawImage(sprite: SVGADrawerSprite, canvas: Canvas) {
|
||||
val imageKey = sprite.imageKey ?: return
|
||||
val isHidden = dynamicItem.dynamicHidden[imageKey] == true
|
||||
if (isHidden) {
|
||||
return
|
||||
}
|
||||
val bitmapKey = if (imageKey.endsWith(".matte")) imageKey.substring(0, imageKey.length - 6) else imageKey
|
||||
val drawingBitmap = (dynamicItem.dynamicImage[bitmapKey] ?: videoItem.imageMap[bitmapKey])
|
||||
?: return
|
||||
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
|
||||
val paint = this.sharedValues.sharedPaint()
|
||||
paint.isAntiAlias = videoItem.antiAlias
|
||||
paint.isFilterBitmap = videoItem.antiAlias
|
||||
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
|
||||
if (sprite.frameEntity.maskPath != null) {
|
||||
val maskPath = sprite.frameEntity.maskPath ?: return
|
||||
canvas.save()
|
||||
val path = this.sharedValues.sharedPath()
|
||||
maskPath.buildPath(path)
|
||||
path.transform(frameMatrix)
|
||||
canvas.clipPath(path)
|
||||
frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
|
||||
if (!drawingBitmap.isRecycled) {
|
||||
canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
|
||||
}
|
||||
canvas.restore()
|
||||
} else {
|
||||
frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
|
||||
if (!drawingBitmap.isRecycled) {
|
||||
canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
|
||||
}
|
||||
}
|
||||
dynamicItem.dynamicIClickArea.let {
|
||||
it.get(imageKey)?.let { listener ->
|
||||
val matrixArray = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
|
||||
frameMatrix.getValues(matrixArray)
|
||||
listener.onResponseArea(imageKey, matrixArray[2].toInt()
|
||||
, matrixArray[5].toInt()
|
||||
, (drawingBitmap.width * matrixArray[0] + matrixArray[2]).toInt()
|
||||
, (drawingBitmap.height * matrixArray[4] + matrixArray[5]).toInt())
|
||||
}
|
||||
}
|
||||
drawTextOnBitmap(canvas, drawingBitmap, sprite, frameMatrix)
|
||||
}
|
||||
|
||||
private fun drawTextOnBitmap(canvas: Canvas, drawingBitmap: Bitmap, sprite: SVGADrawerSprite, frameMatrix: Matrix) {
|
||||
if (dynamicItem.isTextDirty) {
|
||||
this.drawTextCache.clear()
|
||||
dynamicItem.isTextDirty = false
|
||||
}
|
||||
val imageKey = sprite.imageKey ?: return
|
||||
var textBitmap: Bitmap? = null
|
||||
dynamicItem.dynamicText[imageKey]?.let { drawingText ->
|
||||
dynamicItem.dynamicTextPaint[imageKey]?.let { drawingTextPaint ->
|
||||
drawTextCache[imageKey]?.let {
|
||||
textBitmap = it
|
||||
} ?: kotlin.run {
|
||||
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
|
||||
val drawRect = Rect(0, 0, drawingBitmap.width, drawingBitmap.height)
|
||||
val textCanvas = Canvas(textBitmap)
|
||||
drawingTextPaint.isAntiAlias = true
|
||||
val fontMetrics = drawingTextPaint.getFontMetrics();
|
||||
val top = fontMetrics.top
|
||||
val bottom = fontMetrics.bottom
|
||||
val baseLineY = drawRect.centerY() - top / 2 - bottom / 2
|
||||
textCanvas.drawText(drawingText, drawRect.centerX().toFloat(), baseLineY, drawingTextPaint);
|
||||
drawTextCache.put(imageKey, textBitmap as Bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamicItem.dynamicBoringLayoutText[imageKey]?.let {
|
||||
drawTextCache[imageKey]?.let {
|
||||
textBitmap = it
|
||||
} ?: kotlin.run {
|
||||
it.paint.isAntiAlias = true
|
||||
|
||||
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
|
||||
val textCanvas = Canvas(textBitmap)
|
||||
textCanvas.translate(0f, ((drawingBitmap.height - it.height) / 2).toFloat())
|
||||
it.draw(textCanvas)
|
||||
drawTextCache.put(imageKey, textBitmap as Bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
dynamicItem.dynamicStaticLayoutText[imageKey]?.let {
|
||||
drawTextCache[imageKey]?.let {
|
||||
textBitmap = it
|
||||
} ?: kotlin.run {
|
||||
it.paint.isAntiAlias = true
|
||||
var layout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
var lineMax = try {
|
||||
val field = StaticLayout::class.java.getDeclaredField("mMaximumVisibleLineCount")
|
||||
field.isAccessible = true
|
||||
field.getInt(it)
|
||||
} catch (e: Exception) {
|
||||
Int.MAX_VALUE
|
||||
}
|
||||
StaticLayout.Builder
|
||||
.obtain(it.text, 0, it.text.length, it.paint, drawingBitmap.width)
|
||||
.setAlignment(it.alignment)
|
||||
.setMaxLines(lineMax)
|
||||
.setEllipsize(TextUtils.TruncateAt.END)
|
||||
.build()
|
||||
} else {
|
||||
StaticLayout(it.text, 0, it.text.length, it.paint, drawingBitmap.width, it.alignment, it.spacingMultiplier, it.spacingAdd, false)
|
||||
}
|
||||
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
|
||||
val textCanvas = Canvas(textBitmap)
|
||||
textCanvas.translate(0f, ((drawingBitmap.height - layout.height) / 2).toFloat())
|
||||
layout.draw(textCanvas)
|
||||
drawTextCache.put(imageKey, textBitmap as Bitmap)
|
||||
}
|
||||
}
|
||||
textBitmap?.let { textBitmap ->
|
||||
val paint = this.sharedValues.sharedPaint()
|
||||
paint.isAntiAlias = videoItem.antiAlias
|
||||
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
|
||||
if (sprite.frameEntity.maskPath != null) {
|
||||
val maskPath = sprite.frameEntity.maskPath ?: return@let
|
||||
canvas.save()
|
||||
canvas.concat(frameMatrix)
|
||||
canvas.clipRect(0, 0, drawingBitmap.width, drawingBitmap.height)
|
||||
val bitmapShader = BitmapShader(textBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
|
||||
paint.shader = bitmapShader
|
||||
val path = this.sharedValues.sharedPath()
|
||||
maskPath.buildPath(path)
|
||||
canvas.drawPath(path, paint)
|
||||
canvas.restore()
|
||||
} else {
|
||||
paint.isFilterBitmap = videoItem.antiAlias
|
||||
canvas.drawBitmap(textBitmap, frameMatrix, paint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawShape(sprite: SVGADrawerSprite, canvas: Canvas) {
|
||||
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
|
||||
sprite.frameEntity.shapes.forEach { shape ->
|
||||
shape.buildPath()
|
||||
shape.shapePath?.let {
|
||||
val paint = this.sharedValues.sharedPaint()
|
||||
paint.reset()
|
||||
paint.isAntiAlias = videoItem.antiAlias
|
||||
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
|
||||
val path = this.sharedValues.sharedPath()
|
||||
path.reset()
|
||||
path.addPath(this.pathCache.buildPath(shape))
|
||||
val shapeMatrix = this.sharedValues.sharedMatrix2()
|
||||
shapeMatrix.reset()
|
||||
shape.transform?.let {
|
||||
shapeMatrix.postConcat(it)
|
||||
}
|
||||
shapeMatrix.postConcat(frameMatrix)
|
||||
path.transform(shapeMatrix)
|
||||
shape.styles?.fill?.let {
|
||||
if (it != 0x00000000) {
|
||||
paint.style = Paint.Style.FILL
|
||||
paint.color = it
|
||||
val alpha = Math.min(255, Math.max(0, (sprite.frameEntity.alpha * 255).toInt()))
|
||||
if (alpha != 255) {
|
||||
paint.alpha = alpha
|
||||
}
|
||||
if (sprite.frameEntity.maskPath !== null) canvas.save()
|
||||
sprite.frameEntity.maskPath?.let { maskPath ->
|
||||
val path2 = this.sharedValues.sharedPath2()
|
||||
maskPath.buildPath(path2)
|
||||
path2.transform(frameMatrix)
|
||||
canvas.clipPath(path2)
|
||||
}
|
||||
canvas.drawPath(path, paint)
|
||||
if (sprite.frameEntity.maskPath !== null) canvas.restore()
|
||||
}
|
||||
}
|
||||
shape.styles?.strokeWidth?.let {
|
||||
if (it > 0) {
|
||||
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
|
||||
paint.style = Paint.Style.STROKE
|
||||
shape.styles?.stroke?.let {
|
||||
paint.color = it
|
||||
val alpha = Math.min(255, Math.max(0, (sprite.frameEntity.alpha * 255).toInt()))
|
||||
if (alpha != 255) {
|
||||
paint.alpha = alpha
|
||||
}
|
||||
}
|
||||
val scale = matrixScale(frameMatrix)
|
||||
shape.styles?.strokeWidth?.let {
|
||||
paint.strokeWidth = it * scale
|
||||
}
|
||||
shape.styles?.lineCap?.let {
|
||||
when {
|
||||
it.equals("butt", true) -> paint.strokeCap = Paint.Cap.BUTT
|
||||
it.equals("round", true) -> paint.strokeCap = Paint.Cap.ROUND
|
||||
it.equals("square", true) -> paint.strokeCap = Paint.Cap.SQUARE
|
||||
}
|
||||
}
|
||||
shape.styles?.lineJoin?.let {
|
||||
when {
|
||||
it.equals("miter", true) -> paint.strokeJoin = Paint.Join.MITER
|
||||
it.equals("round", true) -> paint.strokeJoin = Paint.Join.ROUND
|
||||
it.equals("bevel", true) -> paint.strokeJoin = Paint.Join.BEVEL
|
||||
}
|
||||
}
|
||||
shape.styles?.miterLimit?.let {
|
||||
paint.strokeMiter = it.toFloat() * scale
|
||||
}
|
||||
shape.styles?.lineDash?.let {
|
||||
if (it.size == 3 && (it[0] > 0 || it[1] > 0)) {
|
||||
paint.pathEffect = DashPathEffect(floatArrayOf(
|
||||
(if (it[0] < 1.0f) 1.0f else it[0]) * scale,
|
||||
(if (it[1] < 0.1f) 0.1f else it[1]) * scale
|
||||
), it[2] * scale)
|
||||
}
|
||||
}
|
||||
if (sprite.frameEntity.maskPath !== null) canvas.save()
|
||||
sprite.frameEntity.maskPath?.let { maskPath ->
|
||||
val path2 = this.sharedValues.sharedPath2()
|
||||
maskPath.buildPath(path2)
|
||||
path2.transform(frameMatrix)
|
||||
canvas.clipPath(path2)
|
||||
}
|
||||
canvas.drawPath(path, paint)
|
||||
if (sprite.frameEntity.maskPath !== null) canvas.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private val matrixScaleTempValues = FloatArray(16)
|
||||
|
||||
private fun matrixScale(matrix: Matrix): Float {
|
||||
matrix.getValues(matrixScaleTempValues)
|
||||
if (matrixScaleTempValues[0] == 0f) {
|
||||
return 0f
|
||||
}
|
||||
var A = matrixScaleTempValues[0].toDouble()
|
||||
var B = matrixScaleTempValues[3].toDouble()
|
||||
var C = matrixScaleTempValues[1].toDouble()
|
||||
var D = matrixScaleTempValues[4].toDouble()
|
||||
if (A * D == B * C) return 0f
|
||||
var scaleX = Math.sqrt(A * A + B * B)
|
||||
A /= scaleX
|
||||
B /= scaleX
|
||||
var skew = A * C + B * D
|
||||
C -= A * skew
|
||||
D -= B * skew
|
||||
var scaleY = Math.sqrt(C * C + D * D)
|
||||
C /= scaleY
|
||||
D /= scaleY
|
||||
skew /= scaleY
|
||||
if (A * D < B * C) {
|
||||
scaleX = -scaleX
|
||||
}
|
||||
return if (scaleInfo.ratioX) Math.abs(scaleX.toFloat()) else Math.abs(scaleY.toFloat())
|
||||
}
|
||||
|
||||
private fun drawDynamic(sprite: SVGADrawerSprite, canvas: Canvas, frameIndex: Int) {
|
||||
val imageKey = sprite.imageKey ?: return
|
||||
dynamicItem.dynamicDrawer[imageKey]?.let {
|
||||
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
|
||||
canvas.save()
|
||||
canvas.concat(frameMatrix)
|
||||
it.invoke(canvas, frameIndex)
|
||||
canvas.restore()
|
||||
}
|
||||
dynamicItem.dynamicDrawerSized[imageKey]?.let {
|
||||
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
|
||||
canvas.save()
|
||||
canvas.concat(frameMatrix)
|
||||
it.invoke(canvas, frameIndex, sprite.frameEntity.layout.width.toInt(), sprite.frameEntity.layout.height.toInt())
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
class ShareValues {
|
||||
|
||||
private val sharedPaint = Paint()
|
||||
private val sharedPath = Path()
|
||||
private val sharedPath2 = Path()
|
||||
private val sharedMatrix = Matrix()
|
||||
private val sharedMatrix2 = Matrix()
|
||||
|
||||
private val shareMattePaint = Paint()
|
||||
private var shareMatteCanvas: Canvas? = null
|
||||
private var sharedMatteBitmap: Bitmap? = null
|
||||
|
||||
fun sharedPaint(): Paint {
|
||||
sharedPaint.reset()
|
||||
return sharedPaint
|
||||
}
|
||||
|
||||
fun sharedPath(): Path {
|
||||
sharedPath.reset()
|
||||
return sharedPath
|
||||
}
|
||||
|
||||
fun sharedPath2(): Path {
|
||||
sharedPath2.reset()
|
||||
return sharedPath2
|
||||
}
|
||||
|
||||
fun sharedMatrix(): Matrix {
|
||||
sharedMatrix.reset()
|
||||
return sharedMatrix
|
||||
}
|
||||
|
||||
fun sharedMatrix2(): Matrix {
|
||||
sharedMatrix2.reset()
|
||||
return sharedMatrix2
|
||||
}
|
||||
|
||||
fun shareMattePaint(): Paint {
|
||||
shareMattePaint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_IN))
|
||||
return shareMattePaint
|
||||
}
|
||||
|
||||
fun sharedMatteBitmap(): Bitmap {
|
||||
return sharedMatteBitmap as Bitmap
|
||||
}
|
||||
|
||||
fun shareMatteCanvas(width: Int, height: Int): Canvas {
|
||||
if (shareMatteCanvas == null) {
|
||||
sharedMatteBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8)
|
||||
// shareMatteCanvas = Canvas(sharedMatteBitmap)
|
||||
}
|
||||
// val matteCanvas = shareMatteCanvas as Canvas
|
||||
// matteCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||
// return matteCanvas
|
||||
return Canvas(sharedMatteBitmap)
|
||||
}
|
||||
}
|
||||
|
||||
class PathCache {
|
||||
|
||||
private var canvasWidth: Int = 0
|
||||
private var canvasHeight: Int = 0
|
||||
private val cache = HashMap<SVGAVideoShapeEntity, Path>()
|
||||
|
||||
fun onSizeChanged(canvas: Canvas) {
|
||||
if (this.canvasWidth != canvas.width || this.canvasHeight != canvas.height) {
|
||||
this.cache.clear()
|
||||
}
|
||||
this.canvasWidth = canvas.width
|
||||
this.canvasHeight = canvas.height
|
||||
}
|
||||
|
||||
fun buildPath(shape: SVGAVideoShapeEntity): Path {
|
||||
if (!this.cache.containsKey(shape)) {
|
||||
val path = Path()
|
||||
path.set(shape.shapePath)
|
||||
this.cache[shape] = path
|
||||
}
|
||||
return this.cache[shape]!!
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.opensource.svgaplayer.entities
|
||||
|
||||
import com.opensource.svgaplayer.proto.AudioEntity
|
||||
import java.io.FileInputStream
|
||||
|
||||
internal class SVGAAudioEntity {
|
||||
|
||||
val audioKey: String?
|
||||
val startFrame: Int
|
||||
val endFrame: Int
|
||||
val startTime: Int
|
||||
val totalTime: Int
|
||||
var soundID: Int? = null
|
||||
var playID: Int? = null
|
||||
|
||||
constructor(audioItem: AudioEntity) {
|
||||
this.audioKey = audioItem.audioKey
|
||||
this.startFrame = audioItem.startFrame ?: 0
|
||||
this.endFrame = audioItem.endFrame ?: 0
|
||||
this.startTime = audioItem.startTime ?: 0
|
||||
this.totalTime = audioItem.totalTime ?: 0
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.opensource.svgaplayer.entities
|
||||
|
||||
import android.graphics.Path
|
||||
import com.opensource.svgaplayer.utils.SVGAPoint
|
||||
import java.util.*
|
||||
|
||||
private val VALID_METHODS: Set<String> = setOf("M", "L", "H", "V", "C", "S", "Q", "R", "A", "Z", "m", "l", "h", "v", "c", "s", "q", "r", "a", "z")
|
||||
|
||||
class SVGAPathEntity(originValue: String) {
|
||||
|
||||
private val replacedValue: String = if (originValue.contains(",")) originValue.replace(",", " ") else originValue
|
||||
|
||||
private var cachedPath: Path? = null
|
||||
|
||||
fun buildPath(toPath: Path) {
|
||||
cachedPath?.let {
|
||||
toPath.set(it)
|
||||
return
|
||||
}
|
||||
val cachedPath = Path()
|
||||
val segments = StringTokenizer(this.replacedValue, "MLHVCSQRAZmlhvcsqraz", true)
|
||||
var currentMethod = ""
|
||||
while (segments.hasMoreTokens()) {
|
||||
val segment = segments.nextToken()
|
||||
if (segment.isEmpty()) { continue }
|
||||
if (VALID_METHODS.contains(segment)) {
|
||||
currentMethod = segment
|
||||
if (currentMethod == "Z" || currentMethod == "z") { operate(cachedPath, currentMethod, StringTokenizer("", "")) }
|
||||
}
|
||||
else {
|
||||
operate(cachedPath, currentMethod, StringTokenizer(segment, " "))
|
||||
}
|
||||
}
|
||||
this.cachedPath = cachedPath
|
||||
toPath.set(cachedPath)
|
||||
}
|
||||
|
||||
private fun operate(finalPath: Path, method: String, args: StringTokenizer) {
|
||||
var x0 = 0.0f
|
||||
var y0 = 0.0f
|
||||
var x1 = 0.0f
|
||||
var y1 = 0.0f
|
||||
var x2 = 0.0f
|
||||
var y2 = 0.0f
|
||||
try {
|
||||
var index = 0
|
||||
while (args.hasMoreTokens()) {
|
||||
val s = args.nextToken()
|
||||
if (s.isEmpty()) {continue}
|
||||
if (index == 0) { x0 = s.toFloat() }
|
||||
if (index == 1) { y0 = s.toFloat() }
|
||||
if (index == 2) { x1 = s.toFloat() }
|
||||
if (index == 3) { y1 = s.toFloat() }
|
||||
if (index == 4) { x2 = s.toFloat() }
|
||||
if (index == 5) { y2 = s.toFloat() }
|
||||
index++
|
||||
}
|
||||
} catch (e: Exception) {}
|
||||
var currentPoint = SVGAPoint(0.0f, 0.0f, 0.0f)
|
||||
if (method == "M") {
|
||||
finalPath.moveTo(x0, y0)
|
||||
currentPoint = SVGAPoint(x0, y0, 0.0f)
|
||||
} else if (method == "m") {
|
||||
finalPath.rMoveTo(x0, y0)
|
||||
currentPoint = SVGAPoint(currentPoint.x + x0, currentPoint.y + y0, 0.0f)
|
||||
}
|
||||
if (method == "L") {
|
||||
finalPath.lineTo(x0, y0)
|
||||
} else if (method == "l") {
|
||||
finalPath.rLineTo(x0, y0)
|
||||
}
|
||||
if (method == "C") {
|
||||
finalPath.cubicTo(x0, y0, x1, y1, x2, y2)
|
||||
} else if (method == "c") {
|
||||
finalPath.rCubicTo(x0, y0, x1, y1, x2, y2)
|
||||
}
|
||||
if (method == "Q") {
|
||||
finalPath.quadTo(x0, y0, x1, y1)
|
||||
} else if (method == "q") {
|
||||
finalPath.rQuadTo(x0, y0, x1, y1)
|
||||
}
|
||||
if (method == "H") {
|
||||
finalPath.lineTo(x0, currentPoint.y)
|
||||
} else if (method == "h") {
|
||||
finalPath.rLineTo(x0, 0f)
|
||||
}
|
||||
if (method == "V") {
|
||||
finalPath.lineTo(currentPoint.x, x0)
|
||||
} else if (method == "v") {
|
||||
finalPath.rLineTo(0f, x0)
|
||||
}
|
||||
if (method == "Z") {
|
||||
finalPath.close()
|
||||
}
|
||||
else if (method == "z") {
|
||||
finalPath.close()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,356 @@
|
||||
package com.opensource.svgaplayer.entities
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import com.opensource.svgaplayer.proto.ShapeEntity
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2017/2/22.
|
||||
*/
|
||||
|
||||
val sharedPath = Path()
|
||||
|
||||
internal class SVGAVideoShapeEntity {
|
||||
|
||||
enum class Type {
|
||||
shape,
|
||||
rect,
|
||||
ellipse,
|
||||
keep
|
||||
}
|
||||
|
||||
class Styles {
|
||||
|
||||
var fill = 0x00000000
|
||||
internal set
|
||||
|
||||
var stroke = 0x00000000
|
||||
internal set
|
||||
|
||||
var strokeWidth = 0.0f
|
||||
internal set
|
||||
|
||||
var lineCap = "butt"
|
||||
internal set
|
||||
|
||||
var lineJoin = "miter"
|
||||
internal set
|
||||
|
||||
var miterLimit = 0
|
||||
internal set
|
||||
|
||||
var lineDash = FloatArray(0)
|
||||
internal set
|
||||
|
||||
}
|
||||
|
||||
var type = Type.shape
|
||||
private set
|
||||
|
||||
var args: Map<String, Any>? = null
|
||||
private set
|
||||
|
||||
var styles: Styles? = null
|
||||
private set
|
||||
|
||||
var transform: Matrix? = null
|
||||
private set
|
||||
|
||||
constructor(obj: JSONObject) {
|
||||
parseType(obj)
|
||||
parseArgs(obj)
|
||||
parseStyles(obj)
|
||||
parseTransform(obj)
|
||||
}
|
||||
|
||||
constructor(obj: ShapeEntity) {
|
||||
parseType(obj)
|
||||
parseArgs(obj)
|
||||
parseStyles(obj)
|
||||
parseTransform(obj)
|
||||
}
|
||||
|
||||
val isKeep: Boolean
|
||||
get() = type == Type.keep
|
||||
|
||||
var shapePath: Path? = null
|
||||
|
||||
private fun parseType(obj: JSONObject) {
|
||||
obj.optString("type")?.let {
|
||||
when {
|
||||
it.equals("shape", ignoreCase = true) -> type = Type.shape
|
||||
it.equals("rect", ignoreCase = true) -> type = Type.rect
|
||||
it.equals("ellipse", ignoreCase = true) -> type = Type.ellipse
|
||||
it.equals("keep", ignoreCase = true) -> type = Type.keep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseType(obj: ShapeEntity) {
|
||||
obj.type?.let {
|
||||
type = when (it) {
|
||||
ShapeEntity.ShapeType.SHAPE -> Type.shape
|
||||
ShapeEntity.ShapeType.RECT -> Type.rect
|
||||
ShapeEntity.ShapeType.ELLIPSE -> Type.ellipse
|
||||
ShapeEntity.ShapeType.KEEP -> Type.keep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseArgs(obj: JSONObject) {
|
||||
val args = HashMap<String, Any>()
|
||||
obj.optJSONObject("args")?.let { values ->
|
||||
values.keys().forEach { key ->
|
||||
values.get(key)?.let {
|
||||
args.put(key, it)
|
||||
}
|
||||
}
|
||||
this.args = args
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseArgs(obj: ShapeEntity) {
|
||||
val args = HashMap<String, Any>()
|
||||
obj.shape?.let {
|
||||
it.d?.let { args.put("d", it) }
|
||||
}
|
||||
obj.ellipse?.let {
|
||||
args.put("x", it.x ?: 0.0f)
|
||||
args.put("y", it.y ?: 0.0f)
|
||||
args.put("radiusX", it.radiusX ?: 0.0f)
|
||||
args.put("radiusY", it.radiusY ?: 0.0f)
|
||||
}
|
||||
obj.rect?.let {
|
||||
args.put("x", it.x ?: 0.0f)
|
||||
args.put("y", it.y ?: 0.0f)
|
||||
args.put("width", it.width ?: 0.0f)
|
||||
args.put("height", it.height ?: 0.0f)
|
||||
args.put("cornerRadius", it.cornerRadius ?: 0.0f)
|
||||
}
|
||||
this.args = args
|
||||
}
|
||||
|
||||
// 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f]
|
||||
private fun checkValueRange(obj: JSONArray): Float {
|
||||
return if (
|
||||
obj.optDouble(0) <= 1 &&
|
||||
obj.optDouble(1) <= 1 &&
|
||||
obj.optDouble(2) <= 1
|
||||
) {
|
||||
255f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 alpha 的范围是否是 [0f, 1f],或者是 [0f, 255f]
|
||||
private fun checkAlphaValueRange(obj: JSONArray): Float {
|
||||
return if (obj.optDouble(3) <= 1) {
|
||||
255f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStyles(obj: JSONObject) {
|
||||
obj.optJSONObject("styles")?.let {
|
||||
val styles = Styles()
|
||||
it.optJSONArray("fill")?.let {
|
||||
if (it.length() == 4) {
|
||||
val mulValue = checkValueRange(it)
|
||||
val alphaRangeValue = checkAlphaValueRange(it)
|
||||
styles.fill = Color.argb(
|
||||
(it.optDouble(3) * alphaRangeValue).toInt(),
|
||||
(it.optDouble(0) * mulValue).toInt(),
|
||||
(it.optDouble(1) * mulValue).toInt(),
|
||||
(it.optDouble(2) * mulValue).toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
it.optJSONArray("stroke")?.let {
|
||||
if (it.length() == 4) {
|
||||
val mulValue = checkValueRange(it)
|
||||
val alphaRangeValue = checkAlphaValueRange(it)
|
||||
styles.stroke = Color.argb(
|
||||
(it.optDouble(3) * alphaRangeValue).toInt(),
|
||||
(it.optDouble(0) * mulValue).toInt(),
|
||||
(it.optDouble(1) * mulValue).toInt(),
|
||||
(it.optDouble(2) * mulValue).toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
styles.strokeWidth = it.optDouble("strokeWidth", 0.0).toFloat()
|
||||
styles.lineCap = it.optString("lineCap", "butt")
|
||||
styles.lineJoin = it.optString("lineJoin", "miter")
|
||||
styles.miterLimit = it.optInt("miterLimit", 0)
|
||||
it.optJSONArray("lineDash")?.let {
|
||||
styles.lineDash = FloatArray(it.length())
|
||||
for (i in 0 until it.length()) {
|
||||
styles.lineDash[i] = it.optDouble(i, 0.0).toFloat()
|
||||
}
|
||||
}
|
||||
this.styles = styles
|
||||
}
|
||||
}
|
||||
|
||||
// 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f]
|
||||
private fun checkValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float {
|
||||
return if (
|
||||
(color.r ?: 0f) <= 1 &&
|
||||
(color.g ?: 0f) <= 1 &&
|
||||
(color.b ?: 0f) <= 1
|
||||
) {
|
||||
255f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 alpha 范围是否是 [0f, 1f],有可能是 [0f, 255f]
|
||||
private fun checkAlphaValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float {
|
||||
return if (color.a <= 1f) {
|
||||
255f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStyles(obj: ShapeEntity) {
|
||||
obj.styles?.let {
|
||||
val styles = Styles()
|
||||
it.fill?.let {
|
||||
val mulValue = checkValueRange(it)
|
||||
val alphaRangeValue = checkAlphaValueRange(it)
|
||||
styles.fill = Color.argb(
|
||||
((it.a ?: 0f) * alphaRangeValue).toInt(),
|
||||
((it.r ?: 0f) * mulValue).toInt(),
|
||||
((it.g ?: 0f) * mulValue).toInt(),
|
||||
((it.b ?: 0f) * mulValue).toInt()
|
||||
)
|
||||
}
|
||||
it.stroke?.let {
|
||||
val mulValue = checkValueRange(it)
|
||||
val alphaRangeValue = checkAlphaValueRange(it)
|
||||
styles.stroke = Color.argb(
|
||||
((it.a ?: 0f) * alphaRangeValue).toInt(),
|
||||
((it.r ?: 0f) * mulValue).toInt(),
|
||||
((it.g ?: 0f) * mulValue).toInt(),
|
||||
((it.b ?: 0f) * mulValue).toInt()
|
||||
)
|
||||
|
||||
}
|
||||
styles.strokeWidth = it.strokeWidth ?: 0.0f
|
||||
it.lineCap?.let {
|
||||
when (it) {
|
||||
ShapeEntity.ShapeStyle.LineCap.LineCap_BUTT -> styles.lineCap = "butt"
|
||||
ShapeEntity.ShapeStyle.LineCap.LineCap_ROUND -> styles.lineCap = "round"
|
||||
ShapeEntity.ShapeStyle.LineCap.LineCap_SQUARE -> styles.lineCap = "square"
|
||||
}
|
||||
}
|
||||
it.lineJoin?.let {
|
||||
when (it) {
|
||||
ShapeEntity.ShapeStyle.LineJoin.LineJoin_BEVEL -> styles.lineJoin = "bevel"
|
||||
ShapeEntity.ShapeStyle.LineJoin.LineJoin_MITER -> styles.lineJoin = "miter"
|
||||
ShapeEntity.ShapeStyle.LineJoin.LineJoin_ROUND -> styles.lineJoin = "round"
|
||||
}
|
||||
}
|
||||
styles.miterLimit = (it.miterLimit ?: 0.0f).toInt()
|
||||
styles.lineDash = kotlin.FloatArray(3)
|
||||
it.lineDashI?.let { styles.lineDash[0] = it }
|
||||
it.lineDashII?.let { styles.lineDash[1] = it }
|
||||
it.lineDashIII?.let { styles.lineDash[2] = it }
|
||||
this.styles = styles
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseTransform(obj: JSONObject) {
|
||||
obj.optJSONObject("transform")?.let {
|
||||
val transform = Matrix()
|
||||
val arr = FloatArray(9)
|
||||
val a = it.optDouble("a", 1.0)
|
||||
val b = it.optDouble("b", 0.0)
|
||||
val c = it.optDouble("c", 0.0)
|
||||
val d = it.optDouble("d", 1.0)
|
||||
val tx = it.optDouble("tx", 0.0)
|
||||
val ty = it.optDouble("ty", 0.0)
|
||||
arr[0] = a.toFloat() // a
|
||||
arr[1] = c.toFloat() // c
|
||||
arr[2] = tx.toFloat() // tx
|
||||
arr[3] = b.toFloat() // b
|
||||
arr[4] = d.toFloat() // d
|
||||
arr[5] = ty.toFloat() // ty
|
||||
arr[6] = 0.0.toFloat()
|
||||
arr[7] = 0.0.toFloat()
|
||||
arr[8] = 1.0.toFloat()
|
||||
transform.setValues(arr)
|
||||
this.transform = transform
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseTransform(obj: ShapeEntity) {
|
||||
obj.transform?.let {
|
||||
val transform = Matrix()
|
||||
val arr = FloatArray(9)
|
||||
val a = it.a ?: 1.0f
|
||||
val b = it.b ?: 0.0f
|
||||
val c = it.c ?: 0.0f
|
||||
val d = it.d ?: 1.0f
|
||||
val tx = it.tx ?: 0.0f
|
||||
val ty = it.ty ?: 0.0f
|
||||
arr[0] = a
|
||||
arr[1] = c
|
||||
arr[2] = tx
|
||||
arr[3] = b
|
||||
arr[4] = d
|
||||
arr[5] = ty
|
||||
arr[6] = 0.0f
|
||||
arr[7] = 0.0f
|
||||
arr[8] = 1.0f
|
||||
transform.setValues(arr)
|
||||
this.transform = transform
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun buildPath() {
|
||||
if (this.shapePath != null) {
|
||||
return
|
||||
}
|
||||
sharedPath.reset()
|
||||
if (this.type == Type.shape) {
|
||||
(this.args?.get("d") as? String)?.let {
|
||||
SVGAPathEntity(it).buildPath(sharedPath)
|
||||
}
|
||||
} else if (this.type == Type.ellipse) {
|
||||
val xv = this.args?.get("x") as? Number ?: return
|
||||
val yv = this.args?.get("y") as? Number ?: return
|
||||
val rxv = this.args?.get("radiusX") as? Number ?: return
|
||||
val ryv = this.args?.get("radiusY") as? Number ?: return
|
||||
val x = xv.toFloat()
|
||||
val y = yv.toFloat()
|
||||
val rx = rxv.toFloat()
|
||||
val ry = ryv.toFloat()
|
||||
sharedPath.addOval(RectF(x - rx, y - ry, x + rx, y + ry), Path.Direction.CW)
|
||||
} else if (this.type == Type.rect) {
|
||||
val xv = this.args?.get("x") as? Number ?: return
|
||||
val yv = this.args?.get("y") as? Number ?: return
|
||||
val wv = this.args?.get("width") as? Number ?: return
|
||||
val hv = this.args?.get("height") as? Number ?: return
|
||||
val crv = this.args?.get("cornerRadius") as? Number ?: return
|
||||
val x = xv.toFloat()
|
||||
val y = yv.toFloat()
|
||||
val width = wv.toFloat()
|
||||
val height = hv.toFloat()
|
||||
val cornerRadius = crv.toFloat()
|
||||
sharedPath.addRoundRect(RectF(x, y, x + width, y + height), cornerRadius, cornerRadius, Path.Direction.CW)
|
||||
}
|
||||
this.shapePath = Path()
|
||||
this.shapePath?.set(sharedPath)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.opensource.svgaplayer.entities
|
||||
|
||||
import com.opensource.svgaplayer.proto.SpriteEntity
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2016/10/17.
|
||||
*/
|
||||
internal class SVGAVideoSpriteEntity {
|
||||
|
||||
val imageKey: String?
|
||||
|
||||
val matteKey: String?
|
||||
|
||||
val frames: List<SVGAVideoSpriteFrameEntity>
|
||||
|
||||
constructor(obj: JSONObject) {
|
||||
this.imageKey = obj.optString("imageKey")
|
||||
this.matteKey = obj.optString("matteKey")
|
||||
val mutableFrames: MutableList<SVGAVideoSpriteFrameEntity> = mutableListOf()
|
||||
obj.optJSONArray("frames")?.let {
|
||||
for (i in 0 until it.length()) {
|
||||
it.optJSONObject(i)?.let {
|
||||
val frameItem = SVGAVideoSpriteFrameEntity(it)
|
||||
if (frameItem.shapes.isNotEmpty()) {
|
||||
frameItem.shapes.first().let {
|
||||
if (it.isKeep && mutableFrames.size > 0) {
|
||||
frameItem.shapes = mutableFrames.last().shapes
|
||||
}
|
||||
}
|
||||
}
|
||||
mutableFrames.add(frameItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
frames = mutableFrames.toList()
|
||||
}
|
||||
|
||||
constructor(obj: SpriteEntity) {
|
||||
this.imageKey = obj.imageKey
|
||||
this.matteKey = obj.matteKey
|
||||
var lastFrame: SVGAVideoSpriteFrameEntity? = null
|
||||
frames = obj.frames?.map {
|
||||
val frameItem = SVGAVideoSpriteFrameEntity(it)
|
||||
if (frameItem.shapes.isNotEmpty()) {
|
||||
frameItem.shapes.first().let {
|
||||
if (it.isKeep) {
|
||||
lastFrame?.let {
|
||||
frameItem.shapes = it.shapes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lastFrame = frameItem
|
||||
return@map frameItem
|
||||
} ?: listOf()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package com.opensource.svgaplayer.entities
|
||||
|
||||
import android.graphics.Matrix
|
||||
import com.opensource.svgaplayer.proto.FrameEntity
|
||||
import com.opensource.svgaplayer.utils.SVGARect
|
||||
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2016/10/17.
|
||||
*/
|
||||
internal class SVGAVideoSpriteFrameEntity {
|
||||
|
||||
var alpha: Double
|
||||
var layout = SVGARect(0.0, 0.0, 0.0, 0.0)
|
||||
var transform = Matrix()
|
||||
var maskPath: SVGAPathEntity? = null
|
||||
var shapes: List<SVGAVideoShapeEntity> = listOf()
|
||||
|
||||
constructor(obj: JSONObject) {
|
||||
this.alpha = obj.optDouble("alpha", 0.0)
|
||||
obj.optJSONObject("layout")?.let {
|
||||
layout = SVGARect(it.optDouble("x", 0.0), it.optDouble("y", 0.0), it.optDouble("width", 0.0), it.optDouble("height", 0.0))
|
||||
}
|
||||
obj.optJSONObject("transform")?.let {
|
||||
val arr = FloatArray(9)
|
||||
val a = it.optDouble("a", 1.0)
|
||||
val b = it.optDouble("b", 0.0)
|
||||
val c = it.optDouble("c", 0.0)
|
||||
val d = it.optDouble("d", 1.0)
|
||||
val tx = it.optDouble("tx", 0.0)
|
||||
val ty = it.optDouble("ty", 0.0)
|
||||
arr[0] = a.toFloat()
|
||||
arr[1] = c.toFloat()
|
||||
arr[2] = tx.toFloat()
|
||||
arr[3] = b.toFloat()
|
||||
arr[4] = d.toFloat()
|
||||
arr[5] = ty.toFloat()
|
||||
arr[6] = 0.0.toFloat()
|
||||
arr[7] = 0.0.toFloat()
|
||||
arr[8] = 1.0.toFloat()
|
||||
transform.setValues(arr)
|
||||
}
|
||||
obj.optString("clipPath")?.let { d ->
|
||||
if (d.isNotEmpty()) {
|
||||
maskPath = SVGAPathEntity(d)
|
||||
}
|
||||
}
|
||||
obj.optJSONArray("shapes")?.let {
|
||||
val mutableList: MutableList<SVGAVideoShapeEntity> = mutableListOf()
|
||||
for (i in 0 until it.length()) {
|
||||
it.optJSONObject(i)?.let {
|
||||
mutableList.add(SVGAVideoShapeEntity(it))
|
||||
}
|
||||
}
|
||||
shapes = mutableList.toList()
|
||||
}
|
||||
}
|
||||
|
||||
constructor(obj: FrameEntity) {
|
||||
this.alpha = (obj.alpha ?: 0.0f).toDouble()
|
||||
obj.layout?.let {
|
||||
this.layout = SVGARect((it.x ?: 0.0f).toDouble(), (it.y
|
||||
?: 0.0f).toDouble(), (it.width ?: 0.0f).toDouble(), (it.height
|
||||
?: 0.0f).toDouble())
|
||||
}
|
||||
obj.transform?.let {
|
||||
val arr = FloatArray(9)
|
||||
val a = it.a ?: 1.0f
|
||||
val b = it.b ?: 0.0f
|
||||
val c = it.c ?: 0.0f
|
||||
val d = it.d ?: 1.0f
|
||||
val tx = it.tx ?: 0.0f
|
||||
val ty = it.ty ?: 0.0f
|
||||
arr[0] = a
|
||||
arr[1] = c
|
||||
arr[2] = tx
|
||||
arr[3] = b
|
||||
arr[4] = d
|
||||
arr[5] = ty
|
||||
arr[6] = 0.0f
|
||||
arr[7] = 0.0f
|
||||
arr[8] = 1.0f
|
||||
transform.setValues(arr)
|
||||
}
|
||||
obj.clipPath?.takeIf { it.isNotEmpty() }?.let {
|
||||
maskPath = SVGAPathEntity(it)
|
||||
}
|
||||
this.shapes = obj.shapes.map {
|
||||
return@map SVGAVideoShapeEntity(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,258 @@
|
||||
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||
// Source file: svga.proto at 19:1
|
||||
package com.opensource.svgaplayer.proto;
|
||||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
import com.squareup.wire.Message;
|
||||
import com.squareup.wire.ProtoAdapter;
|
||||
import com.squareup.wire.ProtoReader;
|
||||
import com.squareup.wire.ProtoWriter;
|
||||
import com.squareup.wire.WireField;
|
||||
import com.squareup.wire.internal.Internal;
|
||||
import java.io.IOException;
|
||||
import java.lang.Integer;
|
||||
import java.lang.Object;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import java.lang.StringBuilder;
|
||||
import okio.ByteString;
|
||||
|
||||
public final class AudioEntity extends Message<AudioEntity, AudioEntity.Builder> {
|
||||
public static final ProtoAdapter<AudioEntity> ADAPTER = new ProtoAdapter_AudioEntity();
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
public static final String DEFAULT_AUDIOKEY = "";
|
||||
|
||||
public static final Integer DEFAULT_STARTFRAME = 0;
|
||||
|
||||
public static final Integer DEFAULT_ENDFRAME = 0;
|
||||
|
||||
public static final Integer DEFAULT_STARTTIME = 0;
|
||||
|
||||
public static final Integer DEFAULT_TOTALTIME = 0;
|
||||
|
||||
/**
|
||||
* 音频文件名
|
||||
*/
|
||||
@WireField(
|
||||
tag = 1,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||
)
|
||||
public final String audioKey;
|
||||
|
||||
/**
|
||||
* 音频播放起始帧
|
||||
*/
|
||||
@WireField(
|
||||
tag = 2,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||
)
|
||||
public final Integer startFrame;
|
||||
|
||||
/**
|
||||
* 音频播放结束帧
|
||||
*/
|
||||
@WireField(
|
||||
tag = 3,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||
)
|
||||
public final Integer endFrame;
|
||||
|
||||
/**
|
||||
* 音频播放起始时间(相对音频长度)
|
||||
*/
|
||||
@WireField(
|
||||
tag = 4,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||
)
|
||||
public final Integer startTime;
|
||||
|
||||
/**
|
||||
* 音频总长度
|
||||
*/
|
||||
@WireField(
|
||||
tag = 5,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||
)
|
||||
public final Integer totalTime;
|
||||
|
||||
public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime) {
|
||||
this(audioKey, startFrame, endFrame, startTime, totalTime, ByteString.EMPTY);
|
||||
}
|
||||
|
||||
public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime, ByteString unknownFields) {
|
||||
super(ADAPTER, unknownFields);
|
||||
this.audioKey = audioKey;
|
||||
this.startFrame = startFrame;
|
||||
this.endFrame = endFrame;
|
||||
this.startTime = startTime;
|
||||
this.totalTime = totalTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder newBuilder() {
|
||||
Builder builder = new Builder();
|
||||
builder.audioKey = audioKey;
|
||||
builder.startFrame = startFrame;
|
||||
builder.endFrame = endFrame;
|
||||
builder.startTime = startTime;
|
||||
builder.totalTime = totalTime;
|
||||
builder.addUnknownFields(unknownFields());
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof AudioEntity)) return false;
|
||||
AudioEntity o = (AudioEntity) other;
|
||||
return unknownFields().equals(o.unknownFields())
|
||||
&& Internal.equals(audioKey, o.audioKey)
|
||||
&& Internal.equals(startFrame, o.startFrame)
|
||||
&& Internal.equals(endFrame, o.endFrame)
|
||||
&& Internal.equals(startTime, o.startTime)
|
||||
&& Internal.equals(totalTime, o.totalTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode;
|
||||
if (result == 0) {
|
||||
result = unknownFields().hashCode();
|
||||
result = result * 37 + (audioKey != null ? audioKey.hashCode() : 0);
|
||||
result = result * 37 + (startFrame != null ? startFrame.hashCode() : 0);
|
||||
result = result * 37 + (endFrame != null ? endFrame.hashCode() : 0);
|
||||
result = result * 37 + (startTime != null ? startTime.hashCode() : 0);
|
||||
result = result * 37 + (totalTime != null ? totalTime.hashCode() : 0);
|
||||
super.hashCode = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (audioKey != null) builder.append(", audioKey=").append(audioKey);
|
||||
if (startFrame != null) builder.append(", startFrame=").append(startFrame);
|
||||
if (endFrame != null) builder.append(", endFrame=").append(endFrame);
|
||||
if (startTime != null) builder.append(", startTime=").append(startTime);
|
||||
if (totalTime != null) builder.append(", totalTime=").append(totalTime);
|
||||
return builder.replace(0, 2, "AudioEntity{").append('}').toString();
|
||||
}
|
||||
|
||||
public static final class Builder extends Message.Builder<AudioEntity, Builder> {
|
||||
public String audioKey;
|
||||
|
||||
public Integer startFrame;
|
||||
|
||||
public Integer endFrame;
|
||||
|
||||
public Integer startTime;
|
||||
|
||||
public Integer totalTime;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频文件名
|
||||
*/
|
||||
public Builder audioKey(String audioKey) {
|
||||
this.audioKey = audioKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频播放起始帧
|
||||
*/
|
||||
public Builder startFrame(Integer startFrame) {
|
||||
this.startFrame = startFrame;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频播放结束帧
|
||||
*/
|
||||
public Builder endFrame(Integer endFrame) {
|
||||
this.endFrame = endFrame;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频播放起始时间(相对音频长度)
|
||||
*/
|
||||
public Builder startTime(Integer startTime) {
|
||||
this.startTime = startTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频总长度
|
||||
*/
|
||||
public Builder totalTime(Integer totalTime) {
|
||||
this.totalTime = totalTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioEntity build() {
|
||||
return new AudioEntity(audioKey, startFrame, endFrame, startTime, totalTime, super.buildUnknownFields());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProtoAdapter_AudioEntity extends ProtoAdapter<AudioEntity> {
|
||||
ProtoAdapter_AudioEntity() {
|
||||
super(FieldEncoding.LENGTH_DELIMITED, AudioEntity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodedSize(AudioEntity value) {
|
||||
return (value.audioKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.audioKey) : 0)
|
||||
+ (value.startFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(2, value.startFrame) : 0)
|
||||
+ (value.endFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.endFrame) : 0)
|
||||
+ (value.startTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.startTime) : 0)
|
||||
+ (value.totalTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(5, value.totalTime) : 0)
|
||||
+ value.unknownFields().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ProtoWriter writer, AudioEntity value) throws IOException {
|
||||
if (value.audioKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.audioKey);
|
||||
if (value.startFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 2, value.startFrame);
|
||||
if (value.endFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.endFrame);
|
||||
if (value.startTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.startTime);
|
||||
if (value.totalTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 5, value.totalTime);
|
||||
writer.writeBytes(value.unknownFields());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioEntity decode(ProtoReader reader) throws IOException {
|
||||
Builder builder = new Builder();
|
||||
long token = reader.beginMessage();
|
||||
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||
switch (tag) {
|
||||
case 1: builder.audioKey(ProtoAdapter.STRING.decode(reader)); break;
|
||||
case 2: builder.startFrame(ProtoAdapter.INT32.decode(reader)); break;
|
||||
case 3: builder.endFrame(ProtoAdapter.INT32.decode(reader)); break;
|
||||
case 4: builder.startTime(ProtoAdapter.INT32.decode(reader)); break;
|
||||
case 5: builder.totalTime(ProtoAdapter.INT32.decode(reader)); break;
|
||||
default: {
|
||||
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||
builder.addUnknownField(tag, fieldEncoding, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endMessage(token);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioEntity redact(AudioEntity value) {
|
||||
Builder builder = value.newBuilder();
|
||||
builder.clearUnknownFields();
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,259 @@
|
||||
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||
// Source file: svga.proto at 115:1
|
||||
package com.opensource.svgaplayer.proto;
|
||||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
import com.squareup.wire.Message;
|
||||
import com.squareup.wire.ProtoAdapter;
|
||||
import com.squareup.wire.ProtoReader;
|
||||
import com.squareup.wire.ProtoWriter;
|
||||
import com.squareup.wire.WireField;
|
||||
import com.squareup.wire.internal.Internal;
|
||||
import java.io.IOException;
|
||||
import java.lang.Float;
|
||||
import java.lang.Object;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import java.lang.StringBuilder;
|
||||
import java.util.List;
|
||||
import okio.ByteString;
|
||||
|
||||
public final class FrameEntity extends Message<FrameEntity, FrameEntity.Builder> {
|
||||
public static final ProtoAdapter<FrameEntity> ADAPTER = new ProtoAdapter_FrameEntity();
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
public static final Float DEFAULT_ALPHA = 0.0f;
|
||||
|
||||
public static final String DEFAULT_CLIPPATH = "";
|
||||
|
||||
/**
|
||||
* 透明度
|
||||
*/
|
||||
@WireField(
|
||||
tag = 1,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float alpha;
|
||||
|
||||
/**
|
||||
* 初始约束大小
|
||||
*/
|
||||
@WireField(
|
||||
tag = 2,
|
||||
adapter = "com.opensource.svgaplayer.proto.Layout#ADAPTER"
|
||||
)
|
||||
public final Layout layout;
|
||||
|
||||
/**
|
||||
* 2D 变换矩阵
|
||||
*/
|
||||
@WireField(
|
||||
tag = 3,
|
||||
adapter = "com.opensource.svgaplayer.proto.Transform#ADAPTER"
|
||||
)
|
||||
public final Transform transform;
|
||||
|
||||
/**
|
||||
* 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。
|
||||
*/
|
||||
@WireField(
|
||||
tag = 4,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||
)
|
||||
public final String clipPath;
|
||||
|
||||
/**
|
||||
* 矢量元素列表
|
||||
*/
|
||||
@WireField(
|
||||
tag = 5,
|
||||
adapter = "com.opensource.svgaplayer.proto.ShapeEntity#ADAPTER",
|
||||
label = WireField.Label.REPEATED
|
||||
)
|
||||
public final List<ShapeEntity> shapes;
|
||||
|
||||
public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List<ShapeEntity> shapes) {
|
||||
this(alpha, layout, transform, clipPath, shapes, ByteString.EMPTY);
|
||||
}
|
||||
|
||||
public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List<ShapeEntity> shapes, ByteString unknownFields) {
|
||||
super(ADAPTER, unknownFields);
|
||||
this.alpha = alpha;
|
||||
this.layout = layout;
|
||||
this.transform = transform;
|
||||
this.clipPath = clipPath;
|
||||
this.shapes = Internal.immutableCopyOf("shapes", shapes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder newBuilder() {
|
||||
Builder builder = new Builder();
|
||||
builder.alpha = alpha;
|
||||
builder.layout = layout;
|
||||
builder.transform = transform;
|
||||
builder.clipPath = clipPath;
|
||||
builder.shapes = Internal.copyOf("shapes", shapes);
|
||||
builder.addUnknownFields(unknownFields());
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof FrameEntity)) return false;
|
||||
FrameEntity o = (FrameEntity) other;
|
||||
return unknownFields().equals(o.unknownFields())
|
||||
&& Internal.equals(alpha, o.alpha)
|
||||
&& Internal.equals(layout, o.layout)
|
||||
&& Internal.equals(transform, o.transform)
|
||||
&& Internal.equals(clipPath, o.clipPath)
|
||||
&& shapes.equals(o.shapes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode;
|
||||
if (result == 0) {
|
||||
result = unknownFields().hashCode();
|
||||
result = result * 37 + (alpha != null ? alpha.hashCode() : 0);
|
||||
result = result * 37 + (layout != null ? layout.hashCode() : 0);
|
||||
result = result * 37 + (transform != null ? transform.hashCode() : 0);
|
||||
result = result * 37 + (clipPath != null ? clipPath.hashCode() : 0);
|
||||
result = result * 37 + shapes.hashCode();
|
||||
super.hashCode = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (alpha != null) builder.append(", alpha=").append(alpha);
|
||||
if (layout != null) builder.append(", layout=").append(layout);
|
||||
if (transform != null) builder.append(", transform=").append(transform);
|
||||
if (clipPath != null) builder.append(", clipPath=").append(clipPath);
|
||||
if (!shapes.isEmpty()) builder.append(", shapes=").append(shapes);
|
||||
return builder.replace(0, 2, "FrameEntity{").append('}').toString();
|
||||
}
|
||||
|
||||
public static final class Builder extends Message.Builder<FrameEntity, Builder> {
|
||||
public Float alpha;
|
||||
|
||||
public Layout layout;
|
||||
|
||||
public Transform transform;
|
||||
|
||||
public String clipPath;
|
||||
|
||||
public List<ShapeEntity> shapes;
|
||||
|
||||
public Builder() {
|
||||
shapes = Internal.newMutableList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 透明度
|
||||
*/
|
||||
public Builder alpha(Float alpha) {
|
||||
this.alpha = alpha;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始约束大小
|
||||
*/
|
||||
public Builder layout(Layout layout) {
|
||||
this.layout = layout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2D 变换矩阵
|
||||
*/
|
||||
public Builder transform(Transform transform) {
|
||||
this.transform = transform;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。
|
||||
*/
|
||||
public Builder clipPath(String clipPath) {
|
||||
this.clipPath = clipPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 矢量元素列表
|
||||
*/
|
||||
public Builder shapes(List<ShapeEntity> shapes) {
|
||||
Internal.checkElementsNotNull(shapes);
|
||||
this.shapes = shapes;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameEntity build() {
|
||||
return new FrameEntity(alpha, layout, transform, clipPath, shapes, super.buildUnknownFields());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProtoAdapter_FrameEntity extends ProtoAdapter<FrameEntity> {
|
||||
ProtoAdapter_FrameEntity() {
|
||||
super(FieldEncoding.LENGTH_DELIMITED, FrameEntity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodedSize(FrameEntity value) {
|
||||
return (value.alpha != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.alpha) : 0)
|
||||
+ (value.layout != null ? Layout.ADAPTER.encodedSizeWithTag(2, value.layout) : 0)
|
||||
+ (value.transform != null ? Transform.ADAPTER.encodedSizeWithTag(3, value.transform) : 0)
|
||||
+ (value.clipPath != null ? ProtoAdapter.STRING.encodedSizeWithTag(4, value.clipPath) : 0)
|
||||
+ ShapeEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.shapes)
|
||||
+ value.unknownFields().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ProtoWriter writer, FrameEntity value) throws IOException {
|
||||
if (value.alpha != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.alpha);
|
||||
if (value.layout != null) Layout.ADAPTER.encodeWithTag(writer, 2, value.layout);
|
||||
if (value.transform != null) Transform.ADAPTER.encodeWithTag(writer, 3, value.transform);
|
||||
if (value.clipPath != null) ProtoAdapter.STRING.encodeWithTag(writer, 4, value.clipPath);
|
||||
ShapeEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.shapes);
|
||||
writer.writeBytes(value.unknownFields());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameEntity decode(ProtoReader reader) throws IOException {
|
||||
Builder builder = new Builder();
|
||||
long token = reader.beginMessage();
|
||||
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||
switch (tag) {
|
||||
case 1: builder.alpha(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 2: builder.layout(Layout.ADAPTER.decode(reader)); break;
|
||||
case 3: builder.transform(Transform.ADAPTER.decode(reader)); break;
|
||||
case 4: builder.clipPath(ProtoAdapter.STRING.decode(reader)); break;
|
||||
case 5: builder.shapes.add(ShapeEntity.ADAPTER.decode(reader)); break;
|
||||
default: {
|
||||
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||
builder.addUnknownField(tag, fieldEncoding, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endMessage(token);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameEntity redact(FrameEntity value) {
|
||||
Builder builder = value.newBuilder();
|
||||
if (builder.layout != null) builder.layout = Layout.ADAPTER.redact(builder.layout);
|
||||
if (builder.transform != null) builder.transform = Transform.ADAPTER.redact(builder.transform);
|
||||
Internal.redactElements(builder.shapes, ShapeEntity.ADAPTER);
|
||||
builder.clearUnknownFields();
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||
// Source file: svga.proto at 27:1
|
||||
package com.opensource.svgaplayer.proto;
|
||||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
import com.squareup.wire.Message;
|
||||
import com.squareup.wire.ProtoAdapter;
|
||||
import com.squareup.wire.ProtoReader;
|
||||
import com.squareup.wire.ProtoWriter;
|
||||
import com.squareup.wire.WireField;
|
||||
import com.squareup.wire.internal.Internal;
|
||||
import java.io.IOException;
|
||||
import java.lang.Float;
|
||||
import java.lang.Object;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import java.lang.StringBuilder;
|
||||
import okio.ByteString;
|
||||
|
||||
public final class Layout extends Message<Layout, Layout.Builder> {
|
||||
public static final ProtoAdapter<Layout> ADAPTER = new ProtoAdapter_Layout();
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
public static final Float DEFAULT_X = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_Y = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_WIDTH = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_HEIGHT = 0.0f;
|
||||
|
||||
@WireField(
|
||||
tag = 1,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float x;
|
||||
|
||||
@WireField(
|
||||
tag = 2,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float y;
|
||||
|
||||
@WireField(
|
||||
tag = 3,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float width;
|
||||
|
||||
@WireField(
|
||||
tag = 4,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float height;
|
||||
|
||||
public Layout(Float x, Float y, Float width, Float height) {
|
||||
this(x, y, width, height, ByteString.EMPTY);
|
||||
}
|
||||
|
||||
public Layout(Float x, Float y, Float width, Float height, ByteString unknownFields) {
|
||||
super(ADAPTER, unknownFields);
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder newBuilder() {
|
||||
Builder builder = new Builder();
|
||||
builder.x = x;
|
||||
builder.y = y;
|
||||
builder.width = width;
|
||||
builder.height = height;
|
||||
builder.addUnknownFields(unknownFields());
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof Layout)) return false;
|
||||
Layout o = (Layout) other;
|
||||
return unknownFields().equals(o.unknownFields())
|
||||
&& Internal.equals(x, o.x)
|
||||
&& Internal.equals(y, o.y)
|
||||
&& Internal.equals(width, o.width)
|
||||
&& Internal.equals(height, o.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode;
|
||||
if (result == 0) {
|
||||
result = unknownFields().hashCode();
|
||||
result = result * 37 + (x != null ? x.hashCode() : 0);
|
||||
result = result * 37 + (y != null ? y.hashCode() : 0);
|
||||
result = result * 37 + (width != null ? width.hashCode() : 0);
|
||||
result = result * 37 + (height != null ? height.hashCode() : 0);
|
||||
super.hashCode = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (x != null) builder.append(", x=").append(x);
|
||||
if (y != null) builder.append(", y=").append(y);
|
||||
if (width != null) builder.append(", width=").append(width);
|
||||
if (height != null) builder.append(", height=").append(height);
|
||||
return builder.replace(0, 2, "Layout{").append('}').toString();
|
||||
}
|
||||
|
||||
public static final class Builder extends Message.Builder<Layout, Builder> {
|
||||
public Float x;
|
||||
|
||||
public Float y;
|
||||
|
||||
public Float width;
|
||||
|
||||
public Float height;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder x(Float x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder y(Float y) {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder width(Float width) {
|
||||
this.width = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder height(Float height) {
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout build() {
|
||||
return new Layout(x, y, width, height, super.buildUnknownFields());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProtoAdapter_Layout extends ProtoAdapter<Layout> {
|
||||
ProtoAdapter_Layout() {
|
||||
super(FieldEncoding.LENGTH_DELIMITED, Layout.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodedSize(Layout value) {
|
||||
return (value.x != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.x) : 0)
|
||||
+ (value.y != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.y) : 0)
|
||||
+ (value.width != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.width) : 0)
|
||||
+ (value.height != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.height) : 0)
|
||||
+ value.unknownFields().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ProtoWriter writer, Layout value) throws IOException {
|
||||
if (value.x != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.x);
|
||||
if (value.y != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.y);
|
||||
if (value.width != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.width);
|
||||
if (value.height != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.height);
|
||||
writer.writeBytes(value.unknownFields());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout decode(ProtoReader reader) throws IOException {
|
||||
Builder builder = new Builder();
|
||||
long token = reader.beginMessage();
|
||||
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||
switch (tag) {
|
||||
case 1: builder.x(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 2: builder.y(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 3: builder.width(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 4: builder.height(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
default: {
|
||||
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||
builder.addUnknownField(tag, fieldEncoding, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endMessage(token);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout redact(Layout value) {
|
||||
Builder builder = value.newBuilder();
|
||||
builder.clearUnknownFields();
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,265 @@
|
||||
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||
// Source file: svga.proto at 123:1
|
||||
package com.opensource.svgaplayer.proto;
|
||||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
import com.squareup.wire.Message;
|
||||
import com.squareup.wire.ProtoAdapter;
|
||||
import com.squareup.wire.ProtoReader;
|
||||
import com.squareup.wire.ProtoWriter;
|
||||
import com.squareup.wire.WireField;
|
||||
import com.squareup.wire.internal.Internal;
|
||||
import java.io.IOException;
|
||||
import java.lang.Object;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import java.lang.StringBuilder;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import okio.ByteString;
|
||||
|
||||
public final class MovieEntity extends Message<MovieEntity, MovieEntity.Builder> {
|
||||
public static final ProtoAdapter<MovieEntity> ADAPTER = new ProtoAdapter_MovieEntity();
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
public static final String DEFAULT_VERSION = "";
|
||||
|
||||
/**
|
||||
* SVGA 格式版本号
|
||||
*/
|
||||
@WireField(
|
||||
tag = 1,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||
)
|
||||
public final String version;
|
||||
|
||||
/**
|
||||
* 动画参数
|
||||
*/
|
||||
@WireField(
|
||||
tag = 2,
|
||||
adapter = "com.opensource.svgaplayer.proto.MovieParams#ADAPTER"
|
||||
)
|
||||
public final MovieParams params;
|
||||
|
||||
/**
|
||||
* Key 是位图键名,Value 是位图文件名或二进制 PNG 数据。
|
||||
*/
|
||||
@WireField(
|
||||
tag = 3,
|
||||
keyAdapter = "com.squareup.wire.ProtoAdapter#STRING",
|
||||
adapter = "com.squareup.wire.ProtoAdapter#BYTES"
|
||||
)
|
||||
public final Map<String, ByteString> images;
|
||||
|
||||
/**
|
||||
* 元素列表
|
||||
*/
|
||||
@WireField(
|
||||
tag = 4,
|
||||
adapter = "com.opensource.svgaplayer.proto.SpriteEntity#ADAPTER",
|
||||
label = WireField.Label.REPEATED
|
||||
)
|
||||
public final List<SpriteEntity> sprites;
|
||||
|
||||
/**
|
||||
* 音频列表
|
||||
*/
|
||||
@WireField(
|
||||
tag = 5,
|
||||
adapter = "com.opensource.svgaplayer.proto.AudioEntity#ADAPTER",
|
||||
label = WireField.Label.REPEATED
|
||||
)
|
||||
public final List<AudioEntity> audios;
|
||||
|
||||
public MovieEntity(String version, MovieParams params, Map<String, ByteString> images, List<SpriteEntity> sprites, List<AudioEntity> audios) {
|
||||
this(version, params, images, sprites, audios, ByteString.EMPTY);
|
||||
}
|
||||
|
||||
public MovieEntity(String version, MovieParams params, Map<String, ByteString> images, List<SpriteEntity> sprites, List<AudioEntity> audios, ByteString unknownFields) {
|
||||
super(ADAPTER, unknownFields);
|
||||
this.version = version;
|
||||
this.params = params;
|
||||
this.images = Internal.immutableCopyOf("images", images);
|
||||
this.sprites = Internal.immutableCopyOf("sprites", sprites);
|
||||
this.audios = Internal.immutableCopyOf("audios", audios);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder newBuilder() {
|
||||
Builder builder = new Builder();
|
||||
builder.version = version;
|
||||
builder.params = params;
|
||||
builder.images = Internal.copyOf("images", images);
|
||||
builder.sprites = Internal.copyOf("sprites", sprites);
|
||||
builder.audios = Internal.copyOf("audios", audios);
|
||||
builder.addUnknownFields(unknownFields());
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof MovieEntity)) return false;
|
||||
MovieEntity o = (MovieEntity) other;
|
||||
return unknownFields().equals(o.unknownFields())
|
||||
&& Internal.equals(version, o.version)
|
||||
&& Internal.equals(params, o.params)
|
||||
&& images.equals(o.images)
|
||||
&& sprites.equals(o.sprites)
|
||||
&& audios.equals(o.audios);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode;
|
||||
if (result == 0) {
|
||||
result = unknownFields().hashCode();
|
||||
result = result * 37 + (version != null ? version.hashCode() : 0);
|
||||
result = result * 37 + (params != null ? params.hashCode() : 0);
|
||||
result = result * 37 + images.hashCode();
|
||||
result = result * 37 + sprites.hashCode();
|
||||
result = result * 37 + audios.hashCode();
|
||||
super.hashCode = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (version != null) builder.append(", version=").append(version);
|
||||
if (params != null) builder.append(", params=").append(params);
|
||||
if (!images.isEmpty()) builder.append(", images=").append(images);
|
||||
if (!sprites.isEmpty()) builder.append(", sprites=").append(sprites);
|
||||
if (!audios.isEmpty()) builder.append(", audios=").append(audios);
|
||||
return builder.replace(0, 2, "MovieEntity{").append('}').toString();
|
||||
}
|
||||
|
||||
public static final class Builder extends Message.Builder<MovieEntity, Builder> {
|
||||
public String version;
|
||||
|
||||
public MovieParams params;
|
||||
|
||||
public Map<String, ByteString> images;
|
||||
|
||||
public List<SpriteEntity> sprites;
|
||||
|
||||
public List<AudioEntity> audios;
|
||||
|
||||
public Builder() {
|
||||
images = Internal.newMutableMap();
|
||||
sprites = Internal.newMutableList();
|
||||
audios = Internal.newMutableList();
|
||||
}
|
||||
|
||||
/**
|
||||
* SVGA 格式版本号
|
||||
*/
|
||||
public Builder version(String version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画参数
|
||||
*/
|
||||
public Builder params(MovieParams params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key 是位图键名,Value 是位图文件名或二进制 PNG 数据。
|
||||
*/
|
||||
public Builder images(Map<String, ByteString> images) {
|
||||
Internal.checkElementsNotNull(images);
|
||||
this.images = images;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 元素列表
|
||||
*/
|
||||
public Builder sprites(List<SpriteEntity> sprites) {
|
||||
Internal.checkElementsNotNull(sprites);
|
||||
this.sprites = sprites;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频列表
|
||||
*/
|
||||
public Builder audios(List<AudioEntity> audios) {
|
||||
Internal.checkElementsNotNull(audios);
|
||||
this.audios = audios;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovieEntity build() {
|
||||
return new MovieEntity(version, params, images, sprites, audios, super.buildUnknownFields());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProtoAdapter_MovieEntity extends ProtoAdapter<MovieEntity> {
|
||||
private final ProtoAdapter<Map<String, ByteString>> images = ProtoAdapter.newMapAdapter(ProtoAdapter.STRING, ProtoAdapter.BYTES);
|
||||
|
||||
ProtoAdapter_MovieEntity() {
|
||||
super(FieldEncoding.LENGTH_DELIMITED, MovieEntity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodedSize(MovieEntity value) {
|
||||
return (value.version != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.version) : 0)
|
||||
+ (value.params != null ? MovieParams.ADAPTER.encodedSizeWithTag(2, value.params) : 0)
|
||||
+ images.encodedSizeWithTag(3, value.images)
|
||||
+ SpriteEntity.ADAPTER.asRepeated().encodedSizeWithTag(4, value.sprites)
|
||||
+ AudioEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.audios)
|
||||
+ value.unknownFields().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ProtoWriter writer, MovieEntity value) throws IOException {
|
||||
if (value.version != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.version);
|
||||
if (value.params != null) MovieParams.ADAPTER.encodeWithTag(writer, 2, value.params);
|
||||
images.encodeWithTag(writer, 3, value.images);
|
||||
SpriteEntity.ADAPTER.asRepeated().encodeWithTag(writer, 4, value.sprites);
|
||||
AudioEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.audios);
|
||||
writer.writeBytes(value.unknownFields());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovieEntity decode(ProtoReader reader) throws IOException {
|
||||
Builder builder = new Builder();
|
||||
long token = reader.beginMessage();
|
||||
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||
switch (tag) {
|
||||
case 1: builder.version(ProtoAdapter.STRING.decode(reader)); break;
|
||||
case 2: builder.params(MovieParams.ADAPTER.decode(reader)); break;
|
||||
case 3: builder.images.putAll(images.decode(reader)); break;
|
||||
case 4: builder.sprites.add(SpriteEntity.ADAPTER.decode(reader)); break;
|
||||
case 5: builder.audios.add(AudioEntity.ADAPTER.decode(reader)); break;
|
||||
default: {
|
||||
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||
builder.addUnknownField(tag, fieldEncoding, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endMessage(token);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovieEntity redact(MovieEntity value) {
|
||||
Builder builder = value.newBuilder();
|
||||
if (builder.params != null) builder.params = MovieParams.ADAPTER.redact(builder.params);
|
||||
Internal.redactElements(builder.sprites, SpriteEntity.ADAPTER);
|
||||
Internal.redactElements(builder.audios, AudioEntity.ADAPTER);
|
||||
builder.clearUnknownFields();
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||
// Source file: svga.proto at 6:1
|
||||
package com.opensource.svgaplayer.proto;
|
||||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
import com.squareup.wire.Message;
|
||||
import com.squareup.wire.ProtoAdapter;
|
||||
import com.squareup.wire.ProtoReader;
|
||||
import com.squareup.wire.ProtoWriter;
|
||||
import com.squareup.wire.WireField;
|
||||
import com.squareup.wire.internal.Internal;
|
||||
import java.io.IOException;
|
||||
import java.lang.Float;
|
||||
import java.lang.Integer;
|
||||
import java.lang.Object;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import java.lang.StringBuilder;
|
||||
import okio.ByteString;
|
||||
|
||||
public final class MovieParams extends Message<MovieParams, MovieParams.Builder> {
|
||||
public static final ProtoAdapter<MovieParams> ADAPTER = new ProtoAdapter_MovieParams();
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
public static final Float DEFAULT_VIEWBOXWIDTH = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_VIEWBOXHEIGHT = 0.0f;
|
||||
|
||||
public static final Integer DEFAULT_FPS = 0;
|
||||
|
||||
public static final Integer DEFAULT_FRAMES = 0;
|
||||
|
||||
/**
|
||||
* 画布宽
|
||||
*/
|
||||
@WireField(
|
||||
tag = 1,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float viewBoxWidth;
|
||||
|
||||
/**
|
||||
* 画布高
|
||||
*/
|
||||
@WireField(
|
||||
tag = 2,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float viewBoxHeight;
|
||||
|
||||
/**
|
||||
* 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
|
||||
*/
|
||||
@WireField(
|
||||
tag = 3,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||
)
|
||||
public final Integer fps;
|
||||
|
||||
/**
|
||||
* 动画总帧数
|
||||
*/
|
||||
@WireField(
|
||||
tag = 4,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||
)
|
||||
public final Integer frames;
|
||||
|
||||
public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames) {
|
||||
this(viewBoxWidth, viewBoxHeight, fps, frames, ByteString.EMPTY);
|
||||
}
|
||||
|
||||
public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames, ByteString unknownFields) {
|
||||
super(ADAPTER, unknownFields);
|
||||
this.viewBoxWidth = viewBoxWidth;
|
||||
this.viewBoxHeight = viewBoxHeight;
|
||||
this.fps = fps;
|
||||
this.frames = frames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder newBuilder() {
|
||||
Builder builder = new Builder();
|
||||
builder.viewBoxWidth = viewBoxWidth;
|
||||
builder.viewBoxHeight = viewBoxHeight;
|
||||
builder.fps = fps;
|
||||
builder.frames = frames;
|
||||
builder.addUnknownFields(unknownFields());
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof MovieParams)) return false;
|
||||
MovieParams o = (MovieParams) other;
|
||||
return unknownFields().equals(o.unknownFields())
|
||||
&& Internal.equals(viewBoxWidth, o.viewBoxWidth)
|
||||
&& Internal.equals(viewBoxHeight, o.viewBoxHeight)
|
||||
&& Internal.equals(fps, o.fps)
|
||||
&& Internal.equals(frames, o.frames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode;
|
||||
if (result == 0) {
|
||||
result = unknownFields().hashCode();
|
||||
result = result * 37 + (viewBoxWidth != null ? viewBoxWidth.hashCode() : 0);
|
||||
result = result * 37 + (viewBoxHeight != null ? viewBoxHeight.hashCode() : 0);
|
||||
result = result * 37 + (fps != null ? fps.hashCode() : 0);
|
||||
result = result * 37 + (frames != null ? frames.hashCode() : 0);
|
||||
super.hashCode = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (viewBoxWidth != null) builder.append(", viewBoxWidth=").append(viewBoxWidth);
|
||||
if (viewBoxHeight != null) builder.append(", viewBoxHeight=").append(viewBoxHeight);
|
||||
if (fps != null) builder.append(", fps=").append(fps);
|
||||
if (frames != null) builder.append(", frames=").append(frames);
|
||||
return builder.replace(0, 2, "MovieParams{").append('}').toString();
|
||||
}
|
||||
|
||||
public static final class Builder extends Message.Builder<MovieParams, Builder> {
|
||||
public Float viewBoxWidth;
|
||||
|
||||
public Float viewBoxHeight;
|
||||
|
||||
public Integer fps;
|
||||
|
||||
public Integer frames;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 画布宽
|
||||
*/
|
||||
public Builder viewBoxWidth(Float viewBoxWidth) {
|
||||
this.viewBoxWidth = viewBoxWidth;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 画布高
|
||||
*/
|
||||
public Builder viewBoxHeight(Float viewBoxHeight) {
|
||||
this.viewBoxHeight = viewBoxHeight;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
|
||||
*/
|
||||
public Builder fps(Integer fps) {
|
||||
this.fps = fps;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画总帧数
|
||||
*/
|
||||
public Builder frames(Integer frames) {
|
||||
this.frames = frames;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovieParams build() {
|
||||
return new MovieParams(viewBoxWidth, viewBoxHeight, fps, frames, super.buildUnknownFields());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProtoAdapter_MovieParams extends ProtoAdapter<MovieParams> {
|
||||
ProtoAdapter_MovieParams() {
|
||||
super(FieldEncoding.LENGTH_DELIMITED, MovieParams.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodedSize(MovieParams value) {
|
||||
return (value.viewBoxWidth != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.viewBoxWidth) : 0)
|
||||
+ (value.viewBoxHeight != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.viewBoxHeight) : 0)
|
||||
+ (value.fps != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.fps) : 0)
|
||||
+ (value.frames != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.frames) : 0)
|
||||
+ value.unknownFields().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ProtoWriter writer, MovieParams value) throws IOException {
|
||||
if (value.viewBoxWidth != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.viewBoxWidth);
|
||||
if (value.viewBoxHeight != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.viewBoxHeight);
|
||||
if (value.fps != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.fps);
|
||||
if (value.frames != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.frames);
|
||||
writer.writeBytes(value.unknownFields());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovieParams decode(ProtoReader reader) throws IOException {
|
||||
Builder builder = new Builder();
|
||||
long token = reader.beginMessage();
|
||||
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||
switch (tag) {
|
||||
case 1: builder.viewBoxWidth(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 2: builder.viewBoxHeight(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 3: builder.fps(ProtoAdapter.INT32.decode(reader)); break;
|
||||
case 4: builder.frames(ProtoAdapter.INT32.decode(reader)); break;
|
||||
default: {
|
||||
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||
builder.addUnknownField(tag, fieldEncoding, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endMessage(token);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovieParams redact(MovieParams value) {
|
||||
Builder builder = value.newBuilder();
|
||||
builder.clearUnknownFields();
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,202 @@
|
||||
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||
// Source file: svga.proto at 13:1
|
||||
package com.opensource.svgaplayer.proto;
|
||||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
import com.squareup.wire.Message;
|
||||
import com.squareup.wire.ProtoAdapter;
|
||||
import com.squareup.wire.ProtoReader;
|
||||
import com.squareup.wire.ProtoWriter;
|
||||
import com.squareup.wire.WireField;
|
||||
import com.squareup.wire.internal.Internal;
|
||||
import java.io.IOException;
|
||||
import java.lang.Object;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import java.lang.StringBuilder;
|
||||
import java.util.List;
|
||||
import okio.ByteString;
|
||||
|
||||
public final class SpriteEntity extends Message<SpriteEntity, SpriteEntity.Builder> {
|
||||
public static final ProtoAdapter<SpriteEntity> ADAPTER = new ProtoAdapter_SpriteEntity();
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
public static final String DEFAULT_IMAGEKEY = "";
|
||||
|
||||
public static final String DEFAULT_MATTEKEY = "";
|
||||
|
||||
/**
|
||||
* 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。
|
||||
*/
|
||||
@WireField(
|
||||
tag = 1,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||
)
|
||||
public final String imageKey;
|
||||
|
||||
/**
|
||||
* 帧列表
|
||||
*/
|
||||
@WireField(
|
||||
tag = 2,
|
||||
adapter = "com.opensource.svgaplayer.proto.FrameEntity#ADAPTER",
|
||||
label = WireField.Label.REPEATED
|
||||
)
|
||||
public final List<FrameEntity> frames;
|
||||
|
||||
/**
|
||||
* 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey.
|
||||
*/
|
||||
@WireField(
|
||||
tag = 3,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||
)
|
||||
public final String matteKey;
|
||||
|
||||
public SpriteEntity(String imageKey, List<FrameEntity> frames, String matteKey) {
|
||||
this(imageKey, frames, matteKey, ByteString.EMPTY);
|
||||
}
|
||||
|
||||
public SpriteEntity(String imageKey, List<FrameEntity> frames, String matteKey, ByteString unknownFields) {
|
||||
super(ADAPTER, unknownFields);
|
||||
this.imageKey = imageKey;
|
||||
this.frames = Internal.immutableCopyOf("frames", frames);
|
||||
this.matteKey = matteKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder newBuilder() {
|
||||
Builder builder = new Builder();
|
||||
builder.imageKey = imageKey;
|
||||
builder.frames = Internal.copyOf("frames", frames);
|
||||
builder.matteKey = matteKey;
|
||||
builder.addUnknownFields(unknownFields());
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof SpriteEntity)) return false;
|
||||
SpriteEntity o = (SpriteEntity) other;
|
||||
return unknownFields().equals(o.unknownFields())
|
||||
&& Internal.equals(imageKey, o.imageKey)
|
||||
&& frames.equals(o.frames)
|
||||
&& Internal.equals(matteKey, o.matteKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode;
|
||||
if (result == 0) {
|
||||
result = unknownFields().hashCode();
|
||||
result = result * 37 + (imageKey != null ? imageKey.hashCode() : 0);
|
||||
result = result * 37 + frames.hashCode();
|
||||
result = result * 37 + (matteKey != null ? matteKey.hashCode() : 0);
|
||||
super.hashCode = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (imageKey != null) builder.append(", imageKey=").append(imageKey);
|
||||
if (!frames.isEmpty()) builder.append(", frames=").append(frames);
|
||||
if (matteKey != null) builder.append(", matteKey=").append(matteKey);
|
||||
return builder.replace(0, 2, "SpriteEntity{").append('}').toString();
|
||||
}
|
||||
|
||||
public static final class Builder extends Message.Builder<SpriteEntity, Builder> {
|
||||
public String imageKey;
|
||||
|
||||
public List<FrameEntity> frames;
|
||||
|
||||
public String matteKey;
|
||||
|
||||
public Builder() {
|
||||
frames = Internal.newMutableList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。
|
||||
*/
|
||||
public Builder imageKey(String imageKey) {
|
||||
this.imageKey = imageKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 帧列表
|
||||
*/
|
||||
public Builder frames(List<FrameEntity> frames) {
|
||||
Internal.checkElementsNotNull(frames);
|
||||
this.frames = frames;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey.
|
||||
*/
|
||||
public Builder matteKey(String matteKey) {
|
||||
this.matteKey = matteKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpriteEntity build() {
|
||||
return new SpriteEntity(imageKey, frames, matteKey, super.buildUnknownFields());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProtoAdapter_SpriteEntity extends ProtoAdapter<SpriteEntity> {
|
||||
ProtoAdapter_SpriteEntity() {
|
||||
super(FieldEncoding.LENGTH_DELIMITED, SpriteEntity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodedSize(SpriteEntity value) {
|
||||
return (value.imageKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.imageKey) : 0)
|
||||
+ FrameEntity.ADAPTER.asRepeated().encodedSizeWithTag(2, value.frames)
|
||||
+ (value.matteKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(3, value.matteKey) : 0)
|
||||
+ value.unknownFields().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ProtoWriter writer, SpriteEntity value) throws IOException {
|
||||
if (value.imageKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.imageKey);
|
||||
FrameEntity.ADAPTER.asRepeated().encodeWithTag(writer, 2, value.frames);
|
||||
if (value.matteKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 3, value.matteKey);
|
||||
writer.writeBytes(value.unknownFields());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpriteEntity decode(ProtoReader reader) throws IOException {
|
||||
Builder builder = new Builder();
|
||||
long token = reader.beginMessage();
|
||||
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||
switch (tag) {
|
||||
case 1: builder.imageKey(ProtoAdapter.STRING.decode(reader)); break;
|
||||
case 2: builder.frames.add(FrameEntity.ADAPTER.decode(reader)); break;
|
||||
case 3: builder.matteKey(ProtoAdapter.STRING.decode(reader)); break;
|
||||
default: {
|
||||
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||
builder.addUnknownField(tag, fieldEncoding, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endMessage(token);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpriteEntity redact(SpriteEntity value) {
|
||||
Builder builder = value.newBuilder();
|
||||
Internal.redactElements(builder.frames, FrameEntity.ADAPTER);
|
||||
builder.clearUnknownFields();
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||
// Source file: svga.proto at 34:1
|
||||
package com.opensource.svgaplayer.proto;
|
||||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
import com.squareup.wire.Message;
|
||||
import com.squareup.wire.ProtoAdapter;
|
||||
import com.squareup.wire.ProtoReader;
|
||||
import com.squareup.wire.ProtoWriter;
|
||||
import com.squareup.wire.WireField;
|
||||
import com.squareup.wire.internal.Internal;
|
||||
import java.io.IOException;
|
||||
import java.lang.Float;
|
||||
import java.lang.Object;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import java.lang.StringBuilder;
|
||||
import okio.ByteString;
|
||||
|
||||
public final class Transform extends Message<Transform, Transform.Builder> {
|
||||
public static final ProtoAdapter<Transform> ADAPTER = new ProtoAdapter_Transform();
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
public static final Float DEFAULT_A = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_B = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_C = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_D = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_TX = 0.0f;
|
||||
|
||||
public static final Float DEFAULT_TY = 0.0f;
|
||||
|
||||
@WireField(
|
||||
tag = 1,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float a;
|
||||
|
||||
@WireField(
|
||||
tag = 2,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float b;
|
||||
|
||||
@WireField(
|
||||
tag = 3,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float c;
|
||||
|
||||
@WireField(
|
||||
tag = 4,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float d;
|
||||
|
||||
@WireField(
|
||||
tag = 5,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float tx;
|
||||
|
||||
@WireField(
|
||||
tag = 6,
|
||||
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||
)
|
||||
public final Float ty;
|
||||
|
||||
public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty) {
|
||||
this(a, b, c, d, tx, ty, ByteString.EMPTY);
|
||||
}
|
||||
|
||||
public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty, ByteString unknownFields) {
|
||||
super(ADAPTER, unknownFields);
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
this.d = d;
|
||||
this.tx = tx;
|
||||
this.ty = ty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder newBuilder() {
|
||||
Builder builder = new Builder();
|
||||
builder.a = a;
|
||||
builder.b = b;
|
||||
builder.c = c;
|
||||
builder.d = d;
|
||||
builder.tx = tx;
|
||||
builder.ty = ty;
|
||||
builder.addUnknownFields(unknownFields());
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof Transform)) return false;
|
||||
Transform o = (Transform) other;
|
||||
return unknownFields().equals(o.unknownFields())
|
||||
&& Internal.equals(a, o.a)
|
||||
&& Internal.equals(b, o.b)
|
||||
&& Internal.equals(c, o.c)
|
||||
&& Internal.equals(d, o.d)
|
||||
&& Internal.equals(tx, o.tx)
|
||||
&& Internal.equals(ty, o.ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode;
|
||||
if (result == 0) {
|
||||
result = unknownFields().hashCode();
|
||||
result = result * 37 + (a != null ? a.hashCode() : 0);
|
||||
result = result * 37 + (b != null ? b.hashCode() : 0);
|
||||
result = result * 37 + (c != null ? c.hashCode() : 0);
|
||||
result = result * 37 + (d != null ? d.hashCode() : 0);
|
||||
result = result * 37 + (tx != null ? tx.hashCode() : 0);
|
||||
result = result * 37 + (ty != null ? ty.hashCode() : 0);
|
||||
super.hashCode = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (a != null) builder.append(", a=").append(a);
|
||||
if (b != null) builder.append(", b=").append(b);
|
||||
if (c != null) builder.append(", c=").append(c);
|
||||
if (d != null) builder.append(", d=").append(d);
|
||||
if (tx != null) builder.append(", tx=").append(tx);
|
||||
if (ty != null) builder.append(", ty=").append(ty);
|
||||
return builder.replace(0, 2, "Transform{").append('}').toString();
|
||||
}
|
||||
|
||||
public static final class Builder extends Message.Builder<Transform, Builder> {
|
||||
public Float a;
|
||||
|
||||
public Float b;
|
||||
|
||||
public Float c;
|
||||
|
||||
public Float d;
|
||||
|
||||
public Float tx;
|
||||
|
||||
public Float ty;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder a(Float a) {
|
||||
this.a = a;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder b(Float b) {
|
||||
this.b = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder c(Float c) {
|
||||
this.c = c;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder d(Float d) {
|
||||
this.d = d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tx(Float tx) {
|
||||
this.tx = tx;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder ty(Float ty) {
|
||||
this.ty = ty;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform build() {
|
||||
return new Transform(a, b, c, d, tx, ty, super.buildUnknownFields());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProtoAdapter_Transform extends ProtoAdapter<Transform> {
|
||||
ProtoAdapter_Transform() {
|
||||
super(FieldEncoding.LENGTH_DELIMITED, Transform.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodedSize(Transform value) {
|
||||
return (value.a != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.a) : 0)
|
||||
+ (value.b != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.b) : 0)
|
||||
+ (value.c != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.c) : 0)
|
||||
+ (value.d != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.d) : 0)
|
||||
+ (value.tx != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(5, value.tx) : 0)
|
||||
+ (value.ty != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(6, value.ty) : 0)
|
||||
+ value.unknownFields().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ProtoWriter writer, Transform value) throws IOException {
|
||||
if (value.a != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.a);
|
||||
if (value.b != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.b);
|
||||
if (value.c != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.c);
|
||||
if (value.d != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.d);
|
||||
if (value.tx != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 5, value.tx);
|
||||
if (value.ty != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 6, value.ty);
|
||||
writer.writeBytes(value.unknownFields());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform decode(ProtoReader reader) throws IOException {
|
||||
Builder builder = new Builder();
|
||||
long token = reader.beginMessage();
|
||||
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||
switch (tag) {
|
||||
case 1: builder.a(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 2: builder.b(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 3: builder.c(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 4: builder.d(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 5: builder.tx(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
case 6: builder.ty(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||
default: {
|
||||
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||
builder.addUnknownField(tag, fieldEncoding, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endMessage(token);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform redact(Transform value) {
|
||||
Builder builder = value.newBuilder();
|
||||
builder.clearUnknownFields();
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package com.opensource.svgaplayer.utils
|
||||
|
||||
/**
|
||||
* Helper class for creating pools of objects. An example use looks like this:
|
||||
* <pre>
|
||||
* public class MyPooledClass {
|
||||
*
|
||||
* private static final SynchronizedPool<MyPooledClass> sPool =
|
||||
* new SynchronizedPool<MyPooledClass>(10);
|
||||
*
|
||||
* public static MyPooledClass obtain() {
|
||||
* MyPooledClass instance = sPool.acquire();
|
||||
* return (instance != null) ? instance : new MyPooledClass();
|
||||
* }
|
||||
*
|
||||
* public void recycle() {
|
||||
* // Clear state if needed.
|
||||
* sPool.release(this);
|
||||
* }
|
||||
*
|
||||
* . . .
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
class Pools private constructor() {
|
||||
|
||||
/**
|
||||
* Interface for managing a pool of objects.
|
||||
*
|
||||
* @param <T> The pooled type.
|
||||
*/
|
||||
interface Pool<T> {
|
||||
/**
|
||||
* @return An instance from the pool if such, null otherwise.
|
||||
*/
|
||||
fun acquire(): T?
|
||||
|
||||
/**
|
||||
* Release an instance to the pool.
|
||||
*
|
||||
* @param instance The instance to release.
|
||||
* @return Whether the instance was put in the pool.
|
||||
*
|
||||
* @throws IllegalStateException If the instance is already in the pool.
|
||||
*/
|
||||
fun release(instance: T): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple (non-synchronized) pool of objects.
|
||||
*
|
||||
* @param maxPoolSize The max pool size.
|
||||
*
|
||||
* @throws IllegalArgumentException If the max pool size is less than zero.
|
||||
*
|
||||
* @param <T> The pooled type.
|
||||
*/
|
||||
open class SimplePool<T>(maxPoolSize: Int) : Pool<T> {
|
||||
private val mPool: Array<Any?>
|
||||
private var mPoolSize = 0
|
||||
|
||||
init {
|
||||
require(maxPoolSize > 0) { "The max pool size must be > 0" }
|
||||
mPool = arrayOfNulls(maxPoolSize)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun acquire(): T? {
|
||||
if (mPoolSize > 0) {
|
||||
val lastPooledIndex = mPoolSize - 1
|
||||
val instance = mPool[lastPooledIndex] as T?
|
||||
mPool[lastPooledIndex] = null
|
||||
mPoolSize--
|
||||
return instance
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun release(instance: T): Boolean {
|
||||
check(!isInPool(instance)) { "Already in the pool!" }
|
||||
if (mPoolSize < mPool.size) {
|
||||
mPool[mPoolSize] = instance
|
||||
mPoolSize++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isInPool(instance: T): Boolean {
|
||||
for (i in 0 until mPoolSize) {
|
||||
if (mPool[i] === instance) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package com.opensource.svgaplayer.utils
|
||||
|
||||
import android.widget.ImageView
|
||||
|
||||
/**
|
||||
* Created by ubt on 2018/1/19.
|
||||
*/
|
||||
class SVGAScaleInfo {
|
||||
|
||||
var tranFx : Float = 0.0f
|
||||
var tranFy : Float = 0.0f
|
||||
var scaleFx : Float = 1.0f
|
||||
var scaleFy : Float = 1.0f
|
||||
var ratio = 1.0f
|
||||
var ratioX = false
|
||||
|
||||
private fun resetVar(){
|
||||
tranFx = 0.0f
|
||||
tranFy = 0.0f
|
||||
scaleFx = 1.0f
|
||||
scaleFy = 1.0f
|
||||
ratio = 1.0f
|
||||
ratioX = false
|
||||
}
|
||||
|
||||
fun performScaleType(canvasWidth : Float, canvasHeight: Float, videoWidth : Float, videoHeight : Float, scaleType: ImageView.ScaleType) {
|
||||
if (canvasWidth == 0.0f || canvasHeight == 0.0f || videoWidth == 0.0f || videoHeight == 0.0f) {
|
||||
return
|
||||
}
|
||||
|
||||
resetVar()
|
||||
val canW_vidW_f = (canvasWidth - videoWidth) / 2.0f
|
||||
val canH_vidH_f = (canvasHeight - videoHeight) / 2.0f
|
||||
|
||||
val videoRatio = videoWidth / videoHeight
|
||||
val canvasRatio = canvasWidth / canvasHeight
|
||||
|
||||
val canH_d_vidH = canvasHeight / videoHeight
|
||||
val canW_d_vidW = canvasWidth / videoWidth
|
||||
|
||||
when (scaleType) {
|
||||
ImageView.ScaleType.CENTER -> {
|
||||
tranFx = canW_vidW_f
|
||||
tranFy = canH_vidH_f
|
||||
}
|
||||
ImageView.ScaleType.CENTER_CROP -> {
|
||||
if (videoRatio > canvasRatio) {
|
||||
ratio = canH_d_vidH
|
||||
ratioX = false
|
||||
scaleFx = canH_d_vidH
|
||||
scaleFy = canH_d_vidH
|
||||
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
|
||||
}
|
||||
else {
|
||||
ratio = canW_d_vidW
|
||||
ratioX = true
|
||||
scaleFx = canW_d_vidW
|
||||
scaleFy = canW_d_vidW
|
||||
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
|
||||
}
|
||||
}
|
||||
ImageView.ScaleType.CENTER_INSIDE -> {
|
||||
if (videoWidth < canvasWidth && videoHeight < canvasHeight) {
|
||||
tranFx = canW_vidW_f
|
||||
tranFy = canH_vidH_f
|
||||
}
|
||||
else {
|
||||
if (videoRatio > canvasRatio) {
|
||||
ratio = canW_d_vidW
|
||||
ratioX = true
|
||||
scaleFx = canW_d_vidW
|
||||
scaleFy = canW_d_vidW
|
||||
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
|
||||
|
||||
}
|
||||
else {
|
||||
ratio = canH_d_vidH
|
||||
ratioX = false
|
||||
scaleFx = canH_d_vidH
|
||||
scaleFy = canH_d_vidH
|
||||
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
|
||||
}
|
||||
}
|
||||
}
|
||||
ImageView.ScaleType.FIT_CENTER -> {
|
||||
if (videoRatio > canvasRatio) {
|
||||
ratio = canW_d_vidW
|
||||
ratioX = true
|
||||
scaleFx = canW_d_vidW
|
||||
scaleFy = canW_d_vidW
|
||||
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
|
||||
}
|
||||
else {
|
||||
ratio = canH_d_vidH
|
||||
ratioX = false
|
||||
scaleFx = canH_d_vidH
|
||||
scaleFy = canH_d_vidH
|
||||
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
|
||||
}
|
||||
}
|
||||
ImageView.ScaleType.FIT_START -> {
|
||||
if (videoRatio > canvasRatio) {
|
||||
ratio = canW_d_vidW
|
||||
ratioX = true
|
||||
scaleFx = canW_d_vidW
|
||||
scaleFy = canW_d_vidW
|
||||
}
|
||||
else {
|
||||
ratio = canH_d_vidH
|
||||
ratioX = false
|
||||
scaleFx = canH_d_vidH
|
||||
scaleFy = canH_d_vidH
|
||||
}
|
||||
}
|
||||
ImageView.ScaleType.FIT_END -> {
|
||||
if (videoRatio > canvasRatio) {
|
||||
ratio = canW_d_vidW
|
||||
ratioX = true
|
||||
scaleFx = canW_d_vidW
|
||||
scaleFy = canW_d_vidW
|
||||
tranFy= canvasHeight - videoHeight * (canW_d_vidW)
|
||||
}
|
||||
else {
|
||||
ratio = canH_d_vidH
|
||||
ratioX = false
|
||||
scaleFx = canH_d_vidH
|
||||
scaleFy = canH_d_vidH
|
||||
tranFx = canvasWidth - videoWidth * (canH_d_vidH)
|
||||
}
|
||||
}
|
||||
ImageView.ScaleType.FIT_XY -> {
|
||||
ratio = Math.max(canW_d_vidW, canH_d_vidH)
|
||||
ratioX = canW_d_vidW > canH_d_vidH
|
||||
scaleFx = canW_d_vidW
|
||||
scaleFy = canH_d_vidH
|
||||
}
|
||||
else -> {
|
||||
ratio = canW_d_vidW
|
||||
ratioX = true
|
||||
scaleFx = canW_d_vidW
|
||||
scaleFy = canW_d_vidW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.opensource.svgaplayer.utils
|
||||
|
||||
/**
|
||||
* Created by cuiminghui on 2017/3/29.
|
||||
*/
|
||||
|
||||
class SVGAPoint(val x: Float, val y: Float, val value: Float)
|
||||
|
||||
class SVGARect(val x: Double, val y: Double, val width: Double, val height: Double)
|
||||
|
||||
class SVGARange(val location: Int, val length: Int)
|
@ -0,0 +1,28 @@
|
||||
package com.opensource.svgaplayer.utils.log
|
||||
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* 内部默认 ILogger 接口实现
|
||||
*/
|
||||
class DefaultLogCat : ILogger {
|
||||
override fun verbose(tag: String, msg: String) {
|
||||
Log.v(tag, msg)
|
||||
}
|
||||
|
||||
override fun info(tag: String, msg: String) {
|
||||
Log.i(tag, msg)
|
||||
}
|
||||
|
||||
override fun debug(tag: String, msg: String) {
|
||||
Log.d(tag, msg)
|
||||
}
|
||||
|
||||
override fun warn(tag: String, msg: String) {
|
||||
Log.w(tag, msg)
|
||||
}
|
||||
|
||||
override fun error(tag: String, msg: String?, error: Throwable?) {
|
||||
Log.e(tag, msg, error)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.opensource.svgaplayer.utils.log
|
||||
|
||||
/**
|
||||
* log 外部接管接口
|
||||
**/
|
||||
interface ILogger {
|
||||
fun verbose(tag: String, msg: String)
|
||||
fun info(tag: String, msg: String)
|
||||
fun debug(tag: String, msg: String)
|
||||
fun warn(tag: String, msg: String)
|
||||
fun error(tag: String, msg: String?, error: Throwable?)
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.opensource.svgaplayer.utils.log
|
||||
|
||||
/**
|
||||
* 日志输出
|
||||
*/
|
||||
internal object LogUtils {
|
||||
private const val TAG = "SVGALog"
|
||||
|
||||
fun verbose(tag: String = TAG, msg: String) {
|
||||
if (!SVGALogger.isLogEnabled()) {
|
||||
return
|
||||
}
|
||||
SVGALogger.getSVGALogger()?.verbose(tag, msg)
|
||||
}
|
||||
|
||||
fun info(tag: String = TAG, msg: String) {
|
||||
if (!SVGALogger.isLogEnabled()) {
|
||||
return
|
||||
}
|
||||
SVGALogger.getSVGALogger()?.info(tag, msg)
|
||||
}
|
||||
|
||||
fun debug(tag: String = TAG, msg: String) {
|
||||
if (!SVGALogger.isLogEnabled()) {
|
||||
return
|
||||
}
|
||||
SVGALogger.getSVGALogger()?.debug(tag, msg)
|
||||
}
|
||||
|
||||
fun warn(tag: String = TAG, msg: String) {
|
||||
if (!SVGALogger.isLogEnabled()) {
|
||||
return
|
||||
}
|
||||
SVGALogger.getSVGALogger()?.warn(tag, msg)
|
||||
}
|
||||
|
||||
fun error(tag: String = TAG, msg: String) {
|
||||
if (!SVGALogger.isLogEnabled()) {
|
||||
return
|
||||
}
|
||||
SVGALogger.getSVGALogger()?.error(tag, msg, null)
|
||||
}
|
||||
|
||||
fun error(tag: String, error: Throwable) {
|
||||
if (!SVGALogger.isLogEnabled()) {
|
||||
return
|
||||
}
|
||||
SVGALogger.getSVGALogger()?.error(tag, error.message, error)
|
||||
}
|
||||
|
||||
fun error(tag: String = TAG, msg: String, error: Throwable) {
|
||||
if (!SVGALogger.isLogEnabled()) {
|
||||
return
|
||||
}
|
||||
SVGALogger.getSVGALogger()?.error(tag, msg, error)
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.opensource.svgaplayer.utils.log
|
||||
|
||||
/**
|
||||
* SVGA logger 配置管理
|
||||
**/
|
||||
object SVGALogger {
|
||||
|
||||
private var mLogger: ILogger? = DefaultLogCat()
|
||||
private var isLogEnabled = false
|
||||
|
||||
/**
|
||||
* log 接管注入
|
||||
*/
|
||||
fun injectSVGALoggerImp(logImp: ILogger): SVGALogger {
|
||||
mLogger = logImp
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否开启 log
|
||||
*/
|
||||
fun setLogEnabled(isEnabled: Boolean): SVGALogger {
|
||||
isLogEnabled = isEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 ILogger 实现类
|
||||
*/
|
||||
fun getSVGALogger(): ILogger? {
|
||||
return mLogger
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否开启 log
|
||||
*/
|
||||
fun isLogEnabled(): Boolean {
|
||||
return isLogEnabled
|
||||
}
|
||||
}
|
16
SVGAlibrary/src/main/res/values/attrs.xml
Normal file
16
SVGAlibrary/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="SVGAImageView">
|
||||
<attr name="source" format="string" />
|
||||
<attr name="autoPlay" format="boolean" />
|
||||
<attr name="antiAlias" format="boolean" />
|
||||
<attr name="loopCount" format="integer" />
|
||||
<attr name="clearsAfterStop" format="boolean" />
|
||||
<attr name="clearsAfterDetached" format="boolean" />
|
||||
<attr name="fillMode" format="enum">
|
||||
<enum name="Backward" value="0" />
|
||||
<enum name="Forward" value="1" />
|
||||
<enum name="Clear" value="2"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
3
SVGAlibrary/src/main/res/values/strings.xml
Normal file
3
SVGAlibrary/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">SVGAPlayer</string>
|
||||
</resources>
|
1
TabLayout/.gitignore
vendored
Normal file
1
TabLayout/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
30
TabLayout/build.gradle
Normal file
30
TabLayout/build.gradle
Normal file
@ -0,0 +1,30 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode rootProject.ext.android.versionCode
|
||||
versionName rootProject.ext.android.versionName
|
||||
manifestPlaceholders = rootProject.ext.manifestPlaceholders
|
||||
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
}
|
||||
|
||||
//apply from: "$gradleHost/master/publish.gradle"
|
0
TabLayout/consumer-rules.pro
Normal file
0
TabLayout/consumer-rules.pro
Normal file
21
TabLayout/proguard-rules.pro
vendored
Normal file
21
TabLayout/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
2
TabLayout/src/main/AndroidManifest.xml
Normal file
2
TabLayout/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.angcyo.tablayout" />
|
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
@ -0,0 +1,200 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextPaint
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.core.view.ViewCompat
|
||||
|
||||
/**
|
||||
* 基础自绘Drawable
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/11/25
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
|
||||
abstract class AbsDslDrawable : Drawable() {
|
||||
|
||||
companion object {
|
||||
/**不绘制*/
|
||||
const val DRAW_TYPE_DRAW_NONE = 0x00
|
||||
|
||||
/**[android.view.View.draw]*/
|
||||
const val DRAW_TYPE_DRAW_AFTER = 0x01
|
||||
const val DRAW_TYPE_DRAW_BEFORE = 0x02
|
||||
|
||||
/**[android.view.View.onDraw]*/
|
||||
const val DRAW_TYPE_ON_DRAW_AFTER = 0x04
|
||||
const val DRAW_TYPE_ON_DRAW_BEFORE = 0x08
|
||||
}
|
||||
|
||||
/**画笔*/
|
||||
val textPaint: TextPaint by lazy {
|
||||
TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
isFilterBitmap = true
|
||||
style = Paint.Style.FILL
|
||||
textSize = 12 * dp
|
||||
}
|
||||
}
|
||||
|
||||
val drawRect = Rect()
|
||||
val drawRectF = RectF()
|
||||
|
||||
/**需要在那个方法中触发绘制*/
|
||||
var drawType = DRAW_TYPE_ON_DRAW_AFTER
|
||||
|
||||
/**xml属性读取*/
|
||||
open fun initAttribute(
|
||||
context: Context,
|
||||
attributeSet: AttributeSet? = null
|
||||
) {
|
||||
//val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.xxx)
|
||||
//typedArray.recycle()
|
||||
}
|
||||
|
||||
//<editor-fold desc="View相关方法">
|
||||
|
||||
/**附着的[View]*/
|
||||
val attachView: View?
|
||||
get() = if (callback is View) callback as? View else null
|
||||
|
||||
val isInEditMode: Boolean
|
||||
get() = attachView?.isInEditMode ?: false
|
||||
val paddingLeft: Int
|
||||
get() = attachView?.paddingLeft ?: 0
|
||||
val paddingRight: Int
|
||||
get() = attachView?.paddingRight ?: 0
|
||||
val paddingTop: Int
|
||||
get() = attachView?.paddingTop ?: 0
|
||||
val paddingBottom: Int
|
||||
get() = attachView?.paddingBottom ?: 0
|
||||
val viewHeight: Int
|
||||
get() = attachView?.measuredHeight ?: 0
|
||||
val viewWidth: Int
|
||||
get() = attachView?.measuredWidth ?: 0
|
||||
val viewDrawHeight: Int
|
||||
get() = viewHeight - paddingTop - paddingBottom
|
||||
val viewDrawWidth: Int
|
||||
get() = viewWidth - paddingLeft - paddingRight
|
||||
|
||||
val isViewRtl: Boolean
|
||||
get() = attachView != null && ViewCompat.getLayoutDirection(attachView!!) == ViewCompat.LAYOUT_DIRECTION_RTL
|
||||
|
||||
//</editor-fold desc="View相关方法">
|
||||
|
||||
//<editor-fold desc="基类方法">
|
||||
|
||||
/**核心方法, 绘制*/
|
||||
override fun draw(canvas: Canvas) {
|
||||
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return super.getIntrinsicWidth()
|
||||
}
|
||||
|
||||
override fun getMinimumWidth(): Int {
|
||||
return super.getMinimumWidth()
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
return super.getIntrinsicHeight()
|
||||
}
|
||||
|
||||
override fun getMinimumHeight(): Int {
|
||||
return super.getMinimumHeight()
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
if (textPaint.alpha != alpha) {
|
||||
textPaint.alpha = alpha
|
||||
invalidateSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAlpha(): Int {
|
||||
return textPaint.alpha
|
||||
}
|
||||
|
||||
override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.setBounds(left, top, right, bottom)
|
||||
}
|
||||
|
||||
override fun setBounds(bounds: Rect) {
|
||||
super.setBounds(bounds)
|
||||
}
|
||||
|
||||
//不透明度
|
||||
override fun getOpacity(): Int {
|
||||
return if (alpha < 255) PixelFormat.TRANSLUCENT else PixelFormat.OPAQUE
|
||||
}
|
||||
|
||||
override fun getColorFilter(): ColorFilter? {
|
||||
return textPaint.colorFilter
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
textPaint.colorFilter = colorFilter
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
override fun mutate(): Drawable {
|
||||
return super.mutate()
|
||||
}
|
||||
|
||||
override fun setDither(dither: Boolean) {
|
||||
textPaint.isDither = dither
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
override fun setFilterBitmap(filter: Boolean) {
|
||||
textPaint.isFilterBitmap = filter
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
override fun isFilterBitmap(): Boolean {
|
||||
return textPaint.isFilterBitmap
|
||||
}
|
||||
|
||||
override fun onBoundsChange(bounds: Rect) {
|
||||
super.onBoundsChange(bounds)
|
||||
}
|
||||
|
||||
override fun onLevelChange(level: Int): Boolean {
|
||||
return super.onLevelChange(level)
|
||||
}
|
||||
|
||||
override fun onStateChange(state: IntArray): Boolean {
|
||||
return super.onStateChange(state)
|
||||
}
|
||||
|
||||
override fun setTintList(tint: ColorStateList?) {
|
||||
super.setTintList(tint)
|
||||
}
|
||||
|
||||
override fun setTintMode(tintMode: PorterDuff.Mode?) {
|
||||
super.setTintMode(tintMode)
|
||||
}
|
||||
|
||||
override fun setTintBlendMode(blendMode: BlendMode?) {
|
||||
super.setTintBlendMode(blendMode)
|
||||
}
|
||||
|
||||
override fun setHotspot(x: Float, y: Float) {
|
||||
super.setHotspot(x, y)
|
||||
}
|
||||
|
||||
override fun setHotspotBounds(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.setHotspotBounds(left, top, right, bottom)
|
||||
}
|
||||
|
||||
override fun getHotspotBounds(outRect: Rect) {
|
||||
super.getHotspotBounds(outRect)
|
||||
}
|
||||
|
||||
//</editor-fold desc="基类方法">
|
||||
}
|
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
@ -0,0 +1,275 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* 未读数, 未读小红点, 角标绘制Drawable
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/12/13
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
open class DslBadgeDrawable : DslGradientDrawable() {
|
||||
|
||||
val dslGravity = DslGravity()
|
||||
|
||||
/**重力*/
|
||||
var badgeGravity: Int = Gravity.CENTER //Gravity.TOP or Gravity.RIGHT
|
||||
|
||||
/**角标文本颜色*/
|
||||
var badgeTextColor = Color.WHITE
|
||||
|
||||
/**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
|
||||
* null 不绘制角标
|
||||
* "" 空字符绘制圆点
|
||||
* 其他 正常绘制
|
||||
* */
|
||||
var badgeText: String? = null
|
||||
|
||||
/**角标的文本大小*/
|
||||
var badgeTextSize: Float = 12 * dp
|
||||
set(value) {
|
||||
field = value
|
||||
textPaint.textSize = field
|
||||
}
|
||||
|
||||
/**当[badgeText]只有1个字符时, 使用圆形背景*/
|
||||
var badgeAutoCircle: Boolean = true
|
||||
|
||||
/**圆点状态时的半径大小*/
|
||||
var badgeCircleRadius = 4 * dpi
|
||||
|
||||
/**原点状态下, 单独配置的偏移*/
|
||||
var badgeCircleOffsetX: Int = 0
|
||||
var badgeCircleOffsetY: Int = 0
|
||||
|
||||
/**额外偏移距离, 会根据[Gravity]自动取负值*/
|
||||
var badgeOffsetX: Int = 0
|
||||
var badgeOffsetY: Int = 0
|
||||
|
||||
/**文本偏移*/
|
||||
var badgeTextOffsetX: Int = 0
|
||||
var badgeTextOffsetY: Int = 0
|
||||
|
||||
/**圆点状态时无效*/
|
||||
var badgePaddingLeft = 0
|
||||
var badgePaddingRight = 0
|
||||
var badgePaddingTop = 0
|
||||
var badgePaddingBottom = 0
|
||||
|
||||
/**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
|
||||
var badgeMinHeight = -2
|
||||
|
||||
/**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
|
||||
* -1 表示使用使用计算出来的高度值*/
|
||||
var badgeMinWidth = -2
|
||||
|
||||
//计算属性
|
||||
val textWidth: Float
|
||||
get() = textPaint.textWidth(badgeText)
|
||||
|
||||
//最大的宽度
|
||||
val maxWidth: Int
|
||||
get() = max(
|
||||
textWidth.toInt(),
|
||||
originDrawable?.minimumWidth ?: 0
|
||||
) + badgePaddingLeft + badgePaddingRight
|
||||
|
||||
//最大的高度
|
||||
val maxHeight: Int
|
||||
get() = max(
|
||||
textHeight.toInt(),
|
||||
originDrawable?.minimumHeight ?: 0
|
||||
) + badgePaddingTop + badgePaddingBottom
|
||||
|
||||
val textHeight: Float
|
||||
get() = textPaint.textHeight()
|
||||
|
||||
//原型状态
|
||||
val isCircle: Boolean
|
||||
get() = TextUtils.isEmpty(badgeText)
|
||||
|
||||
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
||||
super.initAttribute(context, attributeSet)
|
||||
updateOriginDrawable()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
//super.draw(canvas)
|
||||
|
||||
if (badgeText == null) {
|
||||
return
|
||||
}
|
||||
|
||||
with(dslGravity) {
|
||||
gravity = if (isViewRtl) {
|
||||
when (badgeGravity) {
|
||||
Gravity.RIGHT -> {
|
||||
Gravity.LEFT
|
||||
}
|
||||
Gravity.LEFT -> {
|
||||
Gravity.RIGHT
|
||||
}
|
||||
else -> {
|
||||
badgeGravity
|
||||
}
|
||||
}
|
||||
} else {
|
||||
badgeGravity
|
||||
}
|
||||
|
||||
setGravityBounds(bounds)
|
||||
|
||||
if (isCircle) {
|
||||
gravityOffsetX = badgeCircleOffsetX
|
||||
gravityOffsetY = badgeCircleOffsetY
|
||||
} else {
|
||||
gravityOffsetX = badgeOffsetX
|
||||
gravityOffsetY = badgeOffsetY
|
||||
}
|
||||
|
||||
val textWidth = textPaint.textWidth(badgeText)
|
||||
val textHeight = textPaint.textHeight()
|
||||
|
||||
val drawHeight = if (isCircle) {
|
||||
badgeCircleRadius.toFloat()
|
||||
} else {
|
||||
val height = textHeight + badgePaddingTop + badgePaddingBottom
|
||||
if (badgeMinHeight > 0) {
|
||||
max(height, badgeMinHeight.toFloat())
|
||||
} else {
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
val drawWidth = if (isCircle) {
|
||||
badgeCircleRadius.toFloat()
|
||||
} else {
|
||||
val width = textWidth + badgePaddingLeft + badgePaddingRight
|
||||
if (badgeMinWidth == -1) {
|
||||
max(width, drawHeight)
|
||||
} else if (badgeMinWidth > 0) {
|
||||
max(width, badgeMinWidth.toFloat())
|
||||
} else {
|
||||
width
|
||||
}
|
||||
}
|
||||
|
||||
applyGravity(drawWidth, drawHeight) { centerX, centerY ->
|
||||
|
||||
if (isCircle) {
|
||||
textPaint.color = gradientSolidColor
|
||||
|
||||
//圆心计算
|
||||
val cx: Float
|
||||
val cy: Float
|
||||
if (gravity.isCenter()) {
|
||||
cx = centerX.toFloat()
|
||||
cy = centerY.toFloat()
|
||||
} else {
|
||||
cx = centerX.toFloat() + _gravityOffsetX
|
||||
cy = centerY.toFloat() + _gravityOffsetY
|
||||
}
|
||||
|
||||
//绘制圆
|
||||
textPaint.color = gradientSolidColor
|
||||
canvas.drawCircle(
|
||||
cx,
|
||||
cy,
|
||||
badgeCircleRadius.toFloat(),
|
||||
textPaint
|
||||
)
|
||||
|
||||
//圆的描边
|
||||
if (gradientStrokeWidth > 0 && gradientStrokeColor != Color.TRANSPARENT) {
|
||||
val oldWidth = textPaint.strokeWidth
|
||||
val oldStyle = textPaint.style
|
||||
|
||||
textPaint.color = gradientStrokeColor
|
||||
textPaint.strokeWidth = gradientStrokeWidth.toFloat()
|
||||
textPaint.style = Paint.Style.STROKE
|
||||
|
||||
canvas.drawCircle(
|
||||
cx,
|
||||
cy,
|
||||
badgeCircleRadius.toFloat(),
|
||||
textPaint
|
||||
)
|
||||
|
||||
textPaint.strokeWidth = oldWidth
|
||||
textPaint.style = oldStyle
|
||||
}
|
||||
|
||||
} else {
|
||||
textPaint.color = badgeTextColor
|
||||
|
||||
val textDrawX: Float = centerX - textWidth / 2
|
||||
val textDrawY: Float = centerY + textHeight / 2
|
||||
|
||||
val bgLeft = _gravityLeft
|
||||
val bgTop = _gravityTop
|
||||
|
||||
//绘制背景
|
||||
if (badgeAutoCircle && badgeText?.length == 1) {
|
||||
if (gradientSolidColor != Color.TRANSPARENT) {
|
||||
textPaint.color = gradientSolidColor
|
||||
canvas.drawCircle(
|
||||
centerX.toFloat(),
|
||||
centerY.toFloat(),
|
||||
max(maxWidth, maxHeight).toFloat() / 2,
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
} else {
|
||||
originDrawable?.apply {
|
||||
setBounds(
|
||||
bgLeft, bgTop,
|
||||
(bgLeft + drawWidth).toInt(),
|
||||
(bgTop + drawHeight).toInt()
|
||||
)
|
||||
draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
//绘制文本
|
||||
textPaint.color = badgeTextColor
|
||||
canvas.drawText(
|
||||
badgeText!!,
|
||||
textDrawX + badgeTextOffsetX,
|
||||
textDrawY - textPaint.descent() + badgeTextOffsetY,
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
val width = if (isCircle) {
|
||||
badgeCircleRadius * 2
|
||||
} else if (badgeAutoCircle && badgeText?.length == 1) {
|
||||
max(maxWidth, maxHeight)
|
||||
} else {
|
||||
maxWidth
|
||||
}
|
||||
return max(badgeMinWidth, width)
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
val height = if (isCircle) {
|
||||
badgeCircleRadius * 2
|
||||
} else if (badgeAutoCircle && badgeText?.length == 1) {
|
||||
max(maxWidth, maxHeight)
|
||||
} else {
|
||||
maxHeight
|
||||
}
|
||||
return max(badgeMinHeight, height)
|
||||
}
|
||||
}
|
@ -0,0 +1,306 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Build
|
||||
import androidx.annotation.IntDef
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 用来构建GradientDrawable
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/11/27
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
open class DslGradientDrawable : AbsDslDrawable() {
|
||||
|
||||
/**形状*/
|
||||
@Shape
|
||||
var gradientShape = GradientDrawable.RECTANGLE
|
||||
|
||||
/**填充的颜色*/
|
||||
var gradientSolidColor = Color.TRANSPARENT
|
||||
|
||||
/**边框的颜色*/
|
||||
var gradientStrokeColor = Color.TRANSPARENT
|
||||
|
||||
/**边框的宽度*/
|
||||
var gradientStrokeWidth = 0
|
||||
|
||||
/**蚂蚁线的宽度*/
|
||||
var gradientDashWidth = 0f
|
||||
|
||||
/**蚂蚁线之间的间距*/
|
||||
var gradientDashGap = 0f
|
||||
|
||||
/**
|
||||
* 四个角, 8个设置点的圆角信息
|
||||
* 从 左上y轴->左上x轴->右上x轴->右上y轴..., 开始设置.
|
||||
*/
|
||||
var gradientRadii = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
|
||||
|
||||
/**颜色渐变*/
|
||||
var gradientColors: IntArray? = null
|
||||
var gradientColorsOffsets: FloatArray? = null
|
||||
|
||||
/**渐变中心点坐标*/
|
||||
var gradientCenterX = 0.5f
|
||||
var gradientCenterY = 0.5f
|
||||
|
||||
/**渐变半径, 非比例值, 是px值. [GradientDrawable.RADIAL_GRADIENT]类型才有效*/
|
||||
var gradientRadius = 0.5f
|
||||
|
||||
/** 渐变方向, 默认从左到右 */
|
||||
var gradientOrientation = GradientDrawable.Orientation.LEFT_RIGHT
|
||||
|
||||
/** 渐变类型 */
|
||||
@GradientType
|
||||
var gradientType = GradientDrawable.LINEAR_GRADIENT
|
||||
|
||||
/**真正绘制的[Drawable]*/
|
||||
var originDrawable: Drawable? = null
|
||||
|
||||
/**宽度补偿*/
|
||||
var gradientWidthOffset: Int = 0
|
||||
|
||||
/**高度补偿*/
|
||||
var gradientHeightOffset: Int = 0
|
||||
|
||||
/**当前的配置, 是否能生成有效的[GradientDrawable]*/
|
||||
open fun isValidConfig(): Boolean {
|
||||
return gradientSolidColor != Color.TRANSPARENT ||
|
||||
gradientStrokeColor != Color.TRANSPARENT ||
|
||||
gradientColors != null
|
||||
}
|
||||
|
||||
fun _fillRadii(array: FloatArray, radii: String?) {
|
||||
if (radii.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
val split = radii.split(",")
|
||||
if (split.size != 8) {
|
||||
throw IllegalArgumentException("radii 需要8个值.")
|
||||
} else {
|
||||
val dp = Resources.getSystem().displayMetrics.density
|
||||
for (i in split.indices) {
|
||||
array[i] = split[i].toFloat() * dp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fillRadii(radius: Float) {
|
||||
Arrays.fill(gradientRadii, radius)
|
||||
}
|
||||
|
||||
fun fillRadii(radius: Int) {
|
||||
_fillRadii(gradientRadii, radius.toFloat())
|
||||
}
|
||||
|
||||
fun _fillRadii(array: FloatArray, radius: Float) {
|
||||
Arrays.fill(array, radius)
|
||||
}
|
||||
|
||||
fun _fillRadii(array: FloatArray, radius: Int) {
|
||||
_fillRadii(array, radius.toFloat())
|
||||
}
|
||||
|
||||
fun _fillColor(colors: String?): IntArray? {
|
||||
if (colors.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
val split = colors.split(",")
|
||||
|
||||
return IntArray(split.size) {
|
||||
val str = split[it]
|
||||
if (str.startsWith("#")) {
|
||||
Color.parseColor(str)
|
||||
} else {
|
||||
str.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**构建或者更新[originDrawable]*/
|
||||
open fun updateOriginDrawable(): GradientDrawable? {
|
||||
val drawable: GradientDrawable? = when (originDrawable) {
|
||||
null -> GradientDrawable()
|
||||
is GradientDrawable -> originDrawable as GradientDrawable
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
drawable?.apply {
|
||||
bounds = this@DslGradientDrawable.bounds
|
||||
|
||||
shape = gradientShape
|
||||
setStroke(
|
||||
gradientStrokeWidth,
|
||||
gradientStrokeColor,
|
||||
gradientDashWidth,
|
||||
gradientDashGap
|
||||
)
|
||||
setColor(gradientSolidColor)
|
||||
cornerRadii = gradientRadii
|
||||
|
||||
if (gradientColors != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
setGradientCenter(
|
||||
this@DslGradientDrawable.gradientCenterX,
|
||||
this@DslGradientDrawable.gradientCenterY
|
||||
)
|
||||
}
|
||||
gradientRadius = this@DslGradientDrawable.gradientRadius
|
||||
gradientType = this@DslGradientDrawable.gradientType
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
orientation = gradientOrientation
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
setColors(gradientColors, gradientColorsOffsets)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
colors = gradientColors
|
||||
}
|
||||
}
|
||||
|
||||
originDrawable = this
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
return drawable
|
||||
}
|
||||
|
||||
open fun configDrawable(config: DslGradientDrawable.() -> Unit): DslGradientDrawable {
|
||||
this.config()
|
||||
updateOriginDrawable()
|
||||
return this
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
originDrawable?.apply {
|
||||
setBounds(
|
||||
this@DslGradientDrawable.bounds.left - gradientWidthOffset / 2,
|
||||
this@DslGradientDrawable.bounds.top - gradientHeightOffset / 2,
|
||||
this@DslGradientDrawable.bounds.right + gradientWidthOffset / 2,
|
||||
this@DslGradientDrawable.bounds.bottom + gradientHeightOffset / 2
|
||||
)
|
||||
draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
//<editor-fold desc="圆角相关配置">
|
||||
|
||||
/**
|
||||
* 4个角, 8个点 圆角配置
|
||||
*/
|
||||
fun cornerRadii(radii: FloatArray) {
|
||||
gradientRadii = radii
|
||||
}
|
||||
|
||||
fun cornerRadius(radii: Float) {
|
||||
Arrays.fill(gradientRadii, radii)
|
||||
}
|
||||
|
||||
fun cornerRadius(
|
||||
leftTop: Float = 0f,
|
||||
rightTop: Float = 0f,
|
||||
rightBottom: Float = 0f,
|
||||
leftBottom: Float = 0f
|
||||
) {
|
||||
gradientRadii[0] = leftTop
|
||||
gradientRadii[1] = leftTop
|
||||
gradientRadii[2] = rightTop
|
||||
gradientRadii[3] = rightTop
|
||||
gradientRadii[4] = rightBottom
|
||||
gradientRadii[5] = rightBottom
|
||||
gradientRadii[6] = leftBottom
|
||||
gradientRadii[7] = leftBottom
|
||||
}
|
||||
|
||||
/**
|
||||
* 只配置左边的圆角
|
||||
*/
|
||||
fun cornerRadiiLeft(radii: Float) {
|
||||
gradientRadii[0] = radii
|
||||
gradientRadii[1] = radii
|
||||
gradientRadii[6] = radii
|
||||
gradientRadii[7] = radii
|
||||
}
|
||||
|
||||
fun cornerRadiiRight(radii: Float) {
|
||||
gradientRadii[2] = radii
|
||||
gradientRadii[3] = radii
|
||||
gradientRadii[4] = radii
|
||||
gradientRadii[5] = radii
|
||||
}
|
||||
|
||||
fun cornerRadiiTop(radii: Float) {
|
||||
gradientRadii[0] = radii
|
||||
gradientRadii[1] = radii
|
||||
gradientRadii[2] = radii
|
||||
gradientRadii[3] = radii
|
||||
}
|
||||
|
||||
fun cornerRadiiBottom(radii: Float) {
|
||||
gradientRadii[4] = radii
|
||||
gradientRadii[5] = radii
|
||||
gradientRadii[6] = radii
|
||||
gradientRadii[7] = radii
|
||||
}
|
||||
|
||||
//</editor-fold desc="圆角相关配置">
|
||||
|
||||
//<editor-fold desc="传递属性">
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
super.setColorFilter(colorFilter)
|
||||
originDrawable?.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
override fun setTintList(tint: ColorStateList?) {
|
||||
super.setTintList(tint)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
originDrawable?.setTintList(tint)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setState(stateSet: IntArray): Boolean {
|
||||
return originDrawable?.setState(stateSet) ?: super.setState(stateSet)
|
||||
}
|
||||
|
||||
override fun getState(): IntArray {
|
||||
return originDrawable?.state ?: super.getState()
|
||||
}
|
||||
|
||||
//</editor-fold desc="传递属性">
|
||||
}
|
||||
|
||||
@IntDef(
|
||||
GradientDrawable.RECTANGLE,
|
||||
GradientDrawable.OVAL,
|
||||
GradientDrawable.LINE,
|
||||
GradientDrawable.RING
|
||||
)
|
||||
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
|
||||
annotation class Shape
|
||||
|
||||
@IntDef(
|
||||
GradientDrawable.LINEAR_GRADIENT,
|
||||
GradientDrawable.RADIAL_GRADIENT,
|
||||
GradientDrawable.SWEEP_GRADIENT
|
||||
)
|
||||
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
|
||||
annotation class GradientType
|
||||
|
||||
/**快速创建[GradientDrawable]*/
|
||||
fun dslGradientDrawable(action: DslGradientDrawable.() -> Unit): GradientDrawable {
|
||||
return DslGradientDrawable().run {
|
||||
action()
|
||||
updateOriginDrawable()!!
|
||||
}
|
||||
}
|
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
@ -0,0 +1,215 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.view.Gravity
|
||||
|
||||
/**
|
||||
* [android.view.Gravity] 辅助计算类
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/12/13
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
class DslGravity {
|
||||
|
||||
/**束缚范围*/
|
||||
val gravityBounds: RectF = RectF()
|
||||
|
||||
/**束缚重力*/
|
||||
var gravity: Int = Gravity.LEFT or Gravity.TOP
|
||||
|
||||
/**使用中心坐标作为定位参考, 否则就是四条边*/
|
||||
var gravityRelativeCenter: Boolean = true
|
||||
|
||||
/**额外偏移距离, 会根据[Gravity]自动取负值*/
|
||||
var gravityOffsetX: Int = 0
|
||||
var gravityOffsetY: Int = 0
|
||||
|
||||
fun setGravityBounds(rectF: RectF) {
|
||||
gravityBounds.set(rectF)
|
||||
}
|
||||
|
||||
fun setGravityBounds(rect: Rect) {
|
||||
gravityBounds.set(rect)
|
||||
}
|
||||
|
||||
fun setGravityBounds(
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int
|
||||
) {
|
||||
gravityBounds.set(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
|
||||
}
|
||||
|
||||
fun setGravityBounds(
|
||||
left: Float,
|
||||
top: Float,
|
||||
right: Float,
|
||||
bottom: Float
|
||||
) {
|
||||
gravityBounds.set(left, top, right, bottom)
|
||||
}
|
||||
|
||||
//计算后的属性
|
||||
var _horizontalGravity: Int = Gravity.LEFT
|
||||
var _verticalGravity: Int = Gravity.TOP
|
||||
var _isCenterGravity: Boolean = false
|
||||
var _targetWidth = 0f
|
||||
var _targetHeight = 0f
|
||||
var _gravityLeft = 0
|
||||
var _gravityTop = 0
|
||||
var _gravityRight = 0
|
||||
var _gravityBottom = 0
|
||||
|
||||
//根据gravity调整后的offset
|
||||
var _gravityOffsetX = 0
|
||||
var _gravityOffsetY = 0
|
||||
|
||||
/**根据[gravity]返回在[gravityBounds]中的[left] [top]位置*/
|
||||
fun applyGravity(
|
||||
width: Float = _targetWidth,
|
||||
height: Float = _targetHeight,
|
||||
callback: (centerX: Int, centerY: Int) -> Unit
|
||||
) {
|
||||
|
||||
_targetWidth = width
|
||||
_targetHeight = height
|
||||
|
||||
val layoutDirection = 0
|
||||
val absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection)
|
||||
val verticalGravity = gravity and Gravity.VERTICAL_GRAVITY_MASK
|
||||
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||
|
||||
//调整offset
|
||||
_gravityOffsetX = when (horizontalGravity) {
|
||||
Gravity.RIGHT -> -gravityOffsetX
|
||||
Gravity.END -> -gravityOffsetX
|
||||
else -> gravityOffsetX
|
||||
}
|
||||
_gravityOffsetY = when (verticalGravity) {
|
||||
Gravity.BOTTOM -> -gravityOffsetY
|
||||
else -> gravityOffsetY
|
||||
}
|
||||
|
||||
//计算居中的坐标
|
||||
val centerX = when (horizontalGravity) {
|
||||
Gravity.CENTER_HORIZONTAL -> (gravityBounds.left + gravityBounds.width() / 2 + _gravityOffsetX).toInt()
|
||||
Gravity.RIGHT -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
|
||||
Gravity.END -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
|
||||
else -> (gravityBounds.left + _gravityOffsetX + if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
|
||||
}
|
||||
|
||||
val centerY = when (verticalGravity) {
|
||||
Gravity.CENTER_VERTICAL -> (gravityBounds.top + gravityBounds.height() / 2 + _gravityOffsetY).toInt()
|
||||
Gravity.BOTTOM -> (gravityBounds.bottom + _gravityOffsetY - if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
|
||||
else -> (gravityBounds.top + _gravityOffsetY + if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
|
||||
}
|
||||
|
||||
//缓存
|
||||
_horizontalGravity = horizontalGravity
|
||||
_verticalGravity = verticalGravity
|
||||
_isCenterGravity = horizontalGravity == Gravity.CENTER_HORIZONTAL &&
|
||||
verticalGravity == Gravity.CENTER_VERTICAL
|
||||
|
||||
_gravityLeft = (centerX - _targetWidth / 2).toInt()
|
||||
_gravityRight = (centerX + _targetWidth / 2).toInt()
|
||||
_gravityTop = (centerY - _targetHeight / 2).toInt()
|
||||
_gravityBottom = (centerY + _targetHeight / 2).toInt()
|
||||
|
||||
//回调
|
||||
callback(centerX, centerY)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认计算出的是目标中心点坐标参考距离
|
||||
* [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
|
||||
* */
|
||||
fun dslGravity(
|
||||
rect: RectF, //计算的矩形
|
||||
gravity: Int, //重力
|
||||
width: Float, //放置目标的宽度
|
||||
height: Float, //放置目标的高度
|
||||
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
|
||||
offsetY: Int = 0,
|
||||
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
|
||||
): DslGravity {
|
||||
val _dslGravity = DslGravity()
|
||||
_dslGravity.setGravityBounds(rect)
|
||||
_config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
|
||||
return _dslGravity
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认计算出的是目标中心点坐标参考距离
|
||||
* [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
|
||||
* */
|
||||
fun dslGravity(
|
||||
rect: Rect, //计算的矩形
|
||||
gravity: Int, //重力
|
||||
width: Float, //放置目标的宽度
|
||||
height: Float, //放置目标的高度
|
||||
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
|
||||
offsetY: Int = 0,
|
||||
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
|
||||
): DslGravity {
|
||||
val _dslGravity = DslGravity()
|
||||
_dslGravity.setGravityBounds(rect)
|
||||
_config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
|
||||
return _dslGravity
|
||||
}
|
||||
|
||||
private fun _config(
|
||||
_dslGravity: DslGravity,
|
||||
gravity: Int, //重力
|
||||
width: Float, //放置目标的宽度
|
||||
height: Float, //放置目标的高度
|
||||
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
|
||||
offsetY: Int = 0,
|
||||
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
|
||||
) {
|
||||
_dslGravity.gravity = gravity
|
||||
_dslGravity.gravityOffsetX = offsetX
|
||||
_dslGravity.gravityOffsetY = offsetY
|
||||
_dslGravity.applyGravity(width, height) { centerX, centerY ->
|
||||
callback(_dslGravity, centerX, centerY)
|
||||
}
|
||||
}
|
||||
|
||||
/**居中*/
|
||||
fun Int.isCenter(): Boolean {
|
||||
val layoutDirection = 0
|
||||
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
|
||||
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
|
||||
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||
|
||||
return verticalGravity == Gravity.CENTER_VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL
|
||||
}
|
||||
|
||||
fun Int.isLeft(): Boolean {
|
||||
val layoutDirection = 0
|
||||
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
|
||||
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||
|
||||
return horizontalGravity == Gravity.LEFT
|
||||
}
|
||||
|
||||
fun Int.isRight(): Boolean {
|
||||
val layoutDirection = 0
|
||||
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
|
||||
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||
|
||||
return horizontalGravity == Gravity.RIGHT
|
||||
}
|
||||
|
||||
fun Int.isTop(): Boolean {
|
||||
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
|
||||
return verticalGravity == Gravity.TOP
|
||||
}
|
||||
|
||||
fun Int.isBottom(): Boolean {
|
||||
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
|
||||
return verticalGravity == Gravity.BOTTOM
|
||||
}
|
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
@ -0,0 +1,438 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
|
||||
/**
|
||||
* 用来操作[ViewGroup]中的[child], 支持单选, 多选, 拦截.
|
||||
* 操作的都是可见性为[VISIBLE]的[View]
|
||||
*
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/11/24
|
||||
*/
|
||||
|
||||
open class DslSelector {
|
||||
|
||||
var parent: ViewGroup? = null
|
||||
var dslSelectorConfig: DslSelectorConfig = DslSelectorConfig()
|
||||
|
||||
//可见view列表
|
||||
val visibleViewList: MutableList<View> = mutableListOf()
|
||||
|
||||
/**
|
||||
* 选中的索引列表
|
||||
* */
|
||||
val selectorIndexList: MutableList<Int> = mutableListOf()
|
||||
get() {
|
||||
field.clear()
|
||||
visibleViewList.forEachIndexed { index, view ->
|
||||
if (view.isSe()) {
|
||||
field.add(index)
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
/**
|
||||
* 选中的View列表
|
||||
* */
|
||||
val selectorViewList: MutableList<View> = mutableListOf()
|
||||
get() {
|
||||
field.clear()
|
||||
visibleViewList.forEachIndexed { index, view ->
|
||||
if (view.isSe() || index == dslSelectIndex) {
|
||||
field.add(view)
|
||||
}
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
//child 点击事件处理
|
||||
val _onChildClickListener = View.OnClickListener {
|
||||
val index = visibleViewList.indexOf(it)
|
||||
val select =
|
||||
if (dslSelectorConfig.dslMultiMode || dslSelectorConfig.dslMinSelectLimit < 1) {
|
||||
!it.isSe()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
if (!interceptSelector(index, select, true)) {
|
||||
selector(
|
||||
visibleViewList.indexOf(it),
|
||||
select,
|
||||
notify = true,
|
||||
fromUser = true,
|
||||
forceNotify = it is CompoundButton && dslSelectorConfig.dslMultiMode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**兼容[CompoundButton]*/
|
||||
val _onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
|
||||
buttonView.isChecked = buttonView.isSelected //恢复状态 不做任何处理, 在[OnClickListener]中处理
|
||||
/*val index = visibleViewList.indexOf(buttonView)
|
||||
|
||||
if (interceptSelector(index, isChecked, false)) {
|
||||
//拦截了此操作
|
||||
buttonView.isChecked = !isChecked //恢复状态
|
||||
}
|
||||
|
||||
val selectorViewList = selectorViewList
|
||||
val sum = selectorViewList.size
|
||||
//Limit 过滤
|
||||
if (buttonView.isChecked) {
|
||||
if (sum > dslSelectorConfig.dslMaxSelectLimit) {
|
||||
//不允许选择
|
||||
buttonView.isChecked = false //恢复状态
|
||||
}
|
||||
} else {
|
||||
//取消选择, 检查是否达到了 limit
|
||||
if (sum < dslSelectorConfig.dslMinSelectLimit) {
|
||||
//不允许取消选择
|
||||
buttonView.isChecked = true //恢复状态
|
||||
}
|
||||
}
|
||||
|
||||
if (isChecked) {
|
||||
//已经选中了控件
|
||||
} else {
|
||||
//已经取消了控件
|
||||
}*/
|
||||
}
|
||||
|
||||
/**当前选中的索引*/
|
||||
var dslSelectIndex = -1
|
||||
|
||||
/**安装*/
|
||||
fun install(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
|
||||
dslSelectIndex = -1
|
||||
parent = viewGroup
|
||||
updateVisibleList()
|
||||
dslSelectorConfig.config()
|
||||
|
||||
updateStyle()
|
||||
updateClickListener()
|
||||
|
||||
if (dslSelectIndex in 0 until visibleViewList.size) {
|
||||
selector(dslSelectIndex)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**更新样式*/
|
||||
fun updateStyle() {
|
||||
visibleViewList.forEachIndexed { index, view ->
|
||||
val selector = dslSelectIndex == index || view.isSe()
|
||||
dslSelectorConfig.onStyleItemView(view, index, selector)
|
||||
}
|
||||
}
|
||||
|
||||
/**更新child的点击事件*/
|
||||
fun updateClickListener() {
|
||||
parent?.apply {
|
||||
for (i in 0 until childCount) {
|
||||
getChildAt(i)?.let {
|
||||
it.setOnClickListener(_onChildClickListener)
|
||||
if (it is CompoundButton) {
|
||||
it.setOnCheckedChangeListener(_onCheckedChangeListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**更新可见视图列表*/
|
||||
fun updateVisibleList(): List<View> {
|
||||
visibleViewList.clear()
|
||||
parent?.apply {
|
||||
for (i in 0 until childCount) {
|
||||
getChildAt(i)?.let {
|
||||
if (it.visibility == View.VISIBLE) {
|
||||
visibleViewList.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dslSelectIndex in visibleViewList.indices) {
|
||||
if (!visibleViewList[dslSelectIndex].isSe()) {
|
||||
visibleViewList[dslSelectIndex].setSe(true)
|
||||
}
|
||||
} else {
|
||||
//如果当前选中的索引, 不在可见视图列表中
|
||||
dslSelectIndex = -1
|
||||
}
|
||||
return visibleViewList
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作单个
|
||||
* @param index 操作目标的索引值
|
||||
* @param select 选中 or 取消选中
|
||||
* @param notify 是否需要通知事件
|
||||
* @param forceNotify 是否强制通知事件.child使用[CompoundButton]时, 推荐使用
|
||||
* */
|
||||
fun selector(
|
||||
index: Int,
|
||||
select: Boolean = true,
|
||||
notify: Boolean = true,
|
||||
fromUser: Boolean = false,
|
||||
forceNotify: Boolean = false
|
||||
) {
|
||||
val oldSelectorList = selectorIndexList.toList()
|
||||
val lastSelectorIndex: Int? = oldSelectorList.lastOrNull()
|
||||
val reselect = !dslSelectorConfig.dslMultiMode &&
|
||||
oldSelectorList.isNotEmpty() &&
|
||||
oldSelectorList.contains(index)
|
||||
|
||||
var needNotify = _selector(index, select, fromUser) || forceNotify
|
||||
|
||||
if (!oldSelectorList.isChange(selectorIndexList)) {
|
||||
//选中项, 未改变时不通知
|
||||
needNotify = false
|
||||
}
|
||||
|
||||
if (needNotify || reselect) {
|
||||
dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
|
||||
if (notify) {
|
||||
notifySelectChange(lastSelectorIndex ?: -1, reselect, fromUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**选择所有
|
||||
* [select] true:选择所有, false:取消所有*/
|
||||
fun selectorAll(
|
||||
select: Boolean = true,
|
||||
notify: Boolean = true,
|
||||
fromUser: Boolean = true
|
||||
) {
|
||||
val indexList = visibleViewList.mapIndexedTo(mutableListOf()) { index, _ ->
|
||||
index
|
||||
}
|
||||
selector(indexList, select, notify, fromUser)
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作多个
|
||||
* @param select 选中 or 取消选中
|
||||
* [selector]
|
||||
* */
|
||||
fun selector(
|
||||
indexList: MutableList<Int>,
|
||||
select: Boolean = true,
|
||||
notify: Boolean = true,
|
||||
fromUser: Boolean = false
|
||||
) {
|
||||
val oldSelectorIndexList = selectorIndexList
|
||||
val lastSelectorIndex: Int? = oldSelectorIndexList.lastOrNull()
|
||||
|
||||
var result = false
|
||||
|
||||
indexList.forEach {
|
||||
result = _selector(it, select, fromUser) || result
|
||||
}
|
||||
|
||||
if (result) {
|
||||
dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
|
||||
if (notify) {
|
||||
notifySelectChange(lastSelectorIndex ?: -1, false, fromUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**通知事件*/
|
||||
fun notifySelectChange(lastSelectorIndex: Int, reselect: Boolean, fromUser: Boolean) {
|
||||
val indexSelectorList = selectorIndexList
|
||||
dslSelectorConfig.onSelectViewChange(
|
||||
visibleViewList.getOrNull(lastSelectorIndex),
|
||||
selectorViewList,
|
||||
reselect,
|
||||
fromUser
|
||||
)
|
||||
dslSelectorConfig.onSelectIndexChange(
|
||||
lastSelectorIndex,
|
||||
indexSelectorList,
|
||||
reselect,
|
||||
fromUser
|
||||
)
|
||||
}
|
||||
|
||||
/**当前的操作是否被拦截*/
|
||||
fun interceptSelector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
|
||||
val visibleViewList = visibleViewList
|
||||
if (index !in 0 until visibleViewList.size) {
|
||||
return true
|
||||
}
|
||||
return dslSelectorConfig.onSelectItemView(visibleViewList[index], index, select, fromUser)
|
||||
}
|
||||
|
||||
/**@return 是否发生过改变*/
|
||||
fun _selector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
|
||||
val visibleViewList = visibleViewList
|
||||
//超范围过滤
|
||||
if (index !in 0 until visibleViewList.size) {
|
||||
"index out of list.".logi()
|
||||
return false
|
||||
}
|
||||
|
||||
val selectorIndexList = selectorIndexList
|
||||
val selectorViewList = selectorViewList
|
||||
|
||||
if (selectorIndexList.isNotEmpty()) {
|
||||
if (select) {
|
||||
//需要选中某项
|
||||
|
||||
if (dslSelectorConfig.dslMultiMode) {
|
||||
//多选模式
|
||||
if (selectorIndexList.contains(index)) {
|
||||
//已经选中
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
//单选模式
|
||||
|
||||
//取消之前选中
|
||||
selectorIndexList.forEach {
|
||||
if (it != index) {
|
||||
visibleViewList[it].setSe(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectorIndexList.contains(index)) {
|
||||
//已经选中
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
//需要取消选中
|
||||
if (!selectorIndexList.contains(index)) {
|
||||
//目标已经是未选中
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Limit 过滤
|
||||
if (select) {
|
||||
val sum = selectorViewList.size + 1
|
||||
if (sum > dslSelectorConfig.dslMaxSelectLimit) {
|
||||
//不允许选择
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
//取消选择, 检查是否达到了 limit
|
||||
val sum = selectorViewList.size - 1
|
||||
if (sum < dslSelectorConfig.dslMinSelectLimit) {
|
||||
//不允许取消选择
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
val selectorView = visibleViewList[index]
|
||||
|
||||
//更新选中样式
|
||||
selectorView.setSe(select)
|
||||
|
||||
if (dslSelectorConfig.dslMultiMode) {
|
||||
//多选
|
||||
} else {
|
||||
//单选
|
||||
|
||||
//取消之前
|
||||
selectorViewList.forEach { view ->
|
||||
//更新样式
|
||||
val indexOf = visibleViewList.indexOf(view)
|
||||
if (indexOf != index &&
|
||||
!dslSelectorConfig.onSelectItemView(view, indexOf, false, fromUser)
|
||||
) {
|
||||
view.setSe(false)
|
||||
dslSelectorConfig.onStyleItemView(view, indexOf, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dslSelectorConfig.onStyleItemView(selectorView, index, select)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**是否选中状态*/
|
||||
fun View.isSe(): Boolean {
|
||||
return isSelected || if (this is CompoundButton) isChecked else false
|
||||
}
|
||||
|
||||
fun View.setSe(se: Boolean) {
|
||||
isSelected = se
|
||||
if (this is CompoundButton) isChecked = se
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dsl配置项
|
||||
* */
|
||||
open class DslSelectorConfig {
|
||||
|
||||
/**取消选择时, 最小需要保持多个选中. 可以决定单选时, 是否允许取消所有选中*/
|
||||
var dslMinSelectLimit = 1
|
||||
|
||||
/**多选时, 最大允许多个选中*/
|
||||
var dslMaxSelectLimit = Int.MAX_VALUE
|
||||
|
||||
/**是否是多选模式*/
|
||||
var dslMultiMode: Boolean = false
|
||||
|
||||
/**
|
||||
* 用来初始化[itemView]的样式
|
||||
* [onSelectItemView]
|
||||
* */
|
||||
var onStyleItemView: (itemView: View, index: Int, select: Boolean) -> Unit =
|
||||
{ _, _, _ ->
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 选中[View]改变回调, 优先于[onSelectIndexChange]触发, 区别在于参数类型不一样
|
||||
* @param fromView 单选模式下有效, 表示之前选中的[View]
|
||||
* @param reselect 是否是重复选择, 只在单选模式下有效
|
||||
* @param fromUser 是否是用户产生的回调, 而非代码设置
|
||||
* */
|
||||
var onSelectViewChange: (fromView: View?, selectViewList: List<View>, reselect: Boolean, fromUser: Boolean) -> Unit =
|
||||
{ _, _, _, _ ->
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 选中改变回调
|
||||
* [onSelectViewChange]
|
||||
* @param fromIndex 单选模式下有效, 表示之前选中的[View], 在可见性[child]列表中的索引
|
||||
* */
|
||||
var onSelectIndexChange: (fromIndex: Int, selectIndexList: List<Int>, reselect: Boolean, fromUser: Boolean) -> Unit =
|
||||
{ fromIndex, selectList, reselect, fromUser ->
|
||||
"选择:[$fromIndex]->${selectList} reselect:$reselect fromUser:$fromUser".logi()
|
||||
}
|
||||
|
||||
/**
|
||||
* 当需要选中[itemView]时回调, 返回[true]表示拦截默认处理
|
||||
* @param itemView 操作的[View]
|
||||
* @param index [itemView]在可见性view列表中的索引. 非ViewGroup中的索引
|
||||
* @param select 选中 or 取消选中
|
||||
* @return true 表示拦截默认处理
|
||||
* */
|
||||
var onSelectItemView: (itemView: View, index: Int, select: Boolean, fromUser: Boolean) -> Boolean =
|
||||
{ _, _, _, _ ->
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**[DslSelector]组件*/
|
||||
fun dslSelector(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
|
||||
return DslSelector().apply {
|
||||
install(viewGroup, config)
|
||||
}
|
||||
}
|
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
@ -0,0 +1,222 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import androidx.annotation.Px
|
||||
|
||||
/**
|
||||
* 角标
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/12/13
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
open class DslTabBadge : DslBadgeDrawable() {
|
||||
|
||||
/**角标默认配置项*/
|
||||
val defaultBadgeConfig = TabBadgeConfig()
|
||||
|
||||
/**预览的角标属性*/
|
||||
var xmlBadgeText: String? = null
|
||||
|
||||
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
||||
val typedArray =
|
||||
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
|
||||
gradientSolidColor =
|
||||
typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_badge_solid_color,
|
||||
defaultBadgeConfig.badgeSolidColor
|
||||
)
|
||||
defaultBadgeConfig.badgeSolidColor = gradientSolidColor
|
||||
|
||||
badgeTextColor =
|
||||
typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_badge_text_color,
|
||||
defaultBadgeConfig.badgeTextColor
|
||||
)
|
||||
defaultBadgeConfig.badgeTextColor = badgeTextColor
|
||||
|
||||
gradientStrokeColor =
|
||||
typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_badge_stroke_color,
|
||||
defaultBadgeConfig.badgeStrokeColor
|
||||
)
|
||||
defaultBadgeConfig.badgeStrokeColor = gradientStrokeColor
|
||||
|
||||
gradientStrokeWidth =
|
||||
typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_stroke_width,
|
||||
defaultBadgeConfig.badgeStrokeWidth
|
||||
)
|
||||
defaultBadgeConfig.badgeStrokeWidth = gradientStrokeWidth
|
||||
|
||||
badgeGravity = typedArray.getInt(
|
||||
R.styleable.DslTabLayout_tab_badge_gravity,
|
||||
defaultBadgeConfig.badgeGravity
|
||||
)
|
||||
defaultBadgeConfig.badgeGravity = badgeGravity
|
||||
|
||||
badgeOffsetX = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_offset_x,
|
||||
defaultBadgeConfig.badgeOffsetX
|
||||
)
|
||||
defaultBadgeConfig.badgeOffsetX = badgeOffsetX
|
||||
badgeOffsetY = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_offset_y,
|
||||
defaultBadgeConfig.badgeOffsetY
|
||||
)
|
||||
defaultBadgeConfig.badgeOffsetY = badgeOffsetY
|
||||
|
||||
badgeCircleOffsetX = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_circle_offset_x,
|
||||
defaultBadgeConfig.badgeOffsetX
|
||||
)
|
||||
defaultBadgeConfig.badgeCircleOffsetX = badgeCircleOffsetX
|
||||
badgeCircleOffsetY = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_circle_offset_y,
|
||||
defaultBadgeConfig.badgeOffsetY
|
||||
)
|
||||
defaultBadgeConfig.badgeCircleOffsetY = badgeCircleOffsetY
|
||||
|
||||
badgeCircleRadius = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_circle_radius,
|
||||
defaultBadgeConfig.badgeCircleRadius
|
||||
)
|
||||
defaultBadgeConfig.badgeCircleRadius = badgeCircleRadius
|
||||
|
||||
val badgeRadius = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_radius,
|
||||
defaultBadgeConfig.badgeRadius
|
||||
)
|
||||
cornerRadius(badgeRadius.toFloat())
|
||||
defaultBadgeConfig.badgeRadius = badgeRadius
|
||||
|
||||
badgePaddingLeft = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_padding_left,
|
||||
defaultBadgeConfig.badgePaddingLeft
|
||||
)
|
||||
defaultBadgeConfig.badgePaddingLeft = badgePaddingLeft
|
||||
|
||||
badgePaddingRight = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_padding_right,
|
||||
defaultBadgeConfig.badgePaddingRight
|
||||
)
|
||||
defaultBadgeConfig.badgePaddingRight = badgePaddingRight
|
||||
|
||||
badgePaddingTop = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_padding_top,
|
||||
defaultBadgeConfig.badgePaddingTop
|
||||
)
|
||||
defaultBadgeConfig.badgePaddingTop = badgePaddingTop
|
||||
|
||||
badgePaddingBottom = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_padding_bottom,
|
||||
defaultBadgeConfig.badgePaddingBottom
|
||||
)
|
||||
defaultBadgeConfig.badgePaddingBottom = badgePaddingBottom
|
||||
|
||||
xmlBadgeText = typedArray.getString(R.styleable.DslTabLayout_tab_badge_text)
|
||||
|
||||
badgeTextSize = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_badge_text_size,
|
||||
defaultBadgeConfig.badgeTextSize.toInt()
|
||||
).toFloat()
|
||||
defaultBadgeConfig.badgeTextSize = badgeTextSize
|
||||
|
||||
defaultBadgeConfig.badgeAnchorChildIndex =
|
||||
typedArray.getInteger(
|
||||
R.styleable.DslTabLayout_tab_badge_anchor_child_index,
|
||||
defaultBadgeConfig.badgeAnchorChildIndex
|
||||
)
|
||||
defaultBadgeConfig.badgeIgnoreChildPadding = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_badge_ignore_child_padding,
|
||||
defaultBadgeConfig.badgeIgnoreChildPadding
|
||||
)
|
||||
|
||||
defaultBadgeConfig.badgeMinWidth = typedArray.getLayoutDimension(
|
||||
R.styleable.DslTabLayout_tab_badge_min_width,
|
||||
defaultBadgeConfig.badgeMinWidth
|
||||
)
|
||||
|
||||
defaultBadgeConfig.badgeMinHeight = typedArray.getLayoutDimension(
|
||||
R.styleable.DslTabLayout_tab_badge_min_height,
|
||||
defaultBadgeConfig.badgeMinHeight
|
||||
)
|
||||
|
||||
typedArray.recycle()
|
||||
super.initAttribute(context, attributeSet)
|
||||
}
|
||||
|
||||
/**使用指定配置, 更新[DslBadgeDrawable]*/
|
||||
fun updateBadgeConfig(badgeConfig: TabBadgeConfig) {
|
||||
gradientSolidColor = badgeConfig.badgeSolidColor
|
||||
gradientStrokeColor = badgeConfig.badgeStrokeColor
|
||||
gradientStrokeWidth = badgeConfig.badgeStrokeWidth
|
||||
badgeTextColor = badgeConfig.badgeTextColor
|
||||
badgeGravity = badgeConfig.badgeGravity
|
||||
badgeOffsetX = badgeConfig.badgeOffsetX
|
||||
badgeOffsetY = badgeConfig.badgeOffsetY
|
||||
badgeCircleOffsetX = badgeConfig.badgeCircleOffsetX
|
||||
badgeCircleOffsetY = badgeConfig.badgeCircleOffsetY
|
||||
badgeCircleRadius = badgeConfig.badgeCircleRadius
|
||||
badgePaddingLeft = badgeConfig.badgePaddingLeft
|
||||
badgePaddingRight = badgeConfig.badgePaddingRight
|
||||
badgePaddingTop = badgeConfig.badgePaddingTop
|
||||
badgePaddingBottom = badgeConfig.badgePaddingBottom
|
||||
badgeTextSize = badgeConfig.badgeTextSize
|
||||
cornerRadius(badgeConfig.badgeRadius.toFloat())
|
||||
badgeMinHeight = badgeConfig.badgeMinHeight
|
||||
badgeMinWidth = badgeConfig.badgeMinWidth
|
||||
badgeText = badgeConfig.badgeText
|
||||
}
|
||||
}
|
||||
|
||||
/**角标绘制参数配置*/
|
||||
data class TabBadgeConfig(
|
||||
/**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
|
||||
* null 不绘制角标
|
||||
* "" 空字符绘制圆点
|
||||
* 其他 正常绘制
|
||||
* */
|
||||
var badgeText: String? = null,
|
||||
/**重力*/
|
||||
var badgeGravity: Int = Gravity.CENTER,
|
||||
/**角标背景颜色*/
|
||||
var badgeSolidColor: Int = Color.RED,
|
||||
/**角标边框颜色*/
|
||||
var badgeStrokeColor: Int = Color.TRANSPARENT,
|
||||
/**角标边框宽度*/
|
||||
var badgeStrokeWidth: Int = 0,
|
||||
|
||||
/**角标文本颜色*/
|
||||
var badgeTextColor: Int = Color.WHITE,
|
||||
/**角标文本字体大小*/
|
||||
@Px
|
||||
var badgeTextSize: Float = 12 * dp,
|
||||
/**圆点状态时的半径大小*/
|
||||
var badgeCircleRadius: Int = 4 * dpi,
|
||||
/**圆角大小*/
|
||||
var badgeRadius: Int = 10 * dpi,
|
||||
/**额外偏移距离, 会根据[Gravity]自动取负值*/
|
||||
var badgeOffsetX: Int = 0,
|
||||
var badgeOffsetY: Int = 0,
|
||||
var badgeCircleOffsetX: Int = 0,
|
||||
var badgeCircleOffsetY: Int = 0,
|
||||
/**圆点状态时无效*/
|
||||
var badgePaddingLeft: Int = 4 * dpi,
|
||||
var badgePaddingRight: Int = 4 * dpi,
|
||||
var badgePaddingTop: Int = 0,
|
||||
var badgePaddingBottom: Int = 0,
|
||||
|
||||
var badgeAnchorChildIndex: Int = -1,
|
||||
var badgeIgnoreChildPadding: Boolean = true,
|
||||
|
||||
/**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
|
||||
var badgeMinHeight: Int = -2,
|
||||
|
||||
/**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
|
||||
* -1 表示使用使用计算出来的高度值*/
|
||||
var badgeMinWidth: Int = -1
|
||||
)
|
279
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
Normal file
279
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
Normal file
@ -0,0 +1,279 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.core.view.ViewCompat
|
||||
|
||||
/**
|
||||
* 边框绘制, 支持首尾圆角中间不圆角的样式
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/11/27
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
open class DslTabBorder : DslGradientDrawable() {
|
||||
|
||||
/**
|
||||
* 是否要接管[itemView]背景的绘制
|
||||
* [updateItemBackground]
|
||||
* */
|
||||
var borderDrawItemBackground: Boolean = true
|
||||
|
||||
/**是否保持每个[itemView]的圆角都一样, 否则首尾有圆角, 中间没有圆角*/
|
||||
var borderKeepItemRadius: Boolean = false
|
||||
|
||||
var borderBackgroundDrawable: Drawable? = null
|
||||
|
||||
/**宽度补偿*/
|
||||
var borderBackgroundWidthOffset: Int = 0
|
||||
|
||||
/**高度补偿*/
|
||||
var borderBackgroundHeightOffset: Int = 0
|
||||
|
||||
/**强制指定选中item的背景颜色*/
|
||||
var borderItemBackgroundSolidColor: Int? = null
|
||||
|
||||
/**当item不可选中时的背景绘制颜色
|
||||
* [com.angcyo.tablayout.DslTabLayout.itemEnableSelector]
|
||||
* [borderItemBackgroundSolidColor]*/
|
||||
var borderItemBackgroundSolidDisableColor: Int? = null
|
||||
|
||||
/**强制指定选中item的背景渐变颜色*/
|
||||
var borderItemBackgroundGradientColors: IntArray? = null
|
||||
|
||||
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
||||
val typedArray =
|
||||
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
|
||||
|
||||
val borderBackgroundColor =
|
||||
typedArray.getColor(R.styleable.DslTabLayout_tab_border_solid_color, gradientSolidColor)
|
||||
|
||||
gradientStrokeColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_border_stroke_color,
|
||||
gradientStrokeColor
|
||||
)
|
||||
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_border_stroke_width,
|
||||
2 * dpi
|
||||
)
|
||||
val radiusSize =
|
||||
typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_border_radius_size, 0)
|
||||
|
||||
cornerRadius(radiusSize.toFloat())
|
||||
|
||||
originDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_border_drawable)
|
||||
|
||||
borderDrawItemBackground = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_border_draw_item_background,
|
||||
borderDrawItemBackground
|
||||
)
|
||||
|
||||
borderKeepItemRadius = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_border_keep_item_radius,
|
||||
borderKeepItemRadius
|
||||
)
|
||||
|
||||
borderBackgroundWidthOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_border_item_background_width_offset,
|
||||
borderBackgroundWidthOffset
|
||||
)
|
||||
|
||||
borderBackgroundHeightOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_border_item_background_height_offset,
|
||||
borderBackgroundHeightOffset
|
||||
)
|
||||
|
||||
//
|
||||
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_solid_color)) {
|
||||
borderItemBackgroundSolidColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_border_item_background_solid_color,
|
||||
gradientStrokeColor
|
||||
)
|
||||
}
|
||||
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_solid_disable_color)) {
|
||||
borderItemBackgroundSolidDisableColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_border_item_background_solid_disable_color,
|
||||
borderItemBackgroundSolidColor ?: gradientStrokeColor
|
||||
)
|
||||
}
|
||||
|
||||
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_gradient_start_color) ||
|
||||
typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_gradient_end_color)
|
||||
) {
|
||||
val startColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_border_item_background_gradient_start_color,
|
||||
gradientStrokeColor
|
||||
)
|
||||
val endColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_border_item_background_gradient_end_color,
|
||||
gradientStrokeColor
|
||||
)
|
||||
borderItemBackgroundGradientColors = intArrayOf(startColor, endColor)
|
||||
}
|
||||
|
||||
typedArray.recycle()
|
||||
|
||||
if (originDrawable == null) {
|
||||
//无自定义的drawable, 那么自绘.
|
||||
borderBackgroundDrawable = DslGradientDrawable().configDrawable {
|
||||
gradientSolidColor = borderBackgroundColor
|
||||
gradientRadii = this@DslTabBorder.gradientRadii
|
||||
}.originDrawable
|
||||
|
||||
updateOriginDrawable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
|
||||
originDrawable?.apply {
|
||||
setBounds(
|
||||
paddingLeft,
|
||||
paddingBottom,
|
||||
viewWidth - paddingRight,
|
||||
viewHeight - paddingBottom
|
||||
)
|
||||
draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
fun drawBorderBackground(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
|
||||
borderBackgroundDrawable?.apply {
|
||||
setBounds(
|
||||
paddingLeft,
|
||||
paddingBottom,
|
||||
viewWidth - paddingRight,
|
||||
viewHeight - paddingBottom
|
||||
)
|
||||
draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
var itemSelectBgDrawable: Drawable? = null
|
||||
var itemDeselectBgDrawable: Drawable? = null
|
||||
|
||||
/**开启边框绘制后, [itemView]的背景也需要负责设置*/
|
||||
open fun updateItemBackground(
|
||||
tabLayout: DslTabLayout,
|
||||
itemView: View,
|
||||
index: Int,
|
||||
select: Boolean
|
||||
) {
|
||||
|
||||
if (!borderDrawItemBackground) {
|
||||
return
|
||||
}
|
||||
|
||||
if (select) {
|
||||
|
||||
val isFirst = index == 0
|
||||
val isLast = index == tabLayout.dslSelector.visibleViewList.size - 1
|
||||
|
||||
val drawable = DslGradientDrawable().configDrawable {
|
||||
gradientWidthOffset = borderBackgroundWidthOffset
|
||||
gradientHeightOffset = borderBackgroundHeightOffset
|
||||
|
||||
gradientSolidColor =
|
||||
borderItemBackgroundSolidColor ?: this@DslTabBorder.gradientStrokeColor
|
||||
if (!tabLayout.itemEnableSelector) {
|
||||
if (borderItemBackgroundSolidDisableColor != null) {
|
||||
gradientSolidColor = borderItemBackgroundSolidDisableColor!!
|
||||
}
|
||||
}
|
||||
|
||||
gradientColors = borderItemBackgroundGradientColors
|
||||
|
||||
if ((isFirst && isLast) || borderKeepItemRadius) {
|
||||
//只有一个child
|
||||
gradientRadii = this@DslTabBorder.gradientRadii
|
||||
} else if (isFirst) {
|
||||
if (tabLayout.isHorizontal()) {
|
||||
if (tabLayout.isLayoutRtl) {
|
||||
gradientRadii = floatArrayOf(
|
||||
0f,
|
||||
0f,
|
||||
this@DslTabBorder.gradientRadii[2],
|
||||
this@DslTabBorder.gradientRadii[3],
|
||||
this@DslTabBorder.gradientRadii[4],
|
||||
this@DslTabBorder.gradientRadii[5],
|
||||
0f,
|
||||
0f
|
||||
)
|
||||
} else {
|
||||
gradientRadii = floatArrayOf(
|
||||
this@DslTabBorder.gradientRadii[0],
|
||||
this@DslTabBorder.gradientRadii[1],
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
this@DslTabBorder.gradientRadii[6],
|
||||
this@DslTabBorder.gradientRadii[7]
|
||||
)
|
||||
}
|
||||
} else {
|
||||
gradientRadii = floatArrayOf(
|
||||
this@DslTabBorder.gradientRadii[0],
|
||||
this@DslTabBorder.gradientRadii[1],
|
||||
this@DslTabBorder.gradientRadii[2],
|
||||
this@DslTabBorder.gradientRadii[3],
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f
|
||||
)
|
||||
}
|
||||
} else if (isLast) {
|
||||
if (tabLayout.isHorizontal()) {
|
||||
if (tabLayout.isLayoutRtl) {
|
||||
gradientRadii = floatArrayOf(
|
||||
this@DslTabBorder.gradientRadii[0],
|
||||
this@DslTabBorder.gradientRadii[1],
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
this@DslTabBorder.gradientRadii[6],
|
||||
this@DslTabBorder.gradientRadii[7]
|
||||
)
|
||||
} else {
|
||||
gradientRadii = floatArrayOf(
|
||||
0f,
|
||||
0f,
|
||||
this@DslTabBorder.gradientRadii[2],
|
||||
this@DslTabBorder.gradientRadii[3],
|
||||
this@DslTabBorder.gradientRadii[4],
|
||||
this@DslTabBorder.gradientRadii[5],
|
||||
0f,
|
||||
0f
|
||||
)
|
||||
}
|
||||
} else {
|
||||
gradientRadii = floatArrayOf(
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
this@DslTabBorder.gradientRadii[4],
|
||||
this@DslTabBorder.gradientRadii[5],
|
||||
this@DslTabBorder.gradientRadii[6],
|
||||
this@DslTabBorder.gradientRadii[7]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemSelectBgDrawable = drawable
|
||||
|
||||
ViewCompat.setBackground(itemView, itemSelectBgDrawable)
|
||||
} else {
|
||||
ViewCompat.setBackground(itemView, itemDeselectBgDrawable)
|
||||
}
|
||||
}
|
||||
}
|
153
TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
Normal file
153
TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
Normal file
@ -0,0 +1,153 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
|
||||
/**
|
||||
* 垂直分割线
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/11/27
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
open class DslTabDivider : DslGradientDrawable() {
|
||||
|
||||
var dividerWidth = 2 * dpi
|
||||
var dividerHeight = 2 * dpi
|
||||
var dividerMarginLeft = 0
|
||||
var dividerMarginRight = 0
|
||||
var dividerMarginTop = 0
|
||||
var dividerMarginBottom = 0
|
||||
|
||||
/**
|
||||
* [LinearLayout.SHOW_DIVIDER_BEGINNING]
|
||||
* [LinearLayout.SHOW_DIVIDER_MIDDLE]
|
||||
* [LinearLayout.SHOW_DIVIDER_END]
|
||||
* */
|
||||
var dividerShowMode = LinearLayout.SHOW_DIVIDER_MIDDLE
|
||||
|
||||
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
||||
super.initAttribute(context, attributeSet)
|
||||
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
|
||||
|
||||
dividerWidth = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_divider_width,
|
||||
dividerWidth
|
||||
)
|
||||
dividerHeight = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_divider_height,
|
||||
dividerHeight
|
||||
)
|
||||
dividerMarginLeft = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_divider_margin_left,
|
||||
dividerMarginLeft
|
||||
)
|
||||
dividerMarginRight = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_divider_margin_right,
|
||||
dividerMarginRight
|
||||
)
|
||||
dividerMarginTop = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_divider_margin_top,
|
||||
dividerMarginTop
|
||||
)
|
||||
dividerMarginBottom = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_divider_margin_bottom,
|
||||
dividerMarginBottom
|
||||
)
|
||||
|
||||
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_divider_solid_color)) {
|
||||
gradientSolidColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_divider_solid_color,
|
||||
gradientSolidColor
|
||||
)
|
||||
} else if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_stroke_color)) {
|
||||
gradientSolidColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_border_stroke_color,
|
||||
gradientSolidColor
|
||||
)
|
||||
} else {
|
||||
gradientSolidColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_deselect_color,
|
||||
gradientSolidColor
|
||||
)
|
||||
}
|
||||
|
||||
gradientStrokeColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_divider_stroke_color,
|
||||
gradientStrokeColor
|
||||
)
|
||||
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_divider_stroke_width,
|
||||
0
|
||||
)
|
||||
val radiusSize =
|
||||
typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_divider_radius_size,
|
||||
2 * dpi
|
||||
)
|
||||
|
||||
cornerRadius(radiusSize.toFloat())
|
||||
|
||||
originDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_divider_drawable)
|
||||
|
||||
dividerShowMode =
|
||||
typedArray.getInt(R.styleable.DslTabLayout_tab_divider_show_mode, dividerShowMode)
|
||||
|
||||
typedArray.recycle()
|
||||
|
||||
if (originDrawable == null) {
|
||||
//无自定义的drawable, 那么自绘.
|
||||
|
||||
updateOriginDrawable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
|
||||
originDrawable?.apply {
|
||||
bounds = this@DslTabDivider.bounds
|
||||
draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
val _tabLayout: DslTabLayout?
|
||||
get() = if (callback is DslTabLayout) callback as DslTabLayout else null
|
||||
|
||||
/**
|
||||
* [childIndex]位置前面是否需要分割线
|
||||
* */
|
||||
open fun haveBeforeDivider(childIndex: Int, childCount: Int): Boolean {
|
||||
val tabLayout = _tabLayout
|
||||
if (tabLayout != null && tabLayout.isHorizontal() && tabLayout.isLayoutRtl) {
|
||||
if (childIndex == 0) {
|
||||
return dividerShowMode and LinearLayout.SHOW_DIVIDER_END != 0
|
||||
}
|
||||
return dividerShowMode and LinearLayout.SHOW_DIVIDER_MIDDLE != 0
|
||||
}
|
||||
|
||||
if (childIndex == 0) {
|
||||
return dividerShowMode and LinearLayout.SHOW_DIVIDER_BEGINNING != 0
|
||||
}
|
||||
return dividerShowMode and LinearLayout.SHOW_DIVIDER_MIDDLE != 0
|
||||
}
|
||||
|
||||
/**
|
||||
* [childIndex]位置后面是否需要分割线
|
||||
* */
|
||||
open fun haveAfterDivider(childIndex: Int, childCount: Int): Boolean {
|
||||
val tabLayout = _tabLayout
|
||||
if (tabLayout != null && tabLayout.isHorizontal() && tabLayout.isLayoutRtl) {
|
||||
if (childIndex == childCount - 1) {
|
||||
return dividerShowMode and LinearLayout.SHOW_DIVIDER_BEGINNING != 0
|
||||
}
|
||||
}
|
||||
|
||||
if (childIndex == childCount - 1) {
|
||||
return dividerShowMode and LinearLayout.SHOW_DIVIDER_END != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
118
TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
Normal file
118
TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
Normal file
@ -0,0 +1,118 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
|
||||
/**
|
||||
*
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2021/05/19
|
||||
* Copyright (c) 2020 ShenZhen Wayto Ltd. All rights reserved.
|
||||
*/
|
||||
open class DslTabHighlight(val tabLayout: DslTabLayout) : DslGradientDrawable() {
|
||||
|
||||
/**需要绘制的Drawable*/
|
||||
var highlightDrawable: Drawable? = null
|
||||
|
||||
/**宽度测量模式*/
|
||||
var highlightWidth = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
|
||||
/**高度测量模式*/
|
||||
var highlightHeight = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
|
||||
/**宽度补偿*/
|
||||
var highlightWidthOffset = 0
|
||||
|
||||
/**高度补偿*/
|
||||
var highlightHeightOffset = 0
|
||||
|
||||
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
||||
//super.initAttribute(context, attributeSet)
|
||||
|
||||
val typedArray =
|
||||
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
|
||||
highlightDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_highlight_drawable)
|
||||
|
||||
highlightWidth = typedArray.getLayoutDimension(
|
||||
R.styleable.DslTabLayout_tab_highlight_width,
|
||||
highlightWidth
|
||||
)
|
||||
highlightHeight = typedArray.getLayoutDimension(
|
||||
R.styleable.DslTabLayout_tab_highlight_height,
|
||||
highlightHeight
|
||||
)
|
||||
|
||||
highlightWidthOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_highlight_width_offset,
|
||||
highlightWidthOffset
|
||||
)
|
||||
highlightHeightOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_highlight_height_offset,
|
||||
highlightHeightOffset
|
||||
)
|
||||
|
||||
typedArray.recycle()
|
||||
|
||||
if (highlightDrawable == null && isValidConfig()) {
|
||||
updateOriginDrawable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateOriginDrawable(): GradientDrawable? {
|
||||
val drawable = super.updateOriginDrawable()
|
||||
highlightDrawable = originDrawable
|
||||
return drawable
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
//super.draw(canvas)
|
||||
val itemView = tabLayout.currentItemView
|
||||
if (itemView != null) {
|
||||
val lp = itemView.layoutParams
|
||||
|
||||
if (lp is DslTabLayout.LayoutParams) {
|
||||
lp.highlightDrawable ?: highlightDrawable
|
||||
} else {
|
||||
highlightDrawable
|
||||
}?.apply {
|
||||
|
||||
val drawWidth: Int = when (highlightWidth) {
|
||||
ViewGroup.LayoutParams.MATCH_PARENT -> itemView.measuredWidth
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT -> intrinsicWidth
|
||||
else -> highlightWidth
|
||||
} + highlightWidthOffset
|
||||
|
||||
val drawHeight: Int = when (highlightHeight) {
|
||||
ViewGroup.LayoutParams.MATCH_PARENT -> itemView.measuredHeight
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT -> intrinsicHeight
|
||||
else -> highlightHeight
|
||||
} + highlightHeightOffset
|
||||
|
||||
val centerX: Int = itemView.left + (itemView.right - itemView.left) / 2
|
||||
val centerY: Int = itemView.top + (itemView.bottom - itemView.top) / 2
|
||||
|
||||
setBounds(
|
||||
centerX - drawWidth / 2,
|
||||
centerY - drawHeight / 2,
|
||||
centerX + drawWidth / 2,
|
||||
centerY + drawHeight / 2
|
||||
)
|
||||
|
||||
draw(canvas)
|
||||
canvas.save()
|
||||
if (tabLayout.isHorizontal()) {
|
||||
canvas.translate(itemView.left.toFloat(), 0f)
|
||||
} else {
|
||||
canvas.translate(0f, itemView.top.toFloat())
|
||||
}
|
||||
itemView.draw(canvas)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
931
TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
Normal file
931
TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
Normal file
@ -0,0 +1,931 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.withSave
|
||||
import java.util.*
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* 指示器
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/11/25
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
open class DslTabIndicator(val tabLayout: DslTabLayout) : DslGradientDrawable() {
|
||||
|
||||
companion object {
|
||||
|
||||
/**非颜色值*/
|
||||
const val NO_COLOR = -2
|
||||
|
||||
//---style---
|
||||
|
||||
/**不绘制指示器*/
|
||||
const val INDICATOR_STYLE_NONE = 0
|
||||
|
||||
/**指示器绘制在[itemView]的顶部*/
|
||||
const val INDICATOR_STYLE_TOP = 0x1
|
||||
|
||||
/**指示器绘制在[itemView]的底部*/
|
||||
const val INDICATOR_STYLE_BOTTOM = 0x2
|
||||
|
||||
/**默认样式,指示器绘制在[itemView]的中心*/
|
||||
const val INDICATOR_STYLE_CENTER = 0x4
|
||||
|
||||
/**前景绘制,
|
||||
* 默认是背景绘制, 指示器绘制[itemView]的背部, [itemView] 请不要设置background, 否则可能看不见*/
|
||||
const val INDICATOR_STYLE_FOREGROUND = 0x1000
|
||||
|
||||
//---gravity---
|
||||
|
||||
/**指示器重力在开始的位置(横向左边, 纵向上边)*/
|
||||
const val INDICATOR_GRAVITY_START = 0x1
|
||||
|
||||
/**指示器重力在结束的位置(横向右边, 纵向下边)*/
|
||||
const val INDICATOR_GRAVITY_END = 0x2
|
||||
|
||||
/**指示器重力在中间*/
|
||||
const val INDICATOR_GRAVITY_CENTER = 0x4
|
||||
}
|
||||
|
||||
/**指示器绘制的样式*/
|
||||
var indicatorStyle = INDICATOR_STYLE_NONE //初始化
|
||||
|
||||
/**[indicatorStyle]*/
|
||||
val _indicatorDrawStyle: Int
|
||||
get() = indicatorStyle.remove(INDICATOR_STYLE_FOREGROUND)
|
||||
|
||||
/**优先将指示器显示在[DslTabLayout]的什么位置
|
||||
* [INDICATOR_GRAVITY_START] 开始的位置
|
||||
* [INDICATOR_GRAVITY_END] 结束的位置
|
||||
* [INDICATOR_GRAVITY_CENTER] 中间的位置*/
|
||||
var indicatorGravity = INDICATOR_GRAVITY_CENTER
|
||||
|
||||
/**
|
||||
* 指示器在流向下一个位置时, 是否采用[Flow]流线的方式改变宽度
|
||||
* */
|
||||
var indicatorEnableFlow: Boolean = false
|
||||
|
||||
/**指示器闪现效果, 从当前位置直接跨越到目标位置*/
|
||||
var indicatorEnableFlash: Boolean = false
|
||||
|
||||
/**使用clip的方式绘制闪现效果*/
|
||||
var indicatorEnableFlashClip: Boolean = true
|
||||
|
||||
/**当目标和当前的索引差值<=此值时, [Flow]效果才有效*/
|
||||
var indicatorFlowStep: Int = 1
|
||||
|
||||
/**指示器绘制实体*/
|
||||
var indicatorDrawable: Drawable? = null
|
||||
set(value) {
|
||||
field = tintDrawableColor(value, indicatorColor)
|
||||
}
|
||||
|
||||
/**过滤[indicatorDrawable]的颜色*/
|
||||
var indicatorColor: Int = NO_COLOR
|
||||
set(value) {
|
||||
field = value
|
||||
indicatorDrawable = indicatorDrawable
|
||||
}
|
||||
|
||||
/**
|
||||
* 指示器的宽度
|
||||
* WRAP_CONTENT: [childView]内容的宽度,
|
||||
* MATCH_PARENT: [childView]的宽度
|
||||
* 40dp: 固定值
|
||||
* */
|
||||
var indicatorWidth = 0 //初始化
|
||||
|
||||
/**宽度补偿*/
|
||||
var indicatorWidthOffset = 0
|
||||
|
||||
/**
|
||||
* 指示器的高度
|
||||
* WRAP_CONTENT: [childView]内容的高度,
|
||||
* MATCH_PARENT: [childView]的高度
|
||||
* 40dp: 固定值
|
||||
* */
|
||||
var indicatorHeight = 0 //初始化
|
||||
|
||||
/**高度补偿*/
|
||||
var indicatorHeightOffset = 0
|
||||
|
||||
/**XY轴方向补偿*/
|
||||
var indicatorXOffset = 0
|
||||
|
||||
/**会根据[indicatorStyle]自动取负值*/
|
||||
var indicatorYOffset = 0
|
||||
|
||||
/**
|
||||
* 宽高[WRAP_CONTENT]时, 内容view的定位索引
|
||||
* */
|
||||
var indicatorContentIndex = -1
|
||||
var indicatorContentId = View.NO_ID
|
||||
|
||||
/**切换时是否需要动画的支持*/
|
||||
var indicatorAnim = true
|
||||
|
||||
/**在获取锚点view的宽高时, 是否需要忽略对应的padding属性*/
|
||||
var ignoreChildPadding: Boolean = true
|
||||
|
||||
init {
|
||||
callback = tabLayout
|
||||
}
|
||||
|
||||
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
||||
val typedArray =
|
||||
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
|
||||
|
||||
indicatorDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_indicator_drawable)
|
||||
indicatorColor =
|
||||
typedArray.getColor(R.styleable.DslTabLayout_tab_indicator_color, indicatorColor)
|
||||
indicatorStyle = typedArray.getInt(
|
||||
R.styleable.DslTabLayout_tab_indicator_style,
|
||||
if (tabLayout.isHorizontal()) INDICATOR_STYLE_BOTTOM else INDICATOR_STYLE_TOP
|
||||
)
|
||||
indicatorGravity = typedArray.getInt(
|
||||
R.styleable.DslTabLayout_tab_indicator_gravity,
|
||||
indicatorGravity
|
||||
)
|
||||
|
||||
//初始化指示器的高度和宽度
|
||||
if (indicatorStyle.have(INDICATOR_STYLE_FOREGROUND)) {
|
||||
//前景绘制
|
||||
indicatorWidth = typedArray.getLayoutDimension(
|
||||
R.styleable.DslTabLayout_tab_indicator_width,
|
||||
if (tabLayout.isHorizontal()) ViewGroup.LayoutParams.MATCH_PARENT else 3 * dpi
|
||||
)
|
||||
indicatorHeight = typedArray.getLayoutDimension(
|
||||
R.styleable.DslTabLayout_tab_indicator_height,
|
||||
if (tabLayout.isHorizontal()) 3 * dpi else ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
indicatorXOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_x_offset,
|
||||
if (tabLayout.isHorizontal()) 0 else 2 * dpi
|
||||
)
|
||||
indicatorYOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_y_offset,
|
||||
if (tabLayout.isHorizontal()) 2 * dpi else 0
|
||||
)
|
||||
} else {
|
||||
//背景绘制样式
|
||||
if (tabLayout.isHorizontal()) {
|
||||
indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
} else {
|
||||
indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
indicatorWidth = typedArray.getLayoutDimension(
|
||||
R.styleable.DslTabLayout_tab_indicator_width,
|
||||
indicatorWidth
|
||||
)
|
||||
indicatorHeight = typedArray.getLayoutDimension(
|
||||
R.styleable.DslTabLayout_tab_indicator_height,
|
||||
indicatorHeight
|
||||
)
|
||||
indicatorXOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_x_offset,
|
||||
indicatorXOffset
|
||||
)
|
||||
indicatorYOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_y_offset,
|
||||
indicatorYOffset
|
||||
)
|
||||
}
|
||||
|
||||
ignoreChildPadding = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_indicator_ignore_child_padding,
|
||||
!indicatorStyle.have(INDICATOR_STYLE_CENTER)
|
||||
)
|
||||
|
||||
indicatorFlowStep =
|
||||
typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_flow_step, indicatorFlowStep)
|
||||
indicatorEnableFlow = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_indicator_enable_flow,
|
||||
indicatorEnableFlow
|
||||
)
|
||||
indicatorEnableFlash = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_indicator_enable_flash,
|
||||
indicatorEnableFlash
|
||||
)
|
||||
indicatorEnableFlashClip = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_indicator_enable_flash_clip,
|
||||
indicatorEnableFlashClip
|
||||
)
|
||||
|
||||
indicatorWidthOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_width_offset,
|
||||
indicatorWidthOffset
|
||||
)
|
||||
indicatorHeightOffset = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_height_offset,
|
||||
indicatorHeightOffset
|
||||
)
|
||||
indicatorContentIndex = typedArray.getInt(
|
||||
R.styleable.DslTabLayout_tab_indicator_content_index,
|
||||
indicatorContentIndex
|
||||
)
|
||||
indicatorContentId = typedArray.getResourceId(
|
||||
R.styleable.DslTabLayout_tab_indicator_content_id,
|
||||
indicatorContentId
|
||||
)
|
||||
indicatorAnim = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_indicator_anim,
|
||||
indicatorAnim
|
||||
)
|
||||
|
||||
//代码构建Drawable
|
||||
gradientShape =
|
||||
typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_shape, gradientShape)
|
||||
gradientSolidColor =
|
||||
typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_indicator_solid_color,
|
||||
gradientSolidColor
|
||||
)
|
||||
gradientStrokeColor =
|
||||
typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_indicator_stroke_color,
|
||||
gradientStrokeColor
|
||||
)
|
||||
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_stroke_width,
|
||||
gradientStrokeWidth
|
||||
)
|
||||
gradientDashWidth = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_dash_width,
|
||||
gradientDashWidth.toInt()
|
||||
).toFloat()
|
||||
gradientDashGap = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_indicator_dash_gap,
|
||||
gradientDashGap.toInt()
|
||||
).toFloat()
|
||||
|
||||
val gradientRadius =
|
||||
typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_indicator_radius, 0)
|
||||
if (gradientRadius > 0) {
|
||||
Arrays.fill(gradientRadii, gradientRadius.toFloat())
|
||||
} else {
|
||||
typedArray.getString(R.styleable.DslTabLayout_tab_indicator_radii)?.let {
|
||||
_fillRadii(gradientRadii, it)
|
||||
}
|
||||
}
|
||||
|
||||
val gradientColors =
|
||||
typedArray.getString(R.styleable.DslTabLayout_tab_indicator_gradient_colors)
|
||||
|
||||
this.gradientColors = if (gradientColors.isNullOrEmpty()) {
|
||||
val startColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_indicator_gradient_start_color,
|
||||
Color.TRANSPARENT
|
||||
)
|
||||
val endColor = typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_indicator_gradient_end_color,
|
||||
Color.TRANSPARENT
|
||||
)
|
||||
if (startColor != endColor) {
|
||||
intArrayOf(startColor, endColor)
|
||||
} else {
|
||||
this.gradientColors
|
||||
}
|
||||
} else {
|
||||
_fillColor(gradientColors) ?: this.gradientColors
|
||||
}
|
||||
//...end
|
||||
|
||||
typedArray.recycle()
|
||||
|
||||
if (indicatorDrawable == null && isValidConfig()) {
|
||||
updateOriginDrawable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateOriginDrawable(): GradientDrawable? {
|
||||
val drawable = super.updateOriginDrawable()
|
||||
indicatorDrawable = originDrawable
|
||||
return drawable
|
||||
}
|
||||
|
||||
open fun tintDrawableColor(drawable: Drawable?, color: Int): Drawable? {
|
||||
if (drawable == null || color == NO_COLOR) {
|
||||
return drawable
|
||||
}
|
||||
return drawable.tintDrawableColor(color)
|
||||
}
|
||||
|
||||
/**指示器需要参考的目标控件*/
|
||||
open fun indicatorContentView(childView: View): View? {
|
||||
val lp = childView.layoutParams as DslTabLayout.LayoutParams
|
||||
|
||||
val contentId =
|
||||
if (lp.indicatorContentId != View.NO_ID) lp.indicatorContentId else indicatorContentId
|
||||
|
||||
if (contentId != View.NO_ID) {
|
||||
return childView.findViewById(contentId)
|
||||
}
|
||||
|
||||
//如果child强制指定了index, 就用指定的.
|
||||
val contentIndex =
|
||||
if (lp.indicatorContentIndex >= 0) lp.indicatorContentIndex else indicatorContentIndex
|
||||
|
||||
return if (contentIndex >= 0 && childView is ViewGroup && contentIndex in 0 until childView.childCount) {
|
||||
//有指定
|
||||
val contentChildView = childView.getChildAt(contentIndex)
|
||||
contentChildView
|
||||
} else {
|
||||
//没有指定
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**根据指定[index]索引, 获取目标[View]*/
|
||||
open fun targetChildView(
|
||||
index: Int,
|
||||
onChildView: (childView: View, contentChildView: View?) -> Unit
|
||||
) {
|
||||
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
||||
onChildView(childView, indicatorContentView(childView))
|
||||
}
|
||||
}
|
||||
|
||||
open fun getChildTargetPaddingLeft(childView: View): Int =
|
||||
if (ignoreChildPadding) childView.paddingLeft else 0
|
||||
|
||||
open fun getChildTargetPaddingRight(childView: View): Int =
|
||||
if (ignoreChildPadding) childView.paddingRight else 0
|
||||
|
||||
open fun getChildTargetPaddingTop(childView: View): Int =
|
||||
if (ignoreChildPadding) childView.paddingTop else 0
|
||||
|
||||
open fun getChildTargetPaddingBottom(childView: View): Int =
|
||||
if (ignoreChildPadding) childView.paddingBottom else 0
|
||||
|
||||
open fun getChildTargetWidth(childView: View): Int =
|
||||
if (ignoreChildPadding) childView.viewDrawWidth else childView.measuredWidth
|
||||
|
||||
open fun getChildTargetHeight(childView: View): Int =
|
||||
if (ignoreChildPadding) childView.viewDrawHeight else childView.measuredHeight
|
||||
|
||||
/**
|
||||
* [childview]对应的中心x坐标
|
||||
* */
|
||||
open fun getChildTargetX(index: Int, gravity: Int = indicatorGravity): Int {
|
||||
|
||||
var result = if (index > 0) tabLayout.maxWidth else 0
|
||||
|
||||
targetChildView(index) { childView, contentChildView ->
|
||||
result = if (contentChildView == null) {
|
||||
when (gravity) {
|
||||
INDICATOR_GRAVITY_START -> childView.left
|
||||
INDICATOR_GRAVITY_END -> childView.right
|
||||
else -> childView.left + getChildTargetPaddingLeft(childView) + getChildTargetWidth(
|
||||
childView
|
||||
) / 2
|
||||
}
|
||||
} else {
|
||||
when (gravity) {
|
||||
INDICATOR_GRAVITY_START -> childView.left + contentChildView.left
|
||||
INDICATOR_GRAVITY_END -> childView.left + contentChildView.right
|
||||
else -> childView.left + contentChildView.left + getChildTargetPaddingLeft(
|
||||
contentChildView
|
||||
) + getChildTargetWidth(
|
||||
contentChildView
|
||||
) / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
open fun getChildTargetY(index: Int, gravity: Int = indicatorGravity): Int {
|
||||
|
||||
var result = if (index > 0) tabLayout.maxHeight else 0
|
||||
|
||||
targetChildView(index) { childView, contentChildView ->
|
||||
result = if (contentChildView == null) {
|
||||
when (gravity) {
|
||||
INDICATOR_GRAVITY_START -> childView.top
|
||||
INDICATOR_GRAVITY_END -> childView.bottom
|
||||
else -> childView.top + getChildTargetPaddingTop(childView) + getChildTargetHeight(
|
||||
childView
|
||||
) / 2
|
||||
}
|
||||
} else {
|
||||
when (gravity) {
|
||||
INDICATOR_GRAVITY_START -> childView.top + contentChildView.top
|
||||
INDICATOR_GRAVITY_END -> childView.top + childView.bottom
|
||||
else -> childView.top + contentChildView.top + getChildTargetPaddingTop(
|
||||
contentChildView
|
||||
) + getChildTargetHeight(
|
||||
contentChildView
|
||||
) / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
open fun getIndicatorDrawWidth(index: Int): Int {
|
||||
var result = indicatorWidth
|
||||
|
||||
when (indicatorWidth) {
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT -> {
|
||||
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
||||
result = getChildTargetWidth(indicatorContentView(childView) ?: childView)
|
||||
}
|
||||
}
|
||||
ViewGroup.LayoutParams.MATCH_PARENT -> {
|
||||
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
||||
result = childView.measuredWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result + indicatorWidthOffset
|
||||
}
|
||||
|
||||
open fun getIndicatorDrawHeight(index: Int): Int {
|
||||
var result = indicatorHeight
|
||||
|
||||
when (indicatorHeight) {
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT -> {
|
||||
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
||||
result = getChildTargetHeight(indicatorContentView(childView) ?: childView)
|
||||
}
|
||||
}
|
||||
ViewGroup.LayoutParams.MATCH_PARENT -> {
|
||||
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
||||
result = childView.measuredHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result + indicatorHeightOffset
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
//super.draw(canvas)
|
||||
if (!isVisible || _indicatorDrawStyle == INDICATOR_STYLE_NONE || indicatorDrawable == null) {
|
||||
//不绘制
|
||||
return
|
||||
}
|
||||
|
||||
if (tabLayout.isHorizontal()) {
|
||||
drawHorizontal(canvas)
|
||||
} else {
|
||||
drawVertical(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
fun drawHorizontal(canvas: Canvas) {
|
||||
val childSize = tabLayout.dslSelector.visibleViewList.size
|
||||
|
||||
var currentIndex = currentIndex
|
||||
|
||||
if (_targetIndex in 0 until childSize) {
|
||||
currentIndex = max(0, currentIndex)
|
||||
}
|
||||
|
||||
if (currentIndex in 0 until childSize) {
|
||||
|
||||
} else {
|
||||
//无效的index
|
||||
return
|
||||
}
|
||||
|
||||
//"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
|
||||
|
||||
val drawTargetX = getChildTargetX(currentIndex)
|
||||
val drawWidth = getIndicatorDrawWidth(currentIndex)
|
||||
val drawHeight = getIndicatorDrawHeight(currentIndex)
|
||||
|
||||
val drawLeft = drawTargetX - drawWidth / 2 + indicatorXOffset
|
||||
|
||||
//动画过程中的left
|
||||
var animLeft = drawLeft
|
||||
//width
|
||||
var animWidth = drawWidth
|
||||
//动画执行过程中, 高度额外变大的值
|
||||
var animExHeight = 0
|
||||
|
||||
//end value
|
||||
val nextDrawTargetX = getChildTargetX(_targetIndex)
|
||||
val nextDrawWidth = getIndicatorDrawWidth(_targetIndex)
|
||||
val nextDrawLeft = nextDrawTargetX - nextDrawWidth / 2 + indicatorXOffset
|
||||
|
||||
var animEndWidth = nextDrawWidth
|
||||
var animEndLeft = nextDrawLeft
|
||||
|
||||
if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
|
||||
|
||||
//动画过程参数计算变量
|
||||
val animStartLeft = drawLeft
|
||||
val animStartWidth = drawWidth
|
||||
|
||||
val animEndHeight = getIndicatorDrawHeight(_targetIndex)
|
||||
|
||||
if (indicatorEnableFlash) {
|
||||
//闪现效果
|
||||
animWidth = (animWidth * (1 - positionOffset)).toInt()
|
||||
animEndWidth = (animEndWidth * positionOffset).toInt()
|
||||
|
||||
animLeft = drawTargetX - animWidth / 2 + indicatorXOffset
|
||||
animEndLeft = nextDrawLeft
|
||||
} else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
|
||||
//激活了流动效果
|
||||
|
||||
val flowEndWidth: Int
|
||||
if (_targetIndex > currentIndex) {
|
||||
flowEndWidth = animEndLeft - animStartLeft + animEndWidth
|
||||
|
||||
//目标在右边
|
||||
animLeft = if (positionOffset >= 0.5) {
|
||||
(animStartLeft + (animEndLeft - animStartLeft) * (positionOffset - 0.5) / 0.5f).toInt()
|
||||
} else {
|
||||
animStartLeft
|
||||
}
|
||||
} else {
|
||||
flowEndWidth = animStartLeft - animEndLeft + animStartWidth
|
||||
|
||||
//目标在左边
|
||||
animLeft = if (positionOffset >= 0.5) {
|
||||
animEndLeft
|
||||
} else {
|
||||
(animStartLeft - (animStartLeft - animEndLeft) * positionOffset / 0.5f).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
animWidth = if (positionOffset >= 0.5) {
|
||||
(flowEndWidth - (flowEndWidth - animEndWidth) * (positionOffset - 0.5) / 0.5f).toInt()
|
||||
} else {
|
||||
(animStartWidth + (flowEndWidth - animStartWidth) * positionOffset / 0.5f).toInt()
|
||||
}
|
||||
} else {
|
||||
//默认平移效果
|
||||
if (_targetIndex > currentIndex) {
|
||||
//目标在右边
|
||||
animLeft =
|
||||
(animStartLeft + (animEndLeft - animStartLeft) * positionOffset).toInt()
|
||||
} else {
|
||||
//目标在左边
|
||||
animLeft =
|
||||
(animStartLeft - (animStartLeft - animEndLeft) * positionOffset).toInt()
|
||||
}
|
||||
|
||||
//动画过程中的宽度
|
||||
animWidth =
|
||||
(animStartWidth + (animEndWidth - animStartWidth) * positionOffset).toInt()
|
||||
}
|
||||
|
||||
animExHeight = ((animEndHeight - drawHeight) * positionOffset).toInt()
|
||||
}
|
||||
|
||||
//前景
|
||||
val drawTop = when (_indicatorDrawStyle) {
|
||||
//底部绘制
|
||||
INDICATOR_STYLE_BOTTOM -> viewHeight - drawHeight - indicatorYOffset
|
||||
//顶部绘制
|
||||
INDICATOR_STYLE_TOP -> 0 + indicatorYOffset
|
||||
//居中绘制
|
||||
else -> paddingTop + viewDrawHeight / 2 - drawHeight / 2 + indicatorYOffset -
|
||||
animExHeight +
|
||||
(tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
|
||||
}
|
||||
|
||||
indicatorDrawable?.apply {
|
||||
if (indicatorEnableFlash) {
|
||||
//flash
|
||||
if (indicatorEnableFlashClip) {
|
||||
drawIndicatorClipHorizontal(
|
||||
this,
|
||||
canvas,
|
||||
drawLeft,
|
||||
drawTop,
|
||||
drawLeft + drawWidth,
|
||||
drawTop + drawHeight + animExHeight,
|
||||
animWidth,
|
||||
1 - positionOffset
|
||||
)
|
||||
} else {
|
||||
drawIndicator(
|
||||
this, canvas, animLeft,
|
||||
drawTop,
|
||||
animLeft + animWidth,
|
||||
drawTop + drawHeight + animExHeight,
|
||||
1 - positionOffset
|
||||
)
|
||||
}
|
||||
|
||||
if (_targetIndex in 0 until childSize) {
|
||||
if (indicatorEnableFlashClip) {
|
||||
drawIndicatorClipHorizontal(
|
||||
this,
|
||||
canvas,
|
||||
nextDrawLeft,
|
||||
drawTop,
|
||||
nextDrawLeft + nextDrawWidth,
|
||||
drawTop + drawHeight + animExHeight,
|
||||
animEndWidth,
|
||||
positionOffset
|
||||
)
|
||||
} else {
|
||||
drawIndicator(
|
||||
this, canvas, animEndLeft,
|
||||
drawTop,
|
||||
animEndLeft + animEndWidth,
|
||||
drawTop + drawHeight + animExHeight,
|
||||
positionOffset
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//normal
|
||||
drawIndicator(
|
||||
this, canvas, animLeft,
|
||||
drawTop,
|
||||
animLeft + animWidth,
|
||||
drawTop + drawHeight + animExHeight,
|
||||
1 - positionOffset
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun drawIndicator(
|
||||
indicator: Drawable,
|
||||
canvas: Canvas,
|
||||
l: Int,
|
||||
t: Int,
|
||||
r: Int,
|
||||
b: Int,
|
||||
offset: Float
|
||||
) {
|
||||
indicator.apply {
|
||||
if (this is ITabIndicatorDraw) {
|
||||
setBounds(l, t, r, b)
|
||||
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
|
||||
} else {
|
||||
val width = r - l
|
||||
val height = b - t
|
||||
setBounds(0, 0, width, height)
|
||||
canvas.withSave {
|
||||
translate(l.toFloat(), t.toFloat())
|
||||
draw(canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun drawIndicatorClipHorizontal(
|
||||
indicator: Drawable,
|
||||
canvas: Canvas,
|
||||
l: Int,
|
||||
t: Int,
|
||||
r: Int,
|
||||
b: Int,
|
||||
endWidth: Int,
|
||||
offset: Float
|
||||
) {
|
||||
indicator.apply {
|
||||
canvas.save()
|
||||
val dx = (r - l - endWidth) / 2
|
||||
canvas.clipRect(l + dx, t, r - dx, b)
|
||||
setBounds(l, t, r, b)
|
||||
if (this is ITabIndicatorDraw) {
|
||||
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
|
||||
} else {
|
||||
draw(canvas)
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
fun drawIndicatorClipVertical(
|
||||
indicator: Drawable,
|
||||
canvas: Canvas,
|
||||
l: Int,
|
||||
t: Int,
|
||||
r: Int,
|
||||
b: Int,
|
||||
endHeight: Int,
|
||||
offset: Float
|
||||
) {
|
||||
indicator.apply {
|
||||
canvas.save()
|
||||
val dy = (b - t - endHeight) / 2
|
||||
canvas.clipRect(l, t + dy, r, b - dy)
|
||||
setBounds(l, t, r, b)
|
||||
if (this is ITabIndicatorDraw) {
|
||||
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
|
||||
} else {
|
||||
draw(canvas)
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
fun drawVertical(canvas: Canvas) {
|
||||
val childSize = tabLayout.dslSelector.visibleViewList.size
|
||||
|
||||
var currentIndex = currentIndex
|
||||
|
||||
if (_targetIndex in 0 until childSize) {
|
||||
currentIndex = max(0, currentIndex)
|
||||
}
|
||||
|
||||
if (currentIndex in 0 until childSize) {
|
||||
|
||||
} else {
|
||||
//无效的index
|
||||
return
|
||||
}
|
||||
|
||||
//"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
|
||||
|
||||
val drawTargetY = getChildTargetY(currentIndex)
|
||||
val drawWidth = getIndicatorDrawWidth(currentIndex)
|
||||
val drawHeight = getIndicatorDrawHeight(currentIndex)
|
||||
|
||||
val drawTop = drawTargetY - drawHeight / 2 + indicatorYOffset
|
||||
|
||||
//动画过程中的top
|
||||
var animTop = drawTop
|
||||
//height
|
||||
var animHeight = drawHeight
|
||||
//动画执行过程中, 宽度额外变大的值
|
||||
var animExWidth = 0
|
||||
|
||||
//end value
|
||||
val nextDrawTargetY = getChildTargetY(_targetIndex)
|
||||
val nextDrawHeight = getIndicatorDrawHeight(_targetIndex)
|
||||
val nextDrawTop = nextDrawTargetY - nextDrawHeight / 2 + indicatorYOffset
|
||||
|
||||
var animEndHeight = nextDrawHeight
|
||||
var animEndTop = nextDrawTop
|
||||
|
||||
if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
|
||||
|
||||
//动画过程参数计算变量
|
||||
val animStartTop = drawTop
|
||||
val animStartHeight = drawHeight
|
||||
|
||||
val animEndWidth = getIndicatorDrawWidth(_targetIndex)
|
||||
|
||||
if (indicatorEnableFlash) {
|
||||
//闪现效果
|
||||
animHeight = (animHeight * (1 - positionOffset)).toInt()
|
||||
animEndHeight = (animEndHeight * positionOffset).toInt()
|
||||
|
||||
animTop = drawTargetY - animHeight / 2 + indicatorXOffset
|
||||
animEndTop = nextDrawTargetY - animEndHeight / 2 + indicatorXOffset
|
||||
} else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
|
||||
//激活了流动效果
|
||||
|
||||
val flowEndHeight: Int
|
||||
if (_targetIndex > currentIndex) {
|
||||
flowEndHeight = animEndTop - animStartTop + animEndHeight
|
||||
|
||||
//目标在下边
|
||||
animTop = if (positionOffset >= 0.5) {
|
||||
(animStartTop + (animEndTop - animStartTop) * (positionOffset - 0.5) / 0.5f).toInt()
|
||||
} else {
|
||||
animStartTop
|
||||
}
|
||||
} else {
|
||||
flowEndHeight = animStartTop - animEndTop + animStartHeight
|
||||
|
||||
//目标在上边
|
||||
animTop = if (positionOffset >= 0.5) {
|
||||
animEndTop
|
||||
} else {
|
||||
(animStartTop - (animStartTop - animEndTop) * positionOffset / 0.5f).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
animHeight = if (positionOffset >= 0.5) {
|
||||
(flowEndHeight - (flowEndHeight - animEndHeight) * (positionOffset - 0.5) / 0.5f).toInt()
|
||||
} else {
|
||||
(animStartHeight + (flowEndHeight - animStartHeight) * positionOffset / 0.5f).toInt()
|
||||
}
|
||||
} else {
|
||||
if (_targetIndex > currentIndex) {
|
||||
//目标在下边
|
||||
animTop = (animStartTop + (animEndTop - animStartTop) * positionOffset).toInt()
|
||||
} else {
|
||||
//目标在上边
|
||||
animTop = (animStartTop - (animStartTop - animEndTop) * positionOffset).toInt()
|
||||
}
|
||||
|
||||
//动画过程中的宽度
|
||||
animHeight =
|
||||
(animStartHeight + (animEndHeight - animStartHeight) * positionOffset).toInt()
|
||||
}
|
||||
|
||||
animExWidth = ((animEndWidth - drawWidth) * positionOffset).toInt()
|
||||
}
|
||||
|
||||
val drawLeft = when (_indicatorDrawStyle) {
|
||||
INDICATOR_STYLE_BOTTOM -> {
|
||||
//右边/底部绘制
|
||||
viewWidth - drawWidth - indicatorXOffset
|
||||
}
|
||||
INDICATOR_STYLE_TOP -> {
|
||||
//左边/顶部绘制
|
||||
0 + indicatorXOffset
|
||||
}
|
||||
else -> {
|
||||
//居中绘制
|
||||
paddingLeft + indicatorXOffset + (viewDrawWidth / 2 - drawWidth / 2) -
|
||||
(tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
|
||||
}
|
||||
}
|
||||
|
||||
indicatorDrawable?.apply {
|
||||
//flash
|
||||
if (indicatorEnableFlash) {
|
||||
if (indicatorEnableFlashClip) {
|
||||
drawIndicatorClipVertical(
|
||||
this, canvas, drawLeft,
|
||||
drawTop,
|
||||
drawLeft + drawWidth + animExWidth,
|
||||
drawTop + drawHeight,
|
||||
animHeight,
|
||||
1 - positionOffset
|
||||
)
|
||||
} else {
|
||||
drawIndicator(
|
||||
this, canvas, drawLeft,
|
||||
animTop,
|
||||
drawLeft + drawWidth + animExWidth,
|
||||
animTop + animHeight,
|
||||
1 - positionOffset
|
||||
)
|
||||
}
|
||||
|
||||
if (_targetIndex in 0 until childSize) {
|
||||
if (indicatorEnableFlashClip) {
|
||||
drawIndicatorClipVertical(
|
||||
this, canvas, drawLeft,
|
||||
nextDrawTop,
|
||||
drawLeft + drawWidth + animExWidth,
|
||||
nextDrawTop + nextDrawHeight,
|
||||
animEndHeight,
|
||||
positionOffset
|
||||
)
|
||||
} else {
|
||||
drawIndicator(
|
||||
this, canvas, drawLeft,
|
||||
animEndTop,
|
||||
drawLeft + drawWidth + animExWidth,
|
||||
animEndTop + animEndHeight,
|
||||
positionOffset
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
drawIndicator(
|
||||
this, canvas, drawLeft,
|
||||
animTop,
|
||||
drawLeft + drawWidth + animExWidth,
|
||||
animTop + animHeight,
|
||||
1 - positionOffset
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun _childConvexHeight(index: Int): Int {
|
||||
if (attachView is ViewGroup) {
|
||||
((attachView as ViewGroup).getChildAt(index).layoutParams as? DslTabLayout.LayoutParams)?.apply {
|
||||
return layoutConvexHeight
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 距离[_targetIndex]的偏移比例.[0->1]的过程
|
||||
* */
|
||||
var positionOffset: Float = 0f
|
||||
set(value) {
|
||||
field = value
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
/**当前绘制的index*/
|
||||
var currentIndex: Int = -1
|
||||
|
||||
/**滚动目标的index*/
|
||||
var _targetIndex = -1
|
||||
}
|
2042
TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
Normal file
2042
TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,526 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.IdRes
|
||||
import com.angcyo.tablayout.DslTabIndicator.Companion.NO_COLOR
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/11/26
|
||||
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||
*/
|
||||
open class DslTabLayoutConfig(val tabLayout: DslTabLayout) : DslSelectorConfig() {
|
||||
|
||||
/**是否开启文本颜色*/
|
||||
var tabEnableTextColor = true
|
||||
set(value) {
|
||||
field = value
|
||||
if (field) {
|
||||
tabEnableIcoColor = true
|
||||
}
|
||||
}
|
||||
|
||||
/**是否开启颜色渐变效果*/
|
||||
var tabEnableGradientColor = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (field) {
|
||||
tabEnableIcoGradientColor = true
|
||||
}
|
||||
}
|
||||
|
||||
/**是否激活指示器的颜色渐变效果*/
|
||||
var tabEnableIndicatorGradientColor = false
|
||||
|
||||
/**选中的文本颜色*/
|
||||
var tabSelectColor: Int = Color.WHITE //Color.parseColor("#333333")
|
||||
|
||||
/**未选中的文本颜色*/
|
||||
var tabDeselectColor: Int = Color.parseColor("#999999")
|
||||
|
||||
/**是否开启Bold, 文本加粗*/
|
||||
var tabEnableTextBold = false
|
||||
|
||||
/**是否使用粗体字体的方式设置粗体, 否则使用[Paint.FAKE_BOLD_TEXT_FLAG]
|
||||
* 需要先激活[tabEnableTextBold]*/
|
||||
var tabUseTypefaceBold = false
|
||||
|
||||
/**是否开启图标颜色*/
|
||||
var tabEnableIcoColor = true
|
||||
|
||||
/**是否开启图标颜色渐变效果*/
|
||||
var tabEnableIcoGradientColor = false
|
||||
|
||||
/**选中的图标颜色*/
|
||||
var tabIcoSelectColor: Int = NO_COLOR
|
||||
get() {
|
||||
return if (field == NO_COLOR) tabSelectColor else field
|
||||
}
|
||||
|
||||
/**未选中的图标颜色*/
|
||||
var tabIcoDeselectColor: Int = NO_COLOR
|
||||
get() {
|
||||
return if (field == NO_COLOR) tabDeselectColor else field
|
||||
}
|
||||
|
||||
/**是否开启scale渐变效果*/
|
||||
var tabEnableGradientScale = false
|
||||
|
||||
/**最小缩放的比例*/
|
||||
var tabMinScale = 0.8f
|
||||
|
||||
/**最大缩放的比例*/
|
||||
var tabMaxScale = 1.2f
|
||||
|
||||
/**是否开启字体大小渐变效果*/
|
||||
var tabEnableGradientTextSize = true
|
||||
|
||||
/**tab中文本字体未选中时的字体大小, >0时激活*/
|
||||
var tabTextMinSize = -1f
|
||||
|
||||
/**tab中文本字体选中时的字体大小, >0时激活*/
|
||||
var tabTextMaxSize = -1f
|
||||
|
||||
/**渐变效果实现的回调*/
|
||||
var tabGradientCallback = TabGradientCallback()
|
||||
|
||||
/**指定文本控件的id, 所有文本属性改变, 将会发生在这个控件上.
|
||||
* 如果指定的控件不存在, 控件会降权至[ItemView]*/
|
||||
@IdRes
|
||||
var tabTextViewId: Int = View.NO_ID
|
||||
|
||||
/**指定图标控件的id*/
|
||||
@IdRes
|
||||
var tabIconViewId: Int = View.NO_ID
|
||||
|
||||
/**返回用于配置文本样式的控件*/
|
||||
var onGetTextStyleView: (itemView: View, index: Int) -> TextView? = { itemView, _ ->
|
||||
if (tabTextViewId == View.NO_ID) {
|
||||
var tv: TextView? = if (itemView is TextView) itemView else null
|
||||
|
||||
if (tabLayout.tabIndicator.indicatorContentIndex != -1) {
|
||||
itemView.getChildOrNull(tabLayout.tabIndicator.indicatorContentIndex)?.let {
|
||||
if (it is TextView) {
|
||||
tv = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tabLayout.tabIndicator.indicatorContentId != View.NO_ID) {
|
||||
itemView.findViewById<View>(tabLayout.tabIndicator.indicatorContentId)?.let {
|
||||
if (it is TextView) {
|
||||
tv = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val lp = itemView.layoutParams
|
||||
if (lp is DslTabLayout.LayoutParams) {
|
||||
if (lp.indicatorContentIndex != -1 && itemView is ViewGroup) {
|
||||
itemView.getChildOrNull(lp.indicatorContentIndex)?.let {
|
||||
if (it is TextView) {
|
||||
tv = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lp.indicatorContentId != View.NO_ID) {
|
||||
itemView.findViewById<View>(lp.indicatorContentId)?.let {
|
||||
if (it is TextView) {
|
||||
tv = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lp.contentTextViewIndex != -1 && itemView is ViewGroup) {
|
||||
itemView.getChildOrNull(lp.contentTextViewIndex)?.let {
|
||||
if (it is TextView) {
|
||||
tv = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lp.contentTextViewId != View.NO_ID) {
|
||||
itemView.findViewById<View>(lp.contentTextViewId)?.let {
|
||||
if (it is TextView) {
|
||||
tv = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tv
|
||||
} else {
|
||||
itemView.findViewById(tabTextViewId)
|
||||
}
|
||||
}
|
||||
|
||||
/**返回用于配置ico样式的控件*/
|
||||
var onGetIcoStyleView: (itemView: View, index: Int) -> View? = { itemView, _ ->
|
||||
if (tabIconViewId == View.NO_ID) {
|
||||
var iv: View? = itemView
|
||||
|
||||
if (tabLayout.tabIndicator.indicatorContentIndex != -1) {
|
||||
itemView.getChildOrNull(tabLayout.tabIndicator.indicatorContentIndex)?.let {
|
||||
iv = it
|
||||
}
|
||||
}
|
||||
|
||||
if (tabLayout.tabIndicator.indicatorContentId != View.NO_ID) {
|
||||
itemView.findViewById<View>(tabLayout.tabIndicator.indicatorContentId)?.let {
|
||||
iv = it
|
||||
}
|
||||
}
|
||||
|
||||
val lp = itemView.layoutParams
|
||||
if (lp is DslTabLayout.LayoutParams) {
|
||||
if (lp.indicatorContentIndex != -1 && itemView is ViewGroup) {
|
||||
iv = itemView.getChildOrNull(lp.indicatorContentIndex)
|
||||
}
|
||||
|
||||
if (lp.indicatorContentId != View.NO_ID) {
|
||||
itemView.findViewById<View>(lp.indicatorContentId)?.let {
|
||||
iv = it
|
||||
}
|
||||
}
|
||||
|
||||
if (lp.contentIconViewIndex != -1 && itemView is ViewGroup) {
|
||||
iv = itemView.getChildOrNull(lp.contentIconViewIndex)
|
||||
}
|
||||
|
||||
if (lp.contentIconViewId != View.NO_ID) {
|
||||
itemView.findViewById<View>(lp.contentIconViewId)?.let {
|
||||
iv = it
|
||||
}
|
||||
}
|
||||
}
|
||||
iv
|
||||
} else {
|
||||
itemView.findViewById(tabIconViewId)
|
||||
}
|
||||
}
|
||||
|
||||
/**获取渐变结束时,指示器的颜色.*/
|
||||
var onGetGradientIndicatorColor: (fromIndex: Int, toIndex: Int, positionOffset: Float) -> Int =
|
||||
{ fromIndex, toIndex, positionOffset ->
|
||||
tabLayout.tabIndicator.indicatorColor
|
||||
}
|
||||
|
||||
init {
|
||||
onStyleItemView = { itemView, index, select ->
|
||||
onUpdateItemStyle(itemView, index, select)
|
||||
}
|
||||
onSelectIndexChange = { fromIndex, selectIndexList, reselect, fromUser ->
|
||||
val toIndex = selectIndexList.last()
|
||||
tabLayout._viewPagerDelegate?.onSetCurrentItem(fromIndex, toIndex, reselect, fromUser)
|
||||
}
|
||||
}
|
||||
|
||||
/**xml属性读取*/
|
||||
open fun initAttribute(context: Context, attributeSet: AttributeSet? = null) {
|
||||
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
|
||||
|
||||
tabSelectColor =
|
||||
typedArray.getColor(R.styleable.DslTabLayout_tab_select_color, tabSelectColor)
|
||||
tabDeselectColor =
|
||||
typedArray.getColor(
|
||||
R.styleable.DslTabLayout_tab_deselect_color,
|
||||
tabDeselectColor
|
||||
)
|
||||
tabIcoSelectColor =
|
||||
typedArray.getColor(R.styleable.DslTabLayout_tab_ico_select_color, NO_COLOR)
|
||||
tabIcoDeselectColor =
|
||||
typedArray.getColor(R.styleable.DslTabLayout_tab_ico_deselect_color, NO_COLOR)
|
||||
|
||||
tabEnableTextColor = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_enable_text_color,
|
||||
tabEnableTextColor
|
||||
)
|
||||
tabEnableIndicatorGradientColor = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_enable_indicator_gradient_color,
|
||||
tabEnableIndicatorGradientColor
|
||||
)
|
||||
tabEnableGradientColor = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_enable_gradient_color,
|
||||
tabEnableGradientColor
|
||||
)
|
||||
tabEnableIcoColor = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_enable_ico_color,
|
||||
tabEnableIcoColor
|
||||
)
|
||||
tabEnableIcoGradientColor = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_enable_ico_gradient_color,
|
||||
tabEnableIcoGradientColor
|
||||
)
|
||||
|
||||
tabEnableTextBold = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_enable_text_bold,
|
||||
tabEnableTextBold
|
||||
)
|
||||
|
||||
tabUseTypefaceBold = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_use_typeface_bold,
|
||||
tabUseTypefaceBold
|
||||
)
|
||||
|
||||
tabEnableGradientScale = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_enable_gradient_scale,
|
||||
tabEnableGradientScale
|
||||
)
|
||||
tabMinScale = typedArray.getFloat(R.styleable.DslTabLayout_tab_min_scale, tabMinScale)
|
||||
tabMaxScale = typedArray.getFloat(R.styleable.DslTabLayout_tab_max_scale, tabMaxScale)
|
||||
|
||||
tabEnableGradientTextSize = typedArray.getBoolean(
|
||||
R.styleable.DslTabLayout_tab_enable_gradient_text_size,
|
||||
tabEnableGradientTextSize
|
||||
)
|
||||
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_text_min_size)) {
|
||||
tabTextMinSize = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_text_min_size,
|
||||
tabTextMinSize.toInt()
|
||||
).toFloat()
|
||||
}
|
||||
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_text_max_size)) {
|
||||
tabTextMaxSize = typedArray.getDimensionPixelOffset(
|
||||
R.styleable.DslTabLayout_tab_text_max_size,
|
||||
tabTextMaxSize.toInt()
|
||||
).toFloat()
|
||||
}
|
||||
|
||||
tabTextViewId =
|
||||
typedArray.getResourceId(R.styleable.DslTabLayout_tab_text_view_id, tabTextViewId)
|
||||
tabIconViewId =
|
||||
typedArray.getResourceId(R.styleable.DslTabLayout_tab_icon_view_id, tabIconViewId)
|
||||
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
/**更新item的样式*/
|
||||
open fun onUpdateItemStyle(itemView: View, index: Int, select: Boolean) {
|
||||
//"$itemView\n$index\n$select".logw()
|
||||
|
||||
(onGetTextStyleView(itemView, index))?.apply {
|
||||
//文本加粗
|
||||
paint?.apply {
|
||||
if (tabEnableTextBold && select) {
|
||||
//设置粗体
|
||||
if (tabUseTypefaceBold) {
|
||||
typeface = Typeface.defaultFromStyle(Typeface.BOLD)
|
||||
} else {
|
||||
flags = flags or Paint.FAKE_BOLD_TEXT_FLAG
|
||||
isFakeBoldText = true
|
||||
}
|
||||
} else {
|
||||
//取消粗体
|
||||
if (tabUseTypefaceBold) {
|
||||
typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
|
||||
} else {
|
||||
flags = flags and Paint.FAKE_BOLD_TEXT_FLAG.inv()
|
||||
isFakeBoldText = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tabEnableTextColor) {
|
||||
//文本颜色
|
||||
setTextColor(if (select) tabSelectColor else tabDeselectColor)
|
||||
}
|
||||
|
||||
if (tabTextMaxSize > 0 || tabTextMinSize > 0) {
|
||||
//文本字体大小
|
||||
val minTextSize = min(tabTextMinSize, tabTextMaxSize)
|
||||
val maxTextSize = max(tabTextMinSize, tabTextMaxSize)
|
||||
setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX,
|
||||
if (select) maxTextSize else minTextSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (tabEnableIcoColor) {
|
||||
onGetIcoStyleView(itemView, index)?.apply {
|
||||
_updateIcoColor(this, if (select) tabIcoSelectColor else tabIcoDeselectColor)
|
||||
}
|
||||
}
|
||||
|
||||
if (tabEnableGradientScale) {
|
||||
itemView.scaleX = if (select) tabMaxScale else tabMinScale
|
||||
itemView.scaleY = if (select) tabMaxScale else tabMinScale
|
||||
}
|
||||
|
||||
if (tabLayout.drawBorder) {
|
||||
tabLayout.tabBorder?.updateItemBackground(tabLayout, itemView, index, select)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [DslTabLayout]滚动时回调.
|
||||
* */
|
||||
open fun onPageIndexScrolled(fromIndex: Int, toIndex: Int, positionOffset: Float) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* [onPageIndexScrolled]
|
||||
* */
|
||||
open fun onPageViewScrolled(fromView: View?, toView: View, positionOffset: Float) {
|
||||
//"$fromView\n$toView\n$positionOffset".logi()
|
||||
|
||||
if (fromView != toView) {
|
||||
|
||||
val fromIndex = tabLayout.tabIndicator.currentIndex
|
||||
val toIndex = tabLayout.tabIndicator._targetIndex
|
||||
|
||||
if (tabEnableIndicatorGradientColor) {
|
||||
val startColor = onGetGradientIndicatorColor(fromIndex, fromIndex, 0f)
|
||||
val endColor = onGetGradientIndicatorColor(fromIndex, toIndex, positionOffset)
|
||||
|
||||
tabLayout.tabIndicator.indicatorColor =
|
||||
evaluateColor(positionOffset, startColor, endColor)
|
||||
}
|
||||
|
||||
if (tabEnableGradientColor) {
|
||||
//文本渐变
|
||||
fromView?.apply {
|
||||
_gradientColor(
|
||||
onGetTextStyleView(this, fromIndex),
|
||||
tabSelectColor,
|
||||
tabDeselectColor,
|
||||
positionOffset
|
||||
)
|
||||
}
|
||||
_gradientColor(
|
||||
onGetTextStyleView(toView, toIndex),
|
||||
tabDeselectColor,
|
||||
tabSelectColor,
|
||||
positionOffset
|
||||
)
|
||||
}
|
||||
|
||||
if (tabEnableIcoGradientColor) {
|
||||
//图标渐变
|
||||
fromView?.apply {
|
||||
_gradientIcoColor(
|
||||
onGetIcoStyleView(this, fromIndex),
|
||||
tabIcoSelectColor,
|
||||
tabIcoDeselectColor,
|
||||
positionOffset
|
||||
)
|
||||
}
|
||||
|
||||
_gradientIcoColor(
|
||||
onGetIcoStyleView(toView, toIndex),
|
||||
tabIcoDeselectColor,
|
||||
tabIcoSelectColor,
|
||||
positionOffset
|
||||
)
|
||||
}
|
||||
|
||||
if (tabEnableGradientScale) {
|
||||
//scale渐变
|
||||
_gradientScale(fromView, tabMaxScale, tabMinScale, positionOffset)
|
||||
_gradientScale(toView, tabMinScale, tabMaxScale, positionOffset)
|
||||
}
|
||||
|
||||
if (tabEnableGradientTextSize &&
|
||||
tabTextMaxSize > 0 &&
|
||||
tabTextMinSize > 0 &&
|
||||
tabTextMinSize != tabTextMaxSize
|
||||
) {
|
||||
|
||||
//文本字体大小渐变
|
||||
_gradientTextSize(
|
||||
fromView?.run { onGetTextStyleView(this, fromIndex) },
|
||||
tabTextMaxSize,
|
||||
tabTextMinSize,
|
||||
positionOffset
|
||||
)
|
||||
_gradientTextSize(
|
||||
onGetTextStyleView(toView, toIndex),
|
||||
tabTextMinSize,
|
||||
tabTextMaxSize,
|
||||
positionOffset
|
||||
)
|
||||
|
||||
if (toIndex == tabLayout.dslSelector.visibleViewList.lastIndex || toIndex == 0) {
|
||||
tabLayout._scrollToTarget(toIndex, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun _gradientColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
|
||||
tabGradientCallback.onGradientColor(view, startColor, endColor, percent)
|
||||
}
|
||||
|
||||
open fun _gradientIcoColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
|
||||
tabGradientCallback.onGradientIcoColor(view, startColor, endColor, percent)
|
||||
}
|
||||
|
||||
open fun _gradientScale(view: View?, startScale: Float, endScale: Float, percent: Float) {
|
||||
tabGradientCallback.onGradientScale(view, startScale, endScale, percent)
|
||||
}
|
||||
|
||||
open fun _gradientTextSize(
|
||||
view: TextView?,
|
||||
startTextSize: Float,
|
||||
endTextSize: Float,
|
||||
percent: Float
|
||||
) {
|
||||
tabGradientCallback.onGradientTextSize(view, startTextSize, endTextSize, percent)
|
||||
}
|
||||
|
||||
open fun _updateIcoColor(view: View?, color: Int) {
|
||||
tabGradientCallback.onUpdateIcoColor(view, color)
|
||||
}
|
||||
}
|
||||
|
||||
open class TabGradientCallback {
|
||||
|
||||
open fun onGradientColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
|
||||
(view as? TextView)?.apply {
|
||||
setTextColor(evaluateColor(percent, startColor, endColor))
|
||||
}
|
||||
}
|
||||
|
||||
open fun onGradientIcoColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
|
||||
onUpdateIcoColor(view, evaluateColor(percent, startColor, endColor))
|
||||
}
|
||||
|
||||
open fun onUpdateIcoColor(view: View?, color: Int) {
|
||||
view?.tintDrawableColor(color)
|
||||
}
|
||||
|
||||
open fun onGradientScale(view: View?, startScale: Float, endScale: Float, percent: Float) {
|
||||
view?.apply {
|
||||
(startScale + (endScale - startScale) * percent).let {
|
||||
scaleX = it
|
||||
scaleY = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun onGradientTextSize(
|
||||
view: TextView?,
|
||||
startTextSize: Float,
|
||||
endTextSize: Float,
|
||||
percent: Float
|
||||
) {
|
||||
view?.apply {
|
||||
setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX,
|
||||
(startTextSize + (endTextSize - startTextSize) * percent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.graphics.Canvas
|
||||
|
||||
/**
|
||||
* 用来实现[DslTabIndicator]的自绘
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2022/02/21
|
||||
* Copyright (c) 2020 ShenZhen Wayto Ltd. All rights reserved.
|
||||
*/
|
||||
interface ITabIndicatorDraw {
|
||||
|
||||
/**绘制指示器
|
||||
* [positionOffset] 页面偏移量*/
|
||||
fun onDrawTabIndicator(
|
||||
tabIndicator: DslTabIndicator,
|
||||
canvas: Canvas,
|
||||
positionOffset: Float
|
||||
)
|
||||
|
||||
}
|
334
TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt
Normal file
334
TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt
Normal file
@ -0,0 +1,334 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.math.MathUtils
|
||||
|
||||
/**
|
||||
*
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/11/23
|
||||
*/
|
||||
internal val dpi: Int
|
||||
get() = dp.toInt()
|
||||
|
||||
internal val dp: Float
|
||||
get() = Resources.getSystem().displayMetrics.density
|
||||
|
||||
internal val View.dpi: Int
|
||||
get() = context.resources.displayMetrics.density.toInt()
|
||||
|
||||
internal val View.screenWidth: Int
|
||||
get() = context.resources.displayMetrics.widthPixels
|
||||
|
||||
internal val View.screenHeight: Int
|
||||
get() = context.resources.displayMetrics.heightPixels
|
||||
|
||||
internal val View.viewDrawWidth: Int
|
||||
get() = measuredWidth - paddingLeft - paddingRight
|
||||
|
||||
internal val View.viewDrawHeight: Int
|
||||
get() = measuredHeight - paddingTop - paddingBottom
|
||||
|
||||
/**Match_Parent*/
|
||||
internal fun exactlyMeasure(size: Int): Int =
|
||||
View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY)
|
||||
|
||||
internal fun exactlyMeasure(size: Float): Int = exactlyMeasure(size.toInt())
|
||||
|
||||
/**Wrap_Content*/
|
||||
internal fun atmostMeasure(size: Int): Int =
|
||||
View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.AT_MOST)
|
||||
|
||||
internal fun Int.have(value: Int): Boolean = if (this == 0 || value == 0) {
|
||||
false
|
||||
} else if (this == 0 && value == 0) {
|
||||
true
|
||||
} else {
|
||||
((this > 0 && value > 0) || (this < 0 && value < 0)) && this and value == value
|
||||
}
|
||||
|
||||
internal fun Int.remove(value: Int): Int = this and value.inv()
|
||||
|
||||
internal fun clamp(value: Float, min: Float, max: Float): Float {
|
||||
if (value < min) {
|
||||
return min
|
||||
} else if (value > max) {
|
||||
return max
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
internal fun clamp(value: Int, min: Int, max: Int): Int {
|
||||
if (value < min) {
|
||||
return min
|
||||
} else if (value > max) {
|
||||
return max
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
internal fun Any.logi() {
|
||||
Log.i("DslTabLayout", "$this")
|
||||
}
|
||||
|
||||
internal fun Any.logw() {
|
||||
Log.w("DslTabLayout", "$this")
|
||||
}
|
||||
|
||||
internal fun Any.loge() {
|
||||
Log.e("DslTabLayout", "$this")
|
||||
}
|
||||
|
||||
internal fun View.calcLayoutWidthHeight(
|
||||
rLayoutWidth: String?, rLayoutHeight: String?,
|
||||
parentWidth: Int, parentHeight: Int,
|
||||
rLayoutWidthExclude: Int = 0, rLayoutHeightExclude: Int = 0
|
||||
): IntArray {
|
||||
val size = intArrayOf(-1, -1)
|
||||
if (TextUtils.isEmpty(rLayoutWidth) && TextUtils.isEmpty(rLayoutHeight)) {
|
||||
return size
|
||||
}
|
||||
if (!TextUtils.isEmpty(rLayoutWidth)) {
|
||||
if (rLayoutWidth!!.contains("sw", true)) {
|
||||
val ratio = rLayoutWidth.replace("sw", "", true).toFloatOrNull()
|
||||
ratio?.let {
|
||||
size[0] = (ratio * (screenWidth - rLayoutWidthExclude)).toInt()
|
||||
}
|
||||
} else if (rLayoutWidth!!.contains("pw", true)) {
|
||||
val ratio = rLayoutWidth.replace("pw", "", true).toFloatOrNull()
|
||||
ratio?.let {
|
||||
size[0] = (ratio * (parentWidth - rLayoutWidthExclude)).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(rLayoutHeight)) {
|
||||
if (rLayoutHeight!!.contains("sh", true)) {
|
||||
val ratio = rLayoutHeight.replace("sh", "", true).toFloatOrNull()
|
||||
ratio?.let {
|
||||
size[1] = (ratio * (screenHeight - rLayoutHeightExclude)).toInt()
|
||||
}
|
||||
} else if (rLayoutHeight!!.contains("ph", true)) {
|
||||
val ratio = rLayoutHeight.replace("ph", "", true).toFloatOrNull()
|
||||
ratio?.let {
|
||||
size[1] = (ratio * (parentHeight - rLayoutHeightExclude)).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
internal fun evaluateColor(fraction: Float /*0-1*/, startColor: Int, endColor: Int): Int {
|
||||
val fr = MathUtils.clamp(fraction, 0f, 1f)
|
||||
val startA = startColor shr 24 and 0xff
|
||||
val startR = startColor shr 16 and 0xff
|
||||
val startG = startColor shr 8 and 0xff
|
||||
val startB = startColor and 0xff
|
||||
val endA = endColor shr 24 and 0xff
|
||||
val endR = endColor shr 16 and 0xff
|
||||
val endG = endColor shr 8 and 0xff
|
||||
val endB = endColor and 0xff
|
||||
return startA + (fr * (endA - startA)).toInt() shl 24 or
|
||||
(startR + (fr * (endR - startR)).toInt() shl 16) or
|
||||
(startG + (fr * (endG - startG)).toInt() shl 8) or
|
||||
startB + (fr * (endB - startB)).toInt()
|
||||
}
|
||||
|
||||
internal fun Drawable?.tintDrawableColor(color: Int): Drawable? {
|
||||
|
||||
if (this == null) {
|
||||
return this
|
||||
}
|
||||
|
||||
val wrappedDrawable =
|
||||
DrawableCompat.wrap(this).mutate()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
DrawableCompat.setTint(wrappedDrawable, color)
|
||||
} else {
|
||||
wrappedDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
|
||||
}
|
||||
|
||||
return wrappedDrawable
|
||||
}
|
||||
|
||||
internal fun View?.tintDrawableColor(color: Int) {
|
||||
when (this) {
|
||||
is TextView -> {
|
||||
val drawables = arrayOfNulls<Drawable?>(4)
|
||||
compoundDrawables.forEachIndexed { index, drawable ->
|
||||
drawables[index] = drawable?.tintDrawableColor(color)
|
||||
}
|
||||
setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3])
|
||||
}
|
||||
is ImageView -> {
|
||||
setImageDrawable(drawable?.tintDrawableColor(color))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Paint?.textWidth(text: String?): Float {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return 0f
|
||||
}
|
||||
return this?.run {
|
||||
measureText(text)
|
||||
} ?: 0f
|
||||
}
|
||||
|
||||
internal fun Paint?.textHeight(): Float = this?.run { descent() - ascent() } ?: 0f
|
||||
|
||||
internal fun View.getChildOrNull(index: Int): View? {
|
||||
return if (this is ViewGroup) {
|
||||
return if (index in 0 until childCount) {
|
||||
getChildAt(index)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/**获取[View]在指定[parent]中的矩形坐标*/
|
||||
internal fun View.getLocationInParent(parentView: View? = null, result: Rect = Rect()): Rect {
|
||||
val parent: View? = parentView ?: (parent as? View)
|
||||
|
||||
if (parent == null) {
|
||||
getViewRect(result)
|
||||
} else {
|
||||
result.set(0, 0, 0, 0)
|
||||
if (this != parent) {
|
||||
fun doIt(view: View, parent: View, rect: Rect) {
|
||||
val viewParent = view.parent
|
||||
if (viewParent is View) {
|
||||
rect.left += view.left
|
||||
rect.top += view.top
|
||||
if (viewParent != parent) {
|
||||
doIt(viewParent, parent, rect)
|
||||
}
|
||||
}
|
||||
}
|
||||
doIt(this, parent, result)
|
||||
}
|
||||
result.right = result.left + this.measuredWidth
|
||||
result.bottom = result.top + this.measuredHeight
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取View, 相对于手机屏幕的矩形
|
||||
* */
|
||||
internal fun View.getViewRect(result: Rect = Rect()): Rect {
|
||||
var offsetX = 0
|
||||
var offsetY = 0
|
||||
|
||||
//横屏, 并且显示了虚拟导航栏的时候. 需要左边偏移
|
||||
//只计算一次
|
||||
(context as? Activity)?.let {
|
||||
it.window.decorView.getGlobalVisibleRect(result)
|
||||
if (result.width() > result.height()) {
|
||||
//横屏了
|
||||
offsetX = navBarHeight(it)
|
||||
}
|
||||
}
|
||||
|
||||
return getViewRect(offsetX, offsetY, result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取View, 相对于手机屏幕的矩形, 带皮阿尼一
|
||||
* */
|
||||
internal fun View.getViewRect(offsetX: Int, offsetY: Int, result: Rect = Rect()): Rect {
|
||||
//可见位置的坐标, 超出屏幕的距离会被剃掉
|
||||
//getGlobalVisibleRect(r)
|
||||
val r2 = IntArray(2)
|
||||
//val r3 = IntArray(2)
|
||||
//相对于屏幕的坐标
|
||||
getLocationOnScreen(r2)
|
||||
//相对于窗口的坐标
|
||||
//getLocationInWindow(r3)
|
||||
|
||||
val left = r2[0] + offsetX
|
||||
val top = r2[1] + offsetY
|
||||
|
||||
result.set(left, top, left + measuredWidth, top + measuredHeight)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 导航栏的高度(如果显示了)
|
||||
*/
|
||||
internal fun navBarHeight(context: Context): Int {
|
||||
var result = 0
|
||||
|
||||
if (context is Activity) {
|
||||
val decorRect = Rect()
|
||||
val windowRect = Rect()
|
||||
|
||||
context.window.decorView.getGlobalVisibleRect(decorRect)
|
||||
context.window.findViewById<View>(Window.ID_ANDROID_CONTENT)
|
||||
.getGlobalVisibleRect(windowRect)
|
||||
|
||||
if (decorRect.width() > decorRect.height()) {
|
||||
//横屏
|
||||
result = decorRect.width() - windowRect.width()
|
||||
} else {
|
||||
//竖屏
|
||||
result = decorRect.bottom - windowRect.bottom
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun Collection<*>?.size() = this?.size ?: 0
|
||||
|
||||
/**判断2个列表中的数据是否改变过*/
|
||||
internal fun <T> List<T>?.isChange(other: List<T>?): Boolean {
|
||||
if (this.size() != other.size()) {
|
||||
return true
|
||||
}
|
||||
this?.forEachIndexed { index, t ->
|
||||
if (t != other?.getOrNull(index)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun Int.isHorizontal() = this == LinearLayout.HORIZONTAL
|
||||
|
||||
fun Int.isVertical() = this == LinearLayout.VERTICAL
|
||||
|
||||
internal fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = true): View {
|
||||
if (layoutId == -1) {
|
||||
return this
|
||||
}
|
||||
val rootView = LayoutInflater.from(context).inflate(layoutId, this, false)
|
||||
if (attachToRoot) {
|
||||
addView(rootView)
|
||||
}
|
||||
return rootView
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.angcyo.tablayout
|
||||
|
||||
/**
|
||||
* 不依赖ViewPager和ViewPager2
|
||||
* Email:angcyo@126.com
|
||||
* @author angcyo
|
||||
* @date 2019/12/14
|
||||
*/
|
||||
interface ViewPagerDelegate {
|
||||
companion object {
|
||||
const val SCROLL_STATE_IDLE = 0
|
||||
const val SCROLL_STATE_DRAGGING = 1
|
||||
const val SCROLL_STATE_SETTLING = 2
|
||||
}
|
||||
|
||||
/**获取当前页面索引*/
|
||||
fun onGetCurrentItem(): Int
|
||||
|
||||
/**设置当前的页面*/
|
||||
fun onSetCurrentItem(fromIndex: Int, toIndex: Int, reselect: Boolean, fromUser: Boolean)
|
||||
}
|
299
TabLayout/src/main/res/values/attr_dsl_tab_layout.xml
Normal file
299
TabLayout/src/main/res/values/attr_dsl_tab_layout.xml
Normal file
@ -0,0 +1,299 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="DslTabLayout">
|
||||
<!--Item是否等宽-->
|
||||
<attr name="tab_item_is_equ_width" format="boolean" />
|
||||
<!--当子Item数量大于等于指定数量时,开启等宽,此属性优先级最高-->
|
||||
<attr name="tab_item_equ_width_count" format="integer" />
|
||||
<!--[~3] 小于等于3个 [3~] 大于等于3个 [3~5] 3<= <=5 -->
|
||||
<attr name="tab_item_equ_width_count_range" format="string" />
|
||||
<!--智能判断Item是否等宽, 如果所有子项, 未撑满tab时, 开启等宽模式.此属性会覆盖[tab_item_is_equ_width]-->
|
||||
<attr name="tab_item_auto_equ_width" format="boolean" />
|
||||
<!--默认选中的索引值-->
|
||||
<attr name="tab_default_index" format="integer" />
|
||||
<!--等宽模式下, 指定item的宽度. 不指定则平分-->
|
||||
<attr name="tab_item_width" format="dimension" />
|
||||
<!--在TabLayout wrap_content时, child match_parent时的高度-->
|
||||
<attr name="tab_item_default_height" format="dimension" />
|
||||
<!--是否绘制边框-->
|
||||
<attr name="tab_draw_border" format="boolean" />
|
||||
<!--是否绘制分割线-->
|
||||
<attr name="tab_draw_divider" format="boolean" />
|
||||
<!--是否绘制指示器-->
|
||||
<attr name="tab_draw_indicator" format="boolean" />
|
||||
<!--高凸模式下的背景drawable-->
|
||||
<attr name="tab_convex_background" format="reference|color" />
|
||||
<!--是否激活滑动选择模式-->
|
||||
<attr name="tab_enable_selector_mode" format="boolean" />
|
||||
<!--方向-->
|
||||
<attr name="tab_orientation" format="enum">
|
||||
<enum name="VERTICAL" value="1" />
|
||||
<enum name="HORIZONTAL" value="0" />
|
||||
</attr>
|
||||
<attr name="tab_layout_scroll_anim" format="boolean" />
|
||||
<attr name="tab_scroll_anim_duration" format="integer" />
|
||||
<!--预览的布局id-->
|
||||
<attr name="tab_preview_item_layout_id" format="reference" />
|
||||
<!--预览的布局数量-->
|
||||
<attr name="tab_preview_item_count" format="integer" />
|
||||
|
||||
<!--indicator 指示器相关属性-->
|
||||
|
||||
<!--强制指定指示器的Drawable-->
|
||||
<attr name="tab_indicator_drawable" format="reference" />
|
||||
<!--强制指定Drawable的颜色-->
|
||||
<attr name="tab_indicator_color" format="color" />
|
||||
<!--指示器的绘制类型, 可以使用[STYLE_TOP|STYLE_FOREGROUND]组合配置-->
|
||||
<attr name="tab_indicator_style" format="flags">
|
||||
<!--不绘制-->
|
||||
<flag name="STYLE_NONE" value="0" />
|
||||
<flag name="STYLE_TOP" value="0x1" />
|
||||
<flag name="STYLE_BOTTOM" value="0x2" />
|
||||
<flag name="STYLE_CENTER" value="0x4" />
|
||||
<!--前景绘制-->
|
||||
<flag name="STYLE_FOREGROUND" value="0x1000" />
|
||||
</attr>
|
||||
<!--指示器的重力-->
|
||||
<attr name="tab_indicator_gravity" format="enum">
|
||||
<!--指示器靠左显示-->
|
||||
<enum name="GRAVITY_START" value="0x1" />
|
||||
<!--指示器靠右显示-->
|
||||
<enum name="GRAVITY_END" value="0x2" />
|
||||
<!--指示器居中显示-->
|
||||
<enum name="GRAVITY_CENTER" value="0x4" />
|
||||
</attr>
|
||||
<!--是否激活流式效果, ViewPager在滚动时, 指示器的宽度由小变大,再由大变小-->
|
||||
<attr name="tab_indicator_enable_flow" format="boolean" />
|
||||
<!--闪现效果-->
|
||||
<attr name="tab_indicator_enable_flash" format="boolean" />
|
||||
<!--闪现效果使用clip处理-->
|
||||
<attr name="tab_indicator_enable_flash_clip" format="boolean" />
|
||||
<!--tab child的索引相差多少值时, 才开启flow效果-->
|
||||
<attr name="tab_indicator_flow_step" format="integer" />
|
||||
<!--指示器的宽度-->
|
||||
<attr name="tab_indicator_width" format="dimension|flags">
|
||||
<flag name="WRAP_CONTENT" value="-2" />
|
||||
<flag name="MATCH_PARENT" value="-1" />
|
||||
</attr>
|
||||
<!--宽度的补偿-->
|
||||
<attr name="tab_indicator_width_offset" format="dimension" />
|
||||
<!--同上-->
|
||||
<attr name="tab_indicator_height" format="dimension|flags">
|
||||
<flag name="WRAP_CONTENT" value="-2" />
|
||||
<flag name="MATCH_PARENT" value="-1" />
|
||||
</attr>
|
||||
<!--同上-->
|
||||
<attr name="tab_indicator_height_offset" format="dimension" />
|
||||
<!--x轴的补偿-->
|
||||
<attr name="tab_indicator_x_offset" format="dimension" />
|
||||
<!--y轴的补偿, 会根据[tab_indicator_style]的类型, 自动取负值.-->
|
||||
<attr name="tab_indicator_y_offset" format="dimension" />
|
||||
<!--指示器child的锚点索引, 用来辅助定位计算指示器的宽度,高度,中点坐标-->
|
||||
<attr name="tab_indicator_content_index" format="integer" />
|
||||
<!--指示器child的锚点控件id, 用来辅助定位计算指示器的宽度,高度,中点坐标-->
|
||||
<attr name="tab_indicator_content_id" format="reference" />
|
||||
<!--切换指示器时, 是否需要动画的支持-->
|
||||
<attr name="tab_indicator_anim" format="boolean" />
|
||||
|
||||
<!--请参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
|
||||
<attr name="tab_indicator_shape" format="enum">
|
||||
<enum name="RECTANGLE" value="0" />
|
||||
<enum name="OVAL" value="1" />
|
||||
<enum name="LINE" value="2" />
|
||||
<enum name="RING" value="3" />
|
||||
</attr>
|
||||
<attr name="tab_indicator_solid_color" format="color" />
|
||||
<attr name="tab_indicator_stroke_color" format="color" />
|
||||
<attr name="tab_indicator_stroke_width" format="dimension" />
|
||||
<attr name="tab_indicator_dash_width" format="dimension" />
|
||||
<attr name="tab_indicator_dash_gap" format="dimension" />
|
||||
<attr name="tab_indicator_radius" format="dimension" />
|
||||
<attr name="tab_indicator_radii" format="string" />
|
||||
<attr name="tab_indicator_gradient_colors" format="string" />
|
||||
<attr name="tab_indicator_gradient_start_color" format="color" />
|
||||
<attr name="tab_indicator_gradient_end_color" format="color" />
|
||||
<attr name="tab_indicator_ignore_child_padding" format="boolean" />
|
||||
<!--end...-->
|
||||
|
||||
<!--TabLayoutConfig 相关属性-->
|
||||
<!--item选中时 文本的颜色-->
|
||||
<attr name="tab_select_color" format="color" />
|
||||
<!--item未选中时 文本的颜色-->
|
||||
<attr name="tab_deselect_color" format="color" />
|
||||
<!--选中时 图标(Drawable)的颜色, 默认是文本的颜色-->
|
||||
<attr name="tab_ico_select_color" format="color" />
|
||||
<!--未选中时 图标(Drawable)的颜色, 默认是文本的颜色-->
|
||||
<attr name="tab_ico_deselect_color" format="color" />
|
||||
<!--是否激活自动设置文本的颜色-->
|
||||
<attr name="tab_enable_text_color" format="boolean" />
|
||||
<!--是否激活自动设置图标的颜色-->
|
||||
<attr name="tab_enable_ico_color" format="boolean" />
|
||||
<!--是否激活文本变粗-->
|
||||
<attr name="tab_enable_text_bold" format="boolean" />
|
||||
<!--是否使用字体的方式设置变粗效果, 需要先开启[tab_enable_text_bold]-->
|
||||
<attr name="tab_use_typeface_bold" format="boolean" />
|
||||
<!--是否激活文本颜色渐变-->
|
||||
<attr name="tab_enable_gradient_color" format="boolean" />
|
||||
<!--是否激活指示器的颜色渐变效果-->
|
||||
<attr name="tab_enable_indicator_gradient_color" format="boolean" />
|
||||
<!--是否激活图标颜色渐变-->
|
||||
<attr name="tab_enable_ico_gradient_color" format="boolean" />
|
||||
<!--是否激活缩放渐变-->
|
||||
<attr name="tab_enable_gradient_scale" format="boolean" />
|
||||
<!--缩放渐变的最小值-->
|
||||
<attr name="tab_min_scale" format="float" />
|
||||
<!--缩放渐变的最大值-->
|
||||
<attr name="tab_max_scale" format="float" />
|
||||
<!--是否激活文本大小渐变-->
|
||||
<attr name="tab_enable_gradient_text_size" format="boolean" />
|
||||
<!--文本字体大小最小值-->
|
||||
<attr name="tab_text_min_size" format="dimension" />
|
||||
<!--文本字体大小最大值-->
|
||||
<attr name="tab_text_max_size" format="dimension" />
|
||||
<!--指定文本控件的id, 所有文本属性改变, 将会发生在这个控件上, 如果指定的控件不存在, 控件会降权至[ItemView]-->
|
||||
<attr name="tab_text_view_id" format="reference" />
|
||||
<!--指定图标控件的id, 同上-->
|
||||
<attr name="tab_icon_view_id" format="reference" />
|
||||
<!--end...-->
|
||||
|
||||
<!--Divider 分割线相关属性-->
|
||||
<!--强制指定分割线的Drawable-->
|
||||
<attr name="tab_divider_drawable" format="reference" />
|
||||
<!--参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
|
||||
<attr name="tab_divider_stroke_color" format="color" />
|
||||
<attr name="tab_divider_solid_color" format="color" />
|
||||
<attr name="tab_divider_stroke_width" format="dimension" />
|
||||
<attr name="tab_divider_radius_size" format="dimension" />
|
||||
<!--分割线margin距离-->
|
||||
<attr name="tab_divider_margin_left" format="dimension" />
|
||||
<attr name="tab_divider_margin_right" format="dimension" />
|
||||
<attr name="tab_divider_margin_top" format="dimension" />
|
||||
<attr name="tab_divider_margin_bottom" format="dimension" />
|
||||
<!--分割线的宽度-->
|
||||
<attr name="tab_divider_width" format="dimension" />
|
||||
<attr name="tab_divider_height" format="dimension" />
|
||||
<!--分割线显示的位置-->
|
||||
<attr name="tab_divider_show_mode" format="flags">
|
||||
<flag name="SHOW_DIVIDER_BEGINNING" value="1" />
|
||||
<flag name="SHOW_DIVIDER_MIDDLE" value="2" />
|
||||
<flag name="SHOW_DIVIDER_END" value="4" />
|
||||
</attr>
|
||||
<!--end...-->
|
||||
|
||||
<!--Border 边框相关属性-->
|
||||
<!--强制指定边框的Drawable-->
|
||||
<attr name="tab_border_drawable" format="reference" />
|
||||
<!--参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
|
||||
<attr name="tab_border_stroke_color" format="color" />
|
||||
<attr name="tab_border_solid_color" format="color" />
|
||||
<attr name="tab_border_stroke_width" format="dimension" />
|
||||
<attr name="tab_border_radius_size" format="dimension" />
|
||||
<!--边框是否要负责绘制item的背景, 可以自动根据边框的圆角自动配置给item-->
|
||||
<attr name="tab_border_draw_item_background" format="boolean" />
|
||||
<!--高度补偿-->
|
||||
<attr name="tab_border_item_background_height_offset" format="dimension" />
|
||||
<!--宽度补偿-->
|
||||
<attr name="tab_border_item_background_width_offset" format="dimension" />
|
||||
<attr name="tab_border_keep_item_radius" format="boolean" />
|
||||
<attr name="tab_border_item_background_solid_color" format="color" />
|
||||
<attr name="tab_border_item_background_solid_disable_color" format="color" />
|
||||
<attr name="tab_border_item_background_gradient_start_color" format="color" />
|
||||
<attr name="tab_border_item_background_gradient_end_color" format="color" />
|
||||
<!--end...-->
|
||||
|
||||
<!--Badge 角标相关属性-->
|
||||
<!--是否绘制角标-->
|
||||
<attr name="tab_draw_badge" format="boolean" />
|
||||
<!--角标的背景填充颜色-->
|
||||
<attr name="tab_badge_solid_color" format="color" />
|
||||
<!--角标文本的颜色-->
|
||||
<attr name="tab_badge_text_color" format="color" />
|
||||
<!--圆点状态时的半径大小-->
|
||||
<attr name="tab_badge_circle_radius" format="dimension" />
|
||||
<!--角标的圆角半径-->
|
||||
<attr name="tab_badge_radius" format="dimension" />
|
||||
<!--角标重力-->
|
||||
<attr name="tab_badge_gravity" format="flags">
|
||||
<flag name="top" value="0x30" />
|
||||
<flag name="bottom" value="0x50" />
|
||||
<flag name="left" value="0x03" />
|
||||
<flag name="right" value="0x05" />
|
||||
<flag name="center_vertical" value="0x10" />
|
||||
<flag name="center_horizontal" value="0x01" />
|
||||
<flag name="center" value="0x11" />
|
||||
</attr>
|
||||
<!--x轴方向的偏移量, 会根据[Gravity]自动取负值-->
|
||||
<attr name="tab_badge_offset_x" format="dimension" />
|
||||
<!--同上-->
|
||||
<attr name="tab_badge_offset_y" format="dimension" />
|
||||
<!--参考[View]的padding属性-->
|
||||
<attr name="tab_badge_padding_left" format="dimension" />
|
||||
<attr name="tab_badge_padding_right" format="dimension" />
|
||||
<attr name="tab_badge_padding_top" format="dimension" />
|
||||
<attr name="tab_badge_padding_bottom" format="dimension" />
|
||||
<!--角标的文本内容, 多用于xml预览-->
|
||||
<attr name="tab_badge_text" format="string" />
|
||||
<!--角标的文本字体大小-->
|
||||
<attr name="tab_badge_text_size" format="dimension" />
|
||||
|
||||
<!--角标[tab_badge_gravity]定位锚点-->
|
||||
<attr name="tab_badge_anchor_child_index" format="integer" />
|
||||
<!--是否要忽略锚点view的padding-->
|
||||
<attr name="tab_badge_ignore_child_padding" format="boolean" />
|
||||
<!--角标圆形状态下的单独配置的偏移-->
|
||||
<attr name="tab_badge_circle_offset_x" format="dimension" />
|
||||
<!--角标圆形状态下的单独配置的偏移-->
|
||||
<attr name="tab_badge_circle_offset_y" format="dimension" />
|
||||
|
||||
<attr name="tab_badge_stroke_color" format="color" />
|
||||
<attr name="tab_badge_stroke_width" format="dimension" />
|
||||
|
||||
<attr name="tab_badge_min_width" format="dimension|flags">
|
||||
<flag name="WRAP_HEIGHT" value="-1" />
|
||||
<flag name="NONE" value="-2" />
|
||||
</attr>
|
||||
<attr name="tab_badge_min_height" format="dimension">
|
||||
<flag name="NONE" value="-2" />
|
||||
</attr>
|
||||
|
||||
<!--Highlight 突出相关属性-->
|
||||
<attr name="tab_draw_highlight" format="boolean" />
|
||||
<attr name="tab_highlight_drawable" format="reference" />
|
||||
<attr name="tab_highlight_width_offset" format="dimension" />
|
||||
<attr name="tab_highlight_height_offset" format="dimension" />
|
||||
<!--突出的宽度-->
|
||||
<attr name="tab_highlight_width" format="dimension|flags">
|
||||
<flag name="WRAP_CONTENT" value="-2" />
|
||||
<flag name="MATCH_PARENT" value="-1" />
|
||||
</attr>
|
||||
<attr name="tab_highlight_height" format="dimension|flags">
|
||||
<flag name="WRAP_CONTENT" value="-2" />
|
||||
<flag name="MATCH_PARENT" value="-1" />
|
||||
</attr>
|
||||
<!--end...-->
|
||||
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="DslTabLayout_Layout">
|
||||
<!--支持按比例设置宽度, sw屏幕宽度 pw父宽度, 0.5sw:屏幕宽度的0.5倍, 0.3pw:父宽度的0.3倍-->
|
||||
<attr name="layout_tab_width" format="string" />
|
||||
<!--同上 sh, ph-->
|
||||
<attr name="layout_tab_height" format="string" />
|
||||
<!--高凸模式, 需要高凸的高度-->
|
||||
<attr name="layout_tab_convex_height" format="dimension" />
|
||||
<!--单独指定child的锚点索引-->
|
||||
<attr name="layout_tab_indicator_content_index" format="integer" />
|
||||
<!--单独指定child的锚点控件的id-->
|
||||
<attr name="layout_tab_indicator_content_id" format="reference" />
|
||||
<!--android.widget.LinearLayout.LayoutParams.weight 剩余空间所占比例-->
|
||||
<attr name="layout_tab_weight" format="float" />
|
||||
<!--单独配置的drawable, 可以覆盖tab中的配置-->
|
||||
<attr name="layout_highlight_drawable" format="reference" />
|
||||
|
||||
<attr name="layout_tab_text_view_index" format="integer" />
|
||||
<attr name="layout_tab_icon_view_index" format="integer" />
|
||||
<attr name="layout_tab_text_view_id" format="reference" />
|
||||
<attr name="layout_tab_icon_view_id" format="reference" />
|
||||
</declare-styleable>
|
||||
</resources>
|
@ -107,6 +107,7 @@ dependencies {
|
||||
|
||||
//svga播放器
|
||||
//api rootProject.ext.dependencies["SVGAPlayer"]
|
||||
// api project(path:':SVGAlibrary')//svga
|
||||
implementation 'com.squareup.wire:wire-runtime:4.4.1'
|
||||
|
||||
//七牛云存储
|
||||
@ -237,4 +238,7 @@ dependencies {
|
||||
api 'com.google.android.exoplayer:exoplayer-core:2.18.2@aar'
|
||||
|
||||
api rootProject.ext.dependencies["blank-utilcode"]
|
||||
|
||||
//下标切换器 https://github.com/angcyo/DslTabLayout
|
||||
api project(':TabLayout')
|
||||
}
|
||||
|
BIN
common/src/main/assets/gift_wall_gift_info_light.svga
Normal file
BIN
common/src/main/assets/gift_wall_gift_info_light.svga
Normal file
Binary file not shown.
BIN
common/src/main/assets/gift_wall_light_up.svga
Normal file
BIN
common/src/main/assets/gift_wall_light_up.svga
Normal file
Binary file not shown.
@ -20,6 +20,7 @@ public class Constants {
|
||||
public static final String AVATAR = "avatar";
|
||||
public static final String SIGN = "sign";
|
||||
public static final String TO_UID = "toUid";
|
||||
public static final String TO_UNAME = "toUserName";
|
||||
public static final String INTOINDEX = "intoIndex";
|
||||
public static final String FROM_LIVE_ROOM = "fromLiveRoom";
|
||||
public static final String TO_NAME = "toName";
|
||||
|
@ -0,0 +1,77 @@
|
||||
package com.yunbao.common.adapter;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.makeramen.roundedimageview.RoundedImageView;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.bean.GiftWallInfoBean;
|
||||
import com.yunbao.common.glide.ImgLoader;
|
||||
import com.yunbao.common.utils.WordUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class GiftWallGiftInfoListItemAdapter extends RecyclerView.Adapter<GiftWallGiftInfoListItemAdapter.VH> {
|
||||
List<GiftWallInfoBean.Data> data = new ArrayList<>();
|
||||
|
||||
public void setData(List<GiftWallInfoBean.Data> data) {
|
||||
if (data == null) {
|
||||
data = new ArrayList<>();
|
||||
}
|
||||
this.data = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new VH(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_dialog_gift_wall_gift_info, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
holder.setData(data.get(position), position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
public class VH extends RecyclerView.ViewHolder {
|
||||
TextView tv_rank, user_name, tv_rename;
|
||||
RoundedImageView avatar;
|
||||
|
||||
public VH(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
tv_rank = itemView.findViewById(R.id.tv_rank);
|
||||
user_name = itemView.findViewById(R.id.user_name);
|
||||
tv_rename = itemView.findViewById(R.id.tv_rename);
|
||||
avatar = itemView.findViewById(R.id.avatar);
|
||||
}
|
||||
|
||||
public void setData(GiftWallInfoBean.Data data, int position) {
|
||||
tv_rank.setText(String.format(Locale.getDefault(), "%d", (position + 1)));
|
||||
tv_rank.setTextColor(Color.parseColor("#FCC755"));
|
||||
tv_rank.setTextSize(20);
|
||||
tv_rename.setText(String.format(Locale.getDefault(), "%d", (data.getGift_hall_send_num())));
|
||||
avatar.setVisibility(View.VISIBLE);
|
||||
if (data.getActive_rank_hide() == 1) {
|
||||
user_name.setText(WordUtil.getNewString(R.string.mystery_man));
|
||||
avatar.setImageResource(R.mipmap.hide);
|
||||
} else {
|
||||
user_name.setText(data.getUser_name());
|
||||
ImgLoader.display(itemView.getContext(), data.getAvatar(), avatar);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package com.yunbao.common.adapter;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.opensource.svgaplayer.SVGACallback;
|
||||
import com.opensource.svgaplayer.SVGADrawable;
|
||||
import com.opensource.svgaplayer.SVGAImageView;
|
||||
import com.opensource.svgaplayer.SVGAParser;
|
||||
import com.opensource.svgaplayer.SVGAVideoEntity;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.bean.GiftWallBean;
|
||||
import com.yunbao.common.dialog.GiftWallGiftInfoDialog;
|
||||
import com.yunbao.common.glide.ImgLoader;
|
||||
import com.yunbao.common.manager.base.ACache;
|
||||
import com.yunbao.common.utils.DpUtil;
|
||||
import com.yunbao.common.utils.SVGAViewUtils;
|
||||
import com.yunbao.common.utils.WordUtil;
|
||||
import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GiftWallMainTab1List2Adapter extends RecyclerView.Adapter<GiftWallMainTab1List2Adapter.VH> {
|
||||
Context mContext;
|
||||
List<GiftWallBean.Gift> list;
|
||||
boolean isStar;
|
||||
SVGAVideoEntity drawable;
|
||||
String toUid;
|
||||
boolean isAnchor;
|
||||
private boolean isLiveRoom;
|
||||
|
||||
public GiftWallMainTab1List2Adapter(Context mContext) {
|
||||
this.mContext = mContext;
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void setToUid(String toUid) {
|
||||
this.toUid = toUid;
|
||||
}
|
||||
|
||||
public void setAnchor(boolean anchor) {
|
||||
isAnchor = anchor;
|
||||
}
|
||||
|
||||
public void setList(List<GiftWallBean.Gift> list) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
this.list = list;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setDrawable(SVGAVideoEntity drawable) {
|
||||
this.drawable = drawable;
|
||||
}
|
||||
|
||||
public void setStar(boolean star) {
|
||||
isStar = star;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
|
||||
return new VH(LayoutInflater.from(mContext).inflate(R.layout.item_gift_wall_man_tab1_list_2, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
holder.setData(list.get(position), position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(@NonNull VH holder) {
|
||||
super.onViewAttachedToWindow(holder);
|
||||
holder.giftBg.startAnimation();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(@NonNull VH holder) {
|
||||
super.onViewDetachedFromWindow(holder);
|
||||
holder.giftBg.stopAnimation();
|
||||
|
||||
}
|
||||
|
||||
public void setLiveRoom(boolean isLiveRoom) {
|
||||
this.isLiveRoom = isLiveRoom;
|
||||
}
|
||||
|
||||
public class VH extends RecyclerView.ViewHolder {
|
||||
SVGAImageView giftBg;
|
||||
ImageView gift_soles;
|
||||
ImageView giftImage;
|
||||
TextView gift_name;
|
||||
TextView gift_status;
|
||||
ProgressBar progressBar;
|
||||
|
||||
public VH(View itemView) {
|
||||
super(itemView);
|
||||
giftBg = itemView.findViewById(R.id.gift_bg);
|
||||
gift_soles = itemView.findViewById(R.id.gift_soles);
|
||||
giftImage = itemView.findViewById(R.id.gift);
|
||||
gift_name = itemView.findViewById(R.id.gift_name);
|
||||
gift_status = itemView.findViewById(R.id.gift_status);
|
||||
progressBar = itemView.findViewById(R.id.progressBar);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public void setData(GiftWallBean.Gift gift, int position) {
|
||||
gift_name.setText(WordUtil.isNewZh() ? gift.getGift_name() : gift.getGift_name_en());
|
||||
ImgLoader.display(itemView.getContext(), gift.getGift_icon(), giftImage, 40, 40);
|
||||
giftBg.setClearsAfterDetached(false);
|
||||
giftBg.setClearsAfterStop(false);
|
||||
if (gift.getIlluminate_status() == 1) {
|
||||
gift_status.setText(String.format("%s%d", WordUtil.getNewString(R.string.dialog_gift_wall_list_spinner_up), gift.getGift_hall_send_num()));
|
||||
gift_status.setTextColor(Color.parseColor("#FFFFFF"));
|
||||
gift_soles.setImageResource(getSolesrRes());
|
||||
if (drawable != null) {
|
||||
giftBg.post(() -> {
|
||||
giftBg.setImageDrawable(new SVGADrawable(drawable));
|
||||
giftBg.setLoops(0);
|
||||
giftBg.startAnimation();
|
||||
});
|
||||
}
|
||||
progressBar.setMax(gift.getIlluminate_num());
|
||||
progressBar.setProgress(gift.getGift_hall_send_num());
|
||||
} else {
|
||||
gift_status.setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_spinner_down));
|
||||
gift_status.setTextColor(Color.parseColor("#01071A"));
|
||||
gift_soles.setImageResource(getUnSolesrRes());
|
||||
giftBg.setImageResource(R.mipmap.gift_wall_main_item_bg1);
|
||||
}
|
||||
ViewClicksAntiShake.clicksAntiShake(itemView, new ViewClicksAntiShake.ViewClicksCallBack() {
|
||||
@Override
|
||||
public void onViewClicks() {
|
||||
new GiftWallGiftInfoDialog(mContext, gift.getGift_id() + "", toUid, isAnchor)
|
||||
.setFullWindows(!isLiveRoom)
|
||||
.setLiveRoom(isLiveRoom)
|
||||
.setStar(isStar)
|
||||
.showDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int getSolesrRes() {
|
||||
return isStar ? R.mipmap.gift_wall_main_item_select : R.mipmap.gift_wall_main_item_select1;
|
||||
}
|
||||
|
||||
private int getUnSolesrRes() {
|
||||
return isStar ? R.mipmap.gift_wall_main_item_unselect : R.mipmap.gift_wall_main_item_unselect1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
package com.yunbao.common.adapter;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.makeramen.roundedimageview.RoundedImageView;
|
||||
import com.opensource.svgaplayer.SVGADrawable;
|
||||
import com.opensource.svgaplayer.SVGAImageView;
|
||||
import com.opensource.svgaplayer.SVGAVideoEntity;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.bean.GiftWallBean;
|
||||
import com.yunbao.common.bean.GiftWallTab2Bean;
|
||||
import com.yunbao.common.dialog.GiftWallGiftInfoDialog;
|
||||
import com.yunbao.common.dialog.GiftWallMainTab2ClassicInfoDialog;
|
||||
import com.yunbao.common.glide.ImgLoader;
|
||||
import com.yunbao.common.utils.StringUtil;
|
||||
import com.yunbao.common.utils.WordUtil;
|
||||
import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class GiftWallMainTab2ListAdapter extends RecyclerView.Adapter<GiftWallMainTab2ListAdapter.VH> {
|
||||
Context mContext;
|
||||
List<GiftWallTab2Bean.Gift> list;
|
||||
boolean isStar;
|
||||
String toUid;
|
||||
boolean isAnchor;
|
||||
private boolean isLiveRoom;
|
||||
|
||||
public GiftWallMainTab2ListAdapter(Context mContext) {
|
||||
this.mContext = mContext;
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void setToUid(String toUid) {
|
||||
this.toUid = toUid;
|
||||
}
|
||||
|
||||
public void setAnchor(boolean anchor) {
|
||||
isAnchor = anchor;
|
||||
}
|
||||
|
||||
public void setList(List<GiftWallTab2Bean.Gift> list) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
this.list = list;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
public void setStar(boolean star) {
|
||||
isStar = star;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (isStar) {
|
||||
return new VH(LayoutInflater.from(mContext).inflate(R.layout.item_gift_wall_man_tab2_list_1, parent, false));
|
||||
}
|
||||
return new VH(LayoutInflater.from(mContext).inflate(R.layout.item_gift_wall_man_tab2_list_2, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
holder.setData(list.get(position), position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(@NonNull VH holder) {
|
||||
super.onViewAttachedToWindow(holder);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(@NonNull VH holder) {
|
||||
super.onViewDetachedFromWindow(holder);
|
||||
|
||||
}
|
||||
|
||||
public void setLiveRoom(boolean isLiveRoom) {
|
||||
this.isLiveRoom = isLiveRoom;
|
||||
}
|
||||
|
||||
public class VH extends RecyclerView.ViewHolder {
|
||||
ImageView gift;
|
||||
TextView gift_name;
|
||||
TextView gift_number;
|
||||
RoundedImageView user1Avatar, user2Avatar;
|
||||
TextView anchor_nickname;
|
||||
TextView user_nickname;
|
||||
TextView tv_wait;
|
||||
View imageView8;
|
||||
|
||||
public VH(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
gift = itemView.findViewById(R.id.gift);
|
||||
gift_name = itemView.findViewById(R.id.gift_name);
|
||||
gift_number = itemView.findViewById(R.id.gift_number);
|
||||
user1Avatar = itemView.findViewById(R.id.user1_avatar);
|
||||
user2Avatar = itemView.findViewById(R.id.user2_avatar);
|
||||
anchor_nickname = itemView.findViewById(R.id.anchor_nickname);
|
||||
user_nickname = itemView.findViewById(R.id.user_nickname);
|
||||
tv_wait = itemView.findViewById(R.id.tv_wait);
|
||||
imageView8 = itemView.findViewById(R.id.imageView8);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public void setData(GiftWallTab2Bean.Gift giftData, int position) {
|
||||
gift_name.setText(WordUtil.isNewZh() ? giftData.getGiftName() : giftData.getGiftNameEn());
|
||||
ImgLoader.display(itemView.getContext(), giftData.getGiftIcon(), gift, 60, 60);
|
||||
if (!StringUtil.isEmpty(giftData.getNamingLiveNicename(), giftData.getNamingUserNicename())) {
|
||||
anchor_nickname.setVisibility(View.VISIBLE);
|
||||
user_nickname.setVisibility(View.VISIBLE);
|
||||
gift_number.setVisibility(View.VISIBLE);
|
||||
user1Avatar.setVisibility(View.VISIBLE);
|
||||
user2Avatar.setVisibility(View.VISIBLE);
|
||||
imageView8.setVisibility(View.VISIBLE);
|
||||
tv_wait.setVisibility(View.GONE);
|
||||
gift_number.setText(String.format(Locale.getDefault(), "%d", giftData.getNeedCoinTotal()));
|
||||
ImgLoader.display(mContext, giftData.getNamingLiveAvatar(), user1Avatar, 35, 35);
|
||||
ImgLoader.display(mContext, giftData.getNamingUserAvatar(), user2Avatar, 35, 35);
|
||||
anchor_nickname.setText(giftData.getNamingLiveNicename());
|
||||
user_nickname.setText(giftData.getNamingUserNicename());
|
||||
} else {
|
||||
user1Avatar.setVisibility(View.GONE);
|
||||
user2Avatar.setVisibility(View.GONE);
|
||||
anchor_nickname.setVisibility(View.GONE);
|
||||
user_nickname.setVisibility(View.GONE);
|
||||
gift_number.setVisibility(View.GONE);
|
||||
imageView8.setVisibility(View.GONE);
|
||||
tv_wait.setVisibility(View.VISIBLE);
|
||||
}
|
||||
ViewClicksAntiShake.clicksAntiShake(itemView, new ViewClicksAntiShake.ViewClicksCallBack() {
|
||||
@Override
|
||||
public void onViewClicks() {
|
||||
new GiftWallMainTab2ClassicInfoDialog(mContext, giftData, isAnchor).setFullWindows(!isLiveRoom).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int getSolesrRes() {
|
||||
return isStar ? R.mipmap.gift_wall_main_item_select : R.mipmap.gift_wall_main_item_select1;
|
||||
}
|
||||
|
||||
private int getUnSolesrRes() {
|
||||
return isStar ? R.mipmap.gift_wall_main_item_unselect : R.mipmap.gift_wall_main_item_unselect1;
|
||||
}
|
||||
}
|
||||
}
|
253
common/src/main/java/com/yunbao/common/bean/GiftWallBean.java
Normal file
253
common/src/main/java/com/yunbao/common/bean/GiftWallBean.java
Normal file
@ -0,0 +1,253 @@
|
||||
package com.yunbao.common.bean;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.yunbao.common.utils.StringUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GiftWallBean extends BaseModel {
|
||||
private IlluminateData illuminate_data;
|
||||
private int active_rank_hide;
|
||||
|
||||
|
||||
public GiftWallBean() {
|
||||
}
|
||||
|
||||
public IlluminateData getIlluminate_data() {
|
||||
return illuminate_data;
|
||||
}
|
||||
|
||||
public void setIlluminate_data(IlluminateData illuminate_data) {
|
||||
this.illuminate_data = illuminate_data;
|
||||
}
|
||||
|
||||
public int getActive_rank_hide() {
|
||||
return active_rank_hide;
|
||||
}
|
||||
|
||||
public void setActive_rank_hide(int active_rank_hide) {
|
||||
this.active_rank_hide = active_rank_hide;
|
||||
}
|
||||
|
||||
public static class IlluminateData {
|
||||
private List<Gift> week_star_data;
|
||||
private List<Gift> gift_data;
|
||||
private int week_gift_illuminate_num;
|
||||
private int week_star_gift_num;
|
||||
private int gift_illuminate_num;
|
||||
private int gift_num;
|
||||
private String gift_hall_start_date;
|
||||
private String gift_hall_end_date;
|
||||
|
||||
|
||||
public IlluminateData() {
|
||||
}
|
||||
|
||||
public String getGift_hall_start_date() {
|
||||
if(!StringUtil.isEmpty("gift_hall_start_date")){
|
||||
gift_hall_start_date=gift_hall_start_date.replace("-","/");
|
||||
}
|
||||
return gift_hall_start_date;
|
||||
}
|
||||
|
||||
public void setGift_hall_start_date(String gift_hall_start_date) {
|
||||
this.gift_hall_start_date = gift_hall_start_date;
|
||||
}
|
||||
|
||||
public String getGift_hall_end_date() {
|
||||
if(!StringUtil.isEmpty("gift_hall_end_date")){
|
||||
gift_hall_end_date=gift_hall_end_date.replace("-","/");
|
||||
}
|
||||
return gift_hall_end_date;
|
||||
}
|
||||
|
||||
public void setGift_hall_end_date(String gift_hall_end_date) {
|
||||
this.gift_hall_end_date = gift_hall_end_date;
|
||||
}
|
||||
|
||||
public List<Gift> getWeek_star_data() {
|
||||
return week_star_data;
|
||||
}
|
||||
|
||||
public void setWeek_star_data(List<Gift> week_star_data) {
|
||||
this.week_star_data = week_star_data;
|
||||
}
|
||||
|
||||
public List<Gift> getGift_data() {
|
||||
return gift_data;
|
||||
}
|
||||
|
||||
public void setGift_data(List<Gift> gift_data) {
|
||||
this.gift_data = gift_data;
|
||||
}
|
||||
|
||||
public int getWeek_gift_illuminate_num() {
|
||||
return week_gift_illuminate_num;
|
||||
}
|
||||
|
||||
public void setWeek_gift_illuminate_num(int week_gift_illuminate_num) {
|
||||
this.week_gift_illuminate_num = week_gift_illuminate_num;
|
||||
}
|
||||
|
||||
public int getWeek_star_gift_num() {
|
||||
return week_star_gift_num;
|
||||
}
|
||||
|
||||
public void setWeek_star_gift_num(int week_star_gift_num) {
|
||||
this.week_star_gift_num = week_star_gift_num;
|
||||
}
|
||||
|
||||
public int getGift_illuminate_num() {
|
||||
return gift_illuminate_num;
|
||||
}
|
||||
|
||||
public void setGift_illuminate_num(int gift_illuminate_num) {
|
||||
this.gift_illuminate_num = gift_illuminate_num;
|
||||
}
|
||||
|
||||
public int getGift_num() {
|
||||
return gift_num;
|
||||
}
|
||||
|
||||
public void setGift_num(int gift_num) {
|
||||
this.gift_num = gift_num;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Gift {
|
||||
private int gift_id;
|
||||
private int sendtype;
|
||||
private String gift_name;
|
||||
private long need_coin; // 注意:这里可能需要更改为long类型,因为666666超过了int的最大值
|
||||
private String gift_icon;
|
||||
private int week_start_level;
|
||||
private int illuminate_num;
|
||||
private int gift_hall_type;
|
||||
private String gift_name_en;
|
||||
private String gift_hall_start; // 使用Java 8的LocalDate来处理日期
|
||||
private String gift_hall_end;
|
||||
private int gift_hall_send_num;
|
||||
private int illuminate_status;
|
||||
private long needcoin_total;
|
||||
|
||||
public Gift() {
|
||||
}
|
||||
|
||||
public String getGift_hall_start() {
|
||||
if(!StringUtil.isEmpty(gift_hall_start)){
|
||||
gift_hall_start=gift_hall_start.replace("-","/");
|
||||
}
|
||||
return gift_hall_start;
|
||||
}
|
||||
public String getGift_hall_end() {
|
||||
if(!StringUtil.isEmpty(gift_hall_end)){
|
||||
gift_hall_end=gift_hall_end.replace("-","/");
|
||||
}
|
||||
return gift_hall_end;
|
||||
}
|
||||
public int getGift_id() {
|
||||
return gift_id;
|
||||
}
|
||||
|
||||
public void setGift_id(int gift_id) {
|
||||
this.gift_id = gift_id;
|
||||
}
|
||||
|
||||
public int getSendtype() {
|
||||
return sendtype;
|
||||
}
|
||||
|
||||
public void setSendtype(int sendtype) {
|
||||
this.sendtype = sendtype;
|
||||
}
|
||||
|
||||
public String getGift_name() {
|
||||
return gift_name;
|
||||
}
|
||||
|
||||
public void setGift_name(String gift_name) {
|
||||
this.gift_name = gift_name;
|
||||
}
|
||||
|
||||
public long getNeed_coin() {
|
||||
return need_coin;
|
||||
}
|
||||
|
||||
public void setNeed_coin(long need_coin) {
|
||||
this.need_coin = need_coin;
|
||||
}
|
||||
|
||||
public String getGift_icon() {
|
||||
return gift_icon;
|
||||
}
|
||||
|
||||
public void setGift_icon(String gift_icon) {
|
||||
this.gift_icon = gift_icon;
|
||||
}
|
||||
|
||||
public int getWeek_start_level() {
|
||||
return week_start_level;
|
||||
}
|
||||
|
||||
public void setWeek_start_level(int week_start_level) {
|
||||
this.week_start_level = week_start_level;
|
||||
}
|
||||
|
||||
public int getIlluminate_num() {
|
||||
return illuminate_num;
|
||||
}
|
||||
|
||||
public void setIlluminate_num(int illuminate_num) {
|
||||
this.illuminate_num = illuminate_num;
|
||||
}
|
||||
|
||||
public int getGift_hall_type() {
|
||||
return gift_hall_type;
|
||||
}
|
||||
|
||||
public void setGift_hall_type(int gift_hall_type) {
|
||||
this.gift_hall_type = gift_hall_type;
|
||||
}
|
||||
|
||||
public String getGift_name_en() {
|
||||
return gift_name_en;
|
||||
}
|
||||
|
||||
public void setGift_name_en(String gift_name_en) {
|
||||
this.gift_name_en = gift_name_en;
|
||||
}
|
||||
|
||||
public void setGift_hall_start(String gift_hall_start) {
|
||||
this.gift_hall_start = gift_hall_start;
|
||||
}
|
||||
|
||||
|
||||
public void setGift_hall_end(String gift_hall_end) {
|
||||
this.gift_hall_end = gift_hall_end;
|
||||
}
|
||||
|
||||
public int getGift_hall_send_num() {
|
||||
return gift_hall_send_num;
|
||||
}
|
||||
|
||||
public void setGift_hall_send_num(int gift_hall_send_num) {
|
||||
this.gift_hall_send_num = gift_hall_send_num;
|
||||
}
|
||||
|
||||
public int getIlluminate_status() {
|
||||
return illuminate_status;
|
||||
}
|
||||
|
||||
public void setIlluminate_status(int illuminate_status) {
|
||||
this.illuminate_status = illuminate_status;
|
||||
}
|
||||
|
||||
public long getNeedcoin_total() {
|
||||
return needcoin_total;
|
||||
}
|
||||
|
||||
public void setNeedcoin_total(long needcoin_total) {
|
||||
this.needcoin_total = needcoin_total;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package com.yunbao.common.bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GiftWallForUserBean extends BaseModel {
|
||||
private List<Gift> illuminate_data;
|
||||
private int active_rank_hide;
|
||||
|
||||
public GiftWallForUserBean() {
|
||||
}
|
||||
|
||||
public List<Gift> getIlluminate_data() {
|
||||
return illuminate_data;
|
||||
}
|
||||
|
||||
public void setIlluminate_data(List<Gift> illuminate_data) {
|
||||
this.illuminate_data = illuminate_data;
|
||||
}
|
||||
|
||||
public int getActive_rank_hide() {
|
||||
return active_rank_hide;
|
||||
}
|
||||
|
||||
public void setActive_rank_hide(int active_rank_hide) {
|
||||
this.active_rank_hide = active_rank_hide;
|
||||
}
|
||||
|
||||
public static class Gift {
|
||||
private String giftname;
|
||||
private String giftname_en;
|
||||
private String gifticon;
|
||||
private int needcoin;
|
||||
private int needcoin_total;
|
||||
private int gift_hall_send_num;
|
||||
private int illuminate_status;
|
||||
private int gift_id;
|
||||
private int id;
|
||||
|
||||
public Gift() {
|
||||
}
|
||||
|
||||
public String getGiftname() {
|
||||
return giftname;
|
||||
}
|
||||
|
||||
public void setGiftname(String giftname) {
|
||||
this.giftname = giftname;
|
||||
}
|
||||
|
||||
public String getGiftname_en() {
|
||||
return giftname_en;
|
||||
}
|
||||
|
||||
public void setGiftname_en(String giftname_en) {
|
||||
this.giftname_en = giftname_en;
|
||||
}
|
||||
|
||||
public String getGifticon() {
|
||||
return gifticon;
|
||||
}
|
||||
|
||||
public void setGifticon(String gifticon) {
|
||||
this.gifticon = gifticon;
|
||||
}
|
||||
|
||||
public int getNeedcoin() {
|
||||
return needcoin;
|
||||
}
|
||||
|
||||
public void setNeedcoin(int needcoin) {
|
||||
this.needcoin = needcoin;
|
||||
}
|
||||
|
||||
public int getNeedcoin_total() {
|
||||
return needcoin_total;
|
||||
}
|
||||
|
||||
public void setNeedcoin_total(int needcoin_total) {
|
||||
this.needcoin_total = needcoin_total;
|
||||
}
|
||||
|
||||
public int getGift_hall_send_num() {
|
||||
return gift_hall_send_num;
|
||||
}
|
||||
|
||||
public void setGift_hall_send_num(int gift_hall_send_num) {
|
||||
this.gift_hall_send_num = gift_hall_send_num;
|
||||
}
|
||||
|
||||
public int getIlluminate_status() {
|
||||
return illuminate_status;
|
||||
}
|
||||
|
||||
public void setIlluminate_status(int illuminate_status) {
|
||||
this.illuminate_status = illuminate_status;
|
||||
}
|
||||
|
||||
public int getGift_id() {
|
||||
return gift_id;
|
||||
}
|
||||
|
||||
public void setGift_id(int gift_id) {
|
||||
this.gift_id = gift_id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,258 @@
|
||||
package com.yunbao.common.bean;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.yunbao.common.utils.RandomUtil;
|
||||
import com.yunbao.common.utils.StringUtil;
|
||||
import com.yunbao.common.utils.WordUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GiftWallInfoBean extends BaseModel {
|
||||
@SerializedName("gift_info")
|
||||
private GiftInfo gift_info;
|
||||
@SerializedName("data")
|
||||
private List<Data> data;
|
||||
@SerializedName("is_me")
|
||||
private int is_me;
|
||||
|
||||
// 一般情况下,我们会添加getter和setter方法,但根据你的要求,这里省略
|
||||
|
||||
|
||||
@JSONField(name = "gift_info")
|
||||
public GiftInfo getGift_info() {
|
||||
return gift_info;
|
||||
}
|
||||
|
||||
@JSONField(name = "gift_info")
|
||||
public void setGift_info(GiftInfo gift_info) {
|
||||
this.gift_info = gift_info;
|
||||
}
|
||||
|
||||
@JSONField(name = "data")
|
||||
public List<Data> getData() {
|
||||
/* if (data == null || data.isEmpty()) {
|
||||
data = new ArrayList<>();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
Data item = new Data();
|
||||
item.setId(i);
|
||||
item.setActive_rank_hide(i % 4 == 0 ? 0 : 1);
|
||||
item.setUser_name("用户:" + i);
|
||||
item.setGift_hall_send_num(RandomUtil.nextInt(100000));
|
||||
item.setAvatar("https://downs.yaoulive.com/manfive.png");
|
||||
data.add(item);
|
||||
}
|
||||
}*/
|
||||
return data;
|
||||
}
|
||||
|
||||
@JSONField(name = "data")
|
||||
public void setData(List<Data> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getIs_me() {
|
||||
return is_me;
|
||||
}
|
||||
|
||||
public void setIs_me(int is_me) {
|
||||
this.is_me = is_me;
|
||||
}
|
||||
|
||||
// 嵌套类:GiftInfo
|
||||
public static class GiftInfo {
|
||||
@SerializedName("gift_name")
|
||||
private String giftname;
|
||||
@SerializedName("gift_name_en")
|
||||
private String giftname_en;
|
||||
@SerializedName("gift_icon")
|
||||
private String gifticon;
|
||||
@SerializedName("need_coin")
|
||||
private int needcoin;
|
||||
@SerializedName("needcoin_total")
|
||||
private int needcoin_total;
|
||||
@SerializedName("gift_hall_send_num")
|
||||
private String gift_hall_send_num;
|
||||
@SerializedName("illuminate_num")
|
||||
private int illuminate_num;
|
||||
@SerializedName("illuminate_status")
|
||||
private int illuminate_status;
|
||||
@SerializedName("gift_id")
|
||||
private int gift_id;
|
||||
@SerializedName("id")
|
||||
private int id;
|
||||
@SerializedName("gift_hall_start_date")
|
||||
private String gift_hall_start; // 使用Java 8的LocalDate来处理日期
|
||||
@SerializedName("gift_hall_end_date")
|
||||
private String gift_hall_end;
|
||||
|
||||
@JSONField(name = "gift_hall_start_date")
|
||||
public void setGift_hall_start(String gift_hall_start) {
|
||||
this.gift_hall_start = gift_hall_start;
|
||||
}
|
||||
@JSONField(name = "gift_hall_start_date")
|
||||
public String getGift_hall_start() {
|
||||
if(!StringUtil.isEmpty(gift_hall_start)){
|
||||
gift_hall_start=gift_hall_start.replace("-","/");
|
||||
}
|
||||
return gift_hall_start;
|
||||
}
|
||||
@JSONField(name = "gift_hall_end_date")
|
||||
public String getGift_hall_end() {
|
||||
if(!StringUtil.isEmpty(gift_hall_end)){
|
||||
gift_hall_end=gift_hall_end.replace("-","/");
|
||||
}
|
||||
return gift_hall_end;
|
||||
}
|
||||
|
||||
public void setGift_hall_end(String gift_hall_end) {
|
||||
this.gift_hall_end = gift_hall_end;
|
||||
}
|
||||
|
||||
// 同样地,这里省略getter和setter方法
|
||||
|
||||
public String getGiftname() {
|
||||
return giftname;
|
||||
}
|
||||
|
||||
public void setGiftname(String giftname) {
|
||||
this.giftname = giftname;
|
||||
}
|
||||
|
||||
public String getGiftname_en() {
|
||||
return giftname_en;
|
||||
}
|
||||
|
||||
public void setGiftname_en(String giftname_en) {
|
||||
this.giftname_en = giftname_en;
|
||||
}
|
||||
|
||||
public String getGifticon() {
|
||||
return gifticon;
|
||||
}
|
||||
|
||||
public void setGifticon(String gifticon) {
|
||||
this.gifticon = gifticon;
|
||||
}
|
||||
|
||||
public int getNeedcoin() {
|
||||
return needcoin;
|
||||
}
|
||||
|
||||
public void setNeedcoin(int needcoin) {
|
||||
this.needcoin = needcoin;
|
||||
}
|
||||
|
||||
public int getNeedcoin_total() {
|
||||
return needcoin_total;
|
||||
}
|
||||
|
||||
public void setNeedcoin_total(int needcoin_total) {
|
||||
this.needcoin_total = needcoin_total;
|
||||
}
|
||||
|
||||
public int getIlluminate_num() {
|
||||
return illuminate_num;
|
||||
}
|
||||
|
||||
public void setIlluminate_num(int illuminate_num) {
|
||||
this.illuminate_num = illuminate_num;
|
||||
}
|
||||
|
||||
public String getGift_hall_send_num() {
|
||||
if (StringUtil.isEmpty(gift_hall_send_num)) {
|
||||
gift_hall_send_num = "0";
|
||||
}
|
||||
return gift_hall_send_num;
|
||||
}
|
||||
|
||||
public void setGift_hall_send_num(String gift_hall_send_num) {
|
||||
this.gift_hall_send_num = gift_hall_send_num;
|
||||
}
|
||||
|
||||
public int getIlluminate_status() {
|
||||
return illuminate_status;
|
||||
}
|
||||
|
||||
public void setIlluminate_status(int illuminate_status) {
|
||||
this.illuminate_status = illuminate_status;
|
||||
}
|
||||
|
||||
public int getGift_id() {
|
||||
return gift_id;
|
||||
}
|
||||
|
||||
public void setGift_id(int gift_id) {
|
||||
this.gift_id = gift_id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
// 嵌套类:Data
|
||||
public static class Data {
|
||||
private int gift_hall_send_num;
|
||||
private String user_name;
|
||||
private String avatar;
|
||||
private int id;
|
||||
private int active_rank_hide;
|
||||
private String create_time;
|
||||
|
||||
// 同样地,这里省略getter和setter方法
|
||||
|
||||
public int getGift_hall_send_num() {
|
||||
return gift_hall_send_num;
|
||||
}
|
||||
|
||||
public void setGift_hall_send_num(int gift_hall_send_num) {
|
||||
this.gift_hall_send_num = gift_hall_send_num;
|
||||
}
|
||||
|
||||
public String getUser_name() {
|
||||
return user_name;
|
||||
}
|
||||
|
||||
public void setUser_name(String user_name) {
|
||||
this.user_name = user_name;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getActive_rank_hide() {
|
||||
return active_rank_hide;
|
||||
}
|
||||
|
||||
public void setActive_rank_hide(int active_rank_hide) {
|
||||
this.active_rank_hide = active_rank_hide;
|
||||
}
|
||||
|
||||
public String getCreate_time() {
|
||||
return create_time;
|
||||
}
|
||||
|
||||
public void setCreate_time(String create_time) {
|
||||
this.create_time = create_time;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,312 @@
|
||||
package com.yunbao.common.bean;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GiftWallTab2Bean extends BaseModel{
|
||||
|
||||
|
||||
@SerializedName("illuminate_data")
|
||||
private IlluminateData illuminateData;
|
||||
@SerializedName("gift_hall_start_date")
|
||||
private String gift_hall_start_date;
|
||||
@SerializedName("gift_hall_end_date")
|
||||
private String gift_hall_end_date;
|
||||
|
||||
public GiftWallTab2Bean() {
|
||||
}
|
||||
|
||||
public IlluminateData getIlluminateData() {
|
||||
return illuminateData;
|
||||
}
|
||||
|
||||
public void setIlluminateData(IlluminateData illuminateData) {
|
||||
this.illuminateData = illuminateData;
|
||||
}
|
||||
|
||||
public String getGift_hall_start_date() {
|
||||
return gift_hall_start_date;
|
||||
}
|
||||
|
||||
public void setGift_hall_start_date(String gift_hall_start_date) {
|
||||
this.gift_hall_start_date = gift_hall_start_date;
|
||||
}
|
||||
|
||||
public String getGift_hall_end_date() {
|
||||
return gift_hall_end_date;
|
||||
}
|
||||
|
||||
public void setGift_hall_end_date(String gift_hall_end_date) {
|
||||
this.gift_hall_end_date = gift_hall_end_date;
|
||||
}
|
||||
|
||||
public static class IlluminateData{
|
||||
@SerializedName("week_start_data")
|
||||
private List<Gift> weekStartData;
|
||||
@SerializedName("gift_data")
|
||||
private List<Gift> giftData;
|
||||
|
||||
public IlluminateData() {
|
||||
}
|
||||
|
||||
public List<Gift> getWeekStartData() {
|
||||
return weekStartData;
|
||||
}
|
||||
|
||||
public void setWeekStartData(List<Gift> weekStartData) {
|
||||
this.weekStartData = weekStartData;
|
||||
}
|
||||
|
||||
public List<Gift> getGiftData() {
|
||||
return giftData;
|
||||
}
|
||||
|
||||
public void setGiftData(List<Gift> giftData) {
|
||||
this.giftData = giftData;
|
||||
}
|
||||
}
|
||||
public static class Gift{
|
||||
@SerializedName("gift_id")
|
||||
public int giftId;
|
||||
|
||||
@SerializedName("sendtype")
|
||||
public int sendType;
|
||||
|
||||
@SerializedName("gift_name")
|
||||
public String giftName;
|
||||
|
||||
@SerializedName("need_coin")
|
||||
public int needCoin;
|
||||
|
||||
@SerializedName("gift_icon")
|
||||
public String giftIcon;
|
||||
|
||||
@SerializedName("week_star_level")
|
||||
public int weekStarLevel;
|
||||
|
||||
@SerializedName("illuminate_num")
|
||||
public int illuminateNum;
|
||||
|
||||
@SerializedName("gift_hall_type")
|
||||
public int giftHallType;
|
||||
|
||||
@SerializedName("gift_name_en")
|
||||
public String giftNameEn;
|
||||
|
||||
@SerializedName("gift_hall_start")
|
||||
public String giftHallStart;
|
||||
|
||||
@SerializedName("gift_hall_end")
|
||||
public String giftHallEnd;
|
||||
|
||||
@SerializedName("naming_live_id")
|
||||
public int namingLiveId;
|
||||
|
||||
@SerializedName("naming_live_nicename")
|
||||
public String namingLiveNicename;
|
||||
|
||||
@SerializedName("gift_hall_send_num")
|
||||
public int giftHallSendNum;
|
||||
|
||||
@SerializedName("naming_live_avatar")
|
||||
public String namingLiveAvatar;
|
||||
|
||||
@SerializedName("naming_live_active_rank_hide")
|
||||
public int namingLiveActiveRankHide;
|
||||
|
||||
@SerializedName("illuminate_status")
|
||||
public int illuminateStatus;
|
||||
|
||||
@SerializedName("naming_user_id")
|
||||
public int namingUserId;
|
||||
|
||||
@SerializedName("naming_user_nicename")
|
||||
public String namingUserNicename;
|
||||
|
||||
@SerializedName("naming_user_avatar")
|
||||
public String namingUserAvatar;
|
||||
|
||||
@SerializedName("naming_user_active_rank_hide")
|
||||
public int namingUserActiveRankHide;
|
||||
|
||||
@SerializedName("needcoin_total")
|
||||
public int needCoinTotal;
|
||||
|
||||
public int getGiftId() {
|
||||
return giftId;
|
||||
}
|
||||
|
||||
public void setGiftId(int giftId) {
|
||||
this.giftId = giftId;
|
||||
}
|
||||
|
||||
public int getSendType() {
|
||||
return sendType;
|
||||
}
|
||||
|
||||
public void setSendType(int sendType) {
|
||||
this.sendType = sendType;
|
||||
}
|
||||
|
||||
public String getGiftName() {
|
||||
return giftName;
|
||||
}
|
||||
|
||||
public void setGiftName(String giftName) {
|
||||
this.giftName = giftName;
|
||||
}
|
||||
|
||||
public int getNeedCoin() {
|
||||
return needCoin;
|
||||
}
|
||||
|
||||
public void setNeedCoin(int needCoin) {
|
||||
this.needCoin = needCoin;
|
||||
}
|
||||
|
||||
public String getGiftIcon() {
|
||||
return giftIcon;
|
||||
}
|
||||
|
||||
public void setGiftIcon(String giftIcon) {
|
||||
this.giftIcon = giftIcon;
|
||||
}
|
||||
|
||||
public int getWeekStarLevel() {
|
||||
return weekStarLevel;
|
||||
}
|
||||
|
||||
public void setWeekStarLevel(int weekStarLevel) {
|
||||
this.weekStarLevel = weekStarLevel;
|
||||
}
|
||||
|
||||
public int getIlluminateNum() {
|
||||
return illuminateNum;
|
||||
}
|
||||
|
||||
public void setIlluminateNum(int illuminateNum) {
|
||||
this.illuminateNum = illuminateNum;
|
||||
}
|
||||
|
||||
public int getGiftHallType() {
|
||||
return giftHallType;
|
||||
}
|
||||
|
||||
public void setGiftHallType(int giftHallType) {
|
||||
this.giftHallType = giftHallType;
|
||||
}
|
||||
|
||||
public String getGiftNameEn() {
|
||||
return giftNameEn;
|
||||
}
|
||||
|
||||
public void setGiftNameEn(String giftNameEn) {
|
||||
this.giftNameEn = giftNameEn;
|
||||
}
|
||||
|
||||
public String getGiftHallStart() {
|
||||
return giftHallStart;
|
||||
}
|
||||
|
||||
public void setGiftHallStart(String giftHallStart) {
|
||||
this.giftHallStart = giftHallStart;
|
||||
}
|
||||
|
||||
public String getGiftHallEnd() {
|
||||
return giftHallEnd;
|
||||
}
|
||||
|
||||
public void setGiftHallEnd(String giftHallEnd) {
|
||||
this.giftHallEnd = giftHallEnd;
|
||||
}
|
||||
|
||||
public int getNamingLiveId() {
|
||||
return namingLiveId;
|
||||
}
|
||||
|
||||
public void setNamingLiveId(int namingLiveId) {
|
||||
this.namingLiveId = namingLiveId;
|
||||
}
|
||||
|
||||
public String getNamingLiveNicename() {
|
||||
return namingLiveNicename;
|
||||
}
|
||||
|
||||
public void setNamingLiveNicename(String namingLiveNicename) {
|
||||
this.namingLiveNicename = namingLiveNicename;
|
||||
}
|
||||
|
||||
public int getGiftHallSendNum() {
|
||||
return giftHallSendNum;
|
||||
}
|
||||
|
||||
public void setGiftHallSendNum(int giftHallSendNum) {
|
||||
this.giftHallSendNum = giftHallSendNum;
|
||||
}
|
||||
|
||||
public String getNamingLiveAvatar() {
|
||||
return namingLiveAvatar;
|
||||
}
|
||||
|
||||
public void setNamingLiveAvatar(String namingLiveAvatar) {
|
||||
this.namingLiveAvatar = namingLiveAvatar;
|
||||
}
|
||||
|
||||
public int getNamingLiveActiveRankHide() {
|
||||
return namingLiveActiveRankHide;
|
||||
}
|
||||
|
||||
public void setNamingLiveActiveRankHide(int namingLiveActiveRankHide) {
|
||||
this.namingLiveActiveRankHide = namingLiveActiveRankHide;
|
||||
}
|
||||
|
||||
public int getIlluminateStatus() {
|
||||
return illuminateStatus;
|
||||
}
|
||||
|
||||
public void setIlluminateStatus(int illuminateStatus) {
|
||||
this.illuminateStatus = illuminateStatus;
|
||||
}
|
||||
|
||||
public int getNamingUserId() {
|
||||
return namingUserId;
|
||||
}
|
||||
|
||||
public void setNamingUserId(int namingUserId) {
|
||||
this.namingUserId = namingUserId;
|
||||
}
|
||||
|
||||
public String getNamingUserNicename() {
|
||||
return namingUserNicename;
|
||||
}
|
||||
|
||||
public void setNamingUserNicename(String namingUserNicename) {
|
||||
this.namingUserNicename = namingUserNicename;
|
||||
}
|
||||
|
||||
public String getNamingUserAvatar() {
|
||||
return namingUserAvatar;
|
||||
}
|
||||
|
||||
public void setNamingUserAvatar(String namingUserAvatar) {
|
||||
this.namingUserAvatar = namingUserAvatar;
|
||||
}
|
||||
|
||||
public int getNamingUserActiveRankHide() {
|
||||
return namingUserActiveRankHide;
|
||||
}
|
||||
|
||||
public void setNamingUserActiveRankHide(int namingUserActiveRankHide) {
|
||||
this.namingUserActiveRankHide = namingUserActiveRankHide;
|
||||
}
|
||||
|
||||
public int getNeedCoinTotal() {
|
||||
return needCoinTotal;
|
||||
}
|
||||
|
||||
public void setNeedCoinTotal(int needCoinTotal) {
|
||||
this.needCoinTotal = needCoinTotal;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,11 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.lxj.xpopup.XPopup;
|
||||
import com.lxj.xpopup.core.BottomPopupView;
|
||||
import com.yunbao.common.event.ClosePopupDialogEvent;
|
||||
import com.yunbao.common.utils.Bus;
|
||||
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
/**
|
||||
* 底部弹窗
|
||||
@ -16,12 +21,20 @@ public abstract class AbsDialogPopupWindow extends BottomPopupView {
|
||||
public AbsDialogPopupWindow(@NonNull Context context) {
|
||||
super(context);
|
||||
this.mContext = context;
|
||||
Bus.getOn(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
super.dismiss();
|
||||
Bus.getOff(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* <a href="https://github.com/li-xiaojun/XPopup/wiki/5.-%E5%B8%B8%E7%94%A8%E8%AE%BE%E7%BD%AE">参考配置</a>
|
||||
*/
|
||||
public abstract void buildDialog(XPopup.Builder builder);
|
||||
|
||||
public abstract int bindLayoutId();
|
||||
|
||||
@Override
|
||||
@ -36,4 +49,16 @@ public abstract class AbsDialogPopupWindow extends BottomPopupView {
|
||||
buildDialog(builder);
|
||||
builder.asCustom(this).show();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onWishSendGift(ClosePopupDialogEvent bean) {
|
||||
if(bean.getId()==null) {
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
if(bean.getId().equals(getTag())) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 礼物墙
|
||||
@ -46,9 +47,15 @@ public class GiftWallDialog extends AbsDialogPopupWindow {
|
||||
private List<BaseFragment> fragments = new ArrayList<>();
|
||||
|
||||
private boolean isFullWindows;
|
||||
String toUserId;
|
||||
String userName;
|
||||
boolean isAnchor;
|
||||
|
||||
public GiftWallDialog(@NonNull Context context) {
|
||||
public GiftWallDialog(@NonNull Context context, String toUserId, String userName, boolean isAnchor) {
|
||||
super(context);
|
||||
this.toUserId = toUserId;
|
||||
this.isAnchor = isAnchor;
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public GiftWallDialog setFullWindows(boolean fullWindows) {
|
||||
@ -86,8 +93,13 @@ public class GiftWallDialog extends AbsDialogPopupWindow {
|
||||
mViewPager = findViewById(R.id.viewPager2);
|
||||
mIvTabsLayout = findViewById(R.id.tab_layout);
|
||||
|
||||
fragments.add(new GiftWallMainTab1Fragment());
|
||||
fragments.add(new GiftWallMainTab2Fragment());
|
||||
mTvUserName.setText(String.format(Locale.getDefault(), "%s%s",
|
||||
userName,
|
||||
WordUtil.isNewZh() ? "的禮物展館" : "'s Gift Hall"
|
||||
));
|
||||
|
||||
fragments.add(new GiftWallMainTab1Fragment().setToUserId(toUserId).setAnchor(isAnchor).setLiveRoom(!isFullWindows));
|
||||
fragments.add(new GiftWallMainTab2Fragment().setToUserId(toUserId).setAnchor(isAnchor).setLiveRoom(!isFullWindows));
|
||||
mViewPager.setAdapter(new FragmentStateAdapter((FragmentActivity) mContext) {
|
||||
@NonNull
|
||||
@Override
|
||||
@ -101,6 +113,18 @@ public class GiftWallDialog extends AbsDialogPopupWindow {
|
||||
}
|
||||
});
|
||||
mViewPager.setUserInputEnabled(false);
|
||||
mViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
fragments.get(position).updateData();
|
||||
if(position==0){
|
||||
mTvUserName.setTextColor(Color.parseColor("#6BCDFF"));
|
||||
}else{
|
||||
mTvUserName.setTextColor(Color.parseColor("#FFCF94"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ViewClicksAntiShake.clicksAntiShake(mTvTab1, () -> {
|
||||
|
@ -0,0 +1,392 @@
|
||||
package com.yunbao.common.dialog;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.lxj.xpopup.XPopup;
|
||||
import com.makeramen.roundedimageview.RoundedImageView;
|
||||
import com.opensource.svgaplayer.SVGADrawable;
|
||||
import com.opensource.svgaplayer.SVGAImageView;
|
||||
import com.opensource.svgaplayer.SVGAParser;
|
||||
import com.opensource.svgaplayer.SVGAVideoEntity;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.adapter.GiftWallGiftInfoListItemAdapter;
|
||||
import com.yunbao.common.bean.GiftWallInfoBean;
|
||||
import com.yunbao.common.bean.JsWishBean;
|
||||
import com.yunbao.common.custom.ItemDecoration;
|
||||
import com.yunbao.common.event.ClosePopupDialogEvent;
|
||||
import com.yunbao.common.glide.ImgLoader;
|
||||
import com.yunbao.common.http.base.HttpCallback;
|
||||
import com.yunbao.common.http.live.LiveNetManager;
|
||||
import com.yunbao.common.interfaces.OnItemClickListener;
|
||||
import com.yunbao.common.manager.IMLoginManager;
|
||||
import com.yunbao.common.utils.Bus;
|
||||
import com.yunbao.common.utils.ScreenDimenUtil;
|
||||
import com.yunbao.common.utils.WordUtil;
|
||||
import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class GiftWallGiftInfoDialog extends AbsDialogPopupWindow {
|
||||
private boolean isFullWindows;
|
||||
private ImageView mIvBg;
|
||||
private ImageView mIvBack;
|
||||
TextView giftName;
|
||||
ImageView gift;
|
||||
SVGAImageView gift_bg;
|
||||
TextView diamond_text;
|
||||
TextView gift_tv_progress;
|
||||
TextView gift_tv_max;
|
||||
ProgressBar gift_progress;
|
||||
Button gift_btn;
|
||||
Button tab1, tab2;
|
||||
ImageView tips_timer;
|
||||
TextView tv_list_title;
|
||||
RoundedImageView avatar;
|
||||
TextView user_name;
|
||||
TextView send_num;
|
||||
TextView btn_one;
|
||||
Button btn_one_tips;
|
||||
Button btn_lighten;
|
||||
View tab_layout;
|
||||
View bottom_layout;
|
||||
|
||||
GiftWallGiftInfoListItemAdapter adapter;
|
||||
RecyclerView recyclerView;
|
||||
|
||||
String giftId;
|
||||
String toUserId;
|
||||
boolean isAnchor;
|
||||
int gift_hall_type = 1;
|
||||
int list_type = 1;
|
||||
private boolean isLiveRoom;
|
||||
String time;
|
||||
private boolean isStar;
|
||||
|
||||
|
||||
public GiftWallGiftInfoDialog(Context context, String giftId, String toUserId, boolean isAnchor) {
|
||||
super(context);
|
||||
this.giftId = giftId;
|
||||
this.toUserId = toUserId;
|
||||
this.isAnchor = isAnchor;
|
||||
}
|
||||
|
||||
public GiftWallGiftInfoDialog setFullWindows(boolean fullWindows) {
|
||||
isFullWindows = fullWindows;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public GiftWallGiftInfoDialog setLiveRoom(boolean isLiveRoom) {
|
||||
this.isLiveRoom = isLiveRoom;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GiftWallGiftInfoDialog setStar(boolean isStar) {
|
||||
this.isStar = isStar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPopupHeight() {
|
||||
if (isFullWindows) {
|
||||
return super.getPopupHeight();
|
||||
}
|
||||
int screenHeight = ScreenDimenUtil.getInstance().getScreenHeight();
|
||||
return (int) (screenHeight * 0.8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildDialog(XPopup.Builder builder) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bindLayoutId() {
|
||||
return R.layout.dialog_gift_wall_gift_info;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
initView();
|
||||
initData();
|
||||
}
|
||||
|
||||
void initView() {
|
||||
mIvBg = findViewById(R.id.iv_root_bg);
|
||||
mIvBack = findViewById(R.id.iv_back);
|
||||
giftName = findViewById(R.id.gift_name);
|
||||
gift = findViewById(R.id.gift);
|
||||
diamond_text = findViewById(R.id.diamond_text);
|
||||
gift_tv_progress = findViewById(R.id.gift_tv_progress);
|
||||
gift_tv_max = findViewById(R.id.gift_tv_max);
|
||||
gift_progress = findViewById(R.id.gift_progress);
|
||||
gift_btn = findViewById(R.id.gift_btn);
|
||||
tab1 = findViewById(R.id.tab1);
|
||||
tab2 = findViewById(R.id.tab2);
|
||||
tips_timer = findViewById(R.id.tips_timer);
|
||||
tv_list_title = findViewById(R.id.tv_list_title);
|
||||
avatar = findViewById(R.id.bottom_avatar);
|
||||
user_name = findViewById(R.id.bottom_user_name);
|
||||
send_num = findViewById(R.id.send_num);
|
||||
btn_one = findViewById(R.id.btn_one);
|
||||
btn_one_tips = findViewById(R.id.btn_one_tips);
|
||||
gift_bg = findViewById(R.id.gift_bg);
|
||||
btn_lighten = findViewById(R.id.btn_lighten);
|
||||
recyclerView = findViewById(R.id.recyclerView);
|
||||
tab_layout = findViewById(R.id.tab_layout);
|
||||
bottom_layout = findViewById(R.id.bottom_layout);
|
||||
adapter = new GiftWallGiftInfoListItemAdapter();
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.addItemDecoration(new ItemDecoration(mContext, 0x00000000, 0, 10));
|
||||
|
||||
if (isStar) {
|
||||
tab2.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_title_star));
|
||||
((TextView) findViewById(R.id.user_name)).setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_info_list_header_rename1));
|
||||
((TextView) findViewById(R.id.tv_rename)).setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_info_list_header_rename_value1));
|
||||
tv_list_title.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_title_star));
|
||||
} else {
|
||||
tab2.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_title_champion));
|
||||
tv_list_title.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_title_champion));
|
||||
((TextView) findViewById(R.id.user_name)).setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_info_list_header_rename));
|
||||
((TextView) findViewById(R.id.tv_rename)).setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_info_list_header_rename_value));
|
||||
}
|
||||
btn_one.setVisibility(View.GONE);
|
||||
btn_one_tips.setVisibility(View.GONE);
|
||||
ViewClicksAntiShake.clicksAntiShake(tab1, () -> {
|
||||
|
||||
btn_one.setVisibility(View.GONE);
|
||||
btn_one_tips.setVisibility(View.GONE);
|
||||
|
||||
tab1.setBackgroundResource(R.drawable.gift_wall_gift_info_list_btn_up);
|
||||
tab1.setTextColor(Color.parseColor("#31326D"));
|
||||
|
||||
tab2.setBackgroundResource(R.drawable.gift_wall_gift_info_list_btn_down);
|
||||
tab2.setTextColor(Color.parseColor("#FFFFFF"));
|
||||
list_type = 1;
|
||||
initData();
|
||||
});
|
||||
ViewClicksAntiShake.clicksAntiShake(tab2, () -> {
|
||||
btn_one.setVisibility(View.VISIBLE);
|
||||
btn_one_tips.setVisibility(View.VISIBLE);
|
||||
|
||||
tab2.setBackgroundResource(R.drawable.gift_wall_gift_info_list_btn_up);
|
||||
tab2.setTextColor(Color.parseColor("#31326D"));
|
||||
|
||||
tab1.setBackgroundResource(R.drawable.gift_wall_gift_info_list_btn_down);
|
||||
tab1.setTextColor(Color.parseColor("#FFFFFF"));
|
||||
list_type = 2;
|
||||
initData();
|
||||
});
|
||||
XPopup.Builder builder = new XPopup.Builder(getContext())
|
||||
.atView(tips_timer);
|
||||
builder.hasShadowBg(false);
|
||||
tips_timer.setTag(builder);
|
||||
ViewClicksAntiShake.clicksAntiShake(tips_timer, new ViewClicksAntiShake.ViewClicksCallBack() {
|
||||
@Override
|
||||
public void onViewClicks() {
|
||||
XPopup.Builder b = (XPopup.Builder) tips_timer.getTag();
|
||||
b.asCustom(new GiftWallMainTab1TipsDialog(mContext, new OnItemClickListener<Integer>() {
|
||||
@Override
|
||||
public void onItemClick(Integer bean, int position) {
|
||||
|
||||
}
|
||||
}).setTime(time)).show();
|
||||
}
|
||||
});
|
||||
ViewClicksAntiShake.clicksAntiShake(btn_one, new ViewClicksAntiShake.ViewClicksCallBack() {
|
||||
@Override
|
||||
public void onViewClicks() {
|
||||
Bus.get().post(new JsWishBean(giftId));//setUname==by
|
||||
Bus.get().post(new ClosePopupDialogEvent());
|
||||
}
|
||||
});
|
||||
ViewClicksAntiShake.clicksAntiShake(gift_btn, new ViewClicksAntiShake.ViewClicksCallBack() {
|
||||
@Override
|
||||
public void onViewClicks() {
|
||||
Bus.get().post(new JsWishBean(giftId));//setUname==by
|
||||
Bus.get().post(new ClosePopupDialogEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void initData() {
|
||||
if (isAnchor) {
|
||||
LiveNetManager.get(mContext)
|
||||
.liveGiftHallDetail(toUserId, giftId, gift_hall_type, list_type, new HttpCallback<GiftWallInfoBean>() {
|
||||
@Override
|
||||
public void onSuccess(GiftWallInfoBean data) {
|
||||
initData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LiveNetManager.get(mContext)
|
||||
.singleUserGiftHallDetail(toUserId, giftId, new HttpCallback<GiftWallInfoBean>() {
|
||||
@Override
|
||||
public void onSuccess(GiftWallInfoBean data) {
|
||||
initData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void initData(GiftWallInfoBean giftBean) {
|
||||
giftName.setText(WordUtil.isNewZh() ? giftBean.getGift_info().getGiftname() : giftBean.getGift_info().getGiftname_en());
|
||||
ImgLoader.display(mContext, giftBean.getGift_info().getGifticon(), gift);
|
||||
diamond_text.setText(String.format(Locale.getDefault(), "%d", giftBean.getGift_info().getNeedcoin()));
|
||||
gift_tv_max.setText(String.format(Locale.getDefault(), "/%s", giftBean.getGift_info().getIlluminate_num()));
|
||||
gift_tv_progress.setText(String.format(Locale.getDefault(), "%s", giftBean.getGift_info().getGift_hall_send_num()));
|
||||
gift_progress.setMax(Integer.parseInt(giftBean.getGift_info().getGift_hall_send_num()));
|
||||
gift_progress.setProgress(giftBean.getGift_info().getNeedcoin_total());
|
||||
if (giftBean.getGift_info().getIlluminate_status() == 1) {
|
||||
gift_btn.setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_info_top_btn_continue));
|
||||
btn_lighten.setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_spinner_up));
|
||||
btn_lighten.setBackgroundResource(R.drawable.gift_wall_gift_info_lighten);
|
||||
} else {
|
||||
gift_btn.setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_info_top_btn_to));
|
||||
btn_lighten.setText(WordUtil.getNewString(R.string.dialog_gift_wall_list_spinner_down));
|
||||
btn_lighten.setBackgroundResource(R.drawable.gift_wall_gift_info_un_lighten);
|
||||
}
|
||||
if (isAnchor) {
|
||||
tab_layout.setVisibility(View.VISIBLE);
|
||||
bottom_layout.setVisibility(View.VISIBLE);
|
||||
tv_list_title.setVisibility(View.GONE);
|
||||
} else {
|
||||
tab_layout.setVisibility(View.GONE);
|
||||
bottom_layout.setVisibility(View.GONE);
|
||||
tv_list_title.setVisibility(View.VISIBLE);
|
||||
gift_btn.setVisibility(View.GONE);
|
||||
}
|
||||
if (!isLiveRoom) {
|
||||
gift_btn.setEnabled(false);
|
||||
}
|
||||
adapter.setData(giftBean.getData());
|
||||
time = (WordUtil.isNewZh() ? "榜單結算時間" : "Settlement time") + giftBean.getGift_info().getGift_hall_start() + " - " + giftBean.getGift_info().getGift_hall_end();
|
||||
ImgLoader.display(mContext, IMLoginManager.get(mContext).getUserInfo().getAvatar(), avatar);
|
||||
user_name.setText(IMLoginManager.get(mContext).getUserInfo().getUserNicename());
|
||||
send_num.setText(String.format(Locale.getDefault(), "%s", giftBean.getGift_info().getGift_hall_send_num()));
|
||||
|
||||
String tmp = "";
|
||||
if (giftBean.getGift_info().getIlluminate_status() == 0) {//未點亮
|
||||
btn_one.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_bottom_btn_one_light));
|
||||
tmp = String.format(Locale.getDefault(), "%s", (giftBean.getGift_info().getIlluminate_num() - Integer.parseInt(giftBean.getGift_info().getGift_hall_send_num())));
|
||||
setTips(tmp);
|
||||
} else {//冠名/摘星
|
||||
if (isStar) {
|
||||
loadStar(giftBean);
|
||||
} else {
|
||||
loadChampion(giftBean);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
initAnim();
|
||||
}
|
||||
|
||||
private void setTips(String tmp) {
|
||||
btn_one_tips.setText(String.format(Locale.getDefault(), "%s%s%s"
|
||||
, WordUtil.isNewZh() ? "需" : "Need",
|
||||
tmp,
|
||||
WordUtil.isNewZh() ? "個" : ""));
|
||||
}
|
||||
|
||||
private void loadChampion(GiftWallInfoBean giftBean) {
|
||||
int tmp = 0;
|
||||
int mySend = Integer.parseInt(giftBean.getGift_info().getGift_hall_send_num());
|
||||
if (giftBean.getData() != null && !giftBean.getData().isEmpty()) {
|
||||
tmp = giftBean.getData().get(0).getGift_hall_send_num() - Integer.parseInt(giftBean.getGift_info().getGift_hall_send_num()) + 1;
|
||||
}
|
||||
if (mySend > tmp) {
|
||||
btn_one.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_bottom_btn_one_champion_get));
|
||||
btn_one_tips.setVisibility(View.GONE);
|
||||
} else {
|
||||
btn_one.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_bottom_btn_one_champion));
|
||||
setTips(String.valueOf(tmp + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStar(GiftWallInfoBean giftBean) {
|
||||
int tmp = 0;
|
||||
int mySend = Integer.parseInt(giftBean.getGift_info().getGift_hall_send_num());
|
||||
if (giftBean.getData() != null && !giftBean.getData().isEmpty()) {
|
||||
tmp = giftBean.getData().get(0).getGift_hall_send_num() - Integer.parseInt(giftBean.getGift_info().getGift_hall_send_num()) + 1;
|
||||
}
|
||||
if (mySend > tmp) {
|
||||
btn_one.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_bottom_btn_one_star_get));
|
||||
btn_one_tips.setVisibility(View.GONE);
|
||||
} else {
|
||||
btn_one.setText(WordUtil.getNewString(R.string.dialog_gift_wall_gfit_info_list_bottom_btn_one_star));
|
||||
setTips(String.valueOf(tmp + 1));
|
||||
}
|
||||
}
|
||||
|
||||
void initAnim() {
|
||||
if (gift.getTag() != null) {
|
||||
return;
|
||||
}
|
||||
// 创建一个向上移动的动画
|
||||
ObjectAnimator upAnimator = ObjectAnimator.ofFloat(gift, "translationY", 0f, -10f); // 假设 10f 是你想要移动的距离(注意:这里使用的是像素值,而不是 dp)
|
||||
upAnimator.setDuration(1000); // 设置动画时长为 1000 毫秒(即 1 秒)
|
||||
// 创建一个向下移动的动画
|
||||
ObjectAnimator downAnimator = ObjectAnimator.ofFloat(gift, "translationY", -10f, 0f);
|
||||
downAnimator.setDuration(1000); // 同样设置动画时长为 1 秒
|
||||
// 设置动画监听器以在向上动画结束后开始向下动画(如果需要的话)
|
||||
upAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
// 在这里开始向下动画
|
||||
downAnimator.start();
|
||||
}
|
||||
});
|
||||
downAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
gift.postDelayed(upAnimator::start, 1000);
|
||||
}
|
||||
});
|
||||
// 开始向上动画
|
||||
upAnimator.start();
|
||||
gift.setTag("start");
|
||||
|
||||
new SVGAParser(getContext()).decodeFromAssets("gift_wall_gift_info_light.svga", new SVGAParser.ParseCompletion() {
|
||||
@Override
|
||||
public void onComplete(@NonNull SVGAVideoEntity videoItem) {
|
||||
gift_bg.setImageDrawable(new SVGADrawable(videoItem));
|
||||
gift_bg.setLoops(0);
|
||||
gift_bg.startAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
System.err.println("-------------SVGA报错了");
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.yunbao.common.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.lxj.xpopup.core.AttachPopupView;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.interfaces.OnItemClickListener;
|
||||
import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
public class GiftWallMainTab1List2SpinnerDialog extends AttachPopupView {
|
||||
TextView all, up, down;
|
||||
OnItemClickListener<Integer> listener;
|
||||
|
||||
public GiftWallMainTab1List2SpinnerDialog(@NonNull Context context, OnItemClickListener<Integer> listener) {
|
||||
super(context);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDismiss() {
|
||||
super.onDismiss();
|
||||
listener.onItemClick(-1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getImplLayoutId() {
|
||||
return R.layout.dialog_gift_wall_main_tab1_list2_spinner;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
all = findViewById(R.id.spinner_all);
|
||||
up = findViewById(R.id.spinner_up);
|
||||
down = findViewById(R.id.spinner_down);
|
||||
ViewClicksAntiShake.clicksAntiShake(all, () -> {
|
||||
listener.onItemClick(0, 0);
|
||||
dismiss();
|
||||
});
|
||||
|
||||
ViewClicksAntiShake.clicksAntiShake(up, () -> {
|
||||
listener.onItemClick(1, 0);
|
||||
dismiss();
|
||||
});
|
||||
|
||||
ViewClicksAntiShake.clicksAntiShake(down, () -> {
|
||||
listener.onItemClick(2, 0);
|
||||
dismiss();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.yunbao.common.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.lxj.xpopup.core.AttachPopupView;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.interfaces.OnItemClickListener;
|
||||
import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
public class GiftWallMainTab1TipsDialog extends AttachPopupView {
|
||||
TextView tips;
|
||||
OnItemClickListener<Integer> listener;
|
||||
String time;
|
||||
|
||||
public GiftWallMainTab1TipsDialog(@NonNull Context context, OnItemClickListener<Integer> listener) {
|
||||
super(context);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public GiftWallMainTab1TipsDialog setTime(String time) {
|
||||
this.time = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDismiss() {
|
||||
super.onDismiss();
|
||||
listener.onItemClick(-1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getImplLayoutId() {
|
||||
return R.layout.dialog_gift_wall_main_tab1_tips;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
tips = findViewById(R.id.tips);
|
||||
tips.setText(time);
|
||||
ViewClicksAntiShake.clicksAntiShake(tips, () -> {
|
||||
listener.onItemClick(0, 0);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
package com.yunbao.common.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.lxj.xpopup.XPopup;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.bean.GiftWallTab2Bean;
|
||||
import com.yunbao.common.fragment.BaseFragment;
|
||||
import com.yunbao.common.fragment.GiftWallMainTab1Fragment;
|
||||
import com.yunbao.common.fragment.GiftWallMainTab2Fragment;
|
||||
import com.yunbao.common.utils.DpUtil;
|
||||
import com.yunbao.common.utils.ScreenDimenUtil;
|
||||
import com.yunbao.common.utils.WordUtil;
|
||||
import com.yunbao.common.views.CustomEllipsizeTextView;
|
||||
import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class GiftWallMainTab2ClassicInfoDialog extends AbsDialogPopupWindow {
|
||||
|
||||
private ImageView mIvBg;
|
||||
private ImageView mIvTips;
|
||||
private ImageView mIvBack;
|
||||
private CustomEllipsizeTextView mTvUserName;
|
||||
private View mAchievement;
|
||||
private TextView mTvTab1, mTvTab2;
|
||||
private ViewPager2 mViewPager;
|
||||
private LinearLayout mIvTabsLayout;
|
||||
private List<BaseFragment> fragments = new ArrayList<>();
|
||||
|
||||
private boolean isFullWindows;
|
||||
GiftWallTab2Bean.Gift gift;
|
||||
String userName;
|
||||
boolean isAnchor;
|
||||
|
||||
public GiftWallMainTab2ClassicInfoDialog(@NonNull Context context, GiftWallTab2Bean.Gift gift,boolean isAnchor) {
|
||||
super(context);
|
||||
this.gift = gift;
|
||||
this.isAnchor = isAnchor;
|
||||
}
|
||||
|
||||
public GiftWallMainTab2ClassicInfoDialog setFullWindows(boolean fullWindows) {
|
||||
isFullWindows = fullWindows;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildDialog(XPopup.Builder builder) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bindLayoutId() {
|
||||
return R.layout.dialog_gift_wall_tab2_classic_info;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPopupHeight() {
|
||||
if (isFullWindows) {
|
||||
return super.getPopupHeight();
|
||||
}
|
||||
int screenHeight = ScreenDimenUtil.getInstance().getScreenHeight();
|
||||
return (int) (screenHeight * 0.8);
|
||||
}
|
||||
|
||||
void initView() {
|
||||
mIvBg = findViewById(R.id.iv_root_bg);
|
||||
mIvTips = findViewById(R.id.v_tips);
|
||||
mIvBack = findViewById(R.id.iv_back);
|
||||
mTvUserName = findViewById(R.id.user_name);
|
||||
mAchievement = findViewById(R.id.v_achievement);
|
||||
mTvTab1 = findViewById(R.id.tab1);
|
||||
mTvTab2 = findViewById(R.id.tab2);
|
||||
mViewPager = findViewById(R.id.viewPager2);
|
||||
mIvTabsLayout = findViewById(R.id.tab_layout);
|
||||
|
||||
mTvUserName.setText(String.format(Locale.getDefault(), "%s%s",
|
||||
userName,
|
||||
WordUtil.isNewZh() ? "的禮物展館" : "'s Gift Hall"
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
initView();
|
||||
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) mIvBack.getLayoutParams();
|
||||
if (isFullWindows) {
|
||||
params.width = DpUtil.dp2px(20);
|
||||
mIvBack.setVisibility(View.VISIBLE);
|
||||
mIvBg.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
} else {
|
||||
params.width = DpUtil.dp2px(1);
|
||||
mIvBack.setVisibility(View.INVISIBLE);
|
||||
mIvBg.setScaleType(ImageView.ScaleType.FIT_XY);
|
||||
}
|
||||
mIvBack.setLayoutParams(params);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.yunbao.common.event;
|
||||
|
||||
import com.yunbao.common.bean.BaseModel;
|
||||
|
||||
public class ClosePopupDialogEvent extends BaseModel {
|
||||
public Object id=null;
|
||||
|
||||
public ClosePopupDialogEvent(Object id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Object getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Object id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ClosePopupDialogEvent() {
|
||||
}
|
||||
}
|
@ -1,20 +1,58 @@
|
||||
package com.yunbao.common.fragment;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Shader;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.lxj.xpopup.XPopup;
|
||||
import com.opensource.svgaplayer.SVGAParser;
|
||||
import com.opensource.svgaplayer.SVGAVideoEntity;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.adapter.GiftWallMainTab1List2Adapter;
|
||||
import com.yunbao.common.bean.GiftWallBean;
|
||||
import com.yunbao.common.custom.ItemDecoration;
|
||||
import com.yunbao.common.dialog.GiftWallMainTab1List2SpinnerDialog;
|
||||
import com.yunbao.common.http.base.HttpCallback;
|
||||
import com.yunbao.common.http.live.LiveNetManager;
|
||||
import com.yunbao.common.interfaces.OnItemClickListener;
|
||||
import com.yunbao.common.manager.IMLoginManager;
|
||||
import com.yunbao.common.utils.DpUtil;
|
||||
import com.yunbao.common.utils.WordUtil;
|
||||
import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class GiftWallMainTab1Fragment extends BaseFragment {
|
||||
TextView classicGiftsTitle, starGiftsTitle;
|
||||
TextView classicGiftsNumber, starGiftsNumber;
|
||||
RecyclerView recyclerView1, recyclerView2;
|
||||
View spinner;
|
||||
TextView spinnerText;
|
||||
ImageView spinnerImage;
|
||||
|
||||
GiftWallMainTab1List2Adapter list1Adapter, list2Adapter;
|
||||
|
||||
int screen;
|
||||
String toUserId;
|
||||
boolean isAnchor;
|
||||
boolean isLiveRoom;
|
||||
SVGAVideoEntity drawable;
|
||||
View classic_layout, star_layout;
|
||||
TextView show_time;
|
||||
View show_time_layout;
|
||||
|
||||
@Override
|
||||
public View createView(LayoutInflater inflater, ViewGroup container) {
|
||||
TextView textView = new TextView(getActivity());
|
||||
textView.setText("第一页");
|
||||
textView.setTextColor(Color.WHITE);
|
||||
textView.setTextSize(24);
|
||||
return textView;
|
||||
return inflater.inflate(R.layout.fragment_gift_wall_man_tab_1, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -22,9 +60,172 @@ public class GiftWallMainTab1Fragment extends BaseFragment {
|
||||
|
||||
}
|
||||
|
||||
public GiftWallMainTab1Fragment setToUserId(String toUserId) {
|
||||
this.toUserId = toUserId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GiftWallMainTab1Fragment setAnchor(boolean anchor) {
|
||||
isAnchor = anchor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GiftWallMainTab1Fragment setLiveRoom(boolean liveRoom) {
|
||||
isLiveRoom = liveRoom;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateData() {
|
||||
super.updateData();
|
||||
list1Adapter.notifyDataSetChanged();
|
||||
list2Adapter.notifyDataSetChanged();
|
||||
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
if (isAnchor) {
|
||||
LiveNetManager.get(getContext())
|
||||
.liveGiftHall(toUserId, screen, new HttpCallback<GiftWallBean>() {
|
||||
@Override
|
||||
public void onSuccess(GiftWallBean data) {
|
||||
initData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LiveNetManager.get(getContext())
|
||||
.singleUserGiftHall(toUserId, screen, new HttpCallback<GiftWallBean>() {
|
||||
@Override
|
||||
public void onSuccess(GiftWallBean data) {
|
||||
initData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void initData(GiftWallBean bean) {
|
||||
list1Adapter.setDrawable(drawable);
|
||||
list1Adapter.setList(bean.getIlluminate_data().getWeek_star_data());
|
||||
list2Adapter.setList(bean.getIlluminate_data().getGift_data());
|
||||
list1Adapter.setLiveRoom(isLiveRoom);
|
||||
list2Adapter.setLiveRoom(isLiveRoom);
|
||||
|
||||
starGiftsNumber.setText(String.format(Locale.getDefault(), "%s%d/%d",
|
||||
WordUtil.getNewString(R.string.dialog_gift_wall_list_spinner_up),
|
||||
bean.getIlluminate_data().getWeek_gift_illuminate_num(),
|
||||
bean.getIlluminate_data().getWeek_star_gift_num()
|
||||
));
|
||||
classicGiftsNumber.setText(String.format(Locale.getDefault(), "%s%d/%d",
|
||||
WordUtil.getNewString(R.string.dialog_gift_wall_list_spinner_up),
|
||||
bean.getIlluminate_data().getGift_illuminate_num(),
|
||||
bean.getIlluminate_data().getGift_num()
|
||||
));
|
||||
if (bean.getIlluminate_data().getWeek_star_data() != null) {
|
||||
show_time.setText(String.format(Locale.getDefault(), "%s - %s",
|
||||
bean.getIlluminate_data().getGift_hall_start_date(),
|
||||
bean.getIlluminate_data().getGift_hall_end_date()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initViews(Bundle savedInstanceState, View contentView) {
|
||||
classicGiftsTitle = contentView.findViewById(R.id.classic_gifts_title);
|
||||
classicGiftsNumber = contentView.findViewById(R.id.classic_gifts_number);
|
||||
recyclerView1 = contentView.findViewById(R.id.recyclerView1);
|
||||
recyclerView2 = contentView.findViewById(R.id.recyclerView2);
|
||||
starGiftsTitle = contentView.findViewById(R.id.star_gifts_title);
|
||||
starGiftsNumber = contentView.findViewById(R.id.star_gifts_number);
|
||||
spinner = contentView.findViewById(R.id.classic_gift_spinner);
|
||||
spinnerText = contentView.findViewById(R.id.classic_gift_spinner_text);
|
||||
spinnerImage = contentView.findViewById(R.id.classic_gift_spinner_ic);
|
||||
classic_layout = contentView.findViewById(R.id.classic_layout);
|
||||
star_layout = contentView.findViewById(R.id.star_layout);
|
||||
show_time = contentView.findViewById(R.id.show_time);
|
||||
show_time_layout = contentView.findViewById(R.id.show_time_layout);
|
||||
|
||||
list1Adapter = new GiftWallMainTab1List2Adapter(getContext());
|
||||
list2Adapter = new GiftWallMainTab1List2Adapter(getContext());
|
||||
|
||||
list1Adapter.setToUid(toUserId);
|
||||
list2Adapter.setToUid(toUserId);
|
||||
list1Adapter.setAnchor(isAnchor);
|
||||
list2Adapter.setAnchor(isAnchor);
|
||||
list1Adapter.setStar(true);
|
||||
|
||||
recyclerView1.setAdapter(list1Adapter);
|
||||
recyclerView2.setAdapter(list2Adapter);
|
||||
|
||||
recyclerView1.addItemDecoration(new ItemDecoration(getContext(), 0x00000000, DpUtil.dp2px(10), 1));
|
||||
recyclerView2.addItemDecoration(new ItemDecoration(getContext(), 0x00000000, DpUtil.dp2px(2), 1));
|
||||
|
||||
starGiftsTitle.getPaint().setShader(new LinearGradient(0, 0, 0, classicGiftsTitle.getPaint().getTextSize(),
|
||||
Color.parseColor("#FEE8C6"), Color.parseColor("#FFD5A3"), Shader.TileMode.CLAMP));
|
||||
|
||||
classicGiftsTitle.getPaint().setShader(new LinearGradient(0, 0, 0, classicGiftsTitle.getPaint().getTextSize(),
|
||||
Color.parseColor("#A0E9FF"), Color.parseColor("#72D1FF"), Shader.TileMode.CLAMP));
|
||||
XPopup.Builder builder = new XPopup.Builder(getContext())
|
||||
.atView(spinner);
|
||||
spinner.setTag(builder);
|
||||
|
||||
if (!isAnchor) {
|
||||
show_time_layout.setVisibility(View.GONE);
|
||||
star_layout.setVisibility(View.GONE);
|
||||
}
|
||||
if (!isLiveRoom) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
ViewClicksAntiShake.clicksAntiShake(spinner, new ViewClicksAntiShake.ViewClicksCallBack() {
|
||||
@Override
|
||||
public void onViewClicks() {
|
||||
spinnerImage.setRotation(180);
|
||||
((XPopup.Builder) spinner.getTag()).asCustom(new GiftWallMainTab1List2SpinnerDialog(getContext(), new OnItemClickListener<Integer>() {
|
||||
@Override
|
||||
public void onItemClick(Integer bean, int position) {
|
||||
if (bean > -1) {
|
||||
screen = bean;
|
||||
initData();
|
||||
}
|
||||
switch (bean) {
|
||||
case 0:
|
||||
spinnerText.setText(R.string.dialog_gift_wall_list_spinner_all);
|
||||
break;
|
||||
case 1:
|
||||
spinnerText.setText(R.string.dialog_gift_wall_list_spinner_up);
|
||||
break;
|
||||
case 2:
|
||||
spinnerText.setText(R.string.dialog_gift_wall_list_spinner_down);
|
||||
break;
|
||||
}
|
||||
spinnerImage.setRotation(0);
|
||||
}
|
||||
})).show();
|
||||
|
||||
}
|
||||
});
|
||||
new SVGAParser(getContext()).decodeFromAssets("gift_wall_light_up.svga", new SVGAParser.ParseCompletion() {
|
||||
@Override
|
||||
public void onComplete(@NonNull SVGAVideoEntity videoItem) {
|
||||
GiftWallMainTab1Fragment.this.drawable = videoItem;
|
||||
initData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
System.err.println("-------------SVGA报错了");
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,20 +1,75 @@
|
||||
package com.yunbao.common.fragment;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Shader;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.angcyo.tablayout.DslTabLayout;
|
||||
import com.angcyo.tablayout.DslTabLayoutConfig;
|
||||
import com.lxj.xpopup.XPopup;
|
||||
import com.yunbao.common.R;
|
||||
import com.yunbao.common.adapter.GiftWallMainTab2ListAdapter;
|
||||
import com.yunbao.common.bean.GiftWallTab2Bean;
|
||||
import com.yunbao.common.custom.ItemDecoration;
|
||||
import com.yunbao.common.dialog.GiftWallMainTab1List2SpinnerDialog;
|
||||
import com.yunbao.common.http.base.HttpCallback;
|
||||
import com.yunbao.common.http.live.LiveNetManager;
|
||||
import com.yunbao.common.interfaces.OnItemClickListener;
|
||||
import com.yunbao.common.utils.DpUtil;
|
||||
import com.yunbao.common.views.weight.ViewClicksAntiShake;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import kotlin.jvm.functions.Function4;
|
||||
|
||||
public class GiftWallMainTab2Fragment extends BaseFragment {
|
||||
TextView classicGiftsTitle, starGiftsTitle;
|
||||
RecyclerView recyclerView1, recyclerView2;
|
||||
View spinner;
|
||||
TextView spinnerText;
|
||||
ImageView spinnerImage;
|
||||
GiftWallMainTab2ListAdapter list1Adapter, list2Adapter;
|
||||
TextView show_time;
|
||||
DslTabLayout tabLayout;
|
||||
TextView dslTab1, dslTab2;
|
||||
|
||||
|
||||
int screen;
|
||||
int type = 1;
|
||||
String toUserId;
|
||||
boolean isAnchor;
|
||||
private boolean isLiveRoom;
|
||||
|
||||
public GiftWallMainTab2Fragment setToUserId(String toUserId) {
|
||||
this.toUserId = toUserId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GiftWallMainTab2Fragment setAnchor(boolean anchor) {
|
||||
isAnchor = anchor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GiftWallMainTab2Fragment setLiveRoom(boolean liveRoom) {
|
||||
isLiveRoom = liveRoom;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView(LayoutInflater inflater, ViewGroup container) {
|
||||
TextView textView = new TextView(getActivity());
|
||||
textView.setText("第二页");
|
||||
textView.setTextColor(Color.WHITE);
|
||||
textView.setTextSize(24);
|
||||
return textView;
|
||||
return inflater.inflate(R.layout.fragment_gift_wall_man_tab_2, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -24,7 +79,156 @@ public class GiftWallMainTab2Fragment extends BaseFragment {
|
||||
|
||||
@Override
|
||||
protected void initViews(Bundle savedInstanceState, View contentView) {
|
||||
classicGiftsTitle = contentView.findViewById(R.id.classic_gifts_title);
|
||||
recyclerView1 = contentView.findViewById(R.id.recyclerView1);
|
||||
recyclerView2 = contentView.findViewById(R.id.recyclerView2);
|
||||
starGiftsTitle = contentView.findViewById(R.id.star_gifts_title);
|
||||
spinner = contentView.findViewById(R.id.classic_gift_spinner);
|
||||
spinnerText = contentView.findViewById(R.id.classic_gift_spinner_text);
|
||||
spinnerImage = contentView.findViewById(R.id.classic_gift_spinner_ic);
|
||||
show_time = contentView.findViewById(R.id.show_time);
|
||||
tabLayout = findViewById(R.id.tab_layout);
|
||||
dslTab1 = findViewById(R.id.dsl_tab1);
|
||||
dslTab2 = findViewById(R.id.dsl_tab2);
|
||||
|
||||
list1Adapter = new GiftWallMainTab2ListAdapter(getContext());
|
||||
list2Adapter = new GiftWallMainTab2ListAdapter(getContext());
|
||||
|
||||
list1Adapter.setToUid(toUserId);
|
||||
list2Adapter.setToUid(toUserId);
|
||||
list1Adapter.setAnchor(isAnchor);
|
||||
list2Adapter.setAnchor(isAnchor);
|
||||
list1Adapter.setStar(true);
|
||||
|
||||
recyclerView1.setAdapter(list1Adapter);
|
||||
recyclerView2.setAdapter(list2Adapter);
|
||||
|
||||
|
||||
recyclerView1.addItemDecoration(new ItemDecoration(getContext(), 0x00000000, DpUtil.dp2px(2), 1));
|
||||
recyclerView2.addItemDecoration(new ItemDecoration(getContext(), 0x00000000, DpUtil.dp2px(2), 1));
|
||||
|
||||
starGiftsTitle.getPaint().setShader(new LinearGradient(0, 0, 0, classicGiftsTitle.getPaint().getTextSize(),
|
||||
Color.parseColor("#FEE8C6"), Color.parseColor("#FFD5A3"), Shader.TileMode.CLAMP));
|
||||
|
||||
classicGiftsTitle.getPaint().setShader(new LinearGradient(0, 0, 0, classicGiftsTitle.getPaint().getTextSize(),
|
||||
Color.parseColor("#A0E9FF"), Color.parseColor("#72D1FF"), Shader.TileMode.CLAMP));
|
||||
|
||||
setDslTabColor(dslTab1, dslTab2);
|
||||
|
||||
tabLayout.configTabLayoutConfig(new Function1<DslTabLayoutConfig, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(DslTabLayoutConfig dslTabLayoutConfig) {
|
||||
dslTabLayoutConfig.setOnSelectItemView(new Function4<View, Integer, Boolean, Boolean, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(View itemView, Integer index, Boolean select, Boolean fromUser) {
|
||||
//当需要选中itemView时, 触发的回调.
|
||||
//返回true, 拦截库中的默认处理.
|
||||
setDslTabColor(index == 1 ? dslTab1 : dslTab2, index == 0 ? dslTab1 : dslTab2);
|
||||
type = (index + 1);
|
||||
screen=0;
|
||||
spinnerText.setText(R.string.dialog_gift_wall_list_spinner_all);
|
||||
initData();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
dslTabLayoutConfig.setOnSelectViewChange(new Function4<View, List<? extends View>, Boolean, Boolean, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(View fromView, List<? extends View> selectViewList, Boolean select, Boolean fromUser) {
|
||||
//fromView 表示之前选中的view, 多选模式不支持.
|
||||
//selectViewList 表示目前选中view的集合, 单选模式列表里面只有一个,可以使用selectViewList.get(0)获取.
|
||||
return null;
|
||||
}
|
||||
});
|
||||
dslTabLayoutConfig.setOnSelectIndexChange(new Function4<Integer, List<Integer>, Boolean, Boolean, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(Integer fromIndex, List<Integer> selectIndexList, Boolean select, Boolean fromUser) {
|
||||
//参考setOnSelectViewChange
|
||||
//只不过对象从view,变成了view在ViewGroup中的索引
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
XPopup.Builder builder = new XPopup.Builder(getContext())
|
||||
.atView(spinner);
|
||||
spinner.setTag(builder);
|
||||
|
||||
ViewClicksAntiShake.clicksAntiShake(spinner, new ViewClicksAntiShake.ViewClicksCallBack() {
|
||||
@Override
|
||||
public void onViewClicks() {
|
||||
spinnerImage.setRotation(180);
|
||||
((XPopup.Builder) spinner.getTag()).asCustom(new GiftWallMainTab1List2SpinnerDialog(getContext(), new OnItemClickListener<Integer>() {
|
||||
@Override
|
||||
public void onItemClick(Integer bean, int position) {
|
||||
if (bean > -1) {
|
||||
screen = bean;
|
||||
initData();
|
||||
}
|
||||
switch (bean) {
|
||||
case 0:
|
||||
spinnerText.setText(R.string.dialog_gift_wall_list_spinner_all);
|
||||
break;
|
||||
case 1:
|
||||
spinnerText.setText(R.string.dialog_gift_wall_list_spinner_up);
|
||||
break;
|
||||
case 2:
|
||||
spinnerText.setText(R.string.dialog_gift_wall_list_spinner_down);
|
||||
break;
|
||||
}
|
||||
spinnerImage.setRotation(0);
|
||||
}
|
||||
})).show();
|
||||
|
||||
}
|
||||
});
|
||||
initData();
|
||||
}
|
||||
|
||||
void setDslTabColor(TextView select, TextView unSelect) {
|
||||
select.getPaint().setShader(new LinearGradient(0, 0, 0, select.getPaint().getTextSize(),
|
||||
Color.parseColor("#FEE0B6"), Color.parseColor("#FFCF95"), Shader.TileMode.CLAMP));
|
||||
unSelect.setTextColor(Color.parseColor("#FFEED8"));
|
||||
unSelect.getPaint().setShader(null);
|
||||
}
|
||||
|
||||
void initData() {
|
||||
LiveNetManager.get(getContext())
|
||||
.allGiftHall(String.valueOf(type), new HttpCallback<GiftWallTab2Bean>() {
|
||||
@Override
|
||||
public void onSuccess(GiftWallTab2Bean data) {
|
||||
if (screen != 0) {
|
||||
filtrationData(data);
|
||||
}
|
||||
initData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void filtrationData(GiftWallTab2Bean bean) {
|
||||
Iterator<GiftWallTab2Bean.Gift> iterator = bean.getIlluminateData().getGiftData().iterator();
|
||||
while (iterator.hasNext()){
|
||||
GiftWallTab2Bean.Gift next = iterator.next();
|
||||
if (screen == 1 && next.getIlluminateStatus() != 1) {//已点亮
|
||||
iterator.remove();
|
||||
} else if (screen == 2 && next.getIlluminateStatus() == 1) {//未点亮
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void initData(GiftWallTab2Bean bean) {
|
||||
list1Adapter.setList(bean.getIlluminateData().getWeekStartData());
|
||||
list2Adapter.setList(bean.getIlluminateData().getGiftData());
|
||||
show_time.setText(String.format(Locale.getDefault(), "%s - %s",
|
||||
bean.getGift_hall_start_date(),
|
||||
bean.getGift_hall_end_date()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,8 +26,11 @@ import com.yunbao.common.bean.FansGroupGiftPackInfo;
|
||||
import com.yunbao.common.bean.GiftAlreadyWallModel;
|
||||
import com.yunbao.common.bean.GiftGuideModel;
|
||||
import com.yunbao.common.bean.GiftNamingInfoModel;
|
||||
import com.yunbao.common.bean.GiftWallBean;
|
||||
import com.yunbao.common.bean.GiftWallGiftDetail;
|
||||
import com.yunbao.common.bean.GiftWallInfoBean;
|
||||
import com.yunbao.common.bean.GiftWallModel;
|
||||
import com.yunbao.common.bean.GiftWallTab2Bean;
|
||||
import com.yunbao.common.bean.GuardGetGuardOpenInfoModel;
|
||||
import com.yunbao.common.bean.GuardGetGuardUserInfoModel;
|
||||
import com.yunbao.common.bean.HourRank;
|
||||
@ -53,7 +56,6 @@ import com.yunbao.common.bean.NobleRankHideUserListModel;
|
||||
import com.yunbao.common.bean.NobleTrumpetModel;
|
||||
import com.yunbao.common.bean.OpenAdModel;
|
||||
import com.yunbao.common.bean.PkRankBean;
|
||||
import com.yunbao.common.bean.PrankGiftBean;
|
||||
import com.yunbao.common.bean.PrankGiftResultBean;
|
||||
import com.yunbao.common.bean.PrankHttpTurntableBean;
|
||||
import com.yunbao.common.bean.PrankProgressBean;
|
||||
@ -1266,4 +1268,50 @@ public interface PDLiveApi {
|
||||
Observable<ResponseModel<QiniuLog>> getPrankList(
|
||||
@Query("type")String type
|
||||
);
|
||||
|
||||
/**
|
||||
* 个人展馆
|
||||
*/
|
||||
@GET("/api/public/?service=Gift.singleUserGiftHall")
|
||||
Observable<ResponseModel<GiftWallBean>> singleUserGiftHall(
|
||||
@Query("user_id")String userId,
|
||||
@Query("screen")int screen
|
||||
);
|
||||
|
||||
/**
|
||||
* 主播展馆
|
||||
*/
|
||||
@GET("/api/public/?service=Gift.liveGiftHall")
|
||||
Observable<ResponseModel<GiftWallBean>> liveGiftHall(
|
||||
@Query("live_id")String userId,
|
||||
@Query("screen")int screen
|
||||
);
|
||||
|
||||
/**
|
||||
* 个人展馆详情
|
||||
*/
|
||||
@GET("/api/public/?service=Gift.singleUserGiftHallDetail")
|
||||
Observable<ResponseModel<GiftWallInfoBean>> singleUserGiftHallDetail(
|
||||
@Query("user_id")String userId,
|
||||
@Query("gift_id")String gift_id
|
||||
);
|
||||
|
||||
/**
|
||||
* 主播展馆详情
|
||||
*/
|
||||
@GET("/api/public/?service=Gift.liveGiftHallDetail")
|
||||
Observable<ResponseModel<GiftWallInfoBean>> liveGiftHallDetail(
|
||||
@Query("live_id")String live_id,
|
||||
@Query("gift_id")String gift_id,
|
||||
@Query("gift_hall_type")int gift_hall_type,
|
||||
@Query("list_type")int list_type
|
||||
);
|
||||
|
||||
/**
|
||||
* 全站展馆
|
||||
*/
|
||||
@GET("/api/public/?service=Gift.allGiftHall")
|
||||
Observable<ResponseModel<GiftWallTab2Bean>> allGiftHall(
|
||||
@Query("type")String type
|
||||
);
|
||||
}
|
||||
|
@ -29,8 +29,11 @@ import com.yunbao.common.bean.FansGroupGiftPackInfo;
|
||||
import com.yunbao.common.bean.GiftAlreadyWallModel;
|
||||
import com.yunbao.common.bean.GiftGuideModel;
|
||||
import com.yunbao.common.bean.GiftNamingInfoModel;
|
||||
import com.yunbao.common.bean.GiftWallBean;
|
||||
import com.yunbao.common.bean.GiftWallGiftDetail;
|
||||
import com.yunbao.common.bean.GiftWallInfoBean;
|
||||
import com.yunbao.common.bean.GiftWallModel;
|
||||
import com.yunbao.common.bean.GiftWallTab2Bean;
|
||||
import com.yunbao.common.bean.GuardGetGuardOpenInfoModel;
|
||||
import com.yunbao.common.bean.GuardGetGuardUserInfoModel;
|
||||
import com.yunbao.common.bean.HttpCallbackModel;
|
||||
@ -53,7 +56,6 @@ import com.yunbao.common.bean.NobleRankHideUserListModel;
|
||||
import com.yunbao.common.bean.NobleTrumpetModel;
|
||||
import com.yunbao.common.bean.OpenAdModel;
|
||||
import com.yunbao.common.bean.PkRankBean;
|
||||
import com.yunbao.common.bean.PrankGiftBean;
|
||||
import com.yunbao.common.bean.PrankGiftResultBean;
|
||||
import com.yunbao.common.bean.PrankHttpTurntableBean;
|
||||
import com.yunbao.common.bean.PrankProgressBean;
|
||||
@ -99,7 +101,6 @@ import io.reactivex.schedulers.Schedulers;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
|
||||
/**
|
||||
@ -3262,6 +3263,106 @@ public class LiveNetManager {
|
||||
}
|
||||
}).isDisposed();
|
||||
|
||||
}
|
||||
public void singleUserGiftHall(String userId,int screen, HttpCallback<GiftWallBean>callback) {
|
||||
API.get().pdLiveApi(mContext)
|
||||
.singleUserGiftHall(userId,screen)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(listResponseModel -> {
|
||||
if (callback != null) {
|
||||
callback.onSuccess(listResponseModel.getData().getInfo());
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
throwable.printStackTrace();
|
||||
if (callback != null) {
|
||||
callback.onError(mContext.getString(R.string.net_error));
|
||||
}
|
||||
}
|
||||
}).isDisposed();
|
||||
|
||||
}
|
||||
public void liveGiftHall(String userId,int screen, HttpCallback<GiftWallBean>callback) {
|
||||
API.get().pdLiveApi(mContext)
|
||||
.liveGiftHall(userId,screen)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(listResponseModel -> {
|
||||
if (callback != null) {
|
||||
callback.onSuccess(listResponseModel.getData().getInfo());
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
throwable.printStackTrace();
|
||||
if (callback != null) {
|
||||
callback.onError(mContext.getString(R.string.net_error));
|
||||
}
|
||||
}
|
||||
}).isDisposed();
|
||||
|
||||
}
|
||||
public void singleUserGiftHallDetail(String userId,String giftId, HttpCallback<GiftWallInfoBean>callback) {
|
||||
API.get().pdLiveApi(mContext)
|
||||
.singleUserGiftHallDetail(userId,giftId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(listResponseModel -> {
|
||||
if (callback != null) {
|
||||
callback.onSuccess(listResponseModel.getData().getInfo());
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
throwable.printStackTrace();
|
||||
if (callback != null) {
|
||||
callback.onError(mContext.getString(R.string.net_error));
|
||||
}
|
||||
}
|
||||
}).isDisposed();
|
||||
|
||||
}
|
||||
public void liveGiftHallDetail(String liveId,String giftId,int gift_hall_type,int list_type, HttpCallback<GiftWallInfoBean>callback) {
|
||||
API.get().pdLiveApi(mContext)
|
||||
.liveGiftHallDetail(liveId,giftId,gift_hall_type,list_type)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(listResponseModel -> {
|
||||
if (callback != null) {
|
||||
callback.onSuccess(listResponseModel.getData().getInfo());
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
throwable.printStackTrace();
|
||||
if (callback != null) {
|
||||
callback.onError(mContext.getString(R.string.net_error));
|
||||
}
|
||||
}
|
||||
}).isDisposed();
|
||||
|
||||
}
|
||||
public void allGiftHall(String type, HttpCallback<GiftWallTab2Bean>callback) {
|
||||
API.get().pdLiveApi(mContext)
|
||||
.allGiftHall(type)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(listResponseModel -> {
|
||||
if (callback != null) {
|
||||
callback.onSuccess(listResponseModel.getData().getInfo());
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
throwable.printStackTrace();
|
||||
if (callback != null) {
|
||||
callback.onError(mContext.getString(R.string.net_error));
|
||||
}
|
||||
}
|
||||
}).isDisposed();
|
||||
|
||||
}
|
||||
|
||||
public void updateFile(File file, HttpCallback<AvatarBean> callback) {
|
||||
|
@ -13,13 +13,14 @@ import java.util.List;
|
||||
public class SVGAViewUtils {
|
||||
private final static List<SVGAImageView> SVGA_CACHE = new ArrayList<>();
|
||||
|
||||
public static void playEndClear(SVGAImageView svga, boolean isClear, SVGACallback callback) {
|
||||
public static void playEndClear(SVGAImageView svga, boolean isClear,int loop, SVGACallback callback) {
|
||||
if (!isClear) {
|
||||
if (!SVGA_CACHE.contains(svga)) {
|
||||
SVGA_CACHE.add(svga);
|
||||
}
|
||||
}
|
||||
CrashSaveBean.getInstance().addPlaySvga();
|
||||
svga.setLoops(loop);
|
||||
svga.setCallback(new SVGACallback() {
|
||||
@Override
|
||||
public void onPause() {
|
||||
@ -72,7 +73,9 @@ public class SVGAViewUtils {
|
||||
}
|
||||
SVGA_CACHE.clear();
|
||||
}
|
||||
|
||||
public static void playEndClear(SVGAImageView svga, boolean isClear, SVGACallback callback) {
|
||||
playEndClear(svga, isClear,1, null);
|
||||
}
|
||||
public static void playEndClear(SVGAImageView svga, boolean isClear) {
|
||||
playEndClear(svga, isClear,null);
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:bottomLeftRadius="1dp"
|
||||
android:bottomRightRadius="6dp"
|
||||
android:topLeftRadius="5dp"
|
||||
android:topRightRadius="6dp" />
|
||||
<gradient android:angle="0"
|
||||
android:endColor="#FCC755"
|
||||
android:startColor="#FCC755" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
13
common/src/main/res/drawable/gift_wall_gift_info_btn.xml
Normal file
13
common/src/main/res/drawable/gift_wall_gift_info_btn.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp"/>
|
||||
<gradient
|
||||
android:startColor="#B0F1FF"
|
||||
android:endColor="#69CDFF"
|
||||
android:angle="0"
|
||||
/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
13
common/src/main/res/drawable/gift_wall_gift_info_lighten.xml
Normal file
13
common/src/main/res/drawable/gift_wall_gift_info_lighten.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp"/>
|
||||
<gradient
|
||||
android:startColor="#4DC8D7FF"
|
||||
android:endColor="#4DC8D7FF"
|
||||
android:angle="0"
|
||||
/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp"/>
|
||||
<gradient
|
||||
android:startColor="#202053"
|
||||
android:endColor="#202053"
|
||||
android:angle="0"
|
||||
/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp"/>
|
||||
<gradient
|
||||
android:startColor="#D4F6FF"
|
||||
android:endColor="#D4F6FF"
|
||||
android:angle="0"
|
||||
/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
23
common/src/main/res/drawable/gift_wall_gift_info_progres.xml
Normal file
23
common/src/main/res/drawable/gift_wall_gift_info_progres.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!--设置ProgressBar背景色-->
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<!--设置ProgressBar进度条圆角半径-->
|
||||
<corners android:radius="26dp" />
|
||||
<solid android:color="#70BCD3FF" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!--设置ProgressBar进度条颜色-->
|
||||
<item android:id="@android:id/progress">
|
||||
<scale android:scaleWidth="100%">
|
||||
<shape>
|
||||
<corners android:radius="3dp" />
|
||||
<gradient
|
||||
android:startColor="#AAEAF3"
|
||||
android:endColor="#AAEAF3" />
|
||||
</shape>
|
||||
</scale>
|
||||
</item>
|
||||
</layer-list>
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp"/>
|
||||
<gradient
|
||||
android:startColor="#80C8D7FF"
|
||||
android:endColor="#80C8D7FF"
|
||||
android:angle="0"
|
||||
/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!--设置ProgressBar背景色-->
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<!--设置ProgressBar进度条圆角半径-->
|
||||
<corners android:radius="3dp" />
|
||||
<solid android:color="#00000000" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!--设置ProgressBar进度条颜色-->
|
||||
<item android:id="@android:id/progress">
|
||||
<scale android:scaleWidth="100%">
|
||||
<shape>
|
||||
<corners android:radius="3dp" />
|
||||
<gradient
|
||||
android:startColor="#1363FF"
|
||||
android:endColor="#A0E9FF" />
|
||||
</shape>
|
||||
</scale>
|
||||
</item>
|
||||
</layer-list>
|
11
common/src/main/res/drawable/gift_wall_man_tab2_dsl.xml
Normal file
11
common/src/main/res/drawable/gift_wall_man_tab2_dsl.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:width="16dp" android:height="1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<gradient android:type="linear" android:useLevel="true" android:startColor="#fffedfb5" android:endColor="#ffffcf96" android:angle="225" />
|
||||
<corners android:topLeftRadius="2dp" android:topRightRadius="2dp" android:bottomLeftRadius="2dp" android:bottomRightRadius="2dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
<!--fffedfb5-->
|
||||
<!--ffffcf96-->
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:width="112dp" android:height="42dp">
|
||||
<shape android:shape="rectangle">
|
||||
<gradient android:type="linear" android:useLevel="false" android:startColor="#A0E9FF" android:endColor="#72D1FF" android:angle="270" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
371
common/src/main/res/layout/dialog_gift_wall_gift_info.xml
Normal file
371
common/src/main/res/layout/dialog_gift_wall_gift_info.xml
Normal file
@ -0,0 +1,371 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/rootView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_root_bg"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@mipmap/gift_wall_gift_info_bg"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_lighten"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:background="@drawable/gift_wall_gift_info_un_lighten"
|
||||
android:text="@string/dialog_gift_wall_list_spinner_down"
|
||||
android:textColor="#C8D7FF"
|
||||
android:textSize="10sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/gift_name"
|
||||
app:layout_constraintStart_toEndOf="@+id/gift_name"
|
||||
app:layout_constraintTop_toTopOf="@+id/gift_name" />
|
||||
|
||||
<com.opensource.svgaplayer.SVGAImageView
|
||||
android:id="@+id/gift_bg"
|
||||
android:layout_width="349dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="fitXY"
|
||||
app:autoPlay="true"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/linearLayout2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@mipmap/gift_wall_gift_info_light" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gift_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="17dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="PD一號" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_back"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:src="@mipmap/icon_sud_rule_close_bottom"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/gift_name"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/gift_name" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gift"
|
||||
android:layout_width="146dp"
|
||||
android:layout_height="146dp"
|
||||
android:layout_marginTop="30dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/gift_name"
|
||||
app:srcCompat="@mipmap/img_honor_default" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout2"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/gift">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@mipmap/diamond" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/diamond_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="3dp"
|
||||
android:layout_weight="1"
|
||||
android:textColor="#EEFFF8"
|
||||
android:textSize="14sp"
|
||||
tools:text="368888" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gift_tv_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="13sp"
|
||||
tools:text="30" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gift_tv_max"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#D4D4D4"
|
||||
android:textSize="12sp"
|
||||
tools:text="/60" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/gift_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="168dp"
|
||||
android:layout_height="8dp"
|
||||
android:layout_weight="1"
|
||||
android:max="100"
|
||||
android:progress="40"
|
||||
android:progressDrawable="@drawable/gift_wall_gift_info_progres" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/gift_btn"
|
||||
android:layout_width="110dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:background="@drawable/gift_wall_gift_info_btn"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#02215D"
|
||||
android:textSize="16sp"
|
||||
tools:text="繼續點亮" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="17dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="17dp"
|
||||
android:layout_marginBottom="58dp"
|
||||
android:background="@mipmap/gift_wall_gift_info_list"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linearLayout2">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="center"
|
||||
android:scaleType="fitXY"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/tab1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="50dp"
|
||||
android:background="@drawable/gift_wall_gift_info_list_btn_up"
|
||||
android:gravity="center"
|
||||
android:minWidth="70dp"
|
||||
android:text="@string/dialog_gift_wall_gfit_info_list_title_assistance"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#31326D"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/tab2"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="30dp"
|
||||
android:background="@drawable/gift_wall_gift_info_list_btn_down"
|
||||
android:gravity="center"
|
||||
android:text="@string/dialog_gift_wall_gfit_info_list_title_champion"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tips_timer"
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginTop="18dp"
|
||||
android:layout_marginEnd="19dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@mipmap/gift_wall_gift_info_list_time" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_list_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/dialog_gift_wall_gfit_info_list_title_champion"
|
||||
android:textColor="#A8EDFF"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<include
|
||||
android:id="@+id/list_title"
|
||||
layout="@layout/item_dialog_gift_wall_gift_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="18dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tips_timer" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="-25dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/list_title"
|
||||
tools:itemCount="1"
|
||||
tools:listitem="@layout/item_dialog_gift_wall_gift_info" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/bottom_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:background="@mipmap/gift_wall_gift_info_botton"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.makeramen.roundedimageview.RoundedImageView
|
||||
android:id="@+id/bottom_avatar"
|
||||
android:layout_width="37dp"
|
||||
android:layout_height="37dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="17dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@mipmap/icon_avatar_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:riv_oval="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_user_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="TextView"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintStart_toEndOf="@+id/bottom_avatar"
|
||||
app:layout_constraintTop_toTopOf="@+id/bottom_avatar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView7"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="@string/dialog_gift_wall_gfit_info_list_bottom_send"
|
||||
android:textColor="#BDBDBD"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/bottom_avatar"
|
||||
app:layout_constraintStart_toEndOf="@+id/bottom_avatar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/send_num"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/textView7"
|
||||
app:layout_constraintStart_toEndOf="@+id/textView7"
|
||||
tools:text="00" />
|
||||
|
||||
<TextView
|
||||
android:gravity="center"
|
||||
android:id="@+id/btn_one"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="26dp"
|
||||
android:layout_marginEnd="17dp"
|
||||
android:background="@drawable/gift_wall_gift_info_btn"
|
||||
android:minWidth="65dp"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#02215D"
|
||||
android:textSize="11.5sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/dialog_gift_wall_gfit_info_list_bottom_btn_one_champion" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_one_tips"
|
||||
android:layout_width="wrap_content"
|
||||
android:elevation="10dp"
|
||||
android:translationZ="2dp"
|
||||
android:layout_height="11dp"
|
||||
android:layout_marginBottom="-5dp"
|
||||
android:background="@drawable/gift_wall_gift_info_bottom_btn_tips"
|
||||
android:minWidth="32dp"
|
||||
android:text="需10个"
|
||||
android:textSize="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btn_one"
|
||||
app:layout_constraintEnd_toEndOf="@+id/btn_one" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@mipmap/gift_wall_main_tab1_down_bg"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="7dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingBottom="6dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spinner_all"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/dialog_gift_wall_list_spinner_all" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spinner_up"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/dialog_gift_wall_list_spinner_up" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spinner_down"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/dialog_gift_wall_list_spinner_down" />
|
||||
</LinearLayout>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user