chat mode 选择器
This commit is contained in:
parent
293405c8d1
commit
7358fbd8e2
|
|
@ -6,11 +6,6 @@ import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ChatBubble(
|
data class ChatBubble(
|
||||||
/**
|
|
||||||
* code
|
|
||||||
*/
|
|
||||||
val code: String,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* id
|
* id
|
||||||
*/
|
*/
|
||||||
|
|
@ -40,7 +35,7 @@ data class ChatBubble(
|
||||||
* 解锁类型 MEMBER:会员 HEARTBEAT_LEVEL:心动等级
|
* 解锁类型 MEMBER:会员 HEARTBEAT_LEVEL:心动等级
|
||||||
*/
|
*/
|
||||||
val unlockType: String? = null,
|
val unlockType: String? = null,
|
||||||
var isDefault: Boolean,
|
var isDefault: Boolean = false,
|
||||||
var select: Boolean = false
|
var select: Boolean = false
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/18
|
||||||
|
*/
|
||||||
|
data class ChatMode(
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话模型描述
|
||||||
|
*/
|
||||||
|
val description: String? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话模型名称
|
||||||
|
*/
|
||||||
|
val name: String? = null,
|
||||||
|
|
||||||
|
|
||||||
|
val onlyVip: Boolean = false,
|
||||||
|
|
||||||
|
var isSelected: Boolean = false,
|
||||||
|
)
|
||||||
|
|
@ -9,6 +9,8 @@ import android.widget.LinearLayout
|
||||||
import androidx.core.graphics.toColorInt
|
import androidx.core.graphics.toColorInt
|
||||||
import com.remax.visualnovel.R
|
import com.remax.visualnovel.R
|
||||||
import com.remax.visualnovel.databinding.LayoutChatMenuViewBinding
|
import com.remax.visualnovel.databinding.LayoutChatMenuViewBinding
|
||||||
|
import com.remax.visualnovel.entity.response.ChatBubble
|
||||||
|
import com.remax.visualnovel.entity.response.ChatMode
|
||||||
import com.remax.visualnovel.entity.response.ChatSound
|
import com.remax.visualnovel.entity.response.ChatSound
|
||||||
import com.remax.visualnovel.ui.chat.ui.expandableSelector.SelectorItem
|
import com.remax.visualnovel.ui.chat.ui.expandableSelector.SelectorItem
|
||||||
|
|
||||||
|
|
@ -27,7 +29,9 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
initAiModelSelectorView()
|
initAiModelSelectorView()
|
||||||
|
initChatModeSelectorView()
|
||||||
initSoundSelectorView()
|
initSoundSelectorView()
|
||||||
|
initBubbleSelectView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,6 +71,34 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
mBinding.aiModelSelector.selectItem(0)
|
mBinding.aiModelSelector.selectItem(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun initChatModeSelectorView() {
|
||||||
|
val items = listOf(
|
||||||
|
ChatMode(
|
||||||
|
name = "Mode-1",
|
||||||
|
description = "Previous-generation large model",
|
||||||
|
),
|
||||||
|
ChatMode(
|
||||||
|
name = "Mode-2",
|
||||||
|
description = "aaaaaaaaaaaaaaaaaaa",
|
||||||
|
),
|
||||||
|
ChatMode(
|
||||||
|
name = "Mode-3",
|
||||||
|
description = "ccccccccccccccccccccccccc",
|
||||||
|
),
|
||||||
|
ChatMode(
|
||||||
|
name = "Mode-4",
|
||||||
|
description = "Pppppppppppppppppppppp",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
//aiModelSelector.setOnItemSelectedListener()
|
||||||
|
mBinding.chatModelSelector.setTitleIcon(R.mipmap.setting_chat_mode_icon)
|
||||||
|
mBinding.chatModelSelector.setTitleText(R.string.chat_mode)
|
||||||
|
mBinding.chatModelSelector.setItems(items)
|
||||||
|
mBinding.chatModelSelector.selectItem(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun initSoundSelectorView() {
|
fun initSoundSelectorView() {
|
||||||
val items = listOf(
|
val items = listOf(
|
||||||
ChatSound(
|
ChatSound(
|
||||||
|
|
@ -74,14 +106,14 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
name = "Sound-1",
|
name = "Sound-1",
|
||||||
description = "This is description for sound-1",
|
description = "This is description for sound-1",
|
||||||
isMale = true,
|
isMale = true,
|
||||||
imgUrl = "aa"
|
imgUrl = ""
|
||||||
),
|
),
|
||||||
ChatSound(
|
ChatSound(
|
||||||
id = 2L,
|
id = 2L,
|
||||||
name = "Sound-2",
|
name = "Sound-2",
|
||||||
description = "This is description for sound-2",
|
description = "This is description for sound-2",
|
||||||
isMale = true,
|
isMale = true,
|
||||||
imgUrl = "aa"
|
imgUrl = ""
|
||||||
),
|
),
|
||||||
|
|
||||||
ChatSound(
|
ChatSound(
|
||||||
|
|
@ -89,7 +121,7 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
name = "Sound-3",
|
name = "Sound-3",
|
||||||
description = "This is description for sound-3",
|
description = "This is description for sound-3",
|
||||||
isMale = true,
|
isMale = true,
|
||||||
imgUrl = "aa"
|
imgUrl = ""
|
||||||
),
|
),
|
||||||
|
|
||||||
ChatSound(
|
ChatSound(
|
||||||
|
|
@ -97,7 +129,7 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
name = "Sound-4",
|
name = "Sound-4",
|
||||||
description = "This is description for sound-4",
|
description = "This is description for sound-4",
|
||||||
isMale = true,
|
isMale = true,
|
||||||
imgUrl = "aa"
|
imgUrl = ""
|
||||||
),
|
),
|
||||||
|
|
||||||
ChatSound(
|
ChatSound(
|
||||||
|
|
@ -105,12 +137,47 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
name = "Sound-5",
|
name = "Sound-5",
|
||||||
description = "This is description for sound-5",
|
description = "This is description for sound-5",
|
||||||
isMale = true,
|
isMale = true,
|
||||||
imgUrl = "aa"
|
imgUrl = ""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
mBinding.soundActorSelector.setItems(items)
|
mBinding.soundActorSelector.setItems(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun initBubbleSelectView() {
|
||||||
|
val items = listOf(
|
||||||
|
ChatBubble(
|
||||||
|
id = 1L,
|
||||||
|
name = "Bubble-1",
|
||||||
|
imgUrl = ""
|
||||||
|
),
|
||||||
|
ChatBubble(
|
||||||
|
id = 2L,
|
||||||
|
name = "Bubble-2",
|
||||||
|
imgUrl = ""
|
||||||
|
),
|
||||||
|
|
||||||
|
ChatBubble(
|
||||||
|
id = 3L,
|
||||||
|
name = "Bubble-3",
|
||||||
|
imgUrl = ""
|
||||||
|
),
|
||||||
|
|
||||||
|
ChatBubble(
|
||||||
|
id = 4L,
|
||||||
|
name = "Bubble-4",
|
||||||
|
imgUrl = ""
|
||||||
|
),
|
||||||
|
|
||||||
|
ChatBubble(
|
||||||
|
id = 5L,
|
||||||
|
name = "Bubble-5",
|
||||||
|
imgUrl = ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mBinding.bubbleSelectView.setItems(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
package com.remax.visualnovel.ui.chat.ui.expandableSelector
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import com.remax.visualnovel.R
|
||||||
|
import com.remax.visualnovel.databinding.LayoutExpandSelectViewBinding
|
||||||
|
import com.remax.visualnovel.entity.response.ChatBubble
|
||||||
|
import com.remax.visualnovel.entity.response.ChatSound
|
||||||
|
|
||||||
|
|
||||||
|
class ExpandBubbleSelectView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
private lateinit var mBinding: LayoutExpandSelectViewBinding
|
||||||
|
private lateinit var mExpandSubView : ExpandBubbleSubView
|
||||||
|
|
||||||
|
private var isExpanded = false
|
||||||
|
private var animationDuration = 300
|
||||||
|
private var itemSelectedListener: OnItemSelectedListener? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
initView(context, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView(context: Context, attrs: AttributeSet?) {
|
||||||
|
mBinding = LayoutExpandSelectViewBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
|
|
||||||
|
mExpandSubView = ExpandBubbleSubView(context)
|
||||||
|
mBinding.itemsContainer.addView(mExpandSubView,
|
||||||
|
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||||
|
)
|
||||||
|
setTitleText(R.string.setting_chat_bubble_title)
|
||||||
|
setTitleIcon(R.mipmap.setting_bubble_icon)
|
||||||
|
setupClickListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupClickListeners() {
|
||||||
|
mBinding.titleLayout.setOnClickListener { toggle() }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setTitleIcon(resId: Int) {
|
||||||
|
mBinding.icon.setImageResource(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTitleText(titleRes: Int) {
|
||||||
|
mBinding.titleText.text = context.resources.getString(titleRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setItems(newItems: List<ChatBubble>) {
|
||||||
|
mExpandSubView.setItems(newItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun toggle() {
|
||||||
|
if (isExpanded) collapse() else expand()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expand() {
|
||||||
|
if (isExpanded) return
|
||||||
|
|
||||||
|
isExpanded = true
|
||||||
|
mBinding.itemsContainer.visibility = VISIBLE
|
||||||
|
animateArrow(0f, 90f)
|
||||||
|
// param height anim
|
||||||
|
val animator = ValueAnimator.ofInt(0, getItemsHeight())
|
||||||
|
animator.duration = animationDuration.toLong()
|
||||||
|
animator.interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
animator.addUpdateListener { animation ->
|
||||||
|
val value = animation.animatedValue as Int
|
||||||
|
val params = mBinding.itemsContainer.layoutParams
|
||||||
|
params.height = value
|
||||||
|
mBinding.itemsContainer.layoutParams = params
|
||||||
|
}
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun collapse() {
|
||||||
|
if (!isExpanded) return
|
||||||
|
|
||||||
|
isExpanded = false
|
||||||
|
animateArrow(90f, 0f)
|
||||||
|
// param height anim
|
||||||
|
val animator = ValueAnimator.ofInt(getItemsHeight(), 0)
|
||||||
|
animator.duration = animationDuration.toLong()
|
||||||
|
animator.interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
animator.addUpdateListener { animation ->
|
||||||
|
val value = animation.animatedValue as Int
|
||||||
|
val params = mBinding.itemsContainer.layoutParams
|
||||||
|
params.height = value
|
||||||
|
mBinding.itemsContainer.layoutParams = params
|
||||||
|
}
|
||||||
|
animator.addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
mBinding.itemsContainer.visibility = GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateArrow(from: Float, to: Float) {
|
||||||
|
val rotation = ObjectAnimator.ofFloat(mBinding.arrow, "rotation", from, to)
|
||||||
|
rotation.duration = animationDuration.toLong()
|
||||||
|
rotation.interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
rotation.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getItemsHeight(): Int {
|
||||||
|
var height = 0
|
||||||
|
for (i in 0 until mBinding.itemsContainer.childCount) {
|
||||||
|
val child = mBinding.itemsContainer.getChildAt(i)
|
||||||
|
child.measure(
|
||||||
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
||||||
|
)
|
||||||
|
height += child.measuredHeight
|
||||||
|
}
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnItemSelectedListener(listener: OnItemSelectedListener) {
|
||||||
|
this.itemSelectedListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnItemSelectedListener {
|
||||||
|
fun onItemSelected(position: Int, item: SelectorItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,35 +6,73 @@ import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.drake.brv.annotaion.DividerOrientation
|
import com.drake.brv.annotaion.DividerOrientation
|
||||||
|
import com.drake.brv.utils.bindingAdapter
|
||||||
import com.drake.brv.utils.divider
|
import com.drake.brv.utils.divider
|
||||||
import com.drake.brv.utils.grid
|
import com.drake.brv.utils.grid
|
||||||
|
import com.drake.brv.utils.models
|
||||||
import com.drake.brv.utils.setup
|
import com.drake.brv.utils.setup
|
||||||
|
import com.remax.visualnovel.R
|
||||||
|
import com.remax.visualnovel.databinding.LayoutItemSettingBubbleBinding
|
||||||
import com.remax.visualnovel.databinding.LayoutSettingBubbleSubViewBinding
|
import com.remax.visualnovel.databinding.LayoutSettingBubbleSubViewBinding
|
||||||
|
import com.remax.visualnovel.entity.response.ChatBubble
|
||||||
|
import com.remax.visualnovel.extension.glide.load
|
||||||
|
|
||||||
|
|
||||||
class ExpandBubbleSubView @JvmOverloads constructor(
|
class ExpandBubbleSubView @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0
|
defStyleAttr: Int = 0
|
||||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
private lateinit var items: List<ChatBubble>
|
||||||
private var mBinding: LayoutSettingBubbleSubViewBinding
|
private var mBinding: LayoutSettingBubbleSubViewBinding
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mBinding = LayoutSettingBubbleSubViewBinding.inflate(LayoutInflater.from(context))
|
mBinding = LayoutSettingBubbleSubViewBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
with(mBinding) {
|
with(mBinding) {
|
||||||
initRv(itemsRv)
|
initRv(itemsRv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSelectedSound() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private fun initRv(itemsRv: RecyclerView) {
|
private fun initRv(itemsRv: RecyclerView) {
|
||||||
itemsRv.grid(2)
|
itemsRv.grid(3)
|
||||||
.divider {
|
.divider {
|
||||||
setDivider(16, true)
|
setDivider(16, true)
|
||||||
orientation = DividerOrientation.VERTICAL
|
orientation = DividerOrientation.VERTICAL
|
||||||
}.setup {
|
}.setup {
|
||||||
|
addType<ChatBubble>(R.layout.layout_item_setting_bubble)
|
||||||
|
|
||||||
|
onClick(R.id.tv_select) {
|
||||||
|
val bubble = getModel<ChatBubble>()
|
||||||
|
if (!bubble.select) {
|
||||||
|
itemsRv.bindingAdapter.models?.filterIsInstance<ChatBubble>()?.forEach { item ->
|
||||||
|
item.select = item == bubble
|
||||||
}
|
}
|
||||||
|
itemsRv.bindingAdapter.notifyDataSetChanged()
|
||||||
|
setSelectedSound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBind {
|
||||||
|
val item = getModel<ChatBubble>()
|
||||||
|
with(getBinding<LayoutItemSettingBubbleBinding>()) {
|
||||||
|
if (!item.imgUrl.isNullOrEmpty()) {
|
||||||
|
ivBubble.load(item.imgUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
ivBubbleName.text = item.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setItems(newItems: List<ChatBubble>) {
|
||||||
|
items = newItems
|
||||||
|
mBinding.itemsRv.models = items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
package com.remax.visualnovel.ui.chat.ui.expandableSelector
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import com.remax.visualnovel.R
|
||||||
|
import com.remax.visualnovel.databinding.LayoutExpandSelectViewBinding
|
||||||
|
import com.remax.visualnovel.databinding.LayoutItemChatModeBinding
|
||||||
|
import com.remax.visualnovel.entity.response.ChatMode
|
||||||
|
import com.remax.visualnovel.utils.spannablex.utils.dp
|
||||||
|
|
||||||
|
|
||||||
|
class ExpandChatModeSelectView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private lateinit var mBinding: LayoutExpandSelectViewBinding
|
||||||
|
|
||||||
|
private var isExpanded = false
|
||||||
|
private var animationDuration = 300
|
||||||
|
private var items: List<ChatMode> = emptyList()
|
||||||
|
private var itemSelectedListener: OnItemSelectedListener? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
initView(context, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView(context: Context, attrs: AttributeSet?) {
|
||||||
|
mBinding = LayoutExpandSelectViewBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
|
mBinding.itemsContainer.setBackgroundResource(R.drawable.bg_expand_view_items)
|
||||||
|
setupAttributes(attrs)
|
||||||
|
setupClickListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAttributes(attrs: AttributeSet?) {
|
||||||
|
attrs?.let {
|
||||||
|
val typedArray = context.obtainStyledAttributes(it, R.styleable.ExpandableSelector)
|
||||||
|
val title = typedArray.getString(R.styleable.ExpandableSelector_titleText)
|
||||||
|
title?.let { mBinding.titleText.text = it }
|
||||||
|
animationDuration = typedArray.getInt(R.styleable.ExpandableSelector_animationDuration, 300)
|
||||||
|
typedArray.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupClickListeners() {
|
||||||
|
mBinding.titleLayout.setOnClickListener { toggle() }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setTitleIcon(resId: Int) {
|
||||||
|
mBinding.icon.setImageResource(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTitleText(titleRes: Int) {
|
||||||
|
mBinding.titleText.text = context.resources.getString(titleRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setItems(newItems: List<ChatMode>) {
|
||||||
|
items = newItems
|
||||||
|
updateItemsView()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun updateItemsView() {
|
||||||
|
mBinding.itemsContainer.removeAllViews()
|
||||||
|
|
||||||
|
items.forEachIndexed { index, item ->
|
||||||
|
val itemView = createChatModeItemView(item, index)
|
||||||
|
mBinding.itemsContainer.addView(itemView)
|
||||||
|
|
||||||
|
if (index < items.size - 1) {
|
||||||
|
addDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun createChatModeItemView(item: ChatMode, position: Int): View {
|
||||||
|
val binding = LayoutItemChatModeBinding.inflate(LayoutInflater.from(context), mBinding.itemsContainer, false)
|
||||||
|
binding.itemName.text = item.name
|
||||||
|
binding.modeDescription.text = item.description
|
||||||
|
|
||||||
|
|
||||||
|
binding.root.setOnClickListener {
|
||||||
|
selectItem(position)
|
||||||
|
itemSelectedListener?.onItemSelected(position, item)
|
||||||
|
}
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addDivider() {
|
||||||
|
val divider = View(context).apply {
|
||||||
|
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 1.dp).apply {
|
||||||
|
setBackgroundColor(context.resources.getColor(R.color.chat_setting_divider_color))
|
||||||
|
marginStart = 10.dp
|
||||||
|
marginEnd = 10.dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mBinding.itemsContainer.addView(divider)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun selectItem(position: Int) {
|
||||||
|
items.forEachIndexed { index, item ->
|
||||||
|
item.isSelected = index == position
|
||||||
|
}
|
||||||
|
updateItemsView()
|
||||||
|
|
||||||
|
if (position in items.indices) {
|
||||||
|
mBinding.titleText.text = items[position].name
|
||||||
|
}
|
||||||
|
|
||||||
|
collapse()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun toggle() {
|
||||||
|
if (isExpanded) collapse() else expand()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expand() {
|
||||||
|
if (isExpanded) return
|
||||||
|
|
||||||
|
isExpanded = true
|
||||||
|
mBinding.itemsContainer.visibility = VISIBLE
|
||||||
|
animateArrow(0f, 90f)
|
||||||
|
// param height anim
|
||||||
|
val animator = ValueAnimator.ofInt(0, getItemsHeight())
|
||||||
|
animator.duration = animationDuration.toLong()
|
||||||
|
animator.interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
animator.addUpdateListener { animation ->
|
||||||
|
val value = animation.animatedValue as Int
|
||||||
|
val params = mBinding.itemsContainer.layoutParams
|
||||||
|
params.height = value
|
||||||
|
mBinding.itemsContainer.layoutParams = params
|
||||||
|
}
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun collapse() {
|
||||||
|
if (!isExpanded) return
|
||||||
|
|
||||||
|
isExpanded = false
|
||||||
|
animateArrow(90f, 0f)
|
||||||
|
// param height anim
|
||||||
|
val animator = ValueAnimator.ofInt(getItemsHeight(), 0)
|
||||||
|
animator.duration = animationDuration.toLong()
|
||||||
|
animator.interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
animator.addUpdateListener { animation ->
|
||||||
|
val value = animation.animatedValue as Int
|
||||||
|
val params = mBinding.itemsContainer.layoutParams
|
||||||
|
params.height = value
|
||||||
|
mBinding.itemsContainer.layoutParams = params
|
||||||
|
}
|
||||||
|
animator.addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
mBinding.itemsContainer.visibility = GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateArrow(from: Float, to: Float) {
|
||||||
|
val rotation = ObjectAnimator.ofFloat(mBinding.arrow, "rotation", from, to)
|
||||||
|
rotation.duration = animationDuration.toLong()
|
||||||
|
rotation.interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
rotation.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getItemsHeight(): Int {
|
||||||
|
var height = 0
|
||||||
|
for (i in 0 until mBinding.itemsContainer.childCount) {
|
||||||
|
val child = mBinding.itemsContainer.getChildAt(i)
|
||||||
|
child.measure(
|
||||||
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
||||||
|
)
|
||||||
|
height += child.measuredHeight
|
||||||
|
}
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnItemSelectedListener(listener: OnItemSelectedListener) {
|
||||||
|
this.itemSelectedListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnItemSelectedListener {
|
||||||
|
fun onItemSelected(position: Int, item: ChatMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ class ExpandSelectView @JvmOverloads constructor(
|
||||||
|
|
||||||
private fun createItemView(item: SelectorItem, position: Int): View {
|
private fun createItemView(item: SelectorItem, position: Int): View {
|
||||||
val itemView = LayoutInflater.from(context)
|
val itemView = LayoutInflater.from(context)
|
||||||
.inflate(R.layout.layout_expand_view_item, mBinding.itemsContainer, false)
|
.inflate(R.layout.layout_item_ai_model, mBinding.itemsContainer, false)
|
||||||
|
|
||||||
val colorIndicator = itemView.findViewById<View>(R.id.colorIndicator)
|
val colorIndicator = itemView.findViewById<View>(R.id.colorIndicator)
|
||||||
val itemName = itemView.findViewById<TextView>(R.id.itemName)
|
val itemName = itemView.findViewById<TextView>(R.id.itemName)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
package com.remax.visualnovel.widget
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.withStyledAttributes
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.remax.visualnovel.R
|
||||||
|
import com.remax.visualnovel.databinding.WidgetTagIconBinding
|
||||||
|
import com.remax.visualnovel.entity.response.HeartbeatLevelEnum
|
||||||
|
import com.remax.visualnovel.extension.getTemperatureTxt
|
||||||
|
import com.remax.visualnovel.extension.setMargin
|
||||||
|
import com.remax.visualnovel.extension.setSize
|
||||||
|
import com.remax.visualnovel.utils.spannablex.utils.dp
|
||||||
|
import com.remax.visualnovel.widget.uitoken.changeTextColor
|
||||||
|
import com.remax.visualnovel.widget.uitoken.changeTextStyle
|
||||||
|
import com.remax.visualnovel.widget.uitoken.getGradientDrawableOrientation
|
||||||
|
import com.remax.visualnovel.widget.uitoken.handleUIToken
|
||||||
|
import com.dylanc.viewbinding.nonreflection.inflate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/8/2
|
||||||
|
*/
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
class TagIconView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) :
|
||||||
|
LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private var binding: WidgetTagIconBinding? = null
|
||||||
|
|
||||||
|
private var type = GRADIENT_PRIMARY
|
||||||
|
|
||||||
|
private var margin = 8
|
||||||
|
|
||||||
|
init {
|
||||||
|
binding = inflate(WidgetTagIconBinding::inflate)
|
||||||
|
|
||||||
|
context.withStyledAttributes(attrs, R.styleable.TagIconView) {
|
||||||
|
val content = getString(R.styleable.TagIconView_tagIconContent)
|
||||||
|
val iconFont = getString(R.styleable.TagIconView_tagIconFont)
|
||||||
|
type = getInt(R.styleable.TagIconView_tagIconType, GRADIENT_PRIMARY)
|
||||||
|
val size = getInt(R.styleable.TagIconView_tagIconSize, SIZE_L)
|
||||||
|
|
||||||
|
setTagContent(content, iconFont)
|
||||||
|
setTagIconType(type)
|
||||||
|
|
||||||
|
var iconSize = 16f
|
||||||
|
|
||||||
|
var txtToken = R.string.txt_label_m
|
||||||
|
|
||||||
|
// var groupPaddingV = 6.dp
|
||||||
|
// var groupPaddingH = 8.dp
|
||||||
|
|
||||||
|
var groupHeight = 32.dp
|
||||||
|
|
||||||
|
when (size) {
|
||||||
|
SIZE_L -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_M -> {
|
||||||
|
iconSize = 12f
|
||||||
|
margin = 4
|
||||||
|
txtToken = R.string.txt_label_s
|
||||||
|
|
||||||
|
// groupPaddingV = 2.dp
|
||||||
|
// groupPaddingH = 4.dp
|
||||||
|
|
||||||
|
groupHeight = 24.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_S -> {
|
||||||
|
iconSize = 12f
|
||||||
|
margin = 4
|
||||||
|
txtToken = R.string.txt_label_s
|
||||||
|
//
|
||||||
|
// groupPaddingV = 2.dp
|
||||||
|
// groupPaddingH = 2.dp
|
||||||
|
|
||||||
|
groupHeight = 20.dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding?.run {
|
||||||
|
group.setSize(height = groupHeight)
|
||||||
|
// group.setPadding(groupPaddingH, groupPaddingV, groupPaddingH, groupPaddingV)
|
||||||
|
|
||||||
|
with(textView) {
|
||||||
|
changeTextStyle {
|
||||||
|
textUITextToken = context.getString(txtToken)
|
||||||
|
}
|
||||||
|
setMargin(marginStart = margin.dp, marginEnd = margin.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(iconView) {
|
||||||
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, iconSize)
|
||||||
|
setMargin(marginStart = margin.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTagTextView() = binding!!.textView
|
||||||
|
|
||||||
|
fun setTagContent(content: String?, iconFont: String? = null) {
|
||||||
|
binding?.run {
|
||||||
|
textView.text = content
|
||||||
|
|
||||||
|
iconView.text = iconFont
|
||||||
|
iconView.isVisible = iconFont != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setHeartbeatTag(heartbeatLevel: String? = null, heartbeatVal: Double? = null, showLevel: Boolean): String {
|
||||||
|
var returnTxt = ""
|
||||||
|
binding?.run {
|
||||||
|
iconView.isVisible = false
|
||||||
|
// 关系
|
||||||
|
val currLevel =
|
||||||
|
HeartbeatLevelEnum.entries.find { it.levelName == heartbeatLevel }
|
||||||
|
val tagName = if (showLevel && currLevel != null) context.getString(currLevel.tagName) else null
|
||||||
|
|
||||||
|
// 温度值,传入null就只显示关系
|
||||||
|
val temperatureTxt = heartbeatVal?.getTemperatureTxt()
|
||||||
|
|
||||||
|
returnTxt = listOfNotNull(tagName, temperatureTxt).joinToString(" · ")
|
||||||
|
if (returnTxt.isNotEmpty()){
|
||||||
|
returnTxt = " $returnTxt "
|
||||||
|
}
|
||||||
|
val tvWidth = textView.paint.measureText(returnTxt) + 1.dp
|
||||||
|
textView.setSize(width = tvWidth.toInt())
|
||||||
|
textView.text = returnTxt
|
||||||
|
|
||||||
|
val currType = when (currLevel) {
|
||||||
|
HeartbeatLevelEnum.LEVEL_3, HeartbeatLevelEnum.LEVEL_4 -> VIOLET
|
||||||
|
HeartbeatLevelEnum.LEVEL_5, HeartbeatLevelEnum.LEVEL_6 -> ORANGE
|
||||||
|
HeartbeatLevelEnum.LEVEL_7, HeartbeatLevelEnum.LEVEL_8 -> MAGENTA
|
||||||
|
HeartbeatLevelEnum.LEVEL_9, HeartbeatLevelEnum.LEVEL_10 -> GRADIENT_PRIMARY
|
||||||
|
else -> ELEMENT_DARK
|
||||||
|
}
|
||||||
|
if (currType != type) {
|
||||||
|
setTagIconType(currType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnTxt
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setTagIconType(type: Int) {
|
||||||
|
this.type = type
|
||||||
|
binding?.run {
|
||||||
|
var textColorToken = context.getString(R.string.color_txt_primary_normal)
|
||||||
|
var blurColor = ContextCompat.getColor(context, R.color.white_p15)
|
||||||
|
var groupColorToken = R.color.white_p15
|
||||||
|
|
||||||
|
group.setBackgroundResource(R.color.transparent)
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
ELEMENT -> {
|
||||||
|
blurColor = ContextCompat.getColor(context, R.color.glo_color_purple_0_p8)
|
||||||
|
}
|
||||||
|
|
||||||
|
ELEMENT_LIGHT -> {
|
||||||
|
blurColor = ContextCompat.getColor(context, R.color.white_p15)
|
||||||
|
}
|
||||||
|
|
||||||
|
ELEMENT_DARK -> {
|
||||||
|
blurColor = ContextCompat.getColor(context, R.color.black_p65)
|
||||||
|
}
|
||||||
|
|
||||||
|
MINT -> {
|
||||||
|
group.setBackgroundResource(R.color.glo_color_mint_40_p60)
|
||||||
|
}
|
||||||
|
|
||||||
|
BLUE -> {
|
||||||
|
group.setBackgroundResource(R.color.glo_color_blue_40_p60)
|
||||||
|
}
|
||||||
|
|
||||||
|
VIOLET -> {
|
||||||
|
group.setBackgroundResource(R.color.glo_color_violet_40_p60)
|
||||||
|
}
|
||||||
|
|
||||||
|
MAGENTA -> {
|
||||||
|
group.setBackgroundResource(R.color.glo_color_magenta_50_p60)
|
||||||
|
}
|
||||||
|
|
||||||
|
ORANGE -> {
|
||||||
|
group.setBackgroundResource(R.color.glo_color_orange_50_p60)
|
||||||
|
}
|
||||||
|
|
||||||
|
GRADIENT_PRIMARY -> {
|
||||||
|
context.handleUIToken(R.string.color_primary_gradient_normal)?.let {
|
||||||
|
val gradientDrawable = GradientDrawable()
|
||||||
|
gradientDrawable.shape = GradientDrawable.RECTANGLE
|
||||||
|
gradientDrawable.orientation = getGradientDrawableOrientation(it.deg)
|
||||||
|
gradientDrawable.colors = it.colors
|
||||||
|
group.background = gradientDrawable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GRADIENT_SECOND -> {
|
||||||
|
context.handleUIToken(R.string.color_context_vip_normal)?.let {
|
||||||
|
val gradientDrawable = GradientDrawable()
|
||||||
|
gradientDrawable.shape = GradientDrawable.RECTANGLE
|
||||||
|
gradientDrawable.orientation = getGradientDrawableOrientation(it.deg)
|
||||||
|
gradientDrawable.colors = it.colors
|
||||||
|
group.background = gradientDrawable
|
||||||
|
}
|
||||||
|
textColorToken = context.getString(R.string.color_background_default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blurView.setOverlayColor(blurColor)
|
||||||
|
|
||||||
|
textView.changeTextColor {
|
||||||
|
textUIColorToken = textColorToken
|
||||||
|
}
|
||||||
|
iconView.changeTextColor {
|
||||||
|
textUIColorToken = textColorToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ELEMENT = 1
|
||||||
|
const val ELEMENT_LIGHT = 2
|
||||||
|
const val ELEMENT_DARK = 3
|
||||||
|
|
||||||
|
const val MINT = 4
|
||||||
|
const val BLUE = 5
|
||||||
|
const val VIOLET = 6
|
||||||
|
const val MAGENTA = 7
|
||||||
|
const val ORANGE = 8
|
||||||
|
|
||||||
|
const val GRADIENT_PRIMARY = 9
|
||||||
|
const val GRADIENT_SECOND = 10
|
||||||
|
|
||||||
|
const val SIZE_L = 1
|
||||||
|
const val SIZE_M = 2
|
||||||
|
const val SIZE_S = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,975 @@
|
||||||
|
package com.remax.visualnovel.widget.blurview;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapShader;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.StateSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.ColorRes;
|
||||||
|
import androidx.annotation.DimenRes;
|
||||||
|
import androidx.annotation.FloatRange;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.remax.visualnovel.R;
|
||||||
|
import com.remax.visualnovel.widget.blurview.enu.BlurCorner;
|
||||||
|
import com.remax.visualnovel.widget.blurview.enu.BlurMode;
|
||||||
|
import com.remax.visualnovel.widget.blurview.impl.AndroidStockBlurImpl;
|
||||||
|
import com.remax.visualnovel.widget.blurview.impl.AndroidXBlurImpl;
|
||||||
|
import com.remax.visualnovel.widget.blurview.impl.BlurImpl;
|
||||||
|
import com.remax.visualnovel.widget.blurview.impl.EmptyBlurImpl;
|
||||||
|
import com.remax.visualnovel.widget.blurview.impl.SupportLibraryBlurImpl;
|
||||||
|
|
||||||
|
|
||||||
|
public class ShapeBlurView extends View {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default 4
|
||||||
|
*/
|
||||||
|
private float mDownSampleFactor;
|
||||||
|
/**
|
||||||
|
* default #000000
|
||||||
|
*/
|
||||||
|
private int mOverlayColor;
|
||||||
|
/**
|
||||||
|
* default 10dp (0 < r <= 25)
|
||||||
|
*/
|
||||||
|
private float mBlurRadius;
|
||||||
|
public static final int DEFAULT_BORDER_COLOR = Color.WHITE;
|
||||||
|
|
||||||
|
private final BlurImpl mBlurImpl;
|
||||||
|
private boolean mDirty;
|
||||||
|
private Bitmap mBitmapToBlur, mBlurredBitmap;
|
||||||
|
private Canvas mBlurringCanvas;
|
||||||
|
private boolean mIsRendering;
|
||||||
|
|
||||||
|
|
||||||
|
private final Rect mRectSrc = new Rect();
|
||||||
|
private final RectF mRectFDst = new RectF();
|
||||||
|
/**
|
||||||
|
* mDecorView should be the root view of the activity (even if you are on a different window like a dialog)
|
||||||
|
*/
|
||||||
|
private View mDecorView;
|
||||||
|
/**
|
||||||
|
* If the view is on different root view (usually means we are on a PopupWindow),
|
||||||
|
* we need to manually call invalidate() in onPreDraw(), otherwise we will not be able to see the changes
|
||||||
|
*/
|
||||||
|
private boolean mDifferentRoot;
|
||||||
|
private static int RENDERING_COUNT;
|
||||||
|
private static int BLUR_IMPL;
|
||||||
|
|
||||||
|
private int blurMode = BlurMode.MODE_RECTANGLE;
|
||||||
|
private final Paint mBitmapPaint;
|
||||||
|
//圆形 相关
|
||||||
|
private float cx = 0, cy = 0, cRadius = 0;
|
||||||
|
|
||||||
|
//圆角相关
|
||||||
|
private static final float DEFAULT_RADIUS = 0f;
|
||||||
|
private final float[] mCornerRadii = new float[]{DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS};
|
||||||
|
private final Path cornerPath = new Path();
|
||||||
|
private float[] cornerRids;
|
||||||
|
|
||||||
|
//边框相关
|
||||||
|
private static final float DEFAULT_BORDER_WIDTH = 0f;
|
||||||
|
|
||||||
|
private final RectF mBorderRect = new RectF();
|
||||||
|
private final Paint mBorderPaint;
|
||||||
|
private float mBorderWidth = 0;
|
||||||
|
private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
|
||||||
|
private Matrix matrix;
|
||||||
|
private BitmapShader shader;
|
||||||
|
|
||||||
|
public ShapeBlurView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
mContext = context;
|
||||||
|
// provide your own by override getBlurImpl()
|
||||||
|
mBlurImpl = getBlurImpl();
|
||||||
|
try {
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ShapeBlurView);
|
||||||
|
mBlurRadius = a.getDimension(R.styleable.ShapeBlurView_blur_radius,
|
||||||
|
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25, context.getResources().getDisplayMetrics()));
|
||||||
|
mDownSampleFactor = a.getFloat(R.styleable.ShapeBlurView_blur_down_sample, 4);
|
||||||
|
mOverlayColor = a.getColor(R.styleable.ShapeBlurView_blur_overlay_color, 0xA6000000);
|
||||||
|
|
||||||
|
float cornerRadiusOverride =
|
||||||
|
a.getDimensionPixelSize(R.styleable.ShapeBlurView_blur_corner_radius, -1);
|
||||||
|
mCornerRadii[BlurCorner.TOP_LEFT] =
|
||||||
|
a.getDimensionPixelSize(R.styleable.ShapeBlurView_blur_corner_radius_top_left, -1);
|
||||||
|
mCornerRadii[BlurCorner.TOP_RIGHT] =
|
||||||
|
a.getDimensionPixelSize(R.styleable.ShapeBlurView_blur_corner_radius_top_right, -1);
|
||||||
|
mCornerRadii[BlurCorner.BOTTOM_RIGHT] =
|
||||||
|
a.getDimensionPixelSize(R.styleable.ShapeBlurView_blur_corner_radius_bottom_right, -1);
|
||||||
|
mCornerRadii[BlurCorner.BOTTOM_LEFT] =
|
||||||
|
a.getDimensionPixelSize(R.styleable.ShapeBlurView_blur_corner_radius_bottom_left, -1);
|
||||||
|
initCornerData(cornerRadiusOverride);
|
||||||
|
blurMode = a.getInt(R.styleable.ShapeBlurView_blur_mode, BlurMode.MODE_RECTANGLE);
|
||||||
|
|
||||||
|
mBorderWidth = a.getDimensionPixelSize(R.styleable.ShapeBlurView_blur_border_width, -1);
|
||||||
|
if (mBorderWidth < 0) {
|
||||||
|
mBorderWidth = DEFAULT_BORDER_WIDTH;
|
||||||
|
}
|
||||||
|
mBorderColor = a.getColorStateList(R.styleable.ShapeBlurView_blur_border_color);
|
||||||
|
if (mBorderColor == null) {
|
||||||
|
mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
a.recycle();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
mBitmapPaint = new Paint();
|
||||||
|
// mBitmapPaint.setStyle(Paint.Style.FILL);
|
||||||
|
mBitmapPaint.setAntiAlias(true);
|
||||||
|
|
||||||
|
mBorderPaint = new Paint();
|
||||||
|
mBorderPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mBorderPaint.setAntiAlias(true);
|
||||||
|
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
|
||||||
|
mBorderPaint.setStrokeWidth(mBorderWidth);
|
||||||
|
|
||||||
|
// matrix = new Matrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initCornerData(float cornerRadiusOverride) {
|
||||||
|
boolean any = false;
|
||||||
|
for (int i = 0, len = mCornerRadii.length; i < len; i++) {
|
||||||
|
if (mCornerRadii[i] < 0) {
|
||||||
|
mCornerRadii[i] = 0f;
|
||||||
|
} else {
|
||||||
|
any = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!any) {
|
||||||
|
if (cornerRadiusOverride < 0) {
|
||||||
|
cornerRadiusOverride = DEFAULT_RADIUS;
|
||||||
|
}
|
||||||
|
for (int i = 0, len = mCornerRadii.length; i < len; i++) {
|
||||||
|
mCornerRadii[i] = cornerRadiusOverride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initCornerRids();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCornerRids() {
|
||||||
|
if (cornerRids == null) {
|
||||||
|
cornerRids = new float[]{mCornerRadii[BlurCorner.TOP_LEFT], mCornerRadii[BlurCorner.TOP_LEFT],
|
||||||
|
mCornerRadii[BlurCorner.TOP_RIGHT], mCornerRadii[BlurCorner.TOP_RIGHT],
|
||||||
|
mCornerRadii[BlurCorner.BOTTOM_RIGHT], mCornerRadii[BlurCorner.BOTTOM_RIGHT],
|
||||||
|
mCornerRadii[BlurCorner.BOTTOM_LEFT], mCornerRadii[BlurCorner.BOTTOM_LEFT]};
|
||||||
|
} else {
|
||||||
|
cornerRids[0] = mCornerRadii[BlurCorner.TOP_LEFT];
|
||||||
|
cornerRids[1] = mCornerRadii[BlurCorner.TOP_LEFT];
|
||||||
|
cornerRids[2] = mCornerRadii[BlurCorner.TOP_RIGHT];
|
||||||
|
cornerRids[3] = mCornerRadii[BlurCorner.TOP_RIGHT];
|
||||||
|
cornerRids[4] = mCornerRadii[BlurCorner.BOTTOM_RIGHT];
|
||||||
|
cornerRids[5] = mCornerRadii[BlurCorner.BOTTOM_RIGHT];
|
||||||
|
cornerRids[6] = mCornerRadii[BlurCorner.BOTTOM_LEFT];
|
||||||
|
cornerRids[7] = mCornerRadii[BlurCorner.BOTTOM_LEFT];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BlurImpl getBlurImpl() {
|
||||||
|
if (BLUR_IMPL == 0) {
|
||||||
|
// try to use stock impl first
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
try {
|
||||||
|
AndroidStockBlurImpl impl = new AndroidStockBlurImpl();
|
||||||
|
Bitmap bmp = Bitmap.createBitmap(4, 4, Bitmap.Config.ARGB_8888);
|
||||||
|
impl.prepare(getContext(), bmp, 4);
|
||||||
|
impl.release();
|
||||||
|
bmp.recycle();
|
||||||
|
BLUR_IMPL = 3;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BLUR_IMPL == 0) {
|
||||||
|
try {
|
||||||
|
getClass().getClassLoader().loadClass("androidx.renderscript.RenderScript");
|
||||||
|
// initialize RenderScript to load jni impl
|
||||||
|
// may throw unsatisfied link error
|
||||||
|
AndroidXBlurImpl impl = new AndroidXBlurImpl();
|
||||||
|
Bitmap bmp = Bitmap.createBitmap(4, 4, Bitmap.Config.ARGB_8888);
|
||||||
|
impl.prepare(getContext(), bmp, 4);
|
||||||
|
impl.release();
|
||||||
|
bmp.recycle();
|
||||||
|
BLUR_IMPL = 1;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// class not found or unsatisfied link
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BLUR_IMPL == 0) {
|
||||||
|
try {
|
||||||
|
getClass().getClassLoader().loadClass("android.support.v8.renderscript.RenderScript");
|
||||||
|
// initialize RenderScript to load jni impl
|
||||||
|
// may throw unsatisfied link error
|
||||||
|
SupportLibraryBlurImpl impl = new SupportLibraryBlurImpl();
|
||||||
|
Bitmap bmp = Bitmap.createBitmap(4, 4, Bitmap.Config.ARGB_8888);
|
||||||
|
impl.prepare(getContext(), bmp, 4);
|
||||||
|
impl.release();
|
||||||
|
bmp.recycle();
|
||||||
|
BLUR_IMPL = 2;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// class not found or unsatisfied link
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BLUR_IMPL == 0) {
|
||||||
|
// fallback to empty impl, which doesn't have blur effect
|
||||||
|
BLUR_IMPL = -1;
|
||||||
|
}
|
||||||
|
switch (BLUR_IMPL) {
|
||||||
|
case 1:
|
||||||
|
return new AndroidXBlurImpl();
|
||||||
|
case 2:
|
||||||
|
return new SupportLibraryBlurImpl();
|
||||||
|
case 3:
|
||||||
|
return new AndroidStockBlurImpl();
|
||||||
|
default:
|
||||||
|
return new EmptyBlurImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void setBlurRadius(@FloatRange(from = 0, to = 25) float radius) {
|
||||||
|
// if (mBlurRadius != radius) {
|
||||||
|
// mBlurRadius = radius;
|
||||||
|
// mDirty = true;
|
||||||
|
// invalidate();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void setDownSampleFactor(float factor) {
|
||||||
|
// if (factor <= 0) {
|
||||||
|
// throw new IllegalArgumentException("DownSample factor must be greater than 0.");
|
||||||
|
// }
|
||||||
|
// if (mDownSampleFactor != factor) {
|
||||||
|
// mDownSampleFactor = factor;
|
||||||
|
// // may also change blur radius
|
||||||
|
// mDirty = true;
|
||||||
|
// releaseBitmap();
|
||||||
|
// invalidate();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void setOverlayColor(int color) {
|
||||||
|
if (mOverlayColor != color) {
|
||||||
|
mOverlayColor = color;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all the corner radii from a dimension resource id.
|
||||||
|
*
|
||||||
|
* @param resId dimension resource id of radii.
|
||||||
|
*/
|
||||||
|
// public void setCornerRadiusDimen(@DimenRes int resId) {
|
||||||
|
// float radius = getResources().getDimension(resId);
|
||||||
|
// setCornerRadius(radius, radius, radius, radius);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the corner radius of a specific corner in px.
|
||||||
|
*
|
||||||
|
* @param radius
|
||||||
|
*/
|
||||||
|
// public void setCornerRadius(float radius) {
|
||||||
|
// setCornerRadius(radius, radius, radius, radius);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the corner radius of a specific corner in px.
|
||||||
|
*/
|
||||||
|
// public void setCornerRadius(@BlurCorner int corner, float radius) {
|
||||||
|
// if (mCornerRadii[corner] == radius) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// mCornerRadii[corner] = radius;
|
||||||
|
// initCornerRids();
|
||||||
|
// invalidate();
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the corner radius of a specific corner in px.
|
||||||
|
*/
|
||||||
|
// public void setCornerRadius(float topLeft, float topRight, float bottomLeft, float bottomRight) {
|
||||||
|
// if (mCornerRadii[BlurCorner.TOP_LEFT] == topLeft
|
||||||
|
// && mCornerRadii[BlurCorner.TOP_RIGHT] == topRight
|
||||||
|
// && mCornerRadii[BlurCorner.BOTTOM_RIGHT] == bottomRight
|
||||||
|
// && mCornerRadii[BlurCorner.BOTTOM_LEFT] == bottomLeft) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// mCornerRadii[BlurCorner.TOP_LEFT] = topLeft;
|
||||||
|
// mCornerRadii[BlurCorner.TOP_RIGHT] = topRight;
|
||||||
|
// mCornerRadii[BlurCorner.BOTTOM_LEFT] = bottomLeft;
|
||||||
|
// mCornerRadii[BlurCorner.BOTTOM_RIGHT] = bottomRight;
|
||||||
|
// initCornerRids();
|
||||||
|
// invalidate();
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the largest corner radius.
|
||||||
|
*/
|
||||||
|
public float getCornerRadius() {
|
||||||
|
return getMaxCornerRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the largest corner radius.
|
||||||
|
*/
|
||||||
|
public float getMaxCornerRadius() {
|
||||||
|
float maxRadius = 0;
|
||||||
|
for (float r : mCornerRadii) {
|
||||||
|
maxRadius = Math.max(r, maxRadius);
|
||||||
|
}
|
||||||
|
return maxRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public float getBorderWidth() {
|
||||||
|
return mBorderWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void setBorderWidth(@DimenRes int resId) {
|
||||||
|
// setBorderWidth(getResources().getDimension(resId));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void setBorderWidth(float width) {
|
||||||
|
// if (mBorderWidth == width) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// mBorderWidth = width;
|
||||||
|
// invalidate();
|
||||||
|
// }
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public int getBorderColor() {
|
||||||
|
return mBorderColor.getDefaultColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void setBorderColor(@ColorInt int color) {
|
||||||
|
// setBorderColor(ColorStateList.valueOf(color));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void setBorderColor(ColorStateList colors) {
|
||||||
|
// if (mBorderColor.equals(colors)) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// mBorderColor = (colors != null) ? colors : ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
|
||||||
|
// mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
|
||||||
|
// if (mBorderWidth > 0) {
|
||||||
|
// invalidate();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@BlurMode
|
||||||
|
public int getBlurMode() {
|
||||||
|
return this.blurMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void setBlurMode(@BlurMode int blurMode) {
|
||||||
|
// if (this.blurMode == blurMode) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// this.blurMode = blurMode;
|
||||||
|
// invalidate();
|
||||||
|
// }
|
||||||
|
|
||||||
|
private void releaseBitmap() {
|
||||||
|
if (mBitmapToBlur != null) {
|
||||||
|
mBitmapToBlur.recycle();
|
||||||
|
mBitmapToBlur = null;
|
||||||
|
}
|
||||||
|
if (mBlurredBitmap != null) {
|
||||||
|
mBlurredBitmap.recycle();
|
||||||
|
mBlurredBitmap = null;
|
||||||
|
}
|
||||||
|
if (matrix != null) {
|
||||||
|
matrix = null;
|
||||||
|
}
|
||||||
|
if (shader != null) {
|
||||||
|
shader = null;
|
||||||
|
}
|
||||||
|
mContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void release() {
|
||||||
|
releaseBitmap();
|
||||||
|
mBlurImpl.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean prepare() {
|
||||||
|
if (mBlurRadius == 0) {
|
||||||
|
release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
float downSampleFactor = mDownSampleFactor;
|
||||||
|
float radius = mBlurRadius / downSampleFactor;
|
||||||
|
if (radius > 25) {
|
||||||
|
downSampleFactor = downSampleFactor * radius / 25;
|
||||||
|
radius = 25;
|
||||||
|
}
|
||||||
|
final int width = getWidth();
|
||||||
|
final int height = getHeight();
|
||||||
|
int scaledWidth = Math.max(1, (int) (width / downSampleFactor));
|
||||||
|
int scaledHeight = Math.max(1, (int) (height / downSampleFactor));
|
||||||
|
boolean dirty = mDirty;
|
||||||
|
if (mBlurringCanvas == null || mBlurredBitmap == null
|
||||||
|
|| mBlurredBitmap.getWidth() != scaledWidth
|
||||||
|
|| mBlurredBitmap.getHeight() != scaledHeight) {
|
||||||
|
dirty = true;
|
||||||
|
releaseBitmap();
|
||||||
|
boolean r = false;
|
||||||
|
try {
|
||||||
|
mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
|
||||||
|
if (mBitmapToBlur == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mBlurringCanvas = new Canvas(mBitmapToBlur);
|
||||||
|
mBlurredBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
|
||||||
|
if (mBlurredBitmap == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
r = true;
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
// Bitmap.createBitmap() may cause OOM error
|
||||||
|
// Simply ignore and fallback
|
||||||
|
} finally {
|
||||||
|
if (!r) {
|
||||||
|
release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dirty) {
|
||||||
|
if (mBlurImpl.prepare(getContext(), mBitmapToBlur, radius)) {
|
||||||
|
mDirty = false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void blur(Bitmap bitmapToBlur, Bitmap blurredBitmap) {
|
||||||
|
shader = new BitmapShader(blurredBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||||
|
mBlurImpl.blur(bitmapToBlur, blurredBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw() {
|
||||||
|
final int[] locations = new int[2];
|
||||||
|
Bitmap oldBmp = mBlurredBitmap;
|
||||||
|
View decor = mDecorView;
|
||||||
|
if (decor != null && isShown() && prepare()) {
|
||||||
|
boolean redrawBitmap = mBlurredBitmap != oldBmp;
|
||||||
|
oldBmp = null;
|
||||||
|
decor.getLocationOnScreen(locations);
|
||||||
|
int x = -locations[0];
|
||||||
|
int y = -locations[1];
|
||||||
|
getLocationOnScreen(locations);
|
||||||
|
x += locations[0];
|
||||||
|
y += locations[1];
|
||||||
|
// just erase transparent
|
||||||
|
mBitmapToBlur.eraseColor(mOverlayColor & 0xffffff);
|
||||||
|
int rc = mBlurringCanvas.save();
|
||||||
|
mIsRendering = true;
|
||||||
|
RENDERING_COUNT++;
|
||||||
|
try {
|
||||||
|
mBlurringCanvas.scale(1.f * mBitmapToBlur.getWidth() / getWidth(), 1.f * mBitmapToBlur.getHeight() / getHeight());
|
||||||
|
mBlurringCanvas.translate(-x, -y);
|
||||||
|
if (decor.getBackground() != null) {
|
||||||
|
decor.getBackground().draw(mBlurringCanvas);
|
||||||
|
}
|
||||||
|
decor.draw(mBlurringCanvas);
|
||||||
|
} catch (StopException e) {
|
||||||
|
} finally {
|
||||||
|
mIsRendering = false;
|
||||||
|
RENDERING_COUNT--;
|
||||||
|
mBlurringCanvas.restoreToCount(rc);
|
||||||
|
}
|
||||||
|
blur(mBitmapToBlur, mBlurredBitmap);
|
||||||
|
if (redrawBitmap || mDifferentRoot) {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected View getActivityDecorView() {
|
||||||
|
Context ctx = getContext();
|
||||||
|
for (int i = 0; i < 4 && !(ctx instanceof Activity) && ctx instanceof ContextWrapper; i++) {
|
||||||
|
ctx = ((ContextWrapper) ctx).getBaseContext();
|
||||||
|
}
|
||||||
|
if (ctx instanceof Activity) {
|
||||||
|
return ((Activity) ctx).getWindow().getDecorView();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
mDecorView = getActivityDecorView();
|
||||||
|
if (mDecorView != null) {
|
||||||
|
mDecorView.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
|
||||||
|
mDifferentRoot = mDecorView.getRootView() != getRootView();
|
||||||
|
if (mDifferentRoot) {
|
||||||
|
mDecorView.postInvalidate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mDifferentRoot = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
if (mDecorView != null) {
|
||||||
|
mDecorView.getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
|
||||||
|
}
|
||||||
|
release();
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
if (mIsRendering) {
|
||||||
|
// Quit here, don't draw views above me
|
||||||
|
throw STOP_EXCEPTION;
|
||||||
|
} else if (RENDERING_COUNT > 0) {
|
||||||
|
// Doesn't support blurview overlap on another blurview
|
||||||
|
} else {
|
||||||
|
super.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
drawBlurredBitmap(canvas, mBlurredBitmap, mOverlayColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom draw the blurred bitmap and color to define your own shape
|
||||||
|
*
|
||||||
|
* @param canvas
|
||||||
|
* @param blurBitmap
|
||||||
|
* @param overlayColor
|
||||||
|
*/
|
||||||
|
protected void drawBlurredBitmap(Canvas canvas, Bitmap blurBitmap, int overlayColor) {
|
||||||
|
if (blurBitmap != null) {
|
||||||
|
if (blurMode == BlurMode.MODE_CIRCLE) {
|
||||||
|
drawCircleRectBitmap(canvas, blurBitmap, overlayColor);
|
||||||
|
} else if (blurMode == BlurMode.MODE_OVAL) {
|
||||||
|
drawOvalRectBitmap(canvas, blurBitmap, overlayColor);
|
||||||
|
} else {
|
||||||
|
drawRoundRectBitmap(canvas, blurBitmap, overlayColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认或者画矩形可带圆角
|
||||||
|
*
|
||||||
|
* @param canvas
|
||||||
|
* @param blurBitmap
|
||||||
|
* @param overlayColor
|
||||||
|
*/
|
||||||
|
private void drawRoundRectBitmap(Canvas canvas, Bitmap blurBitmap, int overlayColor) {
|
||||||
|
try {
|
||||||
|
//圆角的半径,依次为左上角xy半径,右上角,右下角,左下角
|
||||||
|
mRectFDst.right = getWidth();
|
||||||
|
mRectFDst.bottom = getHeight();
|
||||||
|
/*向路径中添加圆角矩形。radii数组定义圆角矩形的四个圆角的x,y半径。radii长度必须为8*/
|
||||||
|
//Path.Direction.CW:clockwise ,沿顺时针方向绘制,Path.Direction.CCW:counter-clockwise ,沿逆时针方向绘制
|
||||||
|
cornerPath.addRoundRect(mRectFDst, cornerRids, Path.Direction.CW);
|
||||||
|
cornerPath.close();
|
||||||
|
canvas.clipPath(cornerPath);
|
||||||
|
|
||||||
|
mRectSrc.right = blurBitmap.getWidth();
|
||||||
|
mRectSrc.bottom = blurBitmap.getHeight();
|
||||||
|
canvas.drawBitmap(blurBitmap, mRectSrc, mRectFDst, null);
|
||||||
|
mBitmapPaint.setColor(overlayColor);
|
||||||
|
canvas.drawRect(mRectFDst, mBitmapPaint);
|
||||||
|
if (mBorderWidth > 0) {
|
||||||
|
//目前没找到合适方式
|
||||||
|
mBorderPaint.setStrokeWidth(mBorderWidth * 2);
|
||||||
|
canvas.drawPath(cornerPath, mBorderPaint);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画椭圆,如果宽高一样则为圆形
|
||||||
|
*
|
||||||
|
* @param canvas
|
||||||
|
* @param blurBitmap
|
||||||
|
* @param overlayColor
|
||||||
|
*/
|
||||||
|
private void drawOvalRectBitmap(Canvas canvas, Bitmap blurBitmap, int overlayColor) {
|
||||||
|
try {
|
||||||
|
mRectFDst.right = getWidth();
|
||||||
|
mRectFDst.bottom = getHeight();
|
||||||
|
mBitmapPaint.reset();
|
||||||
|
mBitmapPaint.setAntiAlias(true);
|
||||||
|
if (shader == null) {
|
||||||
|
shader = new BitmapShader(blurBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||||
|
}
|
||||||
|
if (matrix == null) {
|
||||||
|
matrix = new Matrix();
|
||||||
|
}
|
||||||
|
matrix.postScale(mRectFDst.width() / blurBitmap.getWidth(), mRectFDst.height() / blurBitmap.getHeight());
|
||||||
|
shader.setLocalMatrix(matrix);
|
||||||
|
mBitmapPaint.setShader(shader);
|
||||||
|
canvas.drawOval(mRectFDst, mBitmapPaint);
|
||||||
|
mBitmapPaint.reset();
|
||||||
|
mBitmapPaint.setAntiAlias(true);
|
||||||
|
mBitmapPaint.setColor(overlayColor);
|
||||||
|
canvas.drawOval(mRectFDst, mBitmapPaint);
|
||||||
|
if (mBorderWidth > 0) {
|
||||||
|
mBorderRect.set(mRectFDst);
|
||||||
|
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||||
|
canvas.drawOval(mBorderRect, mBorderPaint);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画圆形,以宽高最小的为半径
|
||||||
|
*
|
||||||
|
* @param canvas
|
||||||
|
* @param blurBitmap
|
||||||
|
* @param overlayColor
|
||||||
|
*/
|
||||||
|
private void drawCircleRectBitmap(Canvas canvas, Bitmap blurBitmap, int overlayColor) {
|
||||||
|
try {
|
||||||
|
mRectFDst.right = getMeasuredWidth();
|
||||||
|
mRectFDst.bottom = getMeasuredHeight();
|
||||||
|
mRectSrc.right = blurBitmap.getWidth();
|
||||||
|
mRectSrc.bottom = blurBitmap.getHeight();
|
||||||
|
mBitmapPaint.reset();
|
||||||
|
mBitmapPaint.setAntiAlias(true);
|
||||||
|
if (shader == null) {
|
||||||
|
shader = new BitmapShader(blurBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||||
|
}
|
||||||
|
if (matrix == null) {
|
||||||
|
matrix = new Matrix();
|
||||||
|
}
|
||||||
|
matrix.postScale(mRectFDst.width() / mRectSrc.width(), mRectFDst.height() / mRectSrc.height());
|
||||||
|
shader.setLocalMatrix(matrix);
|
||||||
|
mBitmapPaint.setShader(shader);
|
||||||
|
//前面Scale,故判断以哪一个来取中心点和半径
|
||||||
|
if (mRectFDst.width() >= mRectSrc.width()) {
|
||||||
|
cx = mRectFDst.width() / 2;
|
||||||
|
cy = mRectFDst.height() / 2;
|
||||||
|
//取宽高最小的为半径
|
||||||
|
cRadius = Math.min(mRectFDst.width(), mRectFDst.height()) / 2;
|
||||||
|
mBorderRect.set(mRectFDst);
|
||||||
|
} else {
|
||||||
|
cx = mRectSrc.width() / 2f;
|
||||||
|
cy = mRectSrc.height() / 2f;
|
||||||
|
cRadius = Math.min(mRectSrc.width(), mRectSrc.height()) / 2f;
|
||||||
|
mBorderRect.set(mRectSrc);
|
||||||
|
}
|
||||||
|
canvas.drawCircle(cx, cy, cRadius, mBitmapPaint);
|
||||||
|
mBitmapPaint.reset();
|
||||||
|
mBitmapPaint.setAntiAlias(true);
|
||||||
|
mBitmapPaint.setColor(overlayColor);
|
||||||
|
canvas.drawCircle(cx, cy, cRadius, mBitmapPaint);
|
||||||
|
//使用宽高相等的椭圆为圆形来画边框
|
||||||
|
if (mBorderWidth > 0) {
|
||||||
|
if (mBorderRect.width() > mBorderRect.height()) {
|
||||||
|
//原本宽大于高,圆是以中心点为圆心和高的一半为半径,椭圆区域是以初始00为开始,故整体向右移动差值
|
||||||
|
float dif = Math.abs(mBorderRect.height() - mBorderRect.width()) / 2;
|
||||||
|
mBorderRect.left = dif;
|
||||||
|
mBorderRect.right = Math.min(mBorderRect.width(), mBorderRect.height()) + dif;
|
||||||
|
mBorderRect.bottom = Math.min(mBorderRect.width(), mBorderRect.height());
|
||||||
|
} else if (mBorderRect.width() < mBorderRect.height()) {
|
||||||
|
//原本高大于宽,圆是以中心点为圆心和宽的一半为半径,椭圆区域是以初始00为开始,故整体向下移动差值
|
||||||
|
float dif = Math.abs(mBorderRect.height() - mBorderRect.width()) / 2;
|
||||||
|
mBorderRect.top = dif;
|
||||||
|
mBorderRect.right = Math.min(mBorderRect.width(), mBorderRect.height());
|
||||||
|
mBorderRect.bottom = Math.min(mBorderRect.width(), mBorderRect.height()) + dif;
|
||||||
|
} else {
|
||||||
|
//如果快高相同,则不需要偏移,椭圆画出来就是圆
|
||||||
|
mBorderRect.right = Math.min(mBorderRect.width(), mBorderRect.height());
|
||||||
|
mBorderRect.bottom = Math.min(mBorderRect.width(), mBorderRect.height());
|
||||||
|
}
|
||||||
|
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||||
|
canvas.drawOval(mBorderRect, mBorderPaint);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dp转px
|
||||||
|
*
|
||||||
|
* @param dpValue dp值
|
||||||
|
* @return px值
|
||||||
|
*/
|
||||||
|
public int dp2px(final float dpValue) {
|
||||||
|
final float scale = mContext.getResources().getDisplayMetrics().density;
|
||||||
|
return (int) (dpValue * scale + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull
|
||||||
|
int[] getState() {
|
||||||
|
return StateSet.WILD_CARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StopException extends RuntimeException {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StopException STOP_EXCEPTION = new StopException();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传入构造器,避免传统的设置一个参数调用一次invalidate()重新绘制
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public void refreshView(Builder builder) {
|
||||||
|
boolean isInvalidate = false;
|
||||||
|
if (builder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (builder.blurMode != -1 && this.blurMode != builder.blurMode) {
|
||||||
|
this.blurMode = builder.blurMode;
|
||||||
|
isInvalidate = true;
|
||||||
|
}
|
||||||
|
if (builder.mBorderColor != null && !mBorderColor.equals(builder.mBorderColor)) {
|
||||||
|
this.mBorderColor = builder.mBorderColor;
|
||||||
|
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
|
||||||
|
if (mBorderWidth > 0) {
|
||||||
|
isInvalidate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (builder.mBorderWidth > 0) {
|
||||||
|
mBorderWidth = builder.mBorderWidth;
|
||||||
|
mBorderPaint.setStrokeWidth(mBorderWidth);
|
||||||
|
isInvalidate = true;
|
||||||
|
}
|
||||||
|
if (mCornerRadii[BlurCorner.TOP_LEFT] != builder.mCornerRadii[BlurCorner.TOP_LEFT]
|
||||||
|
|| mCornerRadii[BlurCorner.TOP_RIGHT] != builder.mCornerRadii[BlurCorner.TOP_RIGHT]
|
||||||
|
|| mCornerRadii[BlurCorner.BOTTOM_RIGHT] != builder.mCornerRadii[BlurCorner.BOTTOM_RIGHT]
|
||||||
|
|| mCornerRadii[BlurCorner.BOTTOM_LEFT] != builder.mCornerRadii[BlurCorner.BOTTOM_LEFT]) {
|
||||||
|
mCornerRadii[BlurCorner.TOP_LEFT] = builder.mCornerRadii[BlurCorner.TOP_LEFT];
|
||||||
|
mCornerRadii[BlurCorner.TOP_RIGHT] = builder.mCornerRadii[BlurCorner.TOP_RIGHT];
|
||||||
|
mCornerRadii[BlurCorner.BOTTOM_LEFT] = builder.mCornerRadii[BlurCorner.BOTTOM_LEFT];
|
||||||
|
mCornerRadii[BlurCorner.BOTTOM_RIGHT] = builder.mCornerRadii[BlurCorner.BOTTOM_RIGHT];
|
||||||
|
isInvalidate = true;
|
||||||
|
initCornerRids();
|
||||||
|
}
|
||||||
|
if (builder.mOverlayColor != -1 && mOverlayColor != builder.mOverlayColor) {
|
||||||
|
mOverlayColor = builder.mOverlayColor;
|
||||||
|
isInvalidate = true;
|
||||||
|
}
|
||||||
|
if (builder.mBlurRadius > 0 && mBlurRadius != builder.mBlurRadius) {
|
||||||
|
mBlurRadius = builder.mBlurRadius;
|
||||||
|
mDirty = true;
|
||||||
|
isInvalidate = true;
|
||||||
|
}
|
||||||
|
if (builder.mDownSampleFactor > 0 && mDownSampleFactor != builder.mDownSampleFactor) {
|
||||||
|
mDownSampleFactor = builder.mDownSampleFactor;
|
||||||
|
mDirty = true;
|
||||||
|
isInvalidate = true;
|
||||||
|
releaseBitmap();
|
||||||
|
}
|
||||||
|
if (isInvalidate) {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
// default 4
|
||||||
|
private float mDownSampleFactor = -1;
|
||||||
|
// default #aaffffff
|
||||||
|
private int mOverlayColor = -1;
|
||||||
|
// default 10dp (0 < r <= 25)
|
||||||
|
private float mBlurRadius = -1;
|
||||||
|
private float mBorderWidth = -1;
|
||||||
|
private ColorStateList mBorderColor = null;
|
||||||
|
private int blurMode = -1;
|
||||||
|
private final float[] mCornerRadii = new float[]{0f, 0f, 0f, 0f};
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
private Builder(Context context) {
|
||||||
|
mContext = context.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模糊半径
|
||||||
|
*
|
||||||
|
* @param radius 0~25
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setBlurRadius(@FloatRange(from = 0, to = 25) float radius) {
|
||||||
|
mBlurRadius = radius;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 采样率
|
||||||
|
*
|
||||||
|
* @param factor
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setDownSampleFactor(float factor) {
|
||||||
|
if (factor <= 0) {
|
||||||
|
throw new IllegalArgumentException("DownSample factor must be greater than 0.");
|
||||||
|
}
|
||||||
|
mDownSampleFactor = factor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 蒙层颜色
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setOverlayColor(int color) {
|
||||||
|
mOverlayColor = color;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the corner radius of a specific corner in px.
|
||||||
|
* 设置圆角 圆形、椭圆无效
|
||||||
|
*
|
||||||
|
* @param corner 枚举类型 对应4个角
|
||||||
|
* @param radius 角半径幅度
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setCornerRadius(@BlurCorner int corner, float radius) {
|
||||||
|
mCornerRadii[corner] = radius;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all the corner radii from a dimension resource id.
|
||||||
|
* 设置圆角 圆形、椭圆无效
|
||||||
|
*
|
||||||
|
* @param resId dimension resource id of radii.
|
||||||
|
*/
|
||||||
|
public Builder setCornerRadiusDimen(@DimenRes int resId) {
|
||||||
|
float radius = mContext.getResources().getDimension(resId);
|
||||||
|
return setCornerRadius(radius, radius, radius, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the corner radius of a specific corner in px.
|
||||||
|
* 设置圆角 圆形、椭圆无效
|
||||||
|
*
|
||||||
|
* @param radius 4个角同值
|
||||||
|
*/
|
||||||
|
public Builder setCornerRadius(float radius) {
|
||||||
|
return setCornerRadius(radius, radius, radius, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the corner radius of a specific corner in px.
|
||||||
|
* 设置圆角 圆形、椭圆无效
|
||||||
|
*/
|
||||||
|
public Builder setCornerRadius(float topLeft, float topRight, float bottomLeft, float bottomRight) {
|
||||||
|
mCornerRadii[BlurCorner.TOP_LEFT] = topLeft;
|
||||||
|
mCornerRadii[BlurCorner.TOP_RIGHT] = topRight;
|
||||||
|
mCornerRadii[BlurCorner.BOTTOM_LEFT] = bottomLeft;
|
||||||
|
mCornerRadii[BlurCorner.BOTTOM_RIGHT] = bottomRight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置边框的宽度
|
||||||
|
*
|
||||||
|
* @param resId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setBorderWidth(@DimenRes int resId) {
|
||||||
|
return setBorderWidth(mContext.getResources().getDimension(resId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置边框的宽度
|
||||||
|
*
|
||||||
|
* @param width 转px值
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setBorderWidth(float width) {
|
||||||
|
mBorderWidth = width;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置边框颜色
|
||||||
|
*
|
||||||
|
* @param color R.color.xxxx
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setBorderColor(@ColorRes int color) {
|
||||||
|
return setBorderColor(ColorStateList.valueOf(ContextCompat.getColor(mContext, color)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// public Builder setBorderColor(@ColorInt int color) {
|
||||||
|
// return setBorderColor(ColorStateList.valueOf(color));
|
||||||
|
// }
|
||||||
|
|
||||||
|
public Builder setBorderColor(ColorStateList colors) {
|
||||||
|
mBorderColor = (colors != null) ? colors : ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置高斯模糊的类型
|
||||||
|
*
|
||||||
|
* @param blurMode BlurMode枚举值,支持圆、方形、椭圆(宽高相等椭圆为圆)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setBlurMode(@BlurMode int blurMode) {
|
||||||
|
this.blurMode = blurMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 建造者模式,避免设置一个参数调用一次重新绘制
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Builder build(Context context) {
|
||||||
|
return new Builder(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.remax.visualnovel.widget.blurview.enu;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({
|
||||||
|
BlurCorner.TOP_LEFT, BlurCorner.TOP_RIGHT,
|
||||||
|
BlurCorner.BOTTOM_LEFT, BlurCorner.BOTTOM_RIGHT
|
||||||
|
})
|
||||||
|
public @interface BlurCorner {
|
||||||
|
int TOP_LEFT = 0;
|
||||||
|
int TOP_RIGHT = 1;
|
||||||
|
int BOTTOM_RIGHT = 2;
|
||||||
|
int BOTTOM_LEFT = 3;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.remax.visualnovel.widget.blurview.enu;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({
|
||||||
|
BlurMode.MODE_RECTANGLE, BlurMode.MODE_CIRCLE,
|
||||||
|
BlurMode.MODE_OVAL
|
||||||
|
})
|
||||||
|
|
||||||
|
public @interface BlurMode {
|
||||||
|
int MODE_RECTANGLE = 0;
|
||||||
|
int MODE_CIRCLE = 1;
|
||||||
|
int MODE_OVAL = 2;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.remax.visualnovel.widget.blurview.impl;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.renderscript.Allocation;
|
||||||
|
import android.renderscript.Element;
|
||||||
|
import android.renderscript.RenderScript;
|
||||||
|
import android.renderscript.ScriptIntrinsicBlur;
|
||||||
|
|
||||||
|
public class AndroidStockBlurImpl implements BlurImpl{
|
||||||
|
|
||||||
|
private RenderScript mRenderScript;
|
||||||
|
private ScriptIntrinsicBlur mBlurScript;
|
||||||
|
private Allocation mBlurInput, mBlurOutput;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare(Context context, Bitmap buffer, float radius) {
|
||||||
|
if (mRenderScript == null) {
|
||||||
|
try {
|
||||||
|
mRenderScript = RenderScript.create(context);
|
||||||
|
mBlurScript = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript));
|
||||||
|
} catch (android.renderscript.RSRuntimeException e) {
|
||||||
|
if (isDebug(context)) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
// In release mode, just ignore
|
||||||
|
release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mBlurScript.setRadius(radius);
|
||||||
|
|
||||||
|
mBlurInput = Allocation.createFromBitmap(mRenderScript, buffer,
|
||||||
|
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
|
||||||
|
mBlurOutput = Allocation.createTyped(mRenderScript, mBlurInput.getType());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
if (mBlurInput != null) {
|
||||||
|
mBlurInput.destroy();
|
||||||
|
mBlurInput = null;
|
||||||
|
}
|
||||||
|
if (mBlurOutput != null) {
|
||||||
|
mBlurOutput.destroy();
|
||||||
|
mBlurOutput = null;
|
||||||
|
}
|
||||||
|
if (mBlurScript != null) {
|
||||||
|
mBlurScript.destroy();
|
||||||
|
mBlurScript = null;
|
||||||
|
}
|
||||||
|
if (mRenderScript != null) {
|
||||||
|
mRenderScript.destroy();
|
||||||
|
mRenderScript = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void blur(Bitmap input, Bitmap output) {
|
||||||
|
mBlurInput.copyFrom(input);
|
||||||
|
mBlurScript.setInput(mBlurInput);
|
||||||
|
mBlurScript.forEach(mBlurOutput);
|
||||||
|
mBlurOutput.copyTo(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// android:debuggable="true" in AndroidManifest.xml (auto set by build tool)
|
||||||
|
static Boolean DEBUG = null;
|
||||||
|
|
||||||
|
static boolean isDebug(Context ctx) {
|
||||||
|
if (DEBUG == null && ctx != null) {
|
||||||
|
DEBUG = (ctx.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
|
||||||
|
}
|
||||||
|
return DEBUG.equals(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.remax.visualnovel.widget.blurview.impl;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.renderscript.Allocation;
|
||||||
|
import android.renderscript.Element;
|
||||||
|
import android.renderscript.RenderScript;
|
||||||
|
import android.renderscript.ScriptIntrinsicBlur;
|
||||||
|
|
||||||
|
public class AndroidXBlurImpl implements BlurImpl {
|
||||||
|
private RenderScript mRenderScript;
|
||||||
|
private ScriptIntrinsicBlur mBlurScript;
|
||||||
|
private Allocation mBlurInput, mBlurOutput;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare(Context context, Bitmap buffer, float radius) {
|
||||||
|
if (mRenderScript == null) {
|
||||||
|
try {
|
||||||
|
mRenderScript = RenderScript.create(context);
|
||||||
|
mBlurScript = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript));
|
||||||
|
} catch (android.renderscript.RSRuntimeException e) {
|
||||||
|
if (isDebug(context)) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
// In release mode, just ignore
|
||||||
|
release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mBlurScript.setRadius(radius);
|
||||||
|
|
||||||
|
mBlurInput = Allocation.createFromBitmap(mRenderScript, buffer,
|
||||||
|
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
|
||||||
|
mBlurOutput = Allocation.createTyped(mRenderScript, mBlurInput.getType());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
if (mBlurInput != null) {
|
||||||
|
mBlurInput.destroy();
|
||||||
|
mBlurInput = null;
|
||||||
|
}
|
||||||
|
if (mBlurOutput != null) {
|
||||||
|
mBlurOutput.destroy();
|
||||||
|
mBlurOutput = null;
|
||||||
|
}
|
||||||
|
if (mBlurScript != null) {
|
||||||
|
mBlurScript.destroy();
|
||||||
|
mBlurScript = null;
|
||||||
|
}
|
||||||
|
if (mRenderScript != null) {
|
||||||
|
mRenderScript.destroy();
|
||||||
|
mRenderScript = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void blur(Bitmap input, Bitmap output) {
|
||||||
|
mBlurInput.copyFrom(input);
|
||||||
|
mBlurScript.setInput(mBlurInput);
|
||||||
|
mBlurScript.forEach(mBlurOutput);
|
||||||
|
mBlurOutput.copyTo(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// android:debuggable="true" in AndroidManifest.xml (auto set by build tool)
|
||||||
|
static Boolean DEBUG = null;
|
||||||
|
|
||||||
|
static boolean isDebug(Context ctx) {
|
||||||
|
if (DEBUG == null && ctx != null) {
|
||||||
|
DEBUG = (ctx.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
|
||||||
|
}
|
||||||
|
return DEBUG.equals(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.remax.visualnovel.widget.blurview.impl;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
public interface BlurImpl {
|
||||||
|
|
||||||
|
boolean prepare(Context context, Bitmap buffer, float radius);
|
||||||
|
|
||||||
|
void release();
|
||||||
|
|
||||||
|
void blur(Bitmap input, Bitmap output);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.remax.visualnovel.widget.blurview.impl;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
public class EmptyBlurImpl implements BlurImpl {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare(Context context, Bitmap buffer, float radius) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void blur(Bitmap input, Bitmap output) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.remax.visualnovel.widget.blurview.impl;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.renderscript.Allocation;
|
||||||
|
import android.renderscript.Element;
|
||||||
|
import android.renderscript.RenderScript;
|
||||||
|
import android.renderscript.ScriptIntrinsicBlur;
|
||||||
|
|
||||||
|
public class SupportLibraryBlurImpl implements BlurImpl {
|
||||||
|
|
||||||
|
private RenderScript mRenderScript;
|
||||||
|
private ScriptIntrinsicBlur mBlurScript;
|
||||||
|
private Allocation mBlurInput, mBlurOutput;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare(Context context, Bitmap buffer, float radius) {
|
||||||
|
if (mRenderScript == null) {
|
||||||
|
try {
|
||||||
|
mRenderScript = RenderScript.create(context);
|
||||||
|
mBlurScript = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript));
|
||||||
|
} catch (android.renderscript.RSRuntimeException e) {
|
||||||
|
if (isDebug(context)) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
// In release mode, just ignore
|
||||||
|
release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mBlurScript.setRadius(radius);
|
||||||
|
|
||||||
|
mBlurInput = Allocation.createFromBitmap(mRenderScript, buffer,
|
||||||
|
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
|
||||||
|
mBlurOutput = Allocation.createTyped(mRenderScript, mBlurInput.getType());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
if (mBlurInput != null) {
|
||||||
|
mBlurInput.destroy();
|
||||||
|
mBlurInput = null;
|
||||||
|
}
|
||||||
|
if (mBlurOutput != null) {
|
||||||
|
mBlurOutput.destroy();
|
||||||
|
mBlurOutput = null;
|
||||||
|
}
|
||||||
|
if (mBlurScript != null) {
|
||||||
|
mBlurScript.destroy();
|
||||||
|
mBlurScript = null;
|
||||||
|
}
|
||||||
|
if (mRenderScript != null) {
|
||||||
|
mRenderScript.destroy();
|
||||||
|
mRenderScript = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void blur(Bitmap input, Bitmap output) {
|
||||||
|
mBlurInput.copyFrom(input);
|
||||||
|
mBlurScript.setInput(mBlurInput);
|
||||||
|
mBlurScript.forEach(mBlurOutput);
|
||||||
|
mBlurOutput.copyTo(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// android:debuggable="true" in AndroidManifest.xml (auto set by build tool)
|
||||||
|
static Boolean DEBUG = null;
|
||||||
|
|
||||||
|
static boolean isDebug(Context ctx) {
|
||||||
|
if (DEBUG == null && ctx != null) {
|
||||||
|
DEBUG = (ctx.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
|
||||||
|
}
|
||||||
|
return DEBUG.equals(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -222,6 +222,18 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<com.remax.visualnovel.ui.chat.ui.expandableSelector.ExpandChatModeSelectView
|
||||||
|
android:id="@+id/chat_model_selector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<com.remax.visualnovel.ui.chat.ui.expandableSelector.ExpandBubbleSelectView
|
||||||
|
android:id="@+id/bubble_select_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
|
||||||
</com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout>
|
</com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout>
|
||||||
|
|
||||||
<!-- background related -->
|
<!-- background related -->
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenRelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/dp_12" >
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/selectedDot"
|
||||||
|
android:layout_width="@dimen/dp_13"
|
||||||
|
android:layout_height="@dimen/dp_13"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:background="@drawable/circle_selected"
|
||||||
|
android:visibility="visible"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toRightOf="@+id/colorIndicator"
|
||||||
|
android:layout_toLeftOf="@+id/selectedDot"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginHorizontal="@dimen/dp_7"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
|
||||||
|
android:id="@+id/itemName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textSize="@dimen/sp_13"
|
||||||
|
android:text="sssssssss"
|
||||||
|
android:textColor="@color/gray3"/>
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@mipmap/chat_vip_str"
|
||||||
|
android:layout_marginLeft="@dimen/dp_5"
|
||||||
|
/>
|
||||||
|
</com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout>
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
|
||||||
|
android:id="@+id/mode_description"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textSize="@dimen/sp_12"
|
||||||
|
android:text="aaaaaa"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:textColor="@color/chat_setting_ai_model_des_color"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</com.remax.visualnovel.widget.uitoken.view.UITokenRelativeLayout>
|
||||||
|
|
@ -15,46 +15,12 @@
|
||||||
app:layout_constraintDimensionRatio="h,164:120"
|
app:layout_constraintDimensionRatio="h,164:120"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:radiusToken="@string/radius_l"
|
app:radiusToken="@string/radius_l"
|
||||||
app:strokeColorToken="@string/color_primary_normal"
|
app:strokeColorToken="@string/color_txt_tertiary_normal"
|
||||||
app:strokeWidthToken="@string/border_m"
|
app:strokeWidthToken="@string/border_l"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
|
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
|
||||||
android:id="@+id/bubbleIcon"
|
android:id="@+id/iv_bubble_name"
|
||||||
android:layout_width="60dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/selectBg"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/selectBg"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/selectBg"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/selectBg"
|
|
||||||
app:textColorToken="@string/color_txt_primary_normal"
|
|
||||||
android:text="@string/hi"
|
|
||||||
app:textToken="@string/txt_body_m" />
|
|
||||||
|
|
||||||
<com.remax.visualnovel.widget.ui.lock.LockTagView
|
|
||||||
android:id="@+id/lockView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/selectBg"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/selectBg"
|
|
||||||
app:lockTagLabel="privateLabel" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/bubbleLikeIcon"
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="12dp"
|
|
||||||
android:layout_marginEnd="2dp"
|
|
||||||
android:src="@mipmap/icon_checked"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/bubbleName"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/bubbleName"
|
|
||||||
app:layout_constraintHorizontal_chainStyle="packed"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/bubbleName" />
|
|
||||||
|
|
||||||
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
|
|
||||||
android:id="@+id/bubbleName"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="2dp"
|
android:layout_marginStart="2dp"
|
||||||
|
|
@ -62,31 +28,59 @@
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:text="@string/default_txt"
|
android:text="@string/default_txt"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/bubbleLikeIcon"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/selectBg"
|
app:layout_constraintTop_toBottomOf="@+id/selectBg"
|
||||||
app:textColorToken="@string/color_txt_primary_normal"
|
app:textColorToken="@string/color_txt_primary_normal"
|
||||||
app:textToken="@string/txt_label_m" />
|
app:textToken="@string/txt_label_m" />
|
||||||
|
|
||||||
<com.remax.visualnovel.widget.ui.RadioCheckButton
|
|
||||||
android:id="@+id/bubbleCheckBox"
|
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
|
||||||
|
android:id="@+id/iv_lock_hint"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginStart="2dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginTop="@dimen/dp_10"
|
||||||
|
android:gravity="top"
|
||||||
|
android:text="@string/unlocked"
|
||||||
|
android:textSize="@dimen/sp_12"
|
||||||
|
android:textStyle="italic"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenImageView
|
||||||
|
android:id="@+id/iv_left_top"
|
||||||
|
android:layout_width="@dimen/dp_25"
|
||||||
|
android:layout_height="@dimen/dp_25"
|
||||||
|
android:src="@mipmap/chat_vip_str"
|
||||||
|
android:layout_marginStart="@dimen/dp_15"
|
||||||
|
android:layout_marginTop="@dimen/dp_5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenImageView
|
||||||
|
android:id="@+id/iv_lock_indicator"
|
||||||
|
android:layout_width="@dimen/dp_25"
|
||||||
|
android:layout_height="@dimen/dp_25"
|
||||||
|
android:src="@mipmap/chat_vip_str"
|
||||||
|
android:layout_marginEnd="@dimen/dp_15"
|
||||||
|
android:layout_marginTop="@dimen/dp_5"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:radioCheck="true" />
|
/>
|
||||||
|
|
||||||
<!--<com.remax.visualnovel.widget.TagIconView
|
<com.remax.visualnovel.widget.uitoken.view.UITokenImageView
|
||||||
android:id="@+id/bubbleTag"
|
android:id="@+id/iv_bubble"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
app:tagIconSize="SIZE_S"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:src="@mipmap/chat_left_bg"
|
||||||
app:layout_constraintStart_toStartOf="@+id/selectBg"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/selectBg"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:tagIconContent="@string/default_txt"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:tagIconType="ELEMENT_DARK" />-->
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.RoundFrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:radius="4dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/group"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
>
|
||||||
|
<com.remax.visualnovel.widget.RoundFrameLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:radius="4dp">
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.blurview.ShapeBlurView
|
||||||
|
android:id="@+id/blurView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</com.remax.visualnovel.widget.RoundFrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
|
||||||
|
android:id="@+id/iconView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/icon_chat"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/textView"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:onlyIconFont="true"
|
||||||
|
app:textColorToken="@string/color_txt_primary_normal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:text="@string/tag"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/iconView"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:textColorToken="@string/color_txt_primary_normal"
|
||||||
|
app:textToken="@string/txt_label_m" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.remax.visualnovel.widget.RoundFrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -389,7 +389,6 @@
|
||||||
<string name="hi">Hi</string>
|
<string name="hi">Hi</string>
|
||||||
<string name="coin_to_create">%d to Create</string>
|
<string name="coin_to_create">%d to Create</string>
|
||||||
<string name="create_image">Create image</string>
|
<string name="create_image">Create image</string>
|
||||||
<string name="unlocked">Unlocked</string>
|
|
||||||
<string name="waiting_to_be_connected">Waiting to be connected</string>
|
<string name="waiting_to_be_connected">Waiting to be connected</string>
|
||||||
<string name="interrupt">Interrupt</string>
|
<string name="interrupt">Interrupt</string>
|
||||||
<string name="listening">Listening</string>
|
<string name="listening">Listening</string>
|
||||||
|
|
@ -481,5 +480,8 @@
|
||||||
<string name="setting_max_response_num">Maximum number of response tokens</string>
|
<string name="setting_max_response_num">Maximum number of response tokens</string>
|
||||||
<string name="font_size">Font Size</string>
|
<string name="font_size">Font Size</string>
|
||||||
<string name="title_sound_actor">Voice actor</string>
|
<string name="title_sound_actor">Voice actor</string>
|
||||||
|
<string name="setting_chat_bubble_title">Chat buttle</string>
|
||||||
|
<string name="unlocked">Unlocked</string>
|
||||||
|
<string name="chat_mode">Chat Mode</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Reference in New Issue