ฉันต้องใช้คุณลักษณะของตัวเองเช่น com.android.R.attr
ไม่พบอะไรในเอกสารอย่างเป็นทางการดังนั้นฉันต้องการข้อมูลเกี่ยวกับวิธีกำหนด Attrs เหล่านี้และวิธีใช้จากรหัสของฉัน
ฉันต้องใช้คุณลักษณะของตัวเองเช่น com.android.R.attr
ไม่พบอะไรในเอกสารอย่างเป็นทางการดังนั้นฉันต้องการข้อมูลเกี่ยวกับวิธีกำหนด Attrs เหล่านี้และวิธีใช้จากรหัสของฉัน
คำตอบ:
ปัจจุบันเอกสารที่ดีที่สุดคือแหล่งที่มา คุณสามารถใช้ดูที่นี่ (attrs.xml)
คุณสามารถกำหนดคุณสมบัติใน<resources>
องค์ประกอบด้านบนหรือด้านในของ<declare-styleable>
องค์ประกอบ ถ้าฉันจะใช้ attr มากกว่าหนึ่งแห่งฉันจะใส่ไว้ในองค์ประกอบหลัก หมายเหตุแอ็ตทริบิวต์ทั้งหมดใช้เนมสเปซส่วนกลางเดียวกัน นั่นหมายความว่าแม้ว่าคุณจะสร้างแอททริบิวใหม่ภายใน<declare-styleable>
องค์ประกอบก็สามารถใช้งานได้ภายนอกและคุณไม่สามารถสร้างแอททริบิวอื่นด้วยชื่อเดียวกันกับประเภทอื่น
<attr>
องค์ประกอบมีแอตทริบิวต์ XML สองและname
ช่วยให้คุณสามารถเรียกมันว่าบางสิ่งบางอย่างและนี่คือวิธีการที่คุณจะจบลงหมายถึงว่ามันในรหัสเช่น แอททริบิวอาจมีค่าแตกต่างกันไปขึ้นอยู่กับ 'ประเภท' ของแอททริบิวที่คุณต้องการformat
name
R.attr.my_attribute
format
คุณสามารถตั้งค่ารูปแบบการหลายประเภทโดยใช้เช่น|
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 attr ซ้ำเช่น android: แรงโน้มถ่วงคุณสามารถทำได้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();
}
ตอนจบ. :)
View
: github.com/commonsguy/cw-advandroid/tree/master/Views/
xmlns:my="http://schemas.android.com/apk/lib/my.namespace"
- ไม่มีการคัดลอก attrs.xml โปรดทราบว่าเส้นทาง URI ของเนมสเปซต้องเป็น / apk / * lib * not / apk / res
apk/lib
เคล็ดลับใช้ไม่ได้กับแอตทริบิวต์ที่กำหนดเองพร้อมรูปแบบการอ้างอิงจากโครงการห้องสมุด สิ่งที่ได้ผลคือการใช้งานapk/res-auto
ตามที่แนะนำในstackoverflow.com/a/13420366/22904ด้านล่างและในstackoverflow.com/a/10217752
enum
และflag
: อดีตให้เราเลือกหนึ่งและหนึ่งค่าเดียวหลังให้เรารวมหลาย ฉันเขียนคำตอบที่ยาวขึ้นในคำถามที่คล้ายกันที่นี่และหลังจากพบคำถามนี้ฉันคิดว่าฉันจะเชื่อมโยงกับที่
a.recycle()
เป็นสิ่งสำคัญมากที่นี่เพื่อเพิ่มหน่วยความจำ
คำตอบของ Qberticus นั้นดี แต่ไม่มีรายละเอียดที่เป็นประโยชน์อย่างหนึ่ง หากคุณกำลังใช้สิ่งเหล่านี้ในไลบรารีแทนที่:
xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"
ด้วย:
xmlns:whatever="http://schemas.android.com/apk/res-auto"
มิฉะนั้นแอปพลิเคชันที่ใช้ไลบรารีจะมีข้อผิดพลาดรันไทม์
คำตอบข้างต้นครอบคลุมทุกอย่างในรายละเอียดที่ดีนอกเหนือจากสองสิ่ง
ก่อนอื่นถ้าไม่มีสไตล์ก็(Context context, AttributeSet attrs)
จะใช้ลายเซ็นเมธอดเพื่อตั้งค่าอินสแตนซ์ ในกรณีนี้ใช้context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)
เพื่อรับ TypedArray
ประการที่สองมันไม่ครอบคลุมวิธีการจัดการกับทรัพยากร plaurals (สตริงปริมาณ) สิ่งเหล่านี้ไม่สามารถจัดการได้ด้วยการใช้ TypedArray นี่คือข้อมูลโค้ดจาก SeekBarPreference ของฉันที่ตั้งค่าสรุปของการจัดรูปแบบการกำหนดค่าตามค่าของการกำหนดลักษณะ หาก xml สำหรับการกำหนดค่าตามความชอบตั้งค่า android: สรุปเป็นสตริงข้อความหรือสตริงดำเนินการต่อค่าของการกำหนดค่าตามความชอบจะถูกจัดรูปแบบเป็นสตริง (ควรมี% d อยู่เพื่อรับค่า) หาก android: summary ถูกตั้งค่าเป็นรีซอร์ส plaurals แสดงว่าใช้เพื่อจัดรูปแบบผลลัพธ์
// 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
วิธีการของการตั้งค่าวิธีการดั้งเดิมนั้นเต็มไปด้วยรหัสสำเร็จรูปและการจัดการทรัพยากรที่เงอะงะ นั่นเป็นเหตุผลที่ฉันทำกรอบ 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>
เฟรมเวิร์กไม่ได้ จำกัด อยู่ที่รีซอร์สสตริงมีหมายเหตุประกอบต่างกันมากมายสำหรับการจัดการประเภททรัพยากรอื่น ๆ นอกจากนี้ยังมีหมายเหตุประกอบเพื่อกำหนดค่าเริ่มต้นและส่งต่อค่าตัวยึดตำแหน่งหากวิธีการของคุณมีหลายพารามิเตอร์
ดู repo Github สำหรับข้อมูลและตัวอย่างเพิ่มเติม
android:title="@{"Hello, world!"}"
ในกรณีนี้คุณจะต้องเขียนการพูด
หากคุณไม่ใช้แอformat
ททริบิวต์จากattr
องค์ประกอบคุณสามารถใช้มันเพื่ออ้างอิงคลาสจากโครงร่าง XML
Refactor > Rename
โรงงานFind Usages
โรงงานอย่าระบุformat
แอตทริบิวต์ใน... / src / main / res / values / attrs.xml
<?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()
}
}
}