Xác định tùy chỉnh


472

Tôi cần thực hiện các thuộc tính của riêng mình như trong com.android.R.attr

Không tìm thấy gì trong tài liệu chính thức nên tôi cần thông tin về cách xác định các attrs này và cách sử dụng chúng từ mã của tôi.


20
Các tài liệu này có thể mới hơn bài đăng của bạn, nhưng để giữ bản cập nhật này, bạn có thể tìm tài liệu chính thức, tốt cho các thuộc tính ở đây: developer.android.com/training/custom-view/
Lỗi

Tôi đề xuất một bài viết hay với một ví dụ về các thuộc tính tùy chỉnh: amcmobilnterest.org/android/blog/2016/09/11/custom-attribut
Arkadiusz Cieśliński

một ví dụ làm việc nhỏ có thể hữu ích: github.com/yujiaao/MergeLayout1
Yu Jiaao

Câu trả lời:


971

Hiện tại tài liệu tốt nhất là nguồn. Bạn có thể xem nó ở đây (attrs.xml) .

Bạn có thể xác định các thuộc tính trong <resources>phần tử trên cùng hoặc bên trong một <declare-styleable>phần tử. Nếu tôi sẽ sử dụng một attr ở nhiều nơi, tôi sẽ đặt nó vào phần tử gốc. Lưu ý, tất cả các thuộc tính chia sẻ cùng một không gian tên toàn cầu. Điều đó có nghĩa là ngay cả khi bạn tạo một thuộc tính mới bên trong một <declare-styleable>phần tử, nó có thể được sử dụng bên ngoài nó và bạn không thể tạo một thuộc tính khác có cùng tên của một loại khác.

Một <attr>phần tử có hai thuộc tính xml nameformat. namecho phép bạn gọi nó là một cái gì đó và đây là cách bạn kết thúc đề cập đến nó trong mã, ví dụ , R.attr.my_attribute. Các formatthuộc tính có thể có giá trị khác nhau tùy thuộc vào 'loại' của thuộc tính mà bạn muốn.

  • tham chiếu - nếu nó tham chiếu id tài nguyên khác (ví dụ: "@ color / my_color", "@ layout / my_layout")
  • màu sắc
  • boolean
  • kích thước
  • Phao nổi
  • số nguyên
  • chuỗi
  • phân số
  • enum - thường được định nghĩa ngầm
  • cờ - thường được định nghĩa ngầm

Bạn có thể đặt định dạng thành nhiều loại bằng cách sử dụng |, ví dụ : format="reference|color".

enum các thuộc tính có thể được định nghĩa như sau:

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

flag các thuộc tính tương tự nhau ngoại trừ các giá trị cần được xác định để chúng có thể được ghép với nhau:

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

Ngoài các thuộc tính còn có <declare-styleable>yếu tố. Điều này cho phép bạn xác định các thuộc tính mà chế độ xem tùy chỉnh có thể sử dụng. Bạn làm điều này bằng cách chỉ định một <attr>phần tử, nếu nó được xác định trước đó, bạn không chỉ định format. Nếu bạn muốn sử dụng lại một attr android, ví dụ: android: vity, thì bạn có thể làm điều đó trong name, như sau.

Một ví dụ về chế độ xem tùy chỉnh <declare-styleable>:

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

Khi xác định các thuộc tính tùy chỉnh của bạn trong XML trên chế độ xem tùy chỉnh của bạn, bạn cần thực hiện một số điều. Trước tiên, bạn phải khai báo một không gian tên để tìm thuộc tính của bạn. Bạn làm điều này trên các yếu tố bố trí gốc. Thông thường chỉ có xmlns:android="http://schemas.android.com/apk/res/android". Bây giờ bạn cũng phải thêm xmlns:whatever="http://schemas.android.com/apk/res-auto".

Thí dụ:

<?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>

Cuối cùng, để truy cập thuộc tính tùy chỉnh đó, bạn thường làm như vậy trong hàm tạo của chế độ xem tùy chỉnh của mình như sau.

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();
}

Kết thúc. :)


14
Dưới đây là một dự án mẫu thể hiện các thuộc tính tùy chỉnh để sử dụng với một tùy chỉnh View: github.com/commonsguy/cw-advandroid/tree/master/Views/ trộm
CommonsWare

7
Nếu bạn đang sử dụng attrs tùy chỉnh từ một dự án thư viện: hãy xem câu hỏi này: stackoverflow.com/questions/5819369/xmlns:my="http://schemas.android.com/apk/lib/my.namespace" mẹo - Nó dường như hoạt động nếu bạn sử dụng - không sao chép attrs.xml. Lưu ý đường dẫn URI không gian tên phải là / apk / * lib * không / apk / res.
thom_nic

2
@ThomNichols apk/libthủ thuật không hoạt động với tôi về các thuộc tính tùy chỉnh với định dạng tham chiếu từ dự án thư viện. Những gì đã làm việc là sử dụng apk/res-auto, như được đề xuất trong stackoverflow.com/a/13420366/22904 ngay bên dưới và cả trong stackoverflow.com/a/10217752
Giulio Piancastelli

1
Trích dẫn @Qberticus: "các thuộc tính cờ là tương tự ngoại trừ các giá trị cần được xác định để chúng có thể được ghép với nhau". Theo ý kiến ​​của tôi, đây là loại nhấn mạnh sự khác biệt chính giữa enumflag: cái trước cho phép chúng ta chọn một và chỉ một giá trị, cái sau cho phép chúng ta kết hợp nhiều giá trị. Tôi đã viết lên một câu trả lời dài hơn trong một câu hỏi tương tự ở đây , và bây giờ tìm thấy câu hỏi này tôi đã tìm ra tôi liên kết đến đó.
Rad Haring

5
a.recycle()ở đây rất quan trọng để giải phóng bộ nhớ
Tash Pemhiwa

87

Câu trả lời của Qberticus là tốt, nhưng thiếu một chi tiết hữu ích. Nếu bạn đang thực hiện những điều này trong một thư viện thay thế:

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

với:

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

Nếu không, ứng dụng sử dụng thư viện sẽ có lỗi thời gian chạy.


3
Điều này chỉ mới được thêm vào gần đây ... Tôi nghĩ trong vòng vài tuần trước. Chắc chắn nó đã được thêm vào rất lâu sau khi Qberticus viết câu trả lời của mình.
ArtOfWarfare

12
Tôi nghĩ nó cũ hơn thế, nhưng chắc chắn nó đã được thêm vào rất lâu sau khi Qberticus viết câu trả lời của mình. Không có lỗi với anh ta, chỉ cần thêm một chi tiết hữu ích.
Neil Miller

11
Tôi đã cập nhật câu trả lời của Qbericus để sử dụng apk / res-auto để tránh nhầm lẫn.
Mâu thuẫn

15

Câu trả lời ở trên bao gồm mọi thứ rất chi tiết, ngoài một vài điều.

Đầu tiên, nếu không có kiểu, thì (Context context, AttributeSet attrs)chữ ký phương thức sẽ được sử dụng để khởi tạo ưu tiên. Trong trường hợp này, chỉ cần sử dụng context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)để lấy TypedArray.

Thứ hai, nó không bao gồm làm thế nào để đối phó với tài nguyên plaurals (chuỗi số lượng). Chúng không thể được xử lý bằng TypedArray. Đây là đoạn mã từ SeekBarPreference của tôi, đặt tóm tắt về định dạng ưu tiên định dạng giá trị của nó theo giá trị của ưu tiên. Nếu xml cho tùy chọn đặt android: tóm tắt thành chuỗi văn bản hoặc chuỗi nối lại giá trị của tùy chọn được định dạng thành chuỗi (cần có% d trong đó, để chọn giá trị). Nếu android: Tóm tắt được đặt thành tài nguyên plaurals, thì đó được sử dụng để định dạng kết quả.

// 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);
}

  • Đây chỉ là một ví dụ, tuy nhiên, nếu bạn muốn đặt tóm tắt trên màn hình tùy chọn, thì bạn cần gọi notifyChanged()theo onDialogClosedphương thức của tùy chọn .

5

Cách tiếp cận truyền thống có đầy đủ mã soạn sẵn và xử lý tài nguyên vụng về. Đó là lý do tại sao tôi tạo ra khung Spylass . Để giải thích cách thức hoạt động của nó, đây là một ví dụ cho thấy cách tạo chế độ xem tùy chỉnh hiển thị tiêu đề Chuỗi.

Bước 1: Tạo một lớp xem tùy chỉnh.

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);
    }
}

Bước 2: Xác định một thuộc tính chuỗi trong values/attrs.xmltệp tài nguyên:

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

Bước 3: Áp dụng @StringHandlerchú thích cho setTitlephương thức để báo cho khung Spylass định tuyến giá trị thuộc tính đến phương thức này khi chế độ xem bị thổi phồng.

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

Bây giờ lớp của bạn có chú thích Spylass, khung Spylass sẽ phát hiện nó vào thời gian biên dịch và tự động tạo CustomView_SpyglassCompanionlớp.

Bước 4: Sử dụng lớp được tạo trong initphương thức của khung nhìn tùy chỉnh :

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();
}

Đó là nó. Bây giờ khi bạn khởi tạo lớp từ XML, trình đồng hành Spylass sẽ diễn giải các thuộc tính và thực hiện cuộc gọi phương thức cần thiết. Ví dụ, nếu chúng ta thổi phồng bố cục sau thì setTitlesẽ được gọi với "Hello, World!"tư cách là đối số.

<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>

Khung không giới hạn đối với tài nguyên chuỗi có nhiều chú thích khác nhau để xử lý các loại tài nguyên khác. Nó cũng có các chú thích để xác định các giá trị mặc định và để chuyển các giá trị giữ chỗ nếu các phương thức của bạn có nhiều tham số.

Hãy xem repo Github để biết thêm thông tin và ví dụ.


Bạn có thể đạt được điều tương tự với Google Data Binding - nếu không có ràng buộc thuộc tính cho thuộc tính cụ thể, GDB cố gắng tìm phương thức set * và sử dụng nó để thay thế. Trong trường hợp này, bạn phải viết, nói android:title="@{&quot;Hello, world!&quot;}".
Spook

0

nếu bạn bỏ qua formatthuộc tính từ attrphần tử, bạn có thể sử dụng nó để tham chiếu một lớp từ các bố cục XML.

  • ví dụ từ attrs.xml .
  • Android Studio hiểu rằng lớp đang được tham chiếu từ XML
    • I E
      • Refactor > Rename làm
      • Find Usages làm
      • và như thế...

không chỉ định một formatthuộc tính trong ... / src / main / res / value / attrs.xml

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

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

</resources>

sử dụng nó trong một số tệp bố cục ... / 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>

phân tích lớp trong mã khởi tạo chế độ xem của bạn ... / 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()
            }
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.