Membuat View Gabungan di Android
() translation by (you can also view the original English article)
Ketika membuat aplikasi yang kompleks, Anda akan sering ingin menggunakan kembali kelompok view yang sama di berbagai tempat dari aplikasi. Salah satu cara untuk memecahkan masalah ini adalah dengan membuat sebuah view yang merangkum logika dan tata letak sekelompok view sehingga Anda dapat menggunakannya kembali tanpa menduplikasi kode di berbagai tempat dari proyek. Dalam tutorial ini, Anda akan belajar cara menggunakan view gabungan untuk membuat view kustom yang mudah digunakan kembali.
1. Perkenalan
Di Android, view yang terdiri dari sekelompok view disebut view gabungan atau komponen gabungan. Dalam tutorial ini, Anda akan membangun control untuk memilih nilai dari daftar yang bergulir dari sisi ke sisi. Kami akan menamakan gabungan tersebut sebagai side spinner karena tampilan default SDK Android untuk memilih nilai dari daftar disebut sebagai spinner. Tangkapan layar berikut mengilustrasikan apa yang akan kita buat dalam tutorial ini.



2. Pengaturan Proyek
Untuk memulai, Anda harus membuat proyek Android baru dengan Android 4.0 sebagai tingkat SDK minimum yang diperlukan. Proyek ini hanya boleh berisi activity kosong yang disebut MainActivity. Activity
tidak melakukan apa-apa selain menginisialisasi layout seperti yang Anda lihat di cuplikan kode berikut.
1 |
public class MainActivity extends Activity { |
2 |
@Override
|
3 |
protected void onCreate(Bundle savedInstanceState) { |
4 |
super.onCreate(savedInstanceState); |
5 |
setContentView(R.layout.activity_main); |
6 |
}
|
7 |
}
|
Layout untuk MainActivity
terletak di file /res/layout/activity_main.xml dan seharusnya hanya berisi RelativeLayout
kosong dimana view gabungan akan ditampilkan nantinya.
1 |
<RelativeLayout
|
2 |
xmlns:android="https://schemas.android.com/apk/res/android" |
3 |
xmlns:tools="http://schemas.android.com/tools" |
4 |
android:layout_width="match_parent" |
5 |
android:layout_height="match_parent" |
6 |
tools:context=".MainActivity"> |
7 |
</RelativeLayout>
|
3. Membuat sebuah View Gabungan
Untuk membuat view gabungan, Anda harus membuat kelas baru yang mengelola view dalam view gabungan. Untuk side spinner, Anda memerlukan dua view Button
untuk panah dan view TextView
untuk menampilkan nilai yang dipilih.
Untuk memulai, buat file layout /res/layout/sidespinner_view.xml yang akan kita gunakan untuk kelas side spinner, pastikan untuk membungkus tiga view dalam tag <merge>
.
1 |
<merge xmlns:android="http://schemas.android.com/apk/res/android"> |
2 |
<Button
|
3 |
android:id="@+id/sidespinner_view_previous" |
4 |
android:layout_width="wrap_content" |
5 |
android:layout_height="wrap_content" |
6 |
android:layout_toLeftOf="@+id/sidespinner_view_value"/> |
7 |
<TextView
|
8 |
android:id="@+id/sidespinner_view_current_value" |
9 |
android:layout_width="wrap_content" |
10 |
android:layout_height="wrap_content" |
11 |
android:textSize="24sp" /> |
12 |
<Button
|
13 |
android:id="@+id/sidespinner_view_next" |
14 |
android:layout_width="wrap_content" |
15 |
android:layout_height="wrap_content" /> |
16 |
</merge>
|
Selanjutnya, kita perlu membuat kelas SideSpinner
yang mengembangkan layout ini dan menetapkan panah sebagai gambar latar belakang untuk tombol. Pada titik ini, view gabungan tidak melakukan apa-apa karena belum ada yang ditampilkan.
1 |
public class SideSpinner extends LinearLayout { |
2 |
|
3 |
private Button mPreviousButton; |
4 |
private Button mNextButton; |
5 |
|
6 |
public SideSpinner(Context context) { |
7 |
super(context); |
8 |
initializeViews(context); |
9 |
}
|
10 |
|
11 |
public SideSpinner(Context context, AttributeSet attrs) { |
12 |
super(context, attrs); |
13 |
initializeViews(context); |
14 |
}
|
15 |
|
16 |
public SideSpinner(Context context, |
17 |
AttributeSet attrs, |
18 |
int defStyle) { |
19 |
super(context, attrs, defStyle); |
20 |
initializeViews(context); |
21 |
}
|
22 |
|
23 |
/**
|
24 |
* Inflates the views in the layout.
|
25 |
*
|
26 |
* @param context
|
27 |
* the current context for the view.
|
28 |
*/
|
29 |
private void initializeViews(Context context) { |
30 |
LayoutInflater inflater = (LayoutInflater) context |
31 |
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
32 |
inflater.inflate(R.layout.sidespinner_view, this); |
33 |
}
|
34 |
|
35 |
@Override
|
36 |
protected void onFinishInflate() { |
37 |
super.onFinishInflate(); |
38 |
|
39 |
// Sets the images for the previous and next buttons. Uses
|
40 |
// built-in images so you don't need to add images, but in
|
41 |
// a real application your images should be in the
|
42 |
// application package so they are always available.
|
43 |
mPreviousButton = (Button) this |
44 |
.findViewById(R.id.sidespinner_view_previous); |
45 |
mPreviousButton
|
46 |
.setBackgroundResource(android.R.drawable.ic_media_previous); |
47 |
|
48 |
mNextButton = (Button)this |
49 |
.findViewById(R.id.sidespinner_view_next); |
50 |
mNextButton
|
51 |
.setBackgroundResource(android.R.drawable.ic_media_next); |
52 |
}
|
53 |
}
|
Anda akan melihat bahwa view gabungan memperluas grup view LinearLayout
. Ini berarti bahwa layout apa pun yang menggunakan view gabungan memiliki akses ke atribut layout linier. Akibatnya, layout view gabungan sedikit berbeda dari biasanya, tag root adalah tag <merge>
sebagai ganti tag untuk grup view seperti <LinearLayout>
atau <RelativeLayout>
.
Saat Anda menambahkan view gabungan ke layout MainActivity
, tag untuk view gabungan akan bertindak sebagai tag LinearLayout
. Kelas view gabungan dapat berasal dari kelas mana pun yang berasal dari ViewGroup
, tetapi dalam hal ini layout linier adalah yang paling tepat karena view ditata secara horizontal.
4. Menambahkan View Gabungan ke Layout
Pada titik ini, proyek dikompilasi tetapi tidak ada yang terlihat karena view gabungan tidak dalam layout MainActivity
. View side spinner harus ditambahkan ke layout activity seperti view lainnya. Nama tag adalah nama lengkap dari kelas SideSpinner
, termasuk namespace.
Untuk menambahkan side spinner ke MainActivity
, tambahkan yang berikut ini ke layout relatif di file /res/layout/activity_main.xml.
1 |
<com.cindypotvin.sidespinnerexample.SideSpinner
|
2 |
android:id="@+id/sidespinner_fruits" |
3 |
android:layout_width="match_parent" |
4 |
android:layout_height="wrap_content" |
5 |
android:orientation="horizontal" |
6 |
android:gravity="center"/> |
Atribut yang tersedia dalam tag <SideSpinner>
adalah atribut dari layout linier sejak kelas SideSpinner
yang kita buat memperluas kelas LinearLayout
. Jika Anda meluncurkan proyeknya, side spinner harus terlihat, tetapi tidak mengandung nilai apa pun.
5. Menambahkan Metode ke View Gabungan
Masih ada beberapa hal yang hilang jika kita ingin benar-benar menggunakan side spinner. Kita harus dapat menambahkan nilai baru ke spinner, memilih nilai, dan mendapatkan nilai yang dipilih.
Cara termudah untuk menambahkan perilaku baru ke view gabungan adalah menambahkan metode public baru ke kelas SideSpinner
. Metode ini dapat digunakan oleh Activity
apa pun yang memiliki referensi ke view.
1 |
private CharSequence[] mSpinnerValues = null; |
2 |
private int mSelectedIndex = -1; |
3 |
|
4 |
/**
|
5 |
* Sets the list of value in the spinner, selecting the first value
|
6 |
* by default.
|
7 |
*
|
8 |
* @param values
|
9 |
* the values to set in the spinner.
|
10 |
*/
|
11 |
public void setValues(CharSequence[] values) { |
12 |
mSpinnerValues = values; |
13 |
|
14 |
// Select the first item of the string array by default since
|
15 |
// the list of value has changed.
|
16 |
setSelectedIndex(0); |
17 |
}
|
18 |
|
19 |
/**
|
20 |
* Sets the selected index of the spinner.
|
21 |
*
|
22 |
* @param index
|
23 |
* the index of the value to select.
|
24 |
*/
|
25 |
public void setSelectedIndex(int index) { |
26 |
// If no values are set for the spinner, do nothing.
|
27 |
if (mSpinnerValues == null || mSpinnerValues.length == 0) |
28 |
return; |
29 |
|
30 |
// If the index value is invalid, do nothing.
|
31 |
if (index < 0 || index >= mSpinnerValues.length) |
32 |
return; |
33 |
|
34 |
// Set the current index and display the value.
|
35 |
mSelectedIndex = index; |
36 |
TextView currentValue; |
37 |
currentValue = (TextView)this |
38 |
.findViewById(R.id.sidespinner_view_current_value); |
39 |
currentValue.setText(mSpinnerValues[index]); |
40 |
|
41 |
// If the first value is shown, hide the previous button.
|
42 |
if (mSelectedIndex == 0) |
43 |
mPreviousButton.setVisibility(INVISIBLE); |
44 |
else
|
45 |
mPreviousButton.setVisibility(VISIBLE); |
46 |
|
47 |
// If the last value is shown, hide the next button.
|
48 |
if (mSelectedIndex == mSpinnerValues.length - 1) |
49 |
mNextButton.setVisibility(INVISIBLE); |
50 |
else
|
51 |
mNextButton.setVisibility(VISIBLE); |
52 |
}
|
53 |
|
54 |
/**
|
55 |
* Gets the selected value of the spinner, or null if no valid
|
56 |
* selected index is set yet.
|
57 |
*
|
58 |
* @return the selected value of the spinner.
|
59 |
*/
|
60 |
public CharSequence getSelectedValue() { |
61 |
// If no values are set for the spinner, return an empty string.
|
62 |
if (mSpinnerValues == null || mSpinnerValues.length == 0) |
63 |
return ""; |
64 |
|
65 |
// If the current index is invalid, return an empty string.
|
66 |
if (mSelectedIndex < 0 || mSelectedIndex >= mSpinnerValues.length) |
67 |
return ""; |
68 |
|
69 |
return mSpinnerValues[mSelectedIndex]; |
70 |
}
|
71 |
|
72 |
/**
|
73 |
* Gets the selected index of the spinner.
|
74 |
*
|
75 |
* @return the selected index of the spinner.
|
76 |
*/
|
77 |
public int getSelectedIndex() { |
78 |
return mSelectedIndex; |
79 |
}
|
Metode onFinishInflate
dari view gabungan dipanggil ketika semua view dalam layout meningkat dan siap digunakan. Ini adalah tempat untuk menambahkan kode Anda jika Anda perlu mengubah view dalam view gabungan.
Dengan metode yang baru saja Anda tambahkan ke kelas SideSpinner
, perilaku untuk tombol yang memilih nilai sebelumnya dan selanjutnya sekarang dapat ditambahkan. Ganti kode yang ada dalam metode onFinishInflate
dengan yang berikut:
1 |
@Override
|
2 |
protected void onFinishInflate() { |
3 |
|
4 |
// When the controls in the layout are doing being inflated, set
|
5 |
// the callbacks for the side arrows.
|
6 |
super.onFinishInflate(); |
7 |
|
8 |
// When the previous button is pressed, select the previous value
|
9 |
// in the list.
|
10 |
mPreviousButton = (Button) this |
11 |
.findViewById(R.id.sidespinner_view_previous); |
12 |
mPreviousButton
|
13 |
.setBackgroundResource(android.R.drawable.ic_media_previous); |
14 |
|
15 |
mPreviousButton.setOnClickListener(new OnClickListener() { |
16 |
public void onClick(View view) { |
17 |
if (mSelectedIndex > 0) { |
18 |
int newSelectedIndex = mSelectedIndex - 1; |
19 |
setSelectedIndex(newSelectedIndex); |
20 |
}
|
21 |
}
|
22 |
});
|
23 |
|
24 |
// When the next button is pressed, select the next item in the
|
25 |
// list.
|
26 |
mNextButton = (Button)this |
27 |
.findViewById(R.id.sidespinner_view_next); |
28 |
mNextButton
|
29 |
.setBackgroundResource(android.R.drawable.ic_media_next); |
30 |
mNextButton.setOnClickListener(new OnClickListener() { |
31 |
public void onClick(View view) { |
32 |
if (mSpinnerValues != null |
33 |
&& mSelectedIndex < mSpinnerValues.length - 1) { |
34 |
int newSelectedIndex = mSelectedIndex + 1; |
35 |
setSelectedIndex(newSelectedIndex); |
36 |
}
|
37 |
}
|
38 |
});
|
39 |
|
40 |
// Select the first value by default.
|
41 |
setSelectedIndex(0); |
42 |
}
|
Dengan metode setValues
dan setSelectedIndex
yang baru dibuat, kita sekarang dapat menginisialisasi side spinner dari kode kita. Seperti halnya view lain, Anda perlu menemukan view side spinner dalam layout dengan metode findViewById
. Kita kemudian dapat memanggil metode public apa pun pada view dari objek yang dikembalikan, termasuk yang baru saja kita buat.
Potongan kode berikut menunjukkan cara memperbarui metode onCreate
dari kelas MainActivity
untuk menampilkan daftar nilai di side spinner, menggunakan metode setValues
. Kita juga dapat memilih nilai kedua dalam daftar secara default dengan menerapkan metode setSelectedIndex
.
1 |
public class MainActivity extends Activity { |
2 |
|
3 |
@Override
|
4 |
protected void onCreate(Bundle savedInstanceState) { |
5 |
super.onCreate(savedInstanceState); |
6 |
setContentView(R.layout.activity_main); |
7 |
|
8 |
// Initializes the side spinner from code.
|
9 |
SideSpinner fruitsSpinner; |
10 |
fruitsSpinner = (SideSpinner)this |
11 |
.findViewById(R.id.sidespinner_fruits); |
12 |
|
13 |
CharSequence fruitList[] = { "Apple", |
14 |
"Orange", |
15 |
"Pear", |
16 |
"Grapes" }; |
17 |
fruitsSpinner.setValues(fruitList); |
18 |
fruitsSpinner.setSelectedIndex(1); |
19 |
}
|
20 |
}
|
Jika Anda meluncurkan aplikasi, side spinner akan berfungsi seperti yang diharapkan. Daftar nilai ditampilkan dan nilai Orange dipilih secara default.
6. Menambahkan Atribut Layout ke View Gabungan
View yang tersedia di SDK Android dapat dimodifikasi melalui kode, tetapi beberapa atribut juga dapat diatur secara langsung di layout yang sesuai. Mari tambahkan sebuah atribut ke side spinner yang menetapkan nilai yang dibutuhkan oleh side spinner untuk ditampilkan.
Untuk membuat atribut khusus untuk view gabungan, pertama-tama kita perlu mendefinisikan atribut di file /res/values/attr.xml. Setiap atribut view gabungan harus dikelompokkan dalam sebuah styleable dengan tag <declare-styleable>
. Untuk side spinner, nama kelas digunakan seperti yang ditunjukkan di bawah ini.
1 |
<resources>
|
2 |
<declare-styleable name="SideSpinner"> |
3 |
<attr name="values" format="reference" /> |
4 |
</declare-styleable>
|
5 |
</resources>
|
Dalam tag <attr>
, atribut name
berisi identifier yang digunakan untuk merujuk ke atribut baru dalam layout dan atribut format
berisi jenis atribut baru.
Untuk daftar nilai, jenis reference
digunakan karena atribut akan merujuk ke daftar string yang didefinisikan sebagai sumber daya. Jenis nilai yang biasanya digunakan dalam layout dapat digunakan untuk atribut khusus Anda, termasuk boolean
, color
, dimension
, enum
, integer
, float
dan string
.
Di sini adalah bagaimana mendefinisikan sumber daya untuk daftar string yang atribut values
dari side spinner akan merujuk. Ini harus ditambahkan ke file /res/values/strings.xml seperti yang ditunjukkan di bawah ini.
1 |
<resources>
|
2 |
<string-array name="vegetable_array"> |
3 |
<item>Cucumber</item> |
4 |
<item>Potato</item> |
5 |
<item>Tomato</item> |
6 |
<item>Onion</item> |
7 |
<item>Squash</item> |
8 |
</string-array>
|
9 |
</resources>
|
Untuk menguji atribut values
yang baru, buat view side spinner dalam layout MainActivity
di bawah side spinner yang ada. Atribut harus diawali dengan namespace yang ditambahkan ke RelativeLayout
, seperti xmlns:sidespinner="http://schemas.android.com/apk/res-auto"
. Ini adalah layout akhir di /res/layout/activity_main.xml.
1 |
<RelativeLayout
|
2 |
xmlns:android="http://schemas.android.com/apk/res/android" |
3 |
xmlns:tools="http://schemas.android.com/tools" |
4 |
xmlns:sidespinner="http://schemas.android.com/apk/res-auto" |
5 |
android:layout_width="match_parent" |
6 |
android:layout_height="match_parent" |
7 |
tools:context=".MainActivity"> |
8 |
<com.cindypotvin.sidespinnerexample.SideSpinner
|
9 |
android:id="@+id/sidespinner_fruits" |
10 |
android:layout_width="match_parent" |
11 |
android:layout_height="wrap_content" |
12 |
android:orientation="horizontal" |
13 |
android:gravity="center"/> |
14 |
|
15 |
<com.cindypotvin.sidespinnerexample.SideSpinner
|
16 |
android:id="@+id/sidespinner_vegetables" |
17 |
android:layout_width="match_parent" |
18 |
android:layout_height="wrap_content" |
19 |
android:orientation="horizontal" |
20 |
android:gravity="center" |
21 |
android:layout_below="@id/sidespinner_fruits" |
22 |
sidespinner:values="@array/vegetable_array" /> |
23 |
</RelativeLayout>
|
Akhirnya, kelas SideSpinner
perlu dimodifikasi untuk membaca atribut values
. Nilai setiap atribut view tersedia di objek AttributeSet
yang dilewatkan sebagai parameter constructor dari view.
Untuk mendapatkan nilai dari atribut values
kustom Anda, pertama kita memanggil metode obtainStyledAttributes
dari objek AttributeSet
dengan nama styleable yang berisi atributnya. Ini mengembalikan daftar atribut untuk styleable itu sebagai objek TypedArray
.
Kita kemudian memanggil metode getter dari objek TypedArray
yang memiliki tipe yang tepat untuk atribut yang Anda inginkan, mengirimkan identifier atribut sebagai sebuah parameter. Blok kode berikut menunjukkan bagaimana memodifikasi constructor dari side spinner untuk mendapatkan daftar nilai dan menetapkannya di side spinner.
1 |
public SideSpinner(Context context) { |
2 |
super(context); |
3 |
|
4 |
initializeViews(context); |
5 |
}
|
6 |
|
7 |
public SideSpinner(Context context, AttributeSet attrs) { |
8 |
super(context, attrs); |
9 |
|
10 |
TypedArray typedArray; |
11 |
typedArray = context |
12 |
.obtainStyledAttributes(attrs, R.styleable.SideSpinner); |
13 |
mSpinnerValues = typedArray |
14 |
.getTextArray(R.styleable.SideSpinner_values); |
15 |
typedArray.recycle(); |
16 |
|
17 |
initializeViews(context); |
18 |
}
|
19 |
|
20 |
public SideSpinner(Context context, |
21 |
AttributeSet attrs, |
22 |
int defStyle) { |
23 |
super(context, attrs, defStyle); |
24 |
|
25 |
TypedArray typedArray; |
26 |
typedArray = context |
27 |
.obtainStyledAttributes(attrs, R.styleable.SideSpinner); |
28 |
mSpinnerValues = typedArray |
29 |
.getTextArray(R.styleable.SideSpinner_values); |
30 |
typedArray.recycle(); |
31 |
|
32 |
initializeViews(context); |
33 |
}
|
Jika Anda meluncurkan aplikasi, Anda akan melihat dua side spinner yang bekerja secara independen dari satu sama lain.
7. Menyimpan dan Memulihkan Keadaan
Langkah terakhir yang perlu kita selesaikan adalah menyimpan dan memulihkan keadaan dari view gabungan. Ketika suatu activity dihancurkan dan dibuat ulang, misalnya, ketika perangkat diputar, nilai-nilai view asli dengan pengenal unik secara otomatis disimpan dan dipulihkan. Ini saat ini tidak berlaku untuk side spinner.
Keadaan view tidak disimpan. Pengidentifikasi view di kelas SideSpinner
tidak unik karena dapat digunakan kembali berkali-kali. Ini berarti bahwa kita bertanggung jawab untuk menyimpan dan memulihkan nilai-nilai view dalam view gabungan. Kita melakukan ini dengan menerapkan metode onSaveInstanceState
, onRestoreInstanceState
, dan dispatchSaveInstanceState
. Blok kode berikut menunjukkan bagaimana melakukan ini untuk side spinner.
1 |
/**
|
2 |
* Identifier for the state to save the selected index of
|
3 |
* the side spinner.
|
4 |
*/
|
5 |
private static String STATE_SELECTED_INDEX = "SelectedIndex"; |
6 |
|
7 |
/**
|
8 |
* Identifier for the state of the super class.
|
9 |
*/
|
10 |
private static String STATE_SUPER_CLASS = "SuperClass"; |
11 |
|
12 |
@Override
|
13 |
protected Parcelable onSaveInstanceState() { |
14 |
Bundle bundle = new Bundle(); |
15 |
|
16 |
bundle.putParcelable(STATE_SUPER_CLASS, |
17 |
super.onSaveInstanceState()); |
18 |
bundle.putInt(STATE_SELECTED_INDEX, mSelectedIndex); |
19 |
|
20 |
return bundle; |
21 |
}
|
22 |
|
23 |
@Override
|
24 |
protected void onRestoreInstanceState(Parcelable state) { |
25 |
if (state instanceof Bundle) { |
26 |
Bundle bundle = (Bundle)state; |
27 |
|
28 |
super.onRestoreInstanceState(bundle |
29 |
.getParcelable(STATE_SUPER_CLASS)); |
30 |
setSelectedIndex(bundle.getInt(STATE_SELECTED_INDEX)); |
31 |
}
|
32 |
else
|
33 |
super.onRestoreInstanceState(state); |
34 |
}
|
35 |
|
36 |
@Override
|
37 |
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { |
38 |
// Makes sure that the state of the child views in the side
|
39 |
// spinner are not saved since we handle the state in the
|
40 |
// onSaveInstanceState.
|
41 |
super.dispatchFreezeSelfOnly(container); |
42 |
}
|
43 |
|
44 |
@Override
|
45 |
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { |
46 |
// Makes sure that the state of the child views in the side
|
47 |
// spinner are not restored since we handle the state in the
|
48 |
// onSaveInstanceState.
|
49 |
super.dispatchThawSelfOnly(container); |
50 |
}
|
Kesimpulan
Side spinner sekarang selesai. Kedua side spinner bekerja seperti yang diharapkan dan nilai-nilai mereka dipulihkan jika aktivitas dihancurkan dan dibuat ulang. Anda sekarang dapat menerapkan apa yang telah Anda pelajari untuk menggunakan kembali sekumpulan view dalam aplikasi Android dengan menggunakan view gabungan.