อ้างอิงหนึ่งสายจากสายอื่นใน strings.xml?


230

ฉันต้องการอ้างอิงสตริงจากสตริงอื่นในไฟล์ strings.xml ของฉันเช่นด้านล่าง (โดยเฉพาะบันทึกจุดสิ้นสุดของเนื้อหาสตริง "message_text"):

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

ฉันได้ลองใช้ไวยากรณ์ข้างต้น แต่จากนั้นข้อความจะพิมพ์ "@ string / button_text" เป็นข้อความที่ชัดเจน ไม่ใช่สิ่งที่ฉันต้องการ ฉันต้องการพิมพ์ข้อความ "คุณยังไม่มีรายการใด ๆ ! เพิ่มหนึ่งรายการโดยกดปุ่ม 'เพิ่มรายการ'

มีวิธีใดบ้างที่ทราบว่าจะบรรลุสิ่งที่ฉันต้องการ

เหตุผล:
แอปพลิเคชันของฉันมีรายการ แต่เมื่อรายการนั้นว่างฉันจะแสดง TextView "@android: id / empty" แทน ข้อความใน TextView นั้นจะแจ้งให้ผู้ใช้ทราบถึงวิธีการเพิ่มรายการใหม่ ฉันต้องการทำให้เลย์เอาต์ของฉันโง่พิสูจน์การเปลี่ยนแปลง (ใช่ฉันเป็นคนโง่ในคำถาม :-)


3
คำตอบของคำถามที่คล้ายกันนี้ใช้ได้สำหรับฉัน ไม่จำเป็นต้องใช้จาวา แต่ทำงานได้ภายในไฟล์ทรัพยากรเดียวกัน
mpkuth

คำตอบ:


253

วิธีที่ดีในการแทรกสตริงที่ใช้บ่อย (เช่นชื่อแอป) ใน xml โดยไม่ต้องใช้รหัส 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>

UPDATE:

คุณสามารถกำหนดเอนทิตี้ของคุณได้เช่น:

ความละเอียด / ดิบ / entities.ent:

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

ความละเอียด / ค่า / 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
นี่คือคำตอบที่ถูกต้องของ OP โซลูชันนี้มีประโยชน์ที่ยอดเยี่ยมอื่น ๆ : (1)คุณสามารถกำหนด DTD ในไฟล์ภายนอกและอ้างอิงจากไฟล์ทรัพยากรใด ๆ เพื่อใช้การทดแทนทุกที่ในแหล่งข้อมูลของคุณ (2) android studio ให้คุณเปลี่ยนชื่อ / การอ้างอิง / refactor ตามที่กำหนด แม้ว่าจะไม่ได้เติมข้อความอัตโนมัติในขณะที่เขียน (androidstudio 2.2.2)
Mauro Panzeri

3
@RJFares คุณสามารถไปได้ 2 วิธี: (a) "รวมถึง" เอนทิตีไฟล์ทั้งหมด EG: ดูคำตอบนี้ หรือ(ข) : รวมทั้งองค์กรเดียวทุกที่กำหนดไว้ใน DTD ภายนอกตามที่แสดงไว้ที่นี่ โปรดทราบว่าไม่มีใครสมบูรณ์แบบเพราะด้วย (a) คุณจะคลายความสามารถ "refactoring" และ (b) เป็น verbose มาก
Mauro Panzeri

5
ระวังหากคุณใช้สิ่งนี้ในโครงการห้องสมุด Android - คุณไม่สามารถเขียนทับในแอปของคุณได้
zyamys

4
เป็นไปได้หรือไม่ที่จะเปลี่ยนค่าเอนทิตีเมื่อกำหนดรสชาติในไฟล์ gradle
Myoch

7
Android Studio ไม่รองรับเอนทิตีภายนอกอีกต่อไปเนื่องจากข้อผิดพลาดที่กล่าวถึงที่นี่: stackoverflow.com/a/51330953/8154765
Davide Cannizzo

181

มันเป็นไปได้ที่จะอ้างอิงหนึ่งภายในตราบเท่าที่สตริงทั้งหมดของคุณประกอบด้วยชื่ออ้างอิง เช่นนี้จะทำงาน:

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

มันมีประโยชน์มากยิ่งขึ้นสำหรับการตั้งค่าเริ่มต้น:

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

ตอนนี้คุณสามารถใช้string_defaultทุกที่ในรหัสของคุณและคุณสามารถเปลี่ยนค่าเริ่มต้นได้อย่างง่ายดายทุกเวลา


ซึ่งยังตอบคำถาม: ฉันจะอ้างถึงสตริง app_name ในชื่อกิจกรรมได้อย่างไร :)
Stephen Hosking

53
สิ่งนี้ไม่ควรอ่าน "ตราบใดที่คุณอ้างอิงสตริงทั้งหมด" (ซึ่งคุณต้องทำตามคำนิยามเสมอ) แต่ "ตราบใดที่ทรัพยากรสตริงที่อ้างอิงประกอบด้วยชื่ออ้างอิงเท่านั้น"
sschuberth

54
นี่ไม่ใช่การอ้างอิงจริงๆนี่เป็นเพียงนามแฝงซึ่งไม่สามารถแก้ปัญหาการเขียนสตริงได้
Eric Woodruff

2
สิ่งนี้ไม่ตอบคำถามพวกเขาต้องการสตริงหนึ่งฝังในอีก
intrepidis

1
FWIW มันมีประโยชน์มากสำหรับฉัน ขอบคุณ.
Ellen Spertus

95

ฉันคิดว่าคุณทำไม่ได้ แต่คุณสามารถ "จัดรูปแบบ" สตริงตามที่คุณต้องการ:

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

ในรหัส:

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

5
จริง ๆ แล้วฉันก็หวังว่าจะมีทางออกที่ไม่ใช่ตรรกะ แต่คำตอบที่พอ (และความเร็วที่แท้จริงของมันแก่คุณเครื่องหมายสีเขียว :-)
DBM

34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));ทำความสะอาดนิดหน่อย
Andrey Novikov

34

ใน Android คุณไม่สามารถต่อสตริงภายใน xml ได้

ไม่รองรับการติดตาม

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

ตรวจสอบลิงค์ด้านล่างนี้เพื่อทราบวิธีการใช้งาน

วิธีการต่อหลายสตริงใน android XML


16
เรื่องตลก: นั่นคือคำตอบของฉันสำหรับคำถามที่คล้ายกันที่คุณอ้างถึง :-)
dbm

มีวิธีที่จะบรรลุเป้าหมายนี้หรือไม่?

มันเป็นคำตอบที่ซ้ำกันของ @Francesco Laurita วิธีการแทนที่สตริงโดยทางโปรแกรม
CoolMind

14

ฉันสร้างปลั๊กอิน gradle อย่างง่ายซึ่งช่วยให้คุณสามารถอ้างอิงหนึ่งสายจากที่อื่น คุณสามารถอ้างถึงสตริงที่กำหนดไว้ในไฟล์อื่นตัวอย่างเช่นในชุดตัวสร้างหรือชุดตัวเลือกที่แตกต่างกัน ข้อเสียของวิธีนี้ - IDE refactor จะไม่พบการอ้างอิงดังกล่าว

ใช้{{string_name}}ไวยากรณ์เพื่ออ้างอิงสตริง:

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

หากต้องการรวมปลั๊กอินให้เพิ่มโค้ดถัดไปลงในแอปหรือbuild.gradleไฟล์ระดับโมดูลไลบรารี

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"

UPDATE: ไลบรารีไม่สามารถใช้งานกับ Android gradle plugin เวอร์ชัน 3.0 ขึ้นไปเพราะปลั๊กอินรุ่นใหม่ใช้ aapt2 ซึ่งแพ็คทรัพยากรลงในรูปแบบไบนารีแบบแบนดังนั้นทรัพยากรที่รวบรวมไว้จึงไม่พร้อมใช้งานสำหรับห้องสมุด ในฐานะที่เป็นโซลูชั่นชั่วคราวคุณสามารถปิดการใช้งาน aapt2 โดยการตั้งค่า android.enableAapt2 = false ในไฟล์ gradle.properties ของคุณ


ฉันชอบความคิดนี้ แต่มันล้มเหลวสำหรับฉันด้วยข้อผิดพลาดนี้:> Could not get unknown property 'referencedString'
jpage4500

ช่วงนี้หยุดพักด้วย aapt2
Snicolas

2
ฉันได้สร้างปลั๊กอินที่ไม่สวยมากเหมือนกันและทำงานร่วมกับ aapt2: github.com/LikeTheSalad/android-string-reference :)
CésarMuñoz

7

คุณสามารถใช้ตรรกะของคุณเองซึ่งแก้ไขสตริงที่ซ้อนกันแบบวนซ้ำ

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

จากActivityตัวอย่างเช่นเรียกว่าชอบดังนั้น

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

2

ฉันรู้ว่านี่เป็นโพสต์เก่า แต่ฉันต้องการแบ่งปันวิธีการแก้ปัญหาสกปรกที่รวดเร็วที่ฉันได้ทำกับโครงการของฉัน มันใช้งานได้กับ TextViews เท่านั้น แต่สามารถปรับใช้กับวิดเจ็ตอื่นได้เช่นกัน โปรดทราบว่ามันต้องมีการเชื่อมโยงที่จะอยู่ในวงเล็บเหลี่ยม (เช่น[@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);
    }
}

ข้อเสียของวิธีนี้คือการsetText()แปลงCharSequencea Stringซึ่งเป็นปัญหาถ้าคุณผ่านสิ่งต่าง ๆ เช่น a SpannableString; สำหรับโครงการของฉันนี่ไม่ใช่ปัญหาเพราะฉันใช้เพื่อTextViewsที่ฉันไม่จำเป็นต้องเข้าถึงจากของฉันActivityเท่านั้น


นี่เป็นคำตอบที่ใกล้เคียงที่สุดสำหรับคำถามนี้ ฉันคิดว่าเราต้องมองเข้าไปในการแยกวิเคราะห์เลย์เอาต์ xml
Eric Woodruff

2

ด้วยการเชื่อมโยงข้อมูลใหม่คุณสามารถเชื่อมต่อและทำสิ่งต่างๆได้มากขึ้นใน xml ของคุณ

เช่นถ้าคุณได้รับข้อความ 1 และข้อความ 2 คุณสามารถ:

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

คุณยังสามารถนำเข้าข้อความและโทร String.format และเพื่อน ๆ

โชคไม่ดีถ้าคุณต้องการนำมันกลับมาใช้ซ้ำในหลาย ๆ ที่มันอาจทำให้คุณยุ่งได้ และคุณไม่สามารถนิยามพวกมันเป็น xml ได้ในที่เดียว (ไม่ใช่ที่ฉันรู้) เพื่อที่คุณจะได้สร้างคลาสที่จะแค็ปซูลองค์ประกอบเหล่านั้น:

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

จากนั้นคุณสามารถใช้มันแทน (คุณจะต้องนำเข้าชั้นเรียนที่มีการผูกข้อมูล)

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

2

นอกจากคำตอบข้างต้นโดย Francesco Laurita https://stackoverflow.com/a/39870268/9400836

ดูเหมือนว่ามีข้อผิดพลาดในการคอมไพล์ "& entity; ถูกอ้างอิง แต่ไม่ได้ประกาศ" ซึ่งสามารถแก้ไขได้โดยอ้างอิงการประกาศภายนอกเช่นนี้

ความละเอียด / ดิบ / entities.ent

<!ENTITY appname "My App Name">

ความละเอียด / ค่า / strings.xml

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

แม้ว่ามันจะรวบรวมและรัน แต่ก็มีค่าว่าง อาจมีบางคนรู้วิธีแก้ปัญหานี้ ฉันจะโพสต์ความคิดเห็น แต่ชื่อเสียงขั้นต่ำคือ 50


1
ตอนนี้คุณมี 53
CoolMind

1

คุณสามารถใช้ตัวยึดสตริง(% s)และแทนที่โดยใช้ java ในเวลาทำงาน

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

และในจาวา

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

จากนั้นตั้งค่าเป็นตำแหน่งที่ใช้สตริง


คุณคัดลอกโซลูชันอื่น ๆ เมื่ออ้างอิงสตริงหลายการใช้งาน%1$s, %2$sและอื่น ๆ %sแทน
CoolMind

@CoolMind คุณสามารถพูดถึงการอ้างอิงถึงสำเนา
Ismail Iqbal

1

ฉันได้สร้างไลบรารี่ขนาดเล็กที่ให้คุณแก้ปัญหาตัวยึดตำแหน่งเหล่านี้ที่ buildtime ดังนั้นคุณไม่ต้องเพิ่มโค้ด Java / Kotlin ใด ๆ เพื่อให้ได้สิ่งที่คุณต้องการ

จากตัวอย่างของคุณคุณต้องตั้งค่าสายของคุณดังนี้:

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

จากนั้นปลั๊กอินจะดูแลการสร้างสิ่งต่อไปนี้:

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

มันทำงานได้ดีกับสตริงที่แปลเป็นภาษาท้องถิ่นและรสชาติด้วยเช่นกันสตริงที่สร้างขึ้นจะคอยปรับปรุงตัวเองทุกครั้งที่คุณทำการเปลี่ยนแปลงเทมเพลตหรือค่าของมัน

ข้อมูลเพิ่มเติมที่นี่: https://github.com/LikeTheSalad/android-string-reference


ฉันลองห้องสมุดของคุณ หลังจากทำตามขั้นตอนที่คุณได้รับ มันให้ข้อผิดพลาดในการสร้าง ห้องสมุดของคุณใช้งานไม่ได้เลย
นักพัฒนา

ฉันเห็นว่าเสียใจที่ได้ยินเช่นนั้น หากคุณต้องการโปรดสร้างปัญหาที่นี่: github.com/LikeTheSalad/android-string-reference/issuesพร้อมบันทึกและฉันสามารถจัดการปัญหานี้ได้โดยเร็วในกรณีที่เป็นปัญหาคุณลักษณะหรือถ้าเป็นปัญหาการกำหนดค่าฉันสามารถช่วยได้ คุณอยู่ที่นั่น 👍
CésarMuñoz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.