目次
1.1.5 下部ビューのサイズと下部からの距離はハードコードされていません
1.3.3 レイアウトは selfWidth/selfHeight に依存します
0 まえがき
Android は垂直方向の SeekBar をネイティブでサポートしていないことはわかっていますが、なぜですか? 理由を見つけたかったのですが、見つかりませんでした。怠惰はプログラマーの美徳なのかもしれません。さて、本題に入りましょう。
次の UI を実装するにはどうしますか?
まず要件を詳しく見てみましょう。
- 音量または明るさの調整 UI はドラッグ可能である必要があります。
- 背景、前景、アイコンの位置、角の丸み、UI サイズをカスタマイズできます。
Android の SeekBar はドラッグ機能をサポートしていますが、水平方向です。ネイティブを使用する場合は垂直方向に変更する必要があり、そうでない場合は、そのようなコントロールを描画するには View をカスタマイズする必要があります。
さらに、UI 要素のカスタマイズ可能性の要件はハードコード化できず、詳細も多数ありますが、これについては後で説明します。
カスタムビューについて
実際、私はビューのカスタマイズに反対しているわけではありませんが、このような垂直方向の SeekBar を自分で描画することはできますが、ビューの測定、レイアウト、描画を Android ネイティブよりも確実に処理できるでしょうか? 性能的には問題ないのでしょうか?それが不可能だと考えるのは愚かです。また、View のカスタマイズには時間と労力がかかり、UI の復元ではデザイン ドラフトの効果を得ることができません。
したがって、Android のネイティブ View をできるだけ再利用するか、View を組み合わせることで最良の結果が得られると考えています。2 つ目は、画像を切り取ることです。やむを得ない場合のみ View を描画するキャンバスを自分で作成しますが、機能と性能のバランスを考慮しなければなりません。たとえば、以前にカスタム ビューを通じて実装したverticalProgressは、必ずしも機能とパフォーマンスのバランスが最適であるとは限りません。
[カスタマイズされたビュー垂直進行_Swuagg のブログ]
1 垂直シークバーを実装する
垂直 SeekBar を実装するには、現在 3 つのオプションがあります。
オプション 1: View を継承して実装をカスタマイズします。たとえば、VerticalSeekBar の View カスタム実装を継承します。
オプション 2: SeekBar オーバーロード メソッドを継承して内部回転を実装します。たとえば、次のようにします。 SeekBar オーバーロード メソッドの内部回転の継承
オプション 3: API 11 以降のバージョンの場合、XML で回転属性を使用して回転 270 度を指定します。例: 垂直方向の効果を得るには、シークバーの XML 属性 (android:rotation="270") を使用します。
タイトルからもわかるように、この記事で採用した 3 番目のソリューションは SeekBar を継承せず、View をカスタマイズしません。以下のコードを詳しく見てみましょう。
1.1 XML レイアウトの解析
導入効果を見てみましょう。
次に、XML レイアウト ファイルを分析します。
<?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"
android:id="@+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<SeekBar
android:id="@+id/seekbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:progressDrawable="?attr/UISeekBarProgressDrawable"
android:rotation="270"
android:thumb="@null" />
</FrameLayout>
<View
android:id="@+id/icon"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
まず、データがハードコーディングされていないことがわかります。270 で反論したいのはわかりますが、これは要件設計ではありませんか?
1.1.1 FrameLayoutのレイヤーを設定する
1) SeekBar を 270 度回転するとサイズの問題が発生するため、FrameLayout のレイヤーをカバーする必要があります FrameLayout の幅と高さは、SeekBar の高さと幅の逆になります。 SeekBar では、layout_gravity を中心として指定する必要もあります。
2) XML では幅と高さの両方に match_parent が使用されていることがわかりました。これはユーザーがサイズをカスタマイズできるようにするためであり、外部の使用場所に応じてコード内の幅と高さを動的に変更します。
1.1.2 SeekBar は左右の間隔を削除します
SeekBar の周囲にはデフォルトの Padding があります。padding水平を 0 に設定しても効果はありません。paddingStart とpaddingEnd を 0 に設定する必要があります。
1.1.3 シークバーの高さを設定できない
minHeight、minWidth、maxHeight、maxWidth を同時に設定する必要があります。値は幅と高さの最小値です。ハードコーディングされていないため、コードに実装する必要があります。
1.1.4 SeekBarの背景設定
UISeekBarProgressDrawable 属性をカスタマイズし、テーマに応じて対応するドローアブルを指定することで、テーマに合わせて切り替えることができます。バックグラウンド ファイルui_seekbar_vertical_bg.xmlは次のとおりです。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<corners android:radius="32dp" />
<stroke
android:width="2dp"
android:color="#3A4266" />
<solid android:color="#323A60" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<corners android:radius="32dp" />
<solid android:color="#6E779C" />
</shape>
</clip>
</item>
</layer-list>
1.1.5 下部ビューのサイズと下部からの距離はハードコードされていません
1) 半幅比を実装します。幅と高さを 0 に指定し、layout_constraintDimensionRatio="1:1"、layout_constraintWidth_percent="0.5" を設定し、左右に配置します。
2) 底部からの距離はハードコーディングされていないため、コードに実装する必要があります。
1.2 カスタムスタイル属性とテーマ
1.2.1 カスタムスタイル属性
<declare-styleable name="SeekBarVertical">
<attr name="UISeekBarProgress" format="integer" />
<attr name="UISeekBarMax" format="integer" />
<attr name="UISeekBarIcon" format="reference" />
<attr name="UISeekBarProgressDrawable" format="reference" />
</declare-styleable>
1.2.2 テーマ固有の属性をカスタマイズする
<!--应用Theme主题-->
<style name="DefaultAppTheme" parent="android:Theme.Black.NoTitleBar.Fullscreen">
<item name="UISeekBarProgressDrawable">@drawable/ui_seekbar_vertical_bg</item>
</style>
1.3 コーディングの実装
package com.agg.ui
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.SeekBar
import androidx.constraintlayout.widget.ConstraintLayout
/**
* Description:
* CreateDate: 2023/7/11 14:58
* Author: agg
*/
class SeekBarVertical : ConstraintLayout {
lateinit var parentView: ConstraintLayout
lateinit var seekBar: SeekBar
lateinit var icon: View
private var selfWidth: Int = -1
private var selfHeight: Int = -1
private var seekBarMinMaxValue: Int = -1
private var selfProgress: Int = -1
private var selfMax: Int = -1
private var iconDrawable: Drawable? = null
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int = 0) : super(
context, attrs, defStyle
) {
attrs?.let { initAttrs(context, attrs) }
initView()
}
/**
* 代码中动态添加时使用。如:
*
* binding.root.addView(SeekBarVertical(this,192,432))
*/
constructor(context: Context, width: Int, height: Int) : super(context) {
selfWidth = width
selfHeight = height
seekBarMinMaxValue = if (selfHeight < selfWidth) selfHeight else selfWidth
initView()
}
@SuppressLint("ResourceType")
private fun initAttrs(context: Context, attrs: AttributeSet) {
// 获取Android原生宽、高属性
context.obtainStyledAttributes(
attrs, intArrayOf(android.R.attr.layout_width, android.R.attr.layout_height)
).apply {
selfWidth = getDimensionPixelSize(0, -1)
selfHeight = getDimensionPixelSize(1, -1)
seekBarMinMaxValue = if (selfHeight < selfWidth) selfHeight else selfWidth
recycle()
}
// 获取SeekBarVertical自定义属性
context.obtainStyledAttributes(
attrs, R.styleable.SeekBarVertical
).apply {
selfProgress = getInt(R.styleable.SeekBarVertical_UISeekBarProgress, -1)
selfMax = getInt(R.styleable.SeekBarVertical_UISeekBarMax, -1)
iconDrawable = getDrawable(R.styleable.SeekBarVertical_UISeekBarIcon)
recycle()
}
}
@SuppressLint("NewApi")
private fun initView() {
// 未设置宽高则直接返回
if (selfWidth < 0 || selfHeight < 0) return
parentView = View.inflate(context, R.layout.ui_seekbar_vertical, this) as ConstraintLayout
// 设置控件整体宽、高
(parentView.findViewById<ConstraintLayout>(R.id.parent).layoutParams as LayoutParams).apply {
width = selfWidth
height = selfHeight
}
// 设置SeekBar宽、高,以及progress和max值
seekBar = parentView.findViewById<SeekBar>(R.id.seekbar).apply {
(layoutParams as FrameLayout.LayoutParams).apply {
width = selfHeight
height = selfWidth
}
minHeight = seekBarMinMaxValue
maxHeight = seekBarMinMaxValue
minWidth = seekBarMinMaxValue
maxWidth = seekBarMinMaxValue
if (selfProgress >= 0) progress = selfProgress
if (selfMax >= 0) max = selfMax
}
// 设置icon距离底部位置,以及icon背景
icon = parentView.findViewById<View>(R.id.icon).apply {
(layoutParams as LayoutParams).bottomMargin = seekBarMinMaxValue / 4
iconDrawable?.let { setBackgroundDrawable(it) }
}
}
}
1.3.1 外部カスタマイズの提供
この公開変更により、parentView、seekBar、および icon がリリースされ、アプリケーション開発者が関連する属性とメソッドをカスタマイズできるようになります。
1.3.2 コードでの動的な追加と使用のサポート
コード内で動的に SeekBarVertical を使用するために、コンストラクター メソッドconstructor(context: Context, width: Int, height: Int)が提供されています。
1.3.3 レイアウトは selfWidth/selfHeight に依存します
selfWidth と selfHeight をベース参照として使用し、コードは SeekBarVertical のサイズと位置を動的に制御します。
2 垂直シークバーを使用する
<com.metabounds.ui.SeekBarVertical
android:layout_width="96dp"
android:layout_height="216dp"/>
関連する属性値をxmlで指定可能
app:UISeekBarIcon="@drawable/ic_user_head"
app:UISeekBarMax="12"
app:UISeekBarProgress="3"
コード内で指定することもできます
binding.seekBar.icon.setBackgroundResource(R.drawable.ic_user_head)
binding.seekBar.seekBar.max = 12
binding.seekBar.seekBar.progress = 3
コードに SeekBarVertical を動的に追加する
binding.root.addView(SeekBarVertical(this, 96, 216))
3 まとめ
この記事の実装は Android 11 以降の回転属性に依存しています。Android のそれ以前のバージョンとの互換性を持たせたい場合は、解決策 1 のカスタム ビュー、または解決策 2 のキャンバスの回転 + 移動を使用することをお勧めします。
目次
1.1.5 下部ビューのサイズと下部からの距離はハードコードされていません
1.3.3 レイアウトは selfWidth/selfHeight に依存します