Статьи

Как я создал свою первую библиотеку Android с открытым исходным кодом — часть 1

Долгое время я думал о создании библиотеки Android с открытым исходным кодом. Я твердо верю в то, что вклад в открытый исходный код и чтение открытого исходного кода делает человека лучшим инженером. Но у меня никогда не было опыта в создании собственной библиотеки.

Увлеченный тем фактом, что как библиотека Android публикуется в JCenter и как она загружается путем помещения одной строки в gradle, я подумал о публикации простой библиотеки Android.

Идея

Так как я впервые писал библиотеку Android с открытым исходным кодом, я подумал, что было бы лучше сделать ее максимально простой. Я хотел очень быстро создать библиотеку и опубликовать ее, чтобы освоить процесс.

Я знал, что создание какого-то SDK займет много времени. Следовательно, я решил возиться с пользовательским интерфейсом. Я хотел создать простой анимационный вид. Это был случай использования, с которым я столкнулся в своей работе, когда я должен был привлечь внимание пользователя к кнопке.

Я думал о добавлении к нему какой-то анимации . Импульсная анимация (растущая и уменьшающаяся) казалась наиболее эффективной и быстрой. Поэтому я решил продолжить эту линию со своей библиотекой. Просто создаю что-то, что я мог бы использовать в своей повседневной работе.

Кроме того, я был в некоторой степени осведомлен о том, как я мог бы сделать это просто с помощью ValueAnimator. Вишня сверху 😉

Выполнение

Шаг 1

Я создал новый проект Android Studio с пустой деятельностью.

После создания проекта я создал класс с именем Pulsating Button и расширил его из LinearLayout. Это будет мой CustomView / Button, который я буду анимировать.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class PulsatingButton : LinearLayout {
 
    constructor(context: Context?) : super(context) {
        init(context)
    }
 
    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
        parseAttributes(context, attrs)
        init(context)
    }
 
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        parseAttributes(context, attrs)
        init(context)
    }
 
}

С этого момента я должен был присвоить моей кнопке такие атрибуты, как, например, частота пульса / частота пульса. Какой будет размер импульса, количество повторений и т. Д.

Я задавался вопросом, мог ли бы я использовать атрибуты для этого, и как оказалось, я мог. Потратив некоторое время на изучение того, как добавить пользовательские атрибуты xml, я создал 7 пользовательских атрибутов для своей кнопки:

  1. Частота пульса
  2. Счетчик импульсов
  3. Горизонтальное смещение
  4. Вертикальное смещение
  5. Текст
  6. Цвет текста
  7. Фон

Я создал файл с именем: attribute.xml в пакете значений и поместил в него свои атрибуты.

01
02
03
04
05
06
07
08
09
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PulsatingButton">
        <attr name="pulseDuration" format="integer" />
        <attr name="horizontalOffset" format="integer" />
        <attr name="verticalOffset" format="integer" />
        <attr name="pulseCount" format="integer" />
        <attr name="buttonColor" format="color"/>
        <attr name="textColor" format="color"/>
        <attr name="text" format="string"/>
    </declare-styleable>
</resources>

Шаг 2

При наличии атрибутов пришло время применить их к кнопке. Я добавил несколько глобальных переменных и проанализировал атрибуты.

Пришло время создать файл макета для кнопки. Файл макета содержал кнопку как дочерний элемент. Это было бы корневым представлением.

Я хотел бы извлечь кнопку в глобальной переменной. Закончив метод инициализации пульсирующей кнопки, я продолжил и установил атрибуты. Также добавлены некоторые методы для динамической установки этих переменных с помощью кода.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
class PulsatingButton : LinearLayout {
 
    private var buttonText: CharSequence? = ""
    private var textColor: Int = ContextCompat.getColor(context, android.R.color.black)
    private var buttonColor: Int = ContextCompat.getColor(context, R.color.colorAccent)
    private var verticalOffset: Int = 40
    private var horizontalOffset: Int = 40
    private var repeatCount: Int = Int.MAX_VALUE
    private var animationDuration: Int = 1000
    val set = AnimatorSet()
 
    constructor(context: Context?) : super(context) {
        init(context)
    }
 
    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
        parseAttributes(context, attrs)
        init(context)
    }
 
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        parseAttributes(context, attrs)
        init(context)
    }
 
    private fun init(context: Context?) {
        LayoutInflater.from(context).inflate(R.layout.pulsating_button, this, true)
        setColors()
        setText(buttonText)
    }
 
    private fun parseAttributes(context: Context, attributes: AttributeSet) {
        val attrs = context.theme.obtainStyledAttributes(attributes, R.styleable.PulsatingButton, 0, 0)
        try {
            this.animationDuration = attrs.getInt(R.styleable.PulsatingButton_pulseDuration, 100)
            this.verticalOffset = attrs.getInt(R.styleable.PulsatingButton_verticalOffset, 4)
            this.horizontalOffset = attrs.getInt(R.styleable.PulsatingButton_horizontalOffset, 4)
            this.repeatCount = attrs.getInt(R.styleable.PulsatingButton_pulseCount, Int.MAX_VALUE)
            this.buttonColor = attrs.getColor(R.styleable.PulsatingButton_buttonColor, ContextCompat.getColor(context, R.color.colorAccent))
            this.textColor = attrs.getColor(R.styleable.PulsatingButton_textColor, ContextCompat.getColor(context, R.color.colorAccent))
            this.buttonText = attrs.getText(R.styleable.PulsatingButton_text)
        } finally {
            attrs.recycle()
        }
    }
 
    fun setHorizontalOffset(horizontalOffset: Int) {
        this.horizontalOffset = horizontalOffset
    }
 
    fun setVerticalOffset(verticalOffset: Int) {
        this.verticalOffset = verticalOffset
    }
 
    fun setRepeatCount(count: Int) {
        this.repeatCount = count
    }
 
    fun setAnimationDuration(duration: Int) {
        this.animationDuration = duration
    }
 
    private fun setColors() {
        button.setBackgroundColor(buttonColor)
        button.setTextColor(textColor)
    }
 
    fun setButtonDrawable(drawable: Drawable) {
        button.background = drawable
    }
 
    fun setText(text: CharSequence?) {
        if (!TextUtils.isEmpty(text)) {
            button.text = text
        }
    }
 
}

Шаг 3

Теперь пришло время создать настоящую анимацию. Я планировал использовать ValueAnimator для многократного генерирования значений, которые будут использоваться для изменения ширины и высоты моей кнопки.

Я создал функцию с именем startAnimation и два объекта-аниматора значений для горизонтального и вертикального масштабирования:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
class PulsatingButton : LinearLayout {
...
    fun startAnimation() {
        val animator = ValueAnimator.ofInt(0, verticalOffset)
        animator.interpolator = AccelerateDecelerateInterpolator()
        animator.duration = animationDuration.toLong()
 
        val animator2 = ValueAnimator.ofInt(0, horizontalOffset)
        animator2.interpolator = AccelerateDecelerateInterpolator()
        animator2.duration = animationDuration.toLong()
    }
 
...
 
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class PulsatingButton : LinearLayout {
...
    fun startAnimation() {
        val animator = ValueAnimator.ofInt(0, verticalOffset)
        animator.repeatMode = ValueAnimator.REVERSE
        animator.interpolator = AccelerateDecelerateInterpolator()
        animator.duration = animationDuration.toLong()
 
        val animator2 = ValueAnimator.ofInt(0, horizontalOffset)
        animator2.repeatMode = ValueAnimator.REVERSE
        animator2.interpolator = AccelerateDecelerateInterpolator()
        animator2.duration = animationDuration.toLong()
 
        val originalheight = button.layoutParams.height
        val originalWidth = button.layoutParams.width
 
        animator.addUpdateListener { valueAnimator ->
            val params = button.layoutParams
            val animatedValue = valueAnimator.animatedValue as Int
            params.height = (originalheight + animatedValue)
            button.layoutParams = params
        }
 
        animator2.addUpdateListener { valueAnimator ->
            val params = button.layoutParams
            val animatedValue = valueAnimator.animatedValue as Int
            params.width = (originalWidth + animatedValue)
            button.layoutParams = params
        }
    }
 
...
 
}

Затем я проверил, установлен ли счетчик повторов. Если нет, то по умолчанию повторяется бесконечно. Наконец, пришло время объединить эти два объекта анимации и воспроизвести их вместе. Для этого я использовал AnimatorSet.

Это должно работать правильно? Когда все выглядело хорошо, я на это надеялся, но, к моему удивлению, вот что происходит:

Есть подвох в использовании ValueAnimator . Потратив несколько часов на переполнение стека и официальные документы по Android, я узнал о свойстве аниматора значений под названием repeatMode. Должно быть установлено значение ValueAnimator.REVERSE

Что здесь произошло, это:

По умолчанию valueAnimator будет выдавать значения, скажем, от 0 до 40, например, 0, 1, 2, 3 …… ..40, а затем снова вернется к 0 и выдаст 0, 1, …… 40.

То, что я хотел, было 0, 1, 2 …… 40, а затем 40, 39, 38 …… ..1, 0. Это было необходимо для того, чтобы анимация работала, иначе она просто сократилась в мгновение ока.

Это был, вероятно, самый интенсивный и трудоемкий шаг в развитии этой библиотеки. Но, в конце концов, у меня наконец было это:

Релиз

Хорошо, теперь я закончила свою библиотеку. Но ждать! На самом деле это была не библиотека, а обычный проект Android Studio, который я создал.

Мне нужно было выяснить, как легко перенести проект Android Studio в библиотеку, иначе мне пришлось бы создать новый проект библиотеки и скопировать код.

Мне нужно было просто изменить одну строку внутри файла app / build.gradle. Мне нужно было удалить com.android.application и добавить com.android.library.

Обратите внимание, что после этого вы не сможете запустить свой проект, так как это библиотека.

После этого я хотел выяснить, как опубликовать библиотеку на какой-либо онлайн-платформе, чтобы ее можно было легко использовать для разработчиков Android. Самый простой способ сделать это — использовать JitPack.io

Все, что вам нужно сделать, это создать релиз вашего репозитория GitHub на вкладке релиз.

Затем скопируйте URL-адрес вашего репозитория и вставьте его в текстовое поле на JitPack.io. Это создаст ссылку «» для добавления в gradle.

Но подождите, это не может быть так просто, верно?

На самом деле это было до тех пор, пока я не выяснил, что также нужно включить maven url для jitpack в их файл build.gradle уровня проекта.

Проекты Android не имеют этого по умолчанию и включают только google () и jcenter (). Поэтому, чтобы минимизировать трения, я решил опубликовать свою библиотеку в JCenter.

Как я опубликовал свою библиотеку Android на JCenter? потребовал бы полной статьи самостоятельно. Так что следите за обновлениями для части 2.

Вывод

В этой статье рассказывается о создании моей первой библиотеки Android с открытым исходным кодом. Лучший способ учиться — это делать. И лучший способ начать писать лучший код — начать читать и писать больше открытого исходного кода, используемого другими!

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

Смотрите оригинальную статью здесь: Как я создал свою первую библиотеку Android с открытым исходным кодом — Часть 1

Мнения, высказанные участниками Java Code Geeks, являются их собственными.