Tham chiếu một chuỗi từ một chuỗi khác trong chuỗi XML?


230

Tôi muốn tham chiếu một chuỗi từ một chuỗi khác trong tệp String.xml của tôi, như bên dưới (đặc biệt lưu ý phần cuối của nội dung chuỗi "message văn"):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="message_text">You don't have any items yet! Add one by pressing the \'@string/button_text\' button.</string>
</resources>

Tôi đã thử cú pháp trên nhưng sau đó văn bản in ra "@ chuỗi / button lòng" dưới dạng văn bản rõ ràng. Không phải những gì tôi muốn. Tôi muốn văn bản thông báo in "Bạn chưa có mục nào! Thêm một mục bằng cách nhấn nút 'Thêm mục'."

Có cách nào để đạt được những gì tôi muốn không?

RATIONALE:
Ứng dụng của tôi có một danh sách các mục, nhưng khi danh sách đó trống, tôi sẽ hiển thị TextView "@android: id / blank". Văn bản trong TextView đó là để thông báo cho người dùng cách thêm một mục mới. Tôi muốn làm cho bố cục của mình không bị lừa khi thay đổi (vâng, tôi là kẻ ngốc trong câu hỏi :-)


3
Câu trả lời này cho một câu hỏi tương tự khác làm việc cho tôi. Không cần java, nhưng nó chỉ hoạt động trong cùng một tệp tài nguyên.
mpkuth

Câu trả lời:


253

Một cách hay để chèn một chuỗi được sử dụng thường xuyên (ví dụ: tên ứng dụng) trong xml mà không cần sử dụng mã Java: source

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE resources [
      <!ENTITY appname "MyAppName">
      <!ENTITY author "MrGreen">
    ]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>

CẬP NHẬT:

Bạn thậm chí có thể định nghĩa toàn cầu thực thể của mình, vd:

res / raw / entity.ent:

<!ENTITY appname "MyAppName">
<!ENTITY author "MrGreen">

res / value / string.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
    <!ENTITY % ents SYSTEM "./res/raw/entities.ent">
    %ents;   
]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>

9
Đây là câu trả lời đúng cho OP. Giải pháp này có những lợi ích tuyệt vời khác: (1) bạn có thể xác định DTD trong một tệp bên ngoài và tham chiếu nó từ bất kỳ tệp tài nguyên nào để áp dụng thay thế ở mọi nơi trong tài nguyên của bạn. (2) android studio cho phép bạn đổi tên / tham chiếu / cấu trúc lại các thực thể được xác định. Mặc dù vậy, nó không tự động điền tên trong khi viết. (androidstudio 2.2.2)
Mauro Panzeri

3
@RJFares Bạn có thể đi theo 2 cách: (a) "bao gồm" toàn bộ tệp thực thể EG: xem câu trả lời này . HOẶC (b) : bao gồm mọi thực thể đơn lẻ được xác định trong một DTD bên ngoài như được hiển thị ở đây . Lưu ý rằng không ai là hoàn hảo bởi vì với (a) bạn sẽ mất các khả năng "tái cấu trúc" và (b) rất dài dòng
Mauro Panzeri

5
Cẩn thận nếu bạn sử dụng điều này trong Dự án Thư viện Android - bạn không thể ghi đè lên nó trong ứng dụng của mình.
zyamys

4
có thể thay đổi giá trị thực thể khi xác định hương vị trong tệp lớp?
Myoch

7
Các thực thể bên ngoài không được Android Studio hỗ trợ nữa, vì một lỗi được thảo luận ở đây: stackoverflow.com/a/51330953/8154765
Davide Cannizzo

181

Có thể tham chiếu cái này trong cái khác miễn là toàn bộ chuỗi của bạn bao gồm tên tham chiếu. Ví dụ, điều này sẽ hoạt động:

<string name="app_name">My App</string>
<string name="activity_title">@string/app_name</string>
<string name="message_title">@string/app_name</string>

Nó thậm chí còn hữu ích hơn để thiết lập các giá trị mặc định:

<string name="string1">String 1</string>
<string name="string2">String 2</string>
<string name="string3">String 3</string>
<string name="string_default">@string/string1</string>

Bây giờ bạn có thể sử dụng string_defaultmọi nơi trong mã của mình và bạn có thể dễ dàng thay đổi mặc định bất cứ lúc nào.


Điều này cũng trả lời câu hỏi: làm thế nào để tôi tham khảo chuỗi app_name trong tiêu đề hoạt động. :)
Stephen Hosking

53
Điều này không nên đọc "miễn là bạn tham chiếu toàn bộ chuỗi" (mà bạn luôn luôn theo định nghĩa) mà là "miễn là tài nguyên chuỗi tham chiếu chỉ bao gồm tên tham chiếu".
sschuberth

54
Đây không thực sự là một tài liệu tham khảo, đây chỉ là một bí danh, không giải quyết được vấn đề soạn thảo chuỗi.
Eric Woodruff

2
Điều này không trả lời câu hỏi, họ muốn một chuỗi được nhúng trong một chuỗi khác.
intrepidis

1
FWIW, điều này cực kỳ hữu ích với tôi. Cảm ơn bạn.
Ellen Spertus

95

Tôi nghĩ bạn không thể. Nhưng bạn có thể "định dạng" một chuỗi như bạn muốn:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="message_text">You don't have any items yet! Add one by pressing the %1$s button.</string>
</resources>

Trong mã:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));

5
Tôi đã thực sự hy vọng cho một giải pháp "phi logic". Tuy nhiên, một câu trả lời đủ công bằng (và tốc độ tuyệt vời của nó sẽ cho bạn một dấu kiểm màu xanh lá cây :-)
dbm

34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));sạch hơn một chút.
Andrey Novikov

34

Trong Android, bạn không thể nối các chuỗi bên trong xml

Theo dõi không được hỗ trợ

<string name="string_default">@string/string1 TEST</string>

Kiểm tra liên kết dưới đây để biết làm thế nào để đạt được nó

Làm cách nào để nối nhiều chuỗi trong Android XML?


16
Chà, câu chuyện vui: đó là câu trả lời của tôi cho một câu hỏi tương tự mà bạn đang tham khảo :-)
dbm

Có cách nào để đạt được điều này?

Nó là một bản sao của câu trả lời @Francesco Laurita, cách lập trình thay thế một chuỗi.
CoolMind

14

Tôi đã tạo plugin gradle đơn giản cho phép bạn giới thiệu một chuỗi từ chuỗi khác. Bạn có thể tham chiếu các chuỗi được xác định trong tệp khác, ví dụ như trong biến thể xây dựng hoặc thư viện khác nhau. Nhược điểm của phương pháp này - IDE refactor sẽ không tìm thấy các tài liệu tham khảo như vậy.

Sử dụng {{string_name}}cú pháp để tham chiếu một chuỗi:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="super">Super</string>
    <string name="app_name">My {{super}} App</string>
    <string name="app_description">Name of my application is: {{app_name}}</string>
</resources>

Để tích hợp plugin, chỉ cần thêm mã tiếp theo vào build.gradletệp cấp mô-đun ứng dụng hoặc thư viện

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "gradle.plugin.android-text-resolver:buildSrc:1.2.0"
  }
}

apply plugin: "com.icesmith.androidtextresolver"

CẬP NHẬT: Thư viện không hoạt động với plugin Android gradle phiên bản 3.0 trở lên vì phiên bản mới của plugin sử dụng aapt2, gói tài nguyên thành định dạng nhị phân .flat, vì vậy tài nguyên được đóng gói không có sẵn cho thư viện. Là một giải pháp tạm thời, bạn có thể vô hiệu hóa aapt2 bằng cách đặt android.enableAapt2 = false trong tệp gradle.properIES của bạn.


Tôi thích ý tưởng này .. nhưng nó không thành công với lỗi này:> Could not get unknown property 'referencedString'
jpage4500

Điều này phá vỡ với aapt2
Snicolas

2
Tôi đã tạo một plugin hoạt động khá giống nhau và hoạt động với aapt2: github.com/LikeTheSalad/android-opes-reference :)
César Muñoz

7

Bạn có thể sử dụng logic của riêng mình, giải quyết các chuỗi lồng nhau theo cách đệ quy.

/**
 * Regex that matches a resource string such as <code>@string/a-b_c1</code>.
 */
private static final String REGEX_RESOURCE_STRING = "@string/([A-Za-z0-9-_]*)";

/** Name of the resource type "string" as in <code>@string/...</code> */
private static final String DEF_TYPE_STRING = "string";

/**
 * Recursively replaces resources such as <code>@string/abc</code> with
 * their localized values from the app's resource strings (e.g.
 * <code>strings.xml</code>) within a <code>source</code> string.
 * 
 * Also works recursively, that is, when a resource contains another
 * resource that contains another resource, etc.
 * 
 * @param source
 * @return <code>source</code> with replaced resources (if they exist)
 */
public static String replaceResourceStrings(Context context, String source) {
    // Recursively resolve strings
    Pattern p = Pattern.compile(REGEX_RESOURCE_STRING);
    Matcher m = p.matcher(source);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
        String stringFromResources = getStringByName(context, m.group(1));
        if (stringFromResources == null) {
            Log.w(Constants.LOG,
                    "No String resource found for ID \"" + m.group(1)
                            + "\" while inserting resources");
            /*
             * No need to try to load from defaults, android is trying that
             * for us. If we're here, the resource does not exist. Just
             * return its ID.
             */
            stringFromResources = m.group(1);
        }
        m.appendReplacement(sb, // Recurse
                replaceResourceStrings(context, stringFromResources));
    }
    m.appendTail(sb);
    return sb.toString();
}

/**
 * Returns the string value of a string resource (e.g. defined in
 * <code>values.xml</code>).
 * 
 * @param name
 * @return the value of the string resource or <code>null</code> if no
 *         resource found for id
 */
public static String getStringByName(Context context, String name) {
    int resourceId = getResourceId(context, DEF_TYPE_STRING, name);
    if (resourceId != 0) {
        return context.getString(resourceId);
    } else {
        return null;
    }
}

/**
 * Finds the numeric id of a string resource (e.g. defined in
 * <code>values.xml</code>).
 * 
 * @param defType
 *            Optional default resource type to find, if "type/" is not
 *            included in the name. Can be null to require an explicit type.
 * 
 * @param name
 *            the name of the desired resource
 * @return the associated resource identifier. Returns 0 if no such resource
 *         was found. (0 is not a valid resource ID.)
 */
private static int getResourceId(Context context, String defType,
        String name) {
    return context.getResources().getIdentifier(name, defType,
            context.getPackageName());
}

Từ một Activity, ví dụ, gọi nó như vậy

replaceResourceStrings(this, getString(R.string.message_text));

2

Tôi biết rằng đây là một bài viết cũ hơn, nhưng tôi muốn chia sẻ giải pháp nhanh chóng bẩn thỉu mà tôi đã đưa ra cho một dự án của tôi. Nó chỉ hoạt động cho TextViews nhưng cũng có thể được điều chỉnh cho các widget khác. Lưu ý rằng nó yêu cầu liên kết phải được đặt trong dấu ngoặc vuông (ví dụ [@string/foo]).

public class RefResolvingTextView extends TextView
{
    // ...

    @Override
    public void setText(CharSequence text, BufferType type)
    {
        final StringBuilder sb = new StringBuilder(text);
        final String defPackage = getContext().getApplicationContext().
                getPackageName();

        int beg;

        while((beg = sb.indexOf("[@string/")) != -1)
        {
            int end = sb.indexOf("]", beg);
            String name = sb.substring(beg + 2, end);
            int resId = getResources().getIdentifier(name, null, defPackage);
            if(resId == 0)
            {
                throw new IllegalArgumentException(
                        "Failed to resolve link to @" + name);
            }

            sb.replace(beg, end + 1, getContext().getString(resId));
        }

        super.setText(sb, type);
    }
}

Nhược điểm của phương pháp này là setText()chuyển đổi CharSequencethành a String, đây là một vấn đề nếu bạn vượt qua những thứ như a SpannableString; đối với dự án của tôi, đây không phải là vấn đề vì tôi chỉ sử dụng nó cho TextViewsviệc tôi không cần truy cập từ Activity.


Đây là điều gần nhất với một câu trả lời cho câu hỏi này. Tôi nghĩ rằng chúng ta cần phải xem xét việc phân tích cú pháp xml bố cục.
Eric Woodruff

2

Với ràng buộc dữ liệu mới, bạn có thể nối và làm nhiều hơn nữa trong xml của mình.

ví dụ: nếu bạn có message1 và message2, bạn có thể:

android:text="@{@string/message1 + ': ' + @string/message2}"

bạn thậm chí có thể nhập một số tiện ích văn bản và gọi String.format và bạn bè.

Thật không may nếu bạn muốn sử dụng lại nó ở một số nơi nó có thể bị lộn xộn, bạn không muốn các đoạn mã này ở mọi nơi. và bạn không thể định nghĩa chúng trong xml ở một nơi (không phải tôi biết) để bạn có thể tạo một lớp sẽ gói gọn các tác phẩm đó:

public final class StringCompositions {
    public static final String completeMessage = getString(R.string.message1) + ": " + getString(R.string.message2);
}

sau đó bạn có thể sử dụng nó thay thế (bạn sẽ cần nhập lớp có ràng buộc dữ liệu)

android:text="@{StringCompositions.completeMessage}"

2

Ngoài câu trả lời trên của Francesco Laurita https://stackoverflow.com/a/39870268/9400836

Có vẻ như có một lỗi biên dịch "& entity; đã được tham chiếu, nhưng không được khai báo" có thể được giải quyết bằng cách tham chiếu khai báo bên ngoài như thế này

res / raw / entity.ent

<!ENTITY appname "My App Name">

res / value / chuỗi.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
    <!ENTITY appname SYSTEM "/raw/entities.ent">
]>
<resources>
    <string name="app_name">&appname;</string>
</resources

Mặc dù nó biên dịch và chạy nó có một giá trị trống. Có lẽ ai đó biết làm thế nào để giải quyết điều này. Tôi đã có thể đăng một bình luận nhưng danh tiếng tối thiểu là 50.


1
Bây giờ bạn có 53.
CoolMind

1

Bạn có thể sử dụng trình giữ chỗ chuỗi (% s) và thay thế chúng bằng java vào thời gian chạy

<resources>
<string name="button_text">Add item</string>
<string name="message_text">Custom text %s </string>
</resources>

và trong java

String final = String.format(getString(R.string.message_text),getString(R.string.button_text));

và sau đó đặt nó vào nơi nó sử dụng chuỗi


Bạn sao chép các giải pháp khác. Khi tham khảo một số chuỗi, sử dụng %1$s, %2$svv thay vì %s.
CoolMind

@CoolMind bạn có thể đề cập đến tài liệu tham khảo cho bản sao.
Ismail Iqbal

1

Tôi đã tạo một thư viện nhỏ cho phép bạn giải quyết các trình giữ chỗ này trong thời gian xây dựng, do đó bạn sẽ không phải thêm bất kỳ mã Java / Kotlin nào để đạt được những gì bạn muốn.

Dựa trên ví dụ của bạn, bạn phải thiết lập các chuỗi như thế này:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="template_message_text">You don't have any items yet! Add one by pressing the ${button_text} button.</string>
</resources>

Và sau đó, plugin sẽ đảm nhiệm việc tạo ra các mục sau:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="message_text">You don't have any items yet! Add one by pressing the Add item button.</string>
</resources>

Nó cũng hoạt động cho các chuỗi địa phương và hương vị, các chuỗi được tạo sẽ tự cập nhật bất cứ khi nào bạn thay đổi mẫu hoặc giá trị của nó.

Thêm thông tin tại đây: https://github.com/LikeTheSalad/android-opes-reference


Tôi đã thử thư viện của bạn. Sau khi làm theo các bước bạn đã đưa ra. Nó tiếp tục đưa ra các lỗi xây dựng. Thư viện của bạn hoàn toàn không hoạt động
developer1011

Tôi thấy, xin lỗi khi nghe điều đó. Nếu bạn thích, vui lòng tạo một vấn đề ở đây: github.com/LikeTheSalad/android-opes-reference/issues với nhật ký và tôi có thể giải quyết vấn đề đó trong trường hợp đó là sự cố về tính năng hoặc nếu đó là sự cố về cấu hình, tôi cũng có thể giúp đỡ bạn ở đó 👍
César Muñoz
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.