Merge pull request #263 from TouchInstinct/range_filter
Фильтры: Выбор минимального и максимального значения из диапозона
This commit is contained in:
commit
3a3ad0211a
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
1. Выбор одного/нескольких из доступных значений списка
|
||||
2. Выбор одного/нескольких значений из перечня тегов
|
||||
3. Выбор минимального и максимального значения из диапозона
|
||||
|
||||
# Использование
|
||||
|
||||
|
|
@ -89,8 +90,8 @@ val selectorView = ListSelectionView(newContext)
|
|||
|
||||
## 2. Выбор одного/нескольких значений из перечня тегов
|
||||
|
||||
`TagLayoutView` - view-контейнер для тегов
|
||||
`TagView` - view для тега. <em>Кастомная разметка для тега должна содержать в корне `TagView`</em>
|
||||
* `TagLayoutView` - view-контейнер для тегов
|
||||
* `TagView` - view для тега. <em>Кастомная разметка для тега должна содержать в корне `TagView`</em>
|
||||
|
||||
### Как использовать
|
||||
``` kotlin
|
||||
|
|
@ -116,3 +117,62 @@ binding.tagItemLayout
|
|||
* `onMoreValuesAction(FilterMoreAction)` и `onPropertySelectedAction(PropertySelectedAction)` используются для передачи колбэков на клик по тегу типа "Еще" и обычного тега соответственно
|
||||
* после вызова конфигурационных методов обязательно необходимо вызать метод `build()`
|
||||
* в Builder необходимо передать объект `filterItem: FilterItem`
|
||||
|
||||
|
||||
## 3. Выбор минимального и максимального значения из диапозона
|
||||
|
||||
* `RangeChoiceView` - контейнер для слайдера и редактируемых полей
|
||||
* `FilterRangeSlider` - слайдер - <em>Можно использовать как отдельный элемент</em>
|
||||
* `HintInputView` - view для редактируемого поля начала и окончания диапозона
|
||||
|
||||
### Как использовать
|
||||
|
||||
В разметке
|
||||
``` xml
|
||||
<ru.touchin.roboswag.base_filters.range.RangeChoiceView
|
||||
android:id="@+id/range_values_test"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/FilterRangeChoice" //не забудьте указать стиль
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
```
|
||||
|
||||
Настройка в коде
|
||||
``` kotlin
|
||||
fun setupValues(item: FilterRangeItem) {
|
||||
binding.rangeValuesTest.setupRangeValues(
|
||||
rangeFilterItem = item,
|
||||
onChangeCallback = callback
|
||||
)
|
||||
}
|
||||
|
||||
fun resetValues() {
|
||||
binding.rangeValuesTest.resetRangeValue()
|
||||
}
|
||||
```
|
||||
### Конфигурации
|
||||
Вся конфигурация вьюх осуществляется через стили:
|
||||
* Для `RangeChoiceView`:
|
||||
* `filterRange_sliderMargin` - расстояние от слайдера до редактируемых полей
|
||||
* `filterRange_startHint` - ссылка на строку с текстом подсказки в редактируемом поле для начального значения
|
||||
* `filterRange_endHint` - ссылка на строку с текстом подсказки в редактируемом поле для конечного значения
|
||||
* `filterRange_theme` - ссылка на тему
|
||||
* В теме:
|
||||
* атрибут `filterRange_sliderStyle` - ссылка на стиль слайдера
|
||||
* атрибут `filterRange_hintViewStyle` - ссылка на стиль `HintInputView`
|
||||
* атрибут `filterRange_hintTextStyle` - ссылка на стиль `TextView` внутри `HintInputView`
|
||||
* атрибут `filterRange_valueEditTextStyle` - ссылка на стиль `EditText` внутри `HintInputView`
|
||||
* Для `FilterRangeSlider`:
|
||||
* `trackColorActive`
|
||||
* `trackColorInactive`
|
||||
* `trackHeight`
|
||||
* `thumbElevation`
|
||||
* `thumbColor`
|
||||
* `labelBehavior`
|
||||
* `haloRadius`
|
||||
* `filterRange_stepTextAppearance`
|
||||
* `filterRange_activeTickColor`
|
||||
* `filterRange_inactiveTickColor`
|
||||
* `filterRange_stepValueMarginTop`
|
||||
* `filterRange_sliderPointSize`
|
||||
* `filterRange_pointShape`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
package ru.touchin.roboswag.base_filters.range
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Paint.Cap
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.google.android.material.shape.CornerFamily
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import ru.touchin.roboswag.base_filters.R
|
||||
import ru.touchin.roboswag.components.utils.getColorSimple
|
||||
|
||||
class FilterRangeSlider @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : RangeSlider(context, attrs, defStyleAttr) {
|
||||
|
||||
var points: List<Int>? = null
|
||||
set(value) {
|
||||
field = value?.sorted()?.filter { it > valueFrom && it < valueTo }
|
||||
}
|
||||
|
||||
private val innerThumbRadius: Int = thumbRadius
|
||||
|
||||
private var stepValueMarginTop = 0f
|
||||
private var inactiveTickColor: Int = context.getColorSimple(R.color.slider_point_inactive)
|
||||
private var activeTickColor: Int = context.getColorSimple(R.color.slider_point_active)
|
||||
private var sliderPointSize: Float = 5f
|
||||
|
||||
@StyleRes
|
||||
private var stepTextAppearance: Int = -1
|
||||
private var shape: Shape = Shape.CIRCLE
|
||||
|
||||
private var trackCenterY: Float = -1F
|
||||
|
||||
init {
|
||||
// Set original thumb radius to zero to draw custom one on top
|
||||
thumbRadius = 0
|
||||
|
||||
context.withStyledAttributes(attrs, R.styleable.FilterRangeSlider, defStyleAttr, defStyleRes) {
|
||||
stepValueMarginTop = getDimension(R.styleable.FilterRangeSlider_filterRange_stepValueMarginTop, stepValueMarginTop)
|
||||
inactiveTickColor = getColor(R.styleable.FilterRangeSlider_filterRange_inactiveTickColor, inactiveTickColor)
|
||||
activeTickColor = getColor(R.styleable.FilterRangeSlider_filterRange_activeTickColor, activeTickColor)
|
||||
sliderPointSize = getDimension(R.styleable.FilterRangeSlider_filterRange_sliderPointSize, sliderPointSize)
|
||||
stepTextAppearance = getResourceId(R.styleable.FilterRangeSlider_filterRange_stepTextAppearance, -1)
|
||||
shape = Shape.values()[getInt(R.styleable.FilterRangeSlider_filterRange_pointShape, Shape.CIRCLE.ordinal)]
|
||||
}
|
||||
}
|
||||
|
||||
private val thumbDrawable = MaterialShapeDrawable().apply {
|
||||
shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS
|
||||
setBounds(0, 0, innerThumbRadius * 2, innerThumbRadius * 2)
|
||||
elevation = thumbElevation
|
||||
state = drawableState
|
||||
fillColor = thumbTintList
|
||||
shapeAppearanceModel = ShapeAppearanceModel
|
||||
.builder()
|
||||
.setAllCorners(shape.value, innerThumbRadius.toFloat())
|
||||
.build()
|
||||
}
|
||||
|
||||
private val inactiveTicksPaint = getDefaultTickPaint().apply { color = inactiveTickColor }
|
||||
|
||||
private val activeTicksPaint = getDefaultTickPaint().apply { color = activeTickColor }
|
||||
|
||||
private fun getDefaultTickPaint() = Paint().apply {
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Cap.ROUND
|
||||
strokeWidth = sliderPointSize
|
||||
}
|
||||
|
||||
// Using TextView as a bridge to get text params
|
||||
private val stepValuePaint: Paint = AppCompatTextView(context).apply {
|
||||
stepTextAppearance.takeIf { it != -1 }?.let { TextViewCompat.setTextAppearance(this, it) }
|
||||
}.let { textView ->
|
||||
Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = textView.currentTextColor
|
||||
textSize = textView.textSize
|
||||
typeface = textView.typeface
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
trackCenterY = measuredHeight / 2F
|
||||
|
||||
val height = trackCenterY + trackHeight / 2F + stepValueMarginTop + stepValuePaint.textSize
|
||||
setMeasuredDimension(measuredWidth, height.toInt())
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
drawTicks(canvas)
|
||||
drawThumb(canvas)
|
||||
drawStepValues(canvas)
|
||||
}
|
||||
|
||||
private fun drawTicks(canvas: Canvas) {
|
||||
if (points.isNullOrEmpty()) return
|
||||
|
||||
val ticksCoordinates = mutableListOf<Float>()
|
||||
points?.forEach { point ->
|
||||
ticksCoordinates.add(normalizeValue(point.toFloat()) * trackWidth + trackSidePadding)
|
||||
ticksCoordinates.add(trackCenterY)
|
||||
}
|
||||
|
||||
val leftPointsSize = points?.count { it < values[0] } ?: 0
|
||||
val rightPointSize = points?.count { it > values[1] } ?: 0
|
||||
val activePointSize = (points?.size ?: 0) - leftPointsSize - rightPointSize
|
||||
|
||||
// Draw inactive ticks to the left of the smallest thumb.
|
||||
canvas.drawPoints(ticksCoordinates.toFloatArray(), 0, leftPointsSize * 2, inactiveTicksPaint)
|
||||
|
||||
// Draw active ticks between the thumbs.
|
||||
canvas.drawPoints(
|
||||
ticksCoordinates.toFloatArray(),
|
||||
leftPointsSize * 2,
|
||||
activePointSize * 2,
|
||||
activeTicksPaint
|
||||
)
|
||||
|
||||
// Draw inactive ticks to the right of the largest thumb.
|
||||
canvas.drawPoints(
|
||||
ticksCoordinates.toFloatArray(),
|
||||
leftPointsSize * 2 + activePointSize * 2,
|
||||
rightPointSize * 2,
|
||||
inactiveTicksPaint
|
||||
)
|
||||
}
|
||||
|
||||
private fun drawThumb(canvas: Canvas) {
|
||||
for (value in values) {
|
||||
canvas.save()
|
||||
canvas.translate(
|
||||
(trackSidePadding + (normalizeValue(value) * trackWidth).toInt() - innerThumbRadius).toFloat(),
|
||||
trackCenterY - innerThumbRadius
|
||||
)
|
||||
thumbDrawable.draw(canvas)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawStepValues(canvas: Canvas) {
|
||||
points?.forEach { point ->
|
||||
canvas.drawText(
|
||||
point.toString(),
|
||||
normalizeValue(point.toFloat()) * trackWidth + trackSidePadding,
|
||||
trackCenterY + trackHeight / 2F + stepValueMarginTop + stepValuePaint.textSize - 3F,
|
||||
stepValuePaint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeValue(value: Float) = (value - valueFrom) / (valueTo - valueFrom)
|
||||
|
||||
private enum class Shape(val value: Int) {
|
||||
CIRCLE(CornerFamily.ROUNDED),
|
||||
CUT(CornerFamily.CUT)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package ru.touchin.roboswag.base_filters.range
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import ru.touchin.roboswag.base_filters.databinding.ViewHintInputBinding
|
||||
|
||||
class HintInputView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
private val binding = ViewHintInputBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
var inputText: String = ""
|
||||
set(value) {
|
||||
setText(value)
|
||||
field = value
|
||||
}
|
||||
|
||||
fun setHint(value: String?) {
|
||||
binding.startHint.text = value.orEmpty()
|
||||
}
|
||||
|
||||
fun setOnEditorActionListener(listener: TextView.OnEditorActionListener) =
|
||||
binding.editText.setOnEditorActionListener(listener)
|
||||
|
||||
private fun setText(value: String) {
|
||||
binding.editText.run {
|
||||
setText(value)
|
||||
setSelection(text?.length ?: 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
package ru.touchin.roboswag.base_filters.range
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import ru.touchin.roboswag.base_filters.R
|
||||
import ru.touchin.roboswag.base_filters.databinding.RangeChoiceViewBinding
|
||||
import ru.touchin.roboswag.base_filters.range.model.FilterRangeItem
|
||||
import ru.touchin.roboswag.base_filters.range.model.SelectedValues
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
typealias FilterRangeChanged = (FilterRangeItem) -> Unit
|
||||
|
||||
class RangeChoiceView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
private val defaultTheme = R.style.Theme_FilterRangeSlider
|
||||
private var binding: RangeChoiceViewBinding by Delegates.notNull()
|
||||
|
||||
private var valueChangedAction: FilterRangeChanged? = null
|
||||
|
||||
private var rangeItem: FilterRangeItem? = null
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
binding.fromInput.inputText = value?.selectedValues?.min?.toString()
|
||||
?: value?.start?.toString().orEmpty()
|
||||
binding.toInput.inputText = value?.selectedValues?.max?.toString()
|
||||
?: value?.end?.toString().orEmpty()
|
||||
|
||||
binding.rangeSlider.run {
|
||||
values = listOf(
|
||||
value?.selectedValues?.min?.toFloat() ?: value?.start?.toFloat(),
|
||||
value?.selectedValues?.max?.toFloat() ?: value?.end?.toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
context.withStyledAttributes(attrs, R.styleable.FilterRangeChoice, defStyleAttr, defStyleRes) {
|
||||
val theme = getResourceId(R.styleable.FilterRangeChoice_filterRange_theme, defaultTheme)
|
||||
val themeContext = ContextThemeWrapper(context, theme)
|
||||
binding = RangeChoiceViewBinding.inflate(LayoutInflater.from(themeContext), this@RangeChoiceView)
|
||||
|
||||
binding.fromInput.setHint(getString(R.styleable.FilterRangeChoice_filterRange_startHint))
|
||||
binding.toInput.setHint(getString(R.styleable.FilterRangeChoice_filterRange_endHint))
|
||||
binding.rangeSliderGuideline.updateLayoutParams<MarginLayoutParams> {
|
||||
topMargin = getDimension(R.styleable.FilterRangeChoice_filterRange_sliderMargin, 0f).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setupRangeValues(
|
||||
rangeFilterItem: FilterRangeItem,
|
||||
onChangeCallback: FilterRangeChanged
|
||||
) {
|
||||
rangeItem = rangeFilterItem
|
||||
valueChangedAction = onChangeCallback
|
||||
|
||||
with(binding) {
|
||||
addChangeValuesListener()
|
||||
setupRangeSlider(rangeFilterItem)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetRangeValue() {
|
||||
rangeItem = rangeItem?.resetSelectedValues()
|
||||
}
|
||||
|
||||
private fun addChangeValuesListener() {
|
||||
binding.fromInput.addChangeValueListener { rangeItem?.setValue(selectedMinValue = it.toIntOrNull()) }
|
||||
binding.toInput.addChangeValueListener { rangeItem?.setValue(selectedMaxValue = it.toIntOrNull()) }
|
||||
}
|
||||
|
||||
private fun setupRangeSlider(rangeFilterItem: FilterRangeItem) {
|
||||
with(binding) {
|
||||
rangeSlider.apply {
|
||||
valueFrom = rangeFilterItem.start.toFloat()
|
||||
valueTo = rangeFilterItem.end.toFloat()
|
||||
points = rangeFilterItem.intermediates
|
||||
}
|
||||
|
||||
rangeSlider.addOnChangeListener { _, _, _ ->
|
||||
fromInput.inputText = rangeSlider.values[0].toInt().toString()
|
||||
toInput.inputText = rangeSlider.values[1].toInt().toString()
|
||||
}
|
||||
|
||||
rangeSlider.addOnSliderTouchListener(object : RangeSlider.OnSliderTouchListener {
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onStartTrackingTouch(slider: RangeSlider) = Unit
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onStopTrackingTouch(slider: RangeSlider) {
|
||||
binding.rangeSlider.apply {
|
||||
when (focusedThumbIndex) {
|
||||
0 -> {
|
||||
rangeItem = rangeItem?.setValue(selectedMinValue = from().toInt())
|
||||
rangeItem?.let { valueChangedAction?.invoke(it) }
|
||||
}
|
||||
1 -> {
|
||||
rangeItem = rangeItem?.setValue(selectedMaxValue = to().toInt())
|
||||
rangeItem?.let { valueChangedAction?.invoke(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun HintInputView.addChangeValueListener(updateValue: (String) -> FilterRangeItem?) {
|
||||
setOnEditorActionListener { view, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
rangeItem = updateValue(view.text.toString().filterNot { it.isWhitespace() })
|
||||
rangeItem?.let { valueChangedAction?.invoke(it) }
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun RangeSlider.from() = values[0].toInt().toString()
|
||||
|
||||
private fun RangeSlider.to() = values[1].toInt().toString()
|
||||
|
||||
@SuppressWarnings("detekt.ComplexMethod")
|
||||
private fun FilterRangeItem.setValue(
|
||||
selectedMaxValue: Int? = selectedValues?.max,
|
||||
selectedMinValue: Int? = selectedValues?.min
|
||||
): FilterRangeItem {
|
||||
val isMaxValueUpdated = selectedMaxValue != selectedValues?.max
|
||||
val isMinValueUpdated = selectedMinValue != selectedValues?.min
|
||||
|
||||
val isMinValueOutOfRange = selectedMinValue != null && isMinValueUpdated && selectedMinValue > (selectedMaxValue ?: end)
|
||||
val isMaxValueOutOfRange = selectedMaxValue != null && isMaxValueUpdated && selectedMaxValue < (selectedMinValue ?: start)
|
||||
|
||||
val updatedValues = when {
|
||||
selectedMaxValue == end && selectedMinValue == start -> null
|
||||
isMinValueOutOfRange -> SelectedValues(
|
||||
max = selectedMaxValue ?: end,
|
||||
min = selectedMaxValue ?: end
|
||||
)
|
||||
isMaxValueOutOfRange -> SelectedValues(
|
||||
max = selectedMinValue ?: start,
|
||||
min = selectedMinValue ?: start
|
||||
)
|
||||
else -> SelectedValues(
|
||||
max = selectedMaxValue?.takeIf { it < end } ?: end,
|
||||
min = selectedMinValue?.takeIf { it > start } ?: start
|
||||
)
|
||||
}
|
||||
|
||||
return copyWithSelectedValue(selectedValues = updatedValues)
|
||||
}
|
||||
|
||||
private fun FilterRangeItem.resetSelectedValues() = copyWithSelectedValue(selectedValues = null)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package ru.touchin.roboswag.base_filters.range.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
open class FilterRangeItem(
|
||||
val id: String,
|
||||
val start: Int,
|
||||
val end: Int,
|
||||
val title: String,
|
||||
val intermediates: List<Int>? = null,
|
||||
val step: Int? = null,
|
||||
val selectedValues: SelectedValues? = null
|
||||
) : Parcelable {
|
||||
|
||||
fun isCorrectValues() = end > start
|
||||
|
||||
fun copyWithSelectedValue(selectedValues: SelectedValues?) = FilterRangeItem(
|
||||
id = id,
|
||||
start = start,
|
||||
end = end,
|
||||
title = title,
|
||||
intermediates = intermediates,
|
||||
step = step,
|
||||
selectedValues = selectedValues
|
||||
)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class SelectedValues(
|
||||
val max: Int,
|
||||
val min: Int
|
||||
) : Parcelable
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="@android:color/white"/>
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/slider_point_inactive" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
<size
|
||||
android:width="1dp" />
|
||||
<solid
|
||||
android:color="@color/slider_thumb" />
|
||||
<padding
|
||||
android:top="-2sp"
|
||||
android:bottom="-2sp" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/center_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<ru.touchin.roboswag.base_filters.range.HintInputView
|
||||
android:id="@+id/from_input"
|
||||
style="?attr/filterRange_hintViewStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="42dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/center_guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ru.touchin.roboswag.base_filters.range.HintInputView
|
||||
android:id="@+id/to_input"
|
||||
style="?attr/filterRange_hintViewStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="42dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/center_guideline"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ru.touchin.roboswag.base_filters.range.FilterRangeSlider
|
||||
android:id="@+id/range_slider"
|
||||
style="?attr/filterRange_sliderStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="@id/range_slider_guideline"
|
||||
app:layout_constraintBottom_toTopOf="@id/range_slider_guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/range_slider_guideline"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintTop_toBottomOf="@id/from_input"/>
|
||||
|
||||
</merge>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit_text"
|
||||
style="?attr/filterRange_valueEditTextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/start_hint"
|
||||
style="?attr/filterRange_hintTextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/input_text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/input_text" />
|
||||
|
||||
</merge>
|
||||
|
|
@ -6,4 +6,31 @@
|
|||
<attr name="sheetSelection_radioStyle" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="FilterRangeSlider">
|
||||
<attr name="filterRange_activeTickColor" format="color"/>
|
||||
<attr name="filterRange_inactiveTickColor" format="color"/>
|
||||
<attr name="filterRange_stepValueMarginTop" format="dimension"/>
|
||||
<attr name="filterRange_sliderPointSize" format="dimension"/>
|
||||
<attr name="filterRange_stepTextAppearance" format="reference"/>
|
||||
<attr name="filterRange_pointShape" format="enum">
|
||||
<enum name="circle" value="0" />
|
||||
<enum name="cut" value="1" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="FilterRangeChoice">
|
||||
<attr name="filterRange_sliderStyle" format="reference" />
|
||||
<attr name="filterRange_hintViewStyle" format="reference" />
|
||||
<attr name="filterRange_editTextStyle" format="reference" />
|
||||
<attr name="filterRange_sliderMargin" format="dimension" />
|
||||
<attr name="filterRange_startHint" format="reference" />
|
||||
<attr name="filterRange_endHint" format="reference" />
|
||||
<attr name="filterRange_theme" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="FilterEditTextWithHint">
|
||||
<attr name="filterRange_hintTextStyle" format="reference" />
|
||||
<attr name="filterRange_valueEditTextStyle" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="slider_point_inactive">#B9B9B9</color>
|
||||
<color name="slider_point_active">#E35100</color>
|
||||
<color name="slider_track_active_part">#EE9766</color>
|
||||
<color name="slider_track_inactive_part">#E7E7E7</color>
|
||||
<color name="slider_thumb">#E35100</color>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="start_hint">от</string>
|
||||
<string name="end_hint">до</string>
|
||||
</resources>
|
||||
|
|
@ -15,4 +15,55 @@
|
|||
<item name="android:layout_marginEnd">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.FilterRangeSlider.Default" parent="@style/Widget.MaterialComponents.Slider">
|
||||
<item name="trackColorActive">@color/slider_track_active_part</item>
|
||||
<item name="trackColorInactive">@color/slider_track_inactive_part</item>
|
||||
<item name="trackHeight">3dp</item>
|
||||
<item name="thumbElevation">0dp</item>
|
||||
<item name="thumbColor">@color/slider_thumb</item>
|
||||
<item name="labelBehavior">gone</item>
|
||||
<item name="haloRadius">0dp</item>
|
||||
<item name="filterRange_activeTickColor">@color/slider_point_active</item>
|
||||
<item name="filterRange_inactiveTickColor">@color/slider_point_inactive</item>
|
||||
<item name="filterRange_stepValueMarginTop">12dp</item>
|
||||
<item name="filterRange_sliderPointSize">5dp</item>
|
||||
<item name="filterRange_pointShape">circle</item>
|
||||
<item name="android:layout_marginStart">11dp</item>
|
||||
<item name="android:layout_marginEnd">11dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.FilterRangeSlider" parent="@style/Theme.MaterialComponents.Light">
|
||||
<item name="filterRange_sliderStyle">@style/Widget.FilterRangeSlider.Default</item>
|
||||
<item name="filterRange_hintViewStyle">@style/Widget.HintInputView</item>
|
||||
<item name="filterRange_hintTextStyle">@style/Widget.HintInputView.HintText</item>
|
||||
<item name="filterRange_valueEditTextStyle">@style/Widget.HintInputView.EditText</item>
|
||||
</style>
|
||||
|
||||
<style name="FilterRangeChoice">
|
||||
<item name="filterRange_sliderMargin">23dp</item>
|
||||
<item name="filterRange_startHint">@string/start_hint</item>
|
||||
<item name="filterRange_endHint">@string/end_hint</item>
|
||||
<item name="filterRange_theme">@style/Theme.FilterRangeSlider</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.HintInputView" parent="@style/Widget.MaterialComponents.TextView">
|
||||
<item name="android:layout_marginEnd">5dp</item>
|
||||
<item name="android:layout_marginTop">12dp</item>
|
||||
<item name="android:layout_marginStart">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.HintInputView.HintText" parent="@style/Widget.MaterialComponents.TextView">
|
||||
<item name="android:layout_marginStart">12dp</item>
|
||||
<item name="android:elevation">1dp</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.HintInputView.EditText" parent="@style/Widget.AppCompat.EditText">
|
||||
<item name="android:paddingStart">34dp</item>
|
||||
<item name="android:inputType">number</item>
|
||||
<item name="android:imeOptions">actionDone</item>
|
||||
<item name="android:background">@drawable/background_hint_input</item>
|
||||
<item name="android:textCursorDrawable">@drawable/cursor_background_text_input_view</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue