カスタム属性の定義


472

私のように自分の属性を実装する必要があります com.android.R.attr

公式ドキュメントには何も見つからなかったため、これらの属性の定義方法とコードからの使用方法に関する情報が必要です。


20
これらのドキュメントはあなたのポストという新しいかもしれないが、この電流を維持するために、あなたはここでの属性のために良い、公式ドキュメントを見つけることができます:developer.android.com/training/custom-views/...
OYRM

:私は、カスタム属性についての例で素晴らしい記事をお勧めしますamcmobileware.org/android/blog/2016/09/11/custom-attributes
はArkadiuszCieśliński

小さな実用的な例が役立つかもしれません:github.com/yujiaao/MergeLayout1
Yu Jiaao

回答:


971

現在、最良のドキュメントはソースです。ここ(attrs.xml)で確認できます。

属性は、最上位の<resources>要素または要素の内部で定義できます<declare-styleable>。attrを複数の場所で使用する場合は、ルート要素に配置します。すべての属性が同じグローバル名前空間を共有していることに注意してください。つまり、<declare-styleable>要素内に新しい属性を作成した場合でも、それをその要素の外で使用でき、同じタイプの別の属性を別のタイプで作成することはできません。

<attr>要素は2つのXML属性を持ってnameしてformatnameあなたはそれを何かと呼ぶことができます、そしてこれはあなたがコードでそれを参照することになる方法です、例えばR.attr.my_attributeformat属性には、あなたがしたい属性の「タイプ」に応じて、異なる値を持つことができます。

  • reference-別のリソースIDを参照する場合(例: "@ color / my_color"、 "@ layout / my_layout")
  • ブール
  • 寸法
  • 浮く
  • 整数
  • ストリング
  • 分数
  • 列挙型-通常は暗黙的に定義されます
  • フラグ-通常は暗黙的に定義されます

などを使用して|、フォーマットを複数のタイプに設定できますformat="reference|color"

enum 属性は次のように定義できます。

<attr name="my_enum_attr">
  <enum name="value1" value="1" />
  <enum name="value2" value="2" />
</attr>

flag 属性は、値を定義する必要があることを除いて同様です。

<attr name="my_flag_attr">
  <flag name="fuzzy" value="0x01" />
  <flag name="cold" value="0x02" />
</attr>

属性に加えて、<declare-styleable>要素があります。これにより、カスタムビューで使用できる属性を定義できます。<attr>要素を指定してこれを行いますformat。以前に定義されていた場合は、を指定しません。たとえばandroid:gravityなどのandroid attrを再利用する場合はname、次のようにで行うことができます。

カスタムビューの例<declare-styleable>

<declare-styleable name="MyCustomView">
  <attr name="my_custom_attribute" />
  <attr name="android:gravity" />
</declare-styleable>

カスタムビューでカスタム属性をXMLで定義する場合、いくつかのことを行う必要があります。まず、属性を見つけるために名前空間を宣言する必要があります。これは、ルートレイアウト要素で行います。通常はのみxmlns:android="http://schemas.android.com/apk/res/android"です。ここでも追加する必要がありますxmlns:whatever="http://schemas.android.com/apk/res-auto"

例:

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

    <org.example.mypackage.MyCustomView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:gravity="center"
      whatever:my_custom_attribute="Hello, world!" />
</LinearLayout>

最後に、そのカスタム属性にアクセスするには、通常、次のようにカスタムビューのコンストラクターでアクセスします。

public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);

  TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);

  String str = a.getString(R.styleable.MyCustomView_my_custom_attribute);

  //do something with str

  a.recycle();
}

終わり。:)


14
カスタムで使用するカスタム属性を示すサンプルプロジェクトを次に示します。github.comView
commonsguy

7
ライブラリプロジェクトからカスタムattrsを使用している場合:この質問を参照してください:stackoverflow.com/questions/5819369/…-使用すると機能するようですxmlns:my="http://schemas.android.com/apk/lib/my.namespace"-attrs.xmlをコピーしないでください。ネームスペースURIパスは/ apk / resではなく/ apk / * lib *である必要があります。
thom_nic

2
@ThomNichols apk/libトリックは、ライブラリプロジェクトの参照形式のカスタム属性では機能しませんでした。何やった仕事は、利用したことapk/res-autoで提案されているように、stackoverflow.com/a/13420366/22904でもすぐ下とstackoverflow.com/a/10217752
ジュリオPiancastelli

1
@Qberticusの引用:「フラグ属性は、値を一緒にビット順序付けできるように定義する必要があることを除いて、似ています」。私の意見では、これは一種の主な違いを過小にあるenumflag:前者は私たちが唯一つの値を選ぶことができます、後者は、私たちはいくつか組み合わせることができます。私はここで同様の質問に長い回答を書いており、この質問を見つけたので、それにリンクすると思いました。
Rad Haring 2014年

5
a.recycle()ここでメモリを解放することが非常に重要です
Tash Pemhiwa

87

Qberticusの答えは良いですが、1つの有用な詳細がありません。これらをライブラリに実装する場合は、以下を置き換えます。

xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"

と:

xmlns:whatever="http://schemas.android.com/apk/res-auto"

そうしないと、ライブラリを使用するアプリケーションにランタイムエラーが発生します。


3
これは最近追加されたばかりです...数週間前と思います。確かにそれはQberticusが彼の答えを書いたずっと後に追加されました。
ArtOfWarfare 2012年

12
それよりも古いと思いますが、Qberticusが回答を書いてからずいぶん前に追加されました。彼にまったく欠点はなく、有用な詳細を追加するだけです。
Neil Miller、

11
混乱を避けるためにapk / res-autoを使用するようにQbericusの回答を更新しました。
複雑化

15

上記の答えは、いくつかのことを除いて、すべてを非常に詳細にカバーしています。

まず、スタイルがない場合は、(Context context, AttributeSet attrs)メソッドシグネチャを使用して設定をインスタンス化します。この場合は、を使用context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)してTypedArrayを取得します。

第二に、複数のリソース(数量文字列)を処理する方法はカバーしていません。TypedArrayを使用してこれらを処理することはできません。これは、プリファレンスの値に従ってプリファレンスの値をフォーマットするプリファレンスの概要を設定する、私のSeekBarPreferenceからのコードスニペットです。設定のxmlがandroid:summaryをテキスト文字列または文字列リソースに設定する場合、設定の値は文字列にフォーマットされます(値を取得するには%dが必要です)。android:summaryが複数リソースに設定されている場合、それは結果のフォーマットに使用されます。

// Use your own name space if not using an android resource.
final static private String ANDROID_NS = 
    "http://schemas.android.com/apk/res/android";
private int pluralResource;
private Resources resources;
private String summary;

public SeekBarPreference(Context context, AttributeSet attrs) {
    // ...
    TypedArray attributes = context.obtainStyledAttributes(
        attrs, R.styleable.SeekBarPreference);
    pluralResource =  attrs.getAttributeResourceValue(ANDROID_NS, "summary", 0);
    if (pluralResource !=  0) {
        if (! resources.getResourceTypeName(pluralResource).equals("plurals")) {
            pluralResource = 0;
        }
    }
    if (pluralResource ==  0) {
        summary = attributes.getString(
            R.styleable.SeekBarPreference_android_summary);
    }
    attributes.recycle();
}

@Override
public CharSequence getSummary() {
    int value = getPersistedInt(defaultValue);
    if (pluralResource != 0) {
        return resources.getQuantityString(pluralResource, value, value);
    }
    return (summary == null) ? null : String.format(summary, value);
}

  • これは例にすぎませんが、設定画面で概要を設定したい場合notifyChanged()は、設定のonDialogClosedメソッドを呼び出す必要があります。

5

従来のアプローチは、定型コードと不器用なリソース処理でいっぱいです。そのため、私はSpyglassフレームワークを作成しました。これがどのように機能するかを示すために、ここでは文字列タイトルを表示するカスタムビューを作成する方法を示す例を示します。

手順1:カスタムビュークラスを作成します。

public class CustomView extends FrameLayout {
    private TextView titleView;

    public CustomView(Context context) {
        super(context);
        init(null, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr, 0);
    }

    @RequiresApi(21)
    public CustomView(
            Context context, 
            AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes) {

        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    public void setTitle(String title) {
        titleView.setText(title);
    }

    private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        inflate(getContext(), R.layout.custom_view, this);

        titleView = findViewById(R.id.title_view);
    }
}

手順2:values/attrs.xmlリソースファイルで文字列属性を定義します。

<resources>
    <declare-styleable name="CustomView">
        <attr name="title" format="string"/>
    </declare-styleable>
</resources>

ステップ3:@StringHandlerアノテーションをsetTitleメソッドに適用して、ビューが拡張されたときにSpyglassフレームワークに属性値をこのメソッドにルーティングするように指示します。

@HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
    titleView.setText(title);
}

クラスにSpyglassアノテーションが追加されたので、Spyglassフレームワークはコンパイル時にそれを検出し、CustomView_SpyglassCompanionクラスを自動的に生成します。

手順4:生成されたクラスをカスタムビューのinitメソッドで使用します。

private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    inflate(getContext(), R.layout.custom_view, this);

    titleView = findViewById(R.id.title_view);

    CustomView_SpyglassCompanion
            .builder()
            .withTarget(this)
            .withContext(getContext())
            .withAttributeSet(attrs)
            .withDefaultStyleAttribute(defStyleAttr)
            .withDefaultStyleResource(defStyleRes)
            .build()
            .callTargetMethodsNow();
}

それでおしまい。XMLからクラスをインスタンス化すると、Spyglassコンパニオンが属性を解釈し、必要なメソッド呼び出しを行います。たとえば、次のレイアウトを膨らませると、引数としてsetTitleが呼び出さ"Hello, World!"れます。

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:width="match_parent"
    android:height="match_parent">

    <com.example.CustomView
        android:width="match_parent"
        android:height="match_parent"
        app:title="Hello, World!"/>
</FrameLayout>

フレームワークは文字列リソースに限定されず、他のリソースタイプを処理するためのさまざまな注釈がたくさんあります。また、デフォルト値を定義したり、メソッドに複数のパラメーターがある場合はプレースホルダー値を渡したりするための注釈もあります。

詳細と例については、Githubリポジトリをご覧ください。


Googleデータバインディングでも同じことができます。特定の属性に属性バインディングがない場合、GDBはset *メソッドを見つけて代わりに使用します。この場合は、次のように書く必要がありますandroid:title="@{&quot;Hello, world!&quot;}"
Spook 2019

0

要素formatから属性を省略した場合attr、それを使用してXMLレイアウトからクラスを参照できます。

  • attrs.xmlの例。
  • Android Studioは、クラスがXMLから参照されていることを理解しています
    • すなわち
      • Refactor > Rename 働く
      • Find Usages 働く
      • 等々...

... / src / main / res / values / attrs.xmlでformat属性を指定しないでください

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyCustomView">
        ....
        <attr name="give_me_a_class"/>
        ....
    </declare-styleable>

</resources>

いくつかのレイアウトファイルで使用する... / src / main / res / layout / activity__main_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<SomeLayout
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- make sure to use $ dollar signs for nested classes -->
    <MyCustomView
        app:give_me_a_class="class.type.name.Outer$Nested/>

    <MyCustomView
        app:give_me_a_class="class.type.name.AnotherClass/>

</SomeLayout>

ビューの初期化コードでクラスを解析します... / src / main / java /.../ MyCustomView.kt

class MyCustomView(
        context:Context,
        attrs:AttributeSet)
    :View(context,attrs)
{
    // parse XML attributes
    ....
    private val giveMeAClass:SomeCustomInterface
    init
    {
        context.theme.obtainStyledAttributes(attrs,R.styleable.ColorPreference,0,0).apply()
        {
            try
            {
                // very important to use the class loader from the passed-in context
                giveMeAClass = context::class.java.classLoader!!
                        .loadClass(getString(R.styleable.MyCustomView_give_me_a_class))
                        .newInstance() // instantiate using 0-args constructor
                        .let {it as SomeCustomInterface}
            }
            finally
            {
                recycle()
            }
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.