Tôi có cần cả ba nhà xây dựng cho chế độ xem tùy chỉnh Android không?


142

Khi tạo chế độ xem tùy chỉnh, tôi nhận thấy rằng nhiều người dường như làm điều đó như thế này:

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

Câu hỏi đầu tiên của tôi là, những gì về các nhà xây dựng MyView(Context context, AttributeSet attrs, int defStyle)? Tôi không chắc nó được sử dụng ở đâu, nhưng tôi thấy nó trong siêu hạng. Tôi có cần nó không, và nó được sử dụng ở đâu?

một phần khác cho câu hỏi này .

Câu trả lời:


144

Nếu bạn sẽ thêm tùy chỉnh của bạn Viewtừ xmlcũng như:

 <com.mypack.MyView
      ...
      />

bạn sẽ cần nhà xây dựng public MyView(Context context, AttributeSet attrs), nếu không bạn sẽ nhận được Exceptionkhi Android cố gắng tăng cường View.

Nếu bạn thêm Viewtừ xmlvà cũng chỉ định android:stylethuộc tính như:

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

hàm tạo thứ 2 cũng sẽ được gọi và mặc định kiểu MyCustomStyletrước khi áp dụng các thuộc tính XML rõ ràng.

Hàm tạo thứ ba thường được sử dụng khi bạn muốn tất cả các Chế độ xem trong ứng dụng của mình có cùng kiểu.


3
Khi nào sử dụng constructor đầu tiên thì sao?
Kẻ giết người Android

@OvidiuLatcu bạn có thể vui lòng hiển thị một ví dụ về CTOR thứ ba (với 3 tham số) không?
nhà phát triển Android

Tôi có thể thêm các tham số bổ sung cho hàm tạo không và làm cách nào để sử dụng chúng?
Mohammed Subhi Sheikh Quroush

24
Về nhà xây dựng thứ ba, điều này thực sự sai hoàn toàn . XML luôn gọi hàm tạo hai đối số. Các hàm tạo ba đối số (và bốn đối số ) được gọi bởi các lớp con nếu chúng muốn chỉ định một thuộc tính chứa kiểu mặc định hoặc kiểu mặc định trực tiếp (trong trường hợp hàm tạo bốn đối số)
imgx64

Tôi chỉ cần gửi một chỉnh sửa để làm cho câu trả lời chính xác. Tôi cũng đã đề xuất một câu trả lời thay thế dưới đây.
mbonnin

117

Nếu bạn ghi đè cả ba nhà xây dựng, vui lòng KHÔNG GỬI CASCADE this(...). Thay vào đó bạn nên làm điều này:

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
}

Lý do là lớp cha có thể bao gồm các thuộc tính mặc định trong các hàm tạo của chính nó mà bạn có thể vô tình ghi đè. Ví dụ: đây là hàm tạo cho 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);
}

Nếu bạn không gọi super(context), bạn sẽ không đặt đúng R.attr.textViewStylekiểu attr.


12
Đây là lời khuyên cần thiết khi mở rộng ListView. Là một fan hâm mộ (trước đây) của tầng trên, tôi nhớ rằng đã dành hàng giờ để theo dõi một lỗi tinh vi đã biến mất khi tôi gọi phương thức siêu chính xác cho mỗi nhà xây dựng.
Groovee60

BTW @Jin Tôi đã sử dụng mã trong câu trả lời này: stackoverflow.com/a/22780035/294884 dường như dựa trên câu trả lời của bạn - nhưng lưu ý rằng người viết có sử dụng Inflator không?
Fattie

1
Tôi nghĩ rằng nó không cần thiết để gọi init trong tất cả các hàm tạo, bởi vì khi bạn thực hiện theo cấu trúc phân cấp cuộc gọi, bạn sẽ kết thúc trong hàm tạo mặc định để tạo chế độ xem theo chương trình Dù sao (Bối cảnh ngữ cảnh) {}
Marian Klühspies

Tôi đang làm tương tự nhưng không thể đặt giá trị trong chế độ xem văn bản có sẵn trong chế độ xem tùy chỉnh của tôi, tôi muốn đặt giá trị từ hoạt động
Erum

1
Làm thế nào tôi không bao giờ biết điều này?
Suragch

49

MyView (bối cảnh bối cảnh)

Được sử dụng khi kích hoạt Lượt xem theo chương trình.

MyView (Bối cảnh bối cảnh, Attrs Set Attrs)

Được sử dụng bởi các LayoutInflaterthuộc tính xml. Nếu một trong các thuộc tính này được đặt tên style, các thuộc tính sẽ được tra cứu kiểu trước khi tìm các giá trị rõ ràng trong tệp xml bố cục.

MyView (Bối cảnh bối cảnh, Attribution Set attrs, int defStyleAttr)

Giả sử bạn muốn áp dụng một kiểu mặc định cho tất cả các widget mà không phải chỉ định styletrong mỗi tệp bố cục. Đối với một ví dụ làm cho tất cả các hộp kiểm màu hồng theo mặc định. Bạn có thể làm điều này với defStyleAttr và khung sẽ tìm kiếm kiểu mặc định trong chủ đề của bạn.

Lưu ý rằng defStyleAttrđã được đặt tên không chính xác defStylemột thời gian trước đây và có một số cuộc thảo luận về việc liệu nhà xây dựng này có thực sự cần thiết hay không. Xem https://code.google.com.vn/p/android/issues/detail?id=12683

MyView (Ngữ cảnh bối cảnh, Attribution Set attrs, int defStyleAttr, int defStyleRes)

Hàm tạo thứ 3 hoạt động tốt nếu bạn có quyền kiểm soát chủ đề cơ bản của các ứng dụng. Điều đó đang làm việc cho google vì họ vận chuyển các vật dụng của họ dọc theo Chủ đề mặc định. Nhưng giả sử bạn đang viết thư viện widget và bạn muốn đặt kiểu mặc định mà không cần người dùng cần chỉnh chủ đề của họ. Bây giờ bạn có thể làm điều này bằng defStyleRescách đặt nó thành giá trị mặc định trong 2 hàm tạo đầu tiên:

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

Tất cả trong tất cả

Nếu bạn đang thực hiện các quan điểm của riêng mình, chỉ cần có 2 hàm tạo đầu tiên và có thể được gọi bởi khung.

Nếu bạn muốn Chế độ xem của mình có thể mở rộng, bạn có thể triển khai hàm tạo thứ 4 cho trẻ em trong lớp để có thể sử dụng kiểu dáng toàn cầu.

Tôi không thấy trường hợp sử dụng thực sự cho nhà xây dựng thứ 3. Có thể là một phím tắt nếu bạn không cung cấp kiểu mặc định cho widget của mình nhưng vẫn muốn người dùng của bạn có thể làm như vậy. Không nên xảy ra nhiều như vậy.


7

Kotlin dường như lấy đi rất nhiều nỗi đau này:

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

@JvmOverloads sẽ tạo ra tất cả các hàm tạo cần thiết (xem tài liệu của chú thích đó ), mỗi tài liệu có thể gọi là super (). Sau đó, chỉ cần thay thế phương thức khởi tạo của bạn bằng khối Kotlin init {}. Mã nồi hơi đã biến mất!


1

Hàm tạo thứ ba phức tạp hơn nhiều. Hãy giữ một ví dụ.

SwitchCompactGói hỗ trợ -v7 hỗ trợ thumbTinttrackTintthuộc tính kể từ phiên bản 24 trong khi phiên bản 23 không hỗ trợ chúng. Bạn muốn hỗ trợ chúng trong phiên bản 23 và bạn sẽ làm thế nào để đạt được điều này?

Chúng tôi giả sử sử dụng phần SupportedSwitchCompactmở rộng Xem tùy chỉnh SwitchCompact.

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

Đó là một kiểu mã truyền thống. Lưu ý chúng tôi vượt qua 0 đến param thứ ba ở đây . Khi bạn chạy mã, bạn sẽ thấy getThumbDrawable()luôn trả về null nó lạ như thế nào vì phương thức getThumbDrawable()này là phương thức siêu hạng SwitchCompactcủa nó.

Nếu bạn vượt qua thông R.attr.switchStylesố thứ ba, mọi thứ đều ổn. Vậy tại sao?

Param thứ ba là một thuộc tính đơn giản. Thuộc tính trỏ đến một tài nguyên kiểu. Trong trường hợp trên, hệ thống sẽ tìm thấy switchStylethuộc tính trong chủ đề hiện tại may mắn là hệ thống tìm thấy nó.

Trong frameworks/base/core/res/res/values/themes.xml, bạn sẽ thấy:

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

-2

Nếu bạn phải bao gồm ba nhà xây dựng như người đang thảo luận bây giờ, bạn cũng có thể làm điều này.

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
@Jin Đó là một ý tưởng tốt trong nhiều trường hợp, nhưng điều này cũng an toàn trong nhiều trường hợp (ví dụ: RelativeLayout, FrameLayout, RecyclerView, v.v.). Vì vậy, tôi sẽ nói rằng đây có lẽ là một yêu cầu tùy theo từng trường hợp và lớp cơ sở nên được kiểm tra trước khi đưa ra quyết định xếp tầng hay không. Về cơ bản, nếu hàm tạo 2-param trong lớp cơ sở chỉ gọi đây (bối cảnh, attrs, 0), thì nó cũng an toàn để làm như vậy trong lớp xem tùy chỉnh.
ejw

@IanWong, tất nhiên, nó sẽ được gọi, bởi vì phương thức thứ nhất và thứ hai đang gọi thứ ba.
CoolMind
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.