特殊なデザインと一緒に縦向きカスタムSeekBarを使いたい!【Android,Kotlin】
こんにちは。てちこまです。
自己紹介にある通り私は現在某IT系専門学校に通っており今年度で卒業します。
そこでテーマ、媒体(アプリケーション、Web、研究など)はなんでもokで卒業制作してね。
という、いわゆる大学で言うところの卒論的なものを卒業までに作らないといけません。
現在Android(Kotlin)でそのモックアップを作成しているのですがデザインとして下のようなものを作る際に少しハマったので記事にしようと思います。
作りたいデザイン
このデザインはAdobeXDで作成しました。手書きでは面倒なリストや各コンポーネント間の幅を揃えたりなど無料(重要)かつ爆速でデザインを用意できるのでかなりオススメです。
ここでデザインのポイントとやりたいことを少し説明しようと思います。
やりたいこと
- 出発してから到着までの進行状況を縦のSeekBarで表現したい。
- 中継地点を角丸のCardとし任意の数登録できるようにしたい。
今回は1の部分を記事にします。2は軽く触れます。
1.縦向きのSeekBarの用意
これがなかなかの曲者でした。そもそも縦向きのSeekBarというものが想定されていないらしくはじめはSeekBarもどきを自分で実装しないといけないのかと絶望したのですがどうやら標準のSeekBarを回転させればいいようで...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//SeekBarを継承したVerticalSeekBar //一部抜粋 class VerticalSeekBar : SeekBar { ... //ここで回転させている override fun onDraw(c: Canvas) { c.rotate(90.toFloat()) c.translate(0.toFloat(), -width.toFloat()) super.onDraw(c) } ... } |
はい。かなり端折っています。というのもこちらの方のコードをKotlinで置き換えるだけでそのまま動作するのです。先駆者の方々に感謝!
上下反転したければ90を270にするといいでしょう。
ちなみに自作SeekBar以外の方法として標準SeekBarのxmlのプロパティに
を追加するだけでも縦になります。android:rotation="90"
動作のカスタムしないならこれでいいのかな?
デザイン
route_view.xml
1 2 3 4 5 6 7 8 9 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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/route_view" android:layout_width="match_parent" android:layout_height="match_parent"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:overScrollMode="never"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/route_seekBar_container" app:layout_constraintBottom_toTopOf="@+id/arrivalView" app:layout_constraintTop_toBottomOf="@+id/departureView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> <com.techikoma.hwalarm.view.widget.VerticalSeekBar android:layout_width="wrap_content" android:layout_height="match_parent" android:id="@+id/route_seekBar" android:thumb="@drawable/custom_thumb" android:layout_gravity="center" android:maxHeight="20dp" android:minHeight="20dp" android:progressDrawable="@drawable/custom_seekbar" android:background="@null"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/facility_recyclerView" android:paddingTop="50dp" android:clipToPadding="false" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center"> </androidx.recyclerview.widget.RecyclerView> </FrameLayout> <androidx.cardview.widget.CardView android:id="@+id/arrivalView" android:layout_width="200dp" android:layout_height="50dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/route_seekBar_container" app:cardBackgroundColor="@color/backgroundCard" app:cardCornerRadius="10dp" android:layout_marginBottom="8dp" > <TextView android:text="@string/arrivalName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textColor="@color/textColorLight" android:textSize="20sp" android:id="@+id/arrivalTextView"/> </androidx.cardview.widget.CardView> <androidx.cardview.widget.CardView android:layout_width="200dp" android:layout_height="50dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/route_seekBar_container" app:cardBackgroundColor="@color/backgroundCard" app:cardCornerRadius="10dp" android:background="@color/backgroundCard" android:id="@+id/departureView" > <TextView android:text="@string/departureName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textColor="@color/textColorLight" android:textSize="20sp" android:id="@+id/departureTextView"/> </androidx.cardview.widget.CardView> </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> </androidx.constraintlayout.widget.ConstraintLayout> |
とりあえず今回はVerticalSeekBarの説明をします。
まず
です。android:thumb="@drawable/custom_thumb"
こちらのプロパティでSeekBarのつまみを変更できます。今回はつまみは不要なので縦横Size0dpのShapeDrawableを作成してそれを当てはめることで消しました。透明にしてもいいと思います。
次にmaxHeightとminHeightです。
SeekBarの幅です。以上。
次は
です。android:progressDrawable="@drawable/custom_seekbar"
ここでSeekBarの色と形を設定します。今回は以下のように用意しました。
custom_seekbar.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?xml version="1.0" encoding="UTF-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background" android:paddingTop="3px" android:paddingBottom="3px"> <shape> <gradient android:startColor="@color/colorPrimary" android:centerColor="@color/colorPrimary" android:endColor="@color/colorPrimary"/> </shape> </item> <item android:id="@android:id/progress"> <clip> <shape> <gradient android:startColor="@color/colorAccent" android:centerColor="@color/colorAccent" android:endColor="@color/colorAccent"/> </shape> </clip> </item> </layer-list> |
次は
です。android:background="@null"
ここをnullにしないとつまみの影が残り続けて残念な見た目になります。しかも影は横移動します笑
最初これが分からなくて縦にスライドすると横に丸い影が移動するシュールなSeekBarが表示されました...
最後は
1 2 |
android:paddingStart="0dp" android:paddingEnd="0dp" |
です。
設定せずにSeekBarを表示すると以下のように表示されます。
これはSeekBarにデフォルトでpaddingが設定されているからです。多分。
2.Cardのリストを表示したい。数は任意
今回はRecyclerViewで用意しました。上のxmlにあるようにFrameLayoutでVerticalSeekBarとRecyclerViewを重ねることでデザインを作っています。
それぞれ高さをmatch_parentにしているのでRecyclerViewの長さが伸びればその分SeekBarも伸びるため任意の数の中継地点(Card)を表示出来ますね。
完成画像
まとめ
なんとか上記完成画像の状態まで持ってきました。
ブログの記事にあるように今年はAndroidをほとんど触っていなかったので(特にデザイン関係)久々にデザインしてみると制約が崩壊してコンポーネントが画面外に吹っ飛んでいったりこれを用意するだけでも一苦労でした。
しかしながらまあConstraintLayoutは優秀ですね。
今回作成したデザインもConstraintLayout以外でゴリ押しすると果てしなく遠い道のりになっていたと思います。
他にも別アプリでConstraintLayoutの派生である(今は内包してるのかな?)MotionLayoutを試したことがあるのですがコンポーネント毎のアニメーション、
例えば「Cardが上から下にスライドして移動する」
なども始点と終点をxmlで定義するだけで簡単に実装できるすごいやつです。
コードは不要です。
こういったものを利用して後からユーザエクスペリエンス向上もしていきたいと思います(願望
...ただ実は言うとこれ1つ問題を残したままで本当はSeekBarをタップした時にRecyclerViewのCard(中継地点)を追加するといった処理を行いたいのですがRecyclerViewでSeekBarが覆われているのでタッチ判定がSeekBarに届かないんですよね。
無理やりRecyclerViewのSeekBar該当範囲にタッチ判定作ってみたりしたもののスクロールすると動作がおかしくなったりしたため結局右下の自作FABでお茶を濁しています。
RecyclerViewのmargin部分だけタッチ判定を透過するとかないんですかね~?