ラベル View の投稿を表示しています。 すべての投稿を表示
ラベル View の投稿を表示しています。 すべての投稿を表示

2023年9月17日日曜日

Android ViewのコンストラクタとカスタムView実装に伴うスタイル定義について解説

どうも。どっことです。今回は独自ViewとしてEditTextのカスタマイズクラスを実装したときにハマった問題について、備忘録を載せたいと思います。

Viewのコンストラクタとスタイル定義

ListViewなどを使うために追加する独自View(①)と、TextViewなどのコンポーネントのViewをカスタマイズするView(②)とで、特にコンストラクタの実装方針が異なります。今回はそんなコンストラクタの実装方針とそれに伴うスタイル定義について解説していきます。

コンストラクタについて

そもそもAndroidのView派生クラスには、3つのコンストラクタがあります。

1.Contextのみを引数とするコンストラクタ

constructor(context:Context)

これはコードからViewを生成するときに使われるコンストラクタです。LayoutInflaterを使わない場合は、このコンストラクタでViewを生成するコースがほとんどかと思います。属性値がほぼ初期値なので、ViewGroupaddView()するときに自分で値を設定する必要があります。

2.Context, AttributeSetを引数とするコンストラクタ

constructor(context:Context, attrs:AttributeSet?)

これはxmlからViewを追加したときに呼ばれるコンストラクタです。

3.Context, AttributeSet, defStyleAttrを引数とするコンストラクタ

constructor(context:Context, attrs:AttributeSet?, defStyleAttr: Int)

これもxmlからViewを追加したときに呼ばれるコンストラクタです。「2.と何が違うの?」という疑問はAndroidアプリ開発者なら誰しも一度は思う謎ですが、それはxml内にstyleが指定されているか否かの違いです。xml内でstyleが指定されていれば3.、されていなければ2.が呼び出されます。

コンストラクタの実装方針について

それではコンストラクタを紹介したところで、先ほどの①と②での実装方針の違いについて説明していきます。

①の場合

気にしなくて問題ありません。以下のようなコードをコピペして使います。(以下はLinearLayoutの派生クラスとして定義した時の例です。)

class HogeView(context: Context, attrs: AttributeSet?, defStyleAttrs:Int) : LinearLayout(context, attrs, defStyleAttrs) {
  constructor(context: Context): this(context, null, 0)
  constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0)
  // ...
}

②の場合

こちらは適切なスタイルの定義が必要になります。デフォルトの場合、例えばEditTextはこのような実装になっています。

public EditText(Context context) { // ①のコンストラクタ
    this(context, null);
}
public EditText(Context context, AttributeSet attrs) { // ②のコンストラクタ
    this(context, attrs, com.android.internal.R.attr.editTextStyle);
}
public EditText(Context context, AttributeSet attrs, int defStyleAttr) { // ③のコンストラクタ
    this(context, attrs, defStyleAttr, 0);
}

第4引数については参考を見てもらうとして2.のコンストラクタが3.のコンストラクタをオーバーロードしているのは同じですが、引数が異なっています。①の場合は0 を渡していましたが、こちらはcom.android.internal.R.attr.editTextStyleを渡しています。

EditTextStyleにはEditTextが必要としている各属性の初期値が設定されており、これを渡してやらないと非常に残念な表示になってしまいます。(EditTextなのに、テキストが編集できないとか。1敗。)特に気にしないなら、これと同じように com.android.internal.R.attr.editTextStyleを渡してやれば問題ありませんが、「せっかく独自Viewを追加したのだから、スタイルもいい感じにしたい」ということであれば、追加で以下を実装する必要があります。(今回はEditTextを例として挙げます。)

実装手順

属性の追加

とりあえず何も考えずに attrs.xml に以下を追加します。

名前も適当です。必要に応じて適切な名前にしてください。

<resources>
    <attr name="hogeEditTextStyle" format="reference" />
</resources>

スタイルの追加

続いて何も考えずに styles.xml に以下を追加する。

名前も...(略)

<resources>
    <style name="HogeEditTextStyle" parent="android:Widget.EditText">
    </style>
</resources>

テーマの追加

前項までに追加したものをつなぐために、themes.xml に以下を追加します。

な...(略)


<style name="Theme.AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
    <!-- ... -->
    <!-- Customize your theme here. -->
    <item name="hogeEditTextStyle">@style/HogeEditTextStyle</item>
</style>

コンストラクタでの参照

最後に、「属性の追加」で追加したものを参照します。

class HogeEditText(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
    AppCompatEditText(context, attrs, defStyleAttr) { // ③のコンストラクタ
    constructor(context: Context) : this(context, null, R.attr.hogeEditTextStyle) // ①のコンストラクタ
constructor(context: Context, attrs: AttributeSet?) : this( // ②のコンストラクタ context, attrs, R.attr.hogeEditTextStyle ) //... }

必要な実装は以上です。これでカスタムしたHogeEditTextでも残念な表示にならず、いい感じの表示とすることができます。

余談

第3引数が必要なコンストラクタの変数名が defStyleAttrとなっており、これは default Style Attributeを意味しているものと考えられます。つまり、デフォルトのStyleのattributeが設定されることを想定されているということです。xml上でstyleが設定されていなければ②の、styleが設定されていれば③のコンストラクタが呼ばれる点からもこの想定が正しいものと思われます。独自Viewとして想定しているデフォルトのスタイルなどは、①、②の場合に限らず、無理やりコード上で設定するのではなく今回紹介した方法のdefStyleAttr設定するのがスマートな実装方法のように感じられます。

まとめ

今回はコンストラクタと、カスタムView実装におけるスタイル定義について解説しました。カスタムViewの実装は、ソースコードの整理やロジックをまとめる際に非常に有効な手法です。今回のように、必要なコンストラクタがどれか、スタイル定義が必要か、などを理解した上で効率よく開発していきたいですね。

参考

2023年7月12日水曜日

Android OnClickListener はXMLレイアウトファイルからでも設定できる

どうも。どっことです。今回はXML上でクリックリスナーを設定する実装方法について解説します。

XMLでクリックリスナーを設定する

dataBindingでは、XMLでクリックリスナーを設定する処理はよくある実装ですが、実はdataBindingを使わない通常のケースでも似たような実装をすることができます。

解説

XMLでクリックリスナーを設定するには、任意のViewに対して android:onClickを設定してあげます。たとえば、TextViewに設定するときは以下のような形です。

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="テスト"
   android:onClick="testClicked" />

あとはこのXMLを読み込むクラスに、testClicked(View v)関数を実装します。

override fun testClicked(v: View) {
  Log.d("TAG", "TextView テスト is clicked")
}

今回設定したonClickの値はtestClickedなのでそれに合わせていますが、実際に利用する際はTextViewがどんな表示をしているかなどを表現した関数名にしてあげると後々にコードを追いやすくなるかと思います。

まとめ

今回はXMLでクリックリスナーを設定する実装方法について解説しました。dataBindingでも似たような実装方法があると思いますが、そうでなくてもシンプルに利用することができるので、参考にしていただけると幸いです。

参考

2021年4月19日月曜日

Android カスタムViewでもダークモード対応する

経緯

カスタムViewを作ったけど、ダークモードのことをまったく気にしなかったせいで、カスタムViewの背景が真っ白になってしまった。

思い

values-nightフォルダを用意してcolors.xmlを入れるのもいいけど、そこまでしなくても標準の設定で十分なんだよなぁ。じゃあ標準ってどうやればいいんだ?と思って開発者サイト見たら、しっかり書いてあった。

ダークテーマ

ハードコードした色を削除してください(白を指定した背景色など)。その代わりとして ?android:attr/colorBackground テーマ属性を使用します。

なーんだ。これ使えば楽勝じゃん。

残念。mergeタグにbackgroundは効かないんだな。

mergeタグってこういう所で融通きかないよね。duplicateParentStateつけないと正しく動作しない問題は解決してくれるけど。さて、となると?android:attr/colorBackgroundをコード上で参照しないといけないけど、どう実装すればいいんだ?

答え

ググったらバリバリ答えが書いてあるページが出てきた。

how to get background color from current theme programmatically

TypedValueを使えば参照できるらしい。というわけで最終的に実装した形がこれ

typedValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.colorBackground, typedValue, true)
setBackgroundColor(typedValue.data)

全然ちょろかった。

移行予定

どうも。どっことです。 タイトルの通りですが、諸事情により GitHubPage に移行予定です。 https://mkt120.github.io/ この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。