Освой Android играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

NumberPicker

Разделитель

Компонент NumberPicker находился в разделе Advanced старых версий студии. Компонент позволяет выбрать нужное число из заданного диапазона. Принцип работы похож на револьверный барабан - можно прокручивать числа в одну или другую сторону. Когда будет достигнут заданный предел, то числа продолжат изменяться в заданном диапазоне.

Учитывая, что компонент убрали из студии, можно предположить, что он оказался не очень востребованным.

Разместим компонент на экране.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <NumberPicker
        android:id="@+id/numberPicker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:orientation="vertical"
        android:width="100dp" />

</LinearLayout>

Несмотря на то, что есть атрибут orientation, толку от него немного. Работает только вертикальный режим.

Добавляем немного кода:


NumberPicker numberPicker = findViewById(R.id.numberPicker);
numberPicker.setMaxValue(9);
numberPicker.setMinValue(0);
numberPicker.descendantFocusability = NumberPicker.FOCUS_BLOCK_DESCENDANTS // блокируем появление клавиатуры

Мы определили максимальное и минимальные числа для нашего вида. Удивительно, но через XML нельзя установить эти значения. Запустив проект, вы можете теперь выбрать нужное число через стрелочки. Недавно проверял на другом телефоне (API 19) - стрелочек уже не было.

Метод setWrapSelectorWheel() с параметром false может отключить бесконечную прокрутку:


numberPicker.setWrapSelectorWheel(false);

Пример на Kotlin

Добавим на экран текстовую метку, чтобы отслеживать текущее выбранное значение.


package ru.alexanderklimov.hellokot

import android.os.Bundle
import android.widget.NumberPicker
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
		
        setContentView(R.layout.activity_main)
		
        val numberPicker: NumberPicker = findViewById(R.id.numberPicker)
        val textView: TextView = findViewById(R.id.main_text)

        numberPicker.minValue = 0
        numberPicker.maxValue = 9
        numberPicker.wrapSelectorWheel = false

        numberPicker.setOnValueChangedListener { picker, oldVal, newVal ->
            textView.text = "Выбранное значение: $newVal"
        }
    }
}

NumberPicker

Выбрать кота

Мы можем заменить числа на свои значения через метод setDisplayedValues(), например, на имена котов. В этом случае компонент приобретает какой-то смысл.


// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.

package ru.alexanderklimov.hellokot

import android.os.Bundle
import android.widget.NumberPicker
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
		
        setContentView(R.layout.activity_main)

        val cats = arrayOf("Барсик", "Мурзик", "Рыжик", "Васька", "Пушок", "Снежок")
		
        val numberPicker: NumberPicker = findViewById(R.id.numberPicker)
        val textView: TextView = findViewById(R.id.main_text)

        numberPicker.minValue = 0
        numberPicker.maxValue = cats.size - 1
        numberPicker.wrapSelectorWheel = false
        numberPicker.displayedValues = cats

        numberPicker.setOnValueChangedListener { _, _, newVal ->
            textView.text = "Выбранное значение: ${cats[newVal]}"
        }
    }
}

NumberPicker

API 29

В API 29 появились новые методы для настройки компонента.

  • getSelectionDividerHeight/setSelectionDividerHeight
  • getTextColor/setTextColor
  • getTextSize/setTextSize

Применение части из них рассмотрим ниже.

Разделитель

В API 29 появился новый метод setSelectionDividerHeight(), позволяющий установить размер высоты для разделителей. Для старых версий можно написать функцию-расширение для доступа к низкоуровневым системным функциям.


fun NumberPicker.setDividerHeight(height: Int) {
    val pickerFields = NumberPicker::class.java.declaredFields
    for (pf in pickerFields) {
        if (pf.name == "mSelectionDividerHeight") {
            pf.isAccessible = true
            try {
                // set divider height in pixels
                pf.set(this, height)
            } catch (e: java.lang.IllegalArgumentException) {
                // log exception here
            } catch (e: Resources.NotFoundException) {
                // log exception here
            } catch (e: IllegalAccessException) {
                // log exception here
            }
            break
        }
    }
}

// Установим размер высоты в пикселях для API 29+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
    numberPicker.selectionDividerHeight = 20
} else{
    numberPicker.setDividerHeight(20) // для старых версий
}

Схожая проблема существует с установкой цвета. Причём, в документации говорится, что в Android 3.0 можно установить цвет через атрибут divider или программно через setDividerDrawable(), но у меня не заработало. Возможно, это ошибка в документации, и должно работать в API 29+.

Поэтому напишем функцию-расширение.


fun NumberPicker.setDividerColor(color: Int) {
    val pickerFields = NumberPicker::class.java.declaredFields
    for (pf in pickerFields) {
        if (pf.name == "mSelectionDivider") {
            pf.isAccessible = true
            try {
                val colorDrawable = ColorDrawable(color)
                pf[this] = colorDrawable
            } catch (e: java.lang.IllegalArgumentException) {
                e.printStackTrace()
            } catch (e: Resources.NotFoundException) {
                e.printStackTrace()
            } catch (e: IllegalAccessException) {
                e.printStackTrace()
            }
            break
        }
    }
}

// Установим нужный цвет
numberPicker.setDividerColor(Color.MAGENTA)

Опять же, согласно документации, в API 11 есть и атрибут showDividers и метод getShowDividers(), которые удаляют разделители. Но ничего не работает. Снова поможет функция-расширение.


fun NumberPicker.removeDivider() {
    val pickerFields = NumberPicker::class.java.declaredFields
    for (pf in pickerFields) {
        if (pf.name == "mSelectionDivider") {
            pf.isAccessible = true
            try {
                val colorDrawable = ColorDrawable(Color.TRANSPARENT)
                pf[this] = colorDrawable
            } catch (e: java.lang.IllegalArgumentException) {
                // log exception here
            } catch (e: Resources.NotFoundException) {
                // log exception here
            } catch (e: IllegalAccessException) {
                // log exception here
            }
            break
        }
    }
}

// Не работает
// numberPicker.showDividers = LinearLayout.SHOW_DIVIDER_NONE

numberPicker.removeDivider()

Про остальные методы почитайте в документации.

Реклама