ฉันต้องการตัวสร้างทั้งสามสำหรับมุมมองที่กำหนดเองของ Android หรือไม่


143

เมื่อสร้างมุมมองที่กำหนดเองฉันสังเกตว่าหลายคนดูเหมือนจะทำสิ่งนี้:

public MyView(Context context) {
  super(context);
  // this constructor used when programmatically creating view
  doAdditionalConstructorWork();
}

public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  // this constructor used when creating view through XML
  doAdditionalConstructorWork();
}

private void doAdditionalConstructorWork() {

  // init variables etc.
}

คำถามแรกของฉันคืออะไรเกี่ยวกับตัวสร้างMyView(Context context, AttributeSet attrs, int defStyle)? ฉันไม่แน่ใจว่ามันถูกใช้ที่ไหน แต่ฉันเห็นมันในระดับซุปเปอร์ ฉันต้องการมันและใช้ที่ไหน?

มีอีกส่วนหนึ่งกับคำถามนี้

คำตอบ:


145

หากคุณจะเพิ่มแบบกำหนดเองของคุณViewจากxmlเช่น:

 <com.mypack.MyView
      ...
      />

คุณจะต้องสร้างpublic MyView(Context context, AttributeSet attrs)มิฉะนั้นคุณจะได้รับExceptionเมื่อ Android Viewพยายามที่จะขยายตัวของคุณ

หากคุณเพิ่มViewจากxmlและยังระบุandroid:styleแอตทริบิวต์เช่น:

 <com.mypack.MyView
      style="@styles/MyCustomStyle"
      ...
      />

Constructor ที่ 2 จะถูกเรียกใช้และเริ่มต้นสไตล์MyCustomStyleก่อนใช้แอตทริบิวต์ XML ที่ชัดเจน

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


3
เมื่อใดที่จะใช้ตัวสร้างแรกแล้ว
Android Killer

@OvidiuLatcu คุณช่วยกรุณาแสดงตัวอย่างของ CTOR ที่สาม (พร้อมพารามิเตอร์ 3 ตัว) ได้ไหม?
นักพัฒนา android

ฉันสามารถเพิ่มพารามิเตอร์พิเศษให้กับคอนสตรัคเตอร์และฉันจะใช้ได้อย่างไร
Mohammed Subhi Sheikh Quroush

24
เกี่ยวกับคอนสตรัคเตอร์ที่สามนี่เป็นความผิดที่แท้จริง XML เสมอเรียก constructor สองอาร์กิวเมนต์ Constructor สามอาร์กิวเมนต์ (และสี่อาร์กิวเมนต์ ) ถูกเรียกโดยsubclassesหากต้องการระบุแอตทริบิวต์ที่มีสไตล์เริ่มต้นหรือสไตล์เริ่มต้นโดยตรง (ในกรณีของ constructor สี่อาร์กิวเมนต์)
imgx64

ฉันเพิ่งส่งการแก้ไขเพื่อให้คำตอบถูกต้อง ฉันได้เสนอคำตอบอื่นไว้ด้านล่างด้วย
mbonnin

118

หากคุณแทนที่ Constructor ทั้งสามโปรดอย่า CASCADE this(...)CALLS คุณควรทำสิ่งนี้แทน:

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

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

public MyView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context, attrs, defStyle);
}

private void init(Context context, AttributeSet attrs, int defStyle) {
    // do additional work
}

เหตุผลก็คือคลาสพาเรนต์อาจมีแอททริบิวต์เริ่มต้นในคอนสตรัคเตอร์ของตัวเองซึ่งคุณอาจทับโดยบังเอิญ ตัวอย่างเช่นนี่คือตัวสร้างสำหรับTextView:

public TextView(Context context) {
    this(context, null);
}

public TextView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, com.android.internal.R.attr.textViewStyle);
}

public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

หากคุณไม่ได้โทรsuper(context)คุณจะไม่ได้ตั้งค่าอย่างถูกต้องR.attr.textViewStyleเป็นลักษณะ attr


12
นี่คือคำแนะนำที่สำคัญเมื่อขยาย ListView ในฐานะที่เป็นแฟน (ก่อนหน้า) ของการเรียงซ้อนกันข้างต้นฉันจำได้ว่าใช้เวลาหลายชั่วโมงในการติดตามข้อผิดพลาดที่ละเอียดอ่อนซึ่งหายไปเมื่อฉันเรียกวิธีการแก้ไขที่ถูกต้องสำหรับคอนสตรัคเตอร์แต่ละตัว
Groovee60

BTW @Jin ฉันใช้รหัสในคำตอบนี้: stackoverflow.com/a/22780035/294884ซึ่งดูเหมือนจะเป็นไปตามคำตอบของคุณ - แต่โปรดทราบว่าผู้เขียนรวมถึงการใช้ Inflator?
Fattie

1
ฉันคิดว่ามันไม่จำเป็นที่จะต้องเรียกใช้ init ใน Constructor ทั้งหมดเพราะเมื่อคุณทำตามลำดับชั้นการโทรคุณจะได้รับ Constructor เริ่มต้นสำหรับการสร้างมุมมองแบบเป็นโปรแกรมต่อไปดู (บริบทบริบท) {}
Marian Klühspies

ฉันทำแบบเดียวกัน แต่ล้มเหลวในการตั้งค่าใน textview ซึ่งมีอยู่ในมุมมองที่กำหนดเองของฉันฉันต้องการตั้งค่าจากกิจกรรม
Erum

1
ฉันไม่เคยรู้เรื่องนี้ได้อย่างไร
Suragch

49

MyView (บริบทบริบท)

ใช้เมื่อยกเลิกการกำหนดมุมมองโดยทางโปรแกรม

MyView (บริบทบริบท AttributeSet attrs)

ใช้โดยLayoutInflaterเพื่อใช้คุณลักษณะ xml หากมีการตั้งชื่อหนึ่งในแอตทริบิวต์นี้styleแอตทริบิวต์จะถูกค้นหาสไตล์ก่อนที่จะค้นหาค่าที่ชัดเจนในไฟล์ layout xml

MyView (บริบทบริบท, AttributeSet attrs, int defStyleAttr)

สมมติว่าคุณต้องการใช้สไตล์เริ่มต้นกับวิดเจ็ตทั้งหมดโดยไม่ต้องระบุstyleในแต่ละไฟล์เลย์เอาต์ สำหรับตัวอย่างทำให้กล่องกาเครื่องหมายทั้งหมดเป็นสีชมพูโดยค่าเริ่มต้น คุณสามารถทำได้ด้วย defStyleAttr และกรอบงานจะค้นหาสไตล์เริ่มต้นในธีมของคุณ

โปรดทราบว่าdefStyleAttrเมื่อไม่defStyleนานมานี้มีการตั้งชื่ออย่างไม่ถูกต้องและมีการสนทนาบางอย่างเกี่ยวกับตัวสร้างนี้ว่าจำเป็นหรือไม่ ดูhttps://code.google.com/p/android/issues/detail?id=12683

MyView (บริบทบริบท, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Constructor ที่ 3 ใช้งานได้ดีหากคุณมีการควบคุมธีมพื้นฐานของแอปพลิเคชัน ใช้งานได้กับ google เพราะพวกเขาจัดส่งวิดเจ็ตของพวกเขาไปพร้อมกับธีมเริ่มต้น แต่สมมติว่าคุณกำลังเขียนไลบรารีวิดเจ็ตและคุณต้องการตั้งค่าสไตล์เริ่มต้นโดยที่ผู้ใช้ไม่จำเป็นต้องปรับแต่งธีมของพวกเขา ตอนนี้คุณสามารถทำได้defStyleResโดยการตั้งค่าเป็นค่าเริ่มต้นใน 2 constructors แรก:

public MyView(Context context) {
  super(context, null, 0, R.style.MyViewStyle);
  init();
}

public MyView(Context context, AttributeSet attrs) {
  super(context, attrs, 0, R.style.MyViewStyle);
  init();
}

ทั้งหมดในทุก

หากคุณกำลังใช้มุมมองของคุณเองควรมีเพียง 2 Constructor ก่อนเท่านั้นและสามารถเรียกใช้โดย Framework

หากคุณต้องการให้มุมมองของคุณขยายได้คุณอาจใช้ตัวสร้างที่ 4 สำหรับเด็กในชั้นเรียนของคุณเพื่อให้สามารถใช้สไตล์การออกแบบระดับโลกได้

ฉันไม่เห็นกรณีการใช้งานจริงสำหรับตัวสร้างที่ 3 อาจเป็นทางลัดหากคุณไม่มีสไตล์เริ่มต้นสำหรับวิดเจ็ตของคุณ แต่ยังต้องการให้ผู้ใช้ของคุณสามารถทำได้ ไม่ควรเกิดขึ้นมาก


7

Kotlin ดูเหมือนจะกำจัดความเจ็บปวดมากมาย:

class MyView
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
    : View(context, attrs, defStyle)

@JvmOverloads จะสร้างตัวสร้างที่ต้องการทั้งหมด (ดูเอกสารประกอบของคำอธิบายประกอบนั้น) ซึ่งแต่ละอันน่าจะเรียก super () จากนั้นเพียงแทนที่วิธีการเริ่มต้นของคุณด้วย Kotlin init {} block รหัสหม้อไอน้ำหายไป!


1

คอนสตรัคเตอร์ที่สามซับซ้อนกว่านี้มากขอให้ฉันยกตัวอย่าง

Support-v7 SwitchCompactแพ็คเกจรองรับthumbTintและtrackTintคุณสมบัติตั้งแต่ 24 เวอร์ชั่นในขณะที่ 23 รุ่นไม่รองรับพวกเขาตอนนี้คุณต้องการที่จะสนับสนุนพวกเขาใน 23 เวอร์ชั่นและคุณจะทำอย่างไรเพื่อให้บรรลุเป้าหมายนี้?

เราคิดที่จะใช้กำหนดเองดู ขยายSupportedSwitchCompactSwitchCompact

public SupportedSwitchCompat(Context context) {
    this(context, null);
}

public SupportedSwitchCompat(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

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

private void init(){
    mThumbDrawable = getThumbDrawable();
    mTrackDrawable = getTrackDrawable();
    applyTint();
}

มันเป็นรูปแบบรหัสแบบดั้งเดิม หมายเหตุเราผ่าน 0 ถึงพระรามสามที่นี่ เมื่อคุณเรียกใช้รหัสคุณจะพบว่าgetThumbDrawable()ค่า Null นั้นเป็นวิธีที่แปลกเพราะวิธีgetThumbDrawable()นี้เป็นSwitchCompactวิธีที่ดีมาก

หากคุณผ่านR.attr.switchStyleไปยัง Param ที่สามทุกอย่างเป็นไปด้วยดีทำไม

พารามิเตอร์ที่สามเป็นคุณสมบัติที่ง่าย แอ็ตทริบิวต์ชี้ไปที่รีซอร์สสไตล์ในกรณีข้างต้นระบบจะค้นหาswitchStyleแอ็ตทริบิวต์ในธีมปัจจุบันโดยโชคดีที่ระบบค้นหาพบ

ในframeworks/base/core/res/res/values/themes.xmlคุณจะเห็น:

<style name="Theme">
    <item name="switchStyle">@style/Widget.CompoundButton.Switch</item>
</style>

-2

หากคุณต้องรวมสามคอนสตรัคเตอร์เช่นเดียวกับที่อยู่ในการสนทนาตอนนี้คุณสามารถทำได้เช่นกัน

public MyView(Context context) {
  this(context,null,0);
}

public MyView(Context context, AttributeSet attrs) {
  this(context,attrs,0);
}

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

}

2
@ จินมันเป็นความคิดที่ดีในหลาย ๆ กรณี แต่มันก็ปลอดภัยในหลาย ๆ กรณี (เช่น: RelativeLayout, FrameLayout, RecyclerView ฯลฯ ) ดังนั้นฉันจะบอกว่านี่อาจเป็นความต้องการเป็นกรณี ๆ ไปและควรตรวจสอบคลาสพื้นฐานก่อนตัดสินใจน้ำตกหรือไม่ โดยพื้นฐานแล้วถ้าคอนสตรัคเตอร์ 2 พารามิเตอร์ในคลาสพื้นฐานเรียกสิ่งนี้ (บริบท, attrs, 0) แสดงว่าปลอดภัยในมุมมองที่กำหนดเองเช่นกัน
ejw

@ IanWong แน่นอนว่ามันจะถูกเรียกเพราะวิธีแรกและวิธีที่สองกำลังเรียกที่สาม
CoolMind
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.