Lưu trữ

Archive for Tháng Bảy, 2011

Lập trình Android – Tạo Navigation Bar


Chào các bạn, hôm nay mình xin được tiếp tục giới thiệu với các bạn một custom UI Component nữa trong Android đó là NavigationBar hay Toolbar thì tùy cách gọi của các bạn. Nhớ lại lúc mới bắt đầu làm với Android, loay hoay mãi mà không biết phải tạo cái component này như thế nào vì trước đó làm với iPhone thì Apple đã cung cấp sẵn component này, chỉ kéo thả phát là xong.

Component mà chúng ta sẽ tạo trong bài đọc này sẽ là một thanh bar nằm ngang màn hình, thanh bar này chứa các Button, khi ta bấm lên mỗi Button thì chức năng tương ứng sẽ được gọi. Dưới đây là hình ảnh custom NavigationBar của chúng ta sau khi đã hoàn thành:



Để có được component như trên chúng ta phải làm những thao tác sau đây:

1. Tạo Layout,  Color, Drawable cho NavigationBar.

2. Tạo một NavigationBar class extends từ một layout class có sẵn của Android.

3. Tạo MainActivity để gọi và hiển thị NavigationBar.

Và bây giờ chúng ta sẽ đi vào từng thao tác cụ thể:

1. Tạo Layout, Color, Drawable cho NavigationBar

Trong thư mục layout, tạo một file đặt tên là navigation_bar.xml với nội dung như sau:

<merge

xmlns:android=”http://schemas.android.com/apk/res/android”&gt;

<LinearLayout

xmlns:android=”http://schemas.android.com/apk/res/android&#8221;

android:layout_width=”fill_parent”

android:layout_height=”fill_parent”>

<Button android:id=”@+id/add_btn”

android:layout_height=”50dip”

android:layout_width=”wrap_content”

android:clickable=”true”

android:focusable=”true”

android:background=”@drawable/background_states”

android:drawableTop=”@android:drawable/ic_menu_add”

android:gravity=”center”

android:layout_weight=”1″/>

<Button android:id=”@+id/cancel_btn”

android:layout_height=”50dip”

android:layout_width=”wrap_content”

android:clickable=”true”

android:focusable=”true”

android:background=”@drawable/background_states”

android:drawableTop=”@android:drawable/ic_menu_close_clear_cancel”

android:gravity=”center”

android:layout_weight=”1″/>

<Button android:id=”@+id/help_btn”

android:layout_height=”50dip”

android:layout_width=”wrap_content”

android:clickable=”true”

android:focusable=”true”

android:background=”@drawable/background_states”

android:drawableTop=”@android:drawable/ic_menu_help”

android:gravity=”center”

android:layout_weight=”1″/>

</LinearLayout>

</merge>

Có thể các bạn sẽ thấy file layout này hơi khác so với những file layout mà các bạn vẫn thường viết, thậm chí còn không có cả dòng xml declaration. Bạn có thể tìm hiểu trong phần layout tips and tricks của google tại đây.
Các bạn thấy trong phần layout cho các Button, mình sử dụng background cũng là một custom drawable có tên là background_states. Trong thư mục drawable, các bạn tạo một file có tên là background_states.xml với nội dung sau:

<?xml version=”1.0″ encoding=”utf-8″?>

<selector xmlns:android=”http://schemas.android.com/apk/res/android”&gt;

   <item android:state_focused=”true”

           android:state_pressed=”true”

           android:drawable=”@android:color/darker_gray”/>

   <item android:state_focused=”true”

           android:state_pressed=”false”

           android:drawable=”@android:color/darker_gray”/>

   <item android:state_focused=”false”

           android:state_pressed=”true”

           android:drawable=”@android:color/darker_gray”/>          

</selector>

Tương ứng với mỗi trạng thái của nút bấm chúng ta định nghĩa một màu, tuy nhiên ở đây mình chỉ chọn một màu duy nhất là darker_gray đã được định nghĩa sẵn trong thư viện của Android. Mình chọn 3 trạng thái để đặt màu này và không định nghĩa trạng thái sau:

<item android:state_focused=”false”

android:state_pressed=”false”/>

đây chính là trạng thái mà chúng ta không thao tác gì với nút bấm, vì thế khi không định nghĩa trạng thái này chúng ta sẽ lấy background măc định là background mà chúng ta sẽ set cho NavigationBar khi khởi tạo trong constructor của nó. Ở mục 2 chúng ta sẽ thực hiện công việc này. Với việc set background cho 3 trạng thái ở trên thì khi ta bấm lên một button bất kỳ trong NavigationBar, màu nền của button đó sẽ chuyển sang darker_gray.
Tiếp theo, chúng ta sẽ định nghĩa một vài màu riêng để sử dụng trong ví dụ của bài đọc này như màu nền mặc định của NavigationBar, màu của Text trong ví dụ này. Trong thư mục values, tạo file colors.xml như sau:

<?xml version=”1.0″ encoding=”utf-8″?>

<resources>

      <color name=”bgcolor”>#292A2B</color>    

      <color name=”text_color”>#1843C4</color>

      <color name=”white”>#ffffff</color>

</resources>
Tất nhiên các bạn hoàn toàn có thể bỏ file này đi mà dùng cách set trực tiếp các giá trị màu ở trong phần layout đó là tùy các bạn.
Bây giờ ta tạo main layout trong đó có chứa NavigationBar như là các thành phần UI khác trong Android, main.xml sẽ cho các bạn thấy cách chúng ta sử dụng NavigationBar của chúng ta, mở file main.xml và sửa lại như sau:

<?xml version=”1.0″ encoding=”utf-8″?>

<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android&#8221;

       android:orientation=”vertical”

    android:layout_width=”fill_parent”

    android:layout_height=”fill_parent”

    android:background=”@color/white”>         

      <TextView 

          android:layout_width=”fill_parent”

          android:layout_height=”fill_parent”       

          android:textSize=”16sp”  

          android:layout_weight=”1″

          android:gravity=”center”

          android:textStyle=”bold”

          android:textColor=”@color/text_color”

          android:text=”@string/mainpage_text”/>     

       <net.danchanvit.example.android.NavigationBar

            android:id=”@+id/my_navbar” android:layout_width=”fill_parent”

            android:layout_height=”wrap_content”/>

</LinearLayout>
Chú ý rằng nếu bạn build project tại thời điểm này thì project của bạn sẽ bị báo lỗi trong file main.xml vì trình biên dịch chưa tìm thấy NavigationBar class của bạn đâu, đơn giản là bạn chưa viết nó :D. Chúng ta sẽ viết NavigationBar class trong mục 2 dưới đây.

2.Tạo NavigationBar class extends từ một layout có sẵn của Android

Chúng ta tạo class cho NavigationBar và set các thuộc tính cho component của chúng ta như orientation, background color… thông qua constuctor. Dưới đây là source code của file NavigationBar.java:

public class NavigationBar extends LinearLayout{
/*
* Declare some buttons as instance variables so that later
* we can access these buttons and set OnclickListener to them
*/
public Button add_btn, cancel_btn, help_btn;

public NavigationBar(Context context){
super(context);
}
/*
* Constructor to initialize NavigationBar’s view and attribute sets
* here i don’t use any attribute sets
*/
public NavigationBar(Context context, AttributeSet attrs){
super(context, attrs);
setOrientation(HORIZONTAL);

// set default background color for NavigationBar setBackgroundColor(getResources().getColor(R.color.bgcolor));
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.navigation_bar, this);

// initialize all buttons the navigation bar contains
add_btn = (Button) findViewById(R.id.add_btn);
cancel_btn = (Button) findViewById(R.id.cancel_btn);
help_btn = (Button) findViewById(R.id.help_btn);
}
}

3.Tạo MainActivity để gọi và hiển thị NavigationBar.

Công việc cuối cùng của chúng ta là tạo một Activity để gọi và hiển thị NavigationBar mà chúng ta vừa tạo. Dưới đây là source code của MainActivity:

public class MainActivity extends Activity implements View.OnClickListener{

/* Declare some buttons then we will assign all buttons those NavigationBar contains
* to them therefore we can set OnclickListener to them
*/
Button add_btn, cancel_btn, help_btn;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// we can declare and initialize our custom NavigationBar like other UI Components in Android
NavigationBar navBar = (NavigationBar) findViewById(R.id.my_navbar);
// initialize and set OnclickListener to all buttons
add_btn = navBar.add_btn;
add_btn.setOnClickListener(this);
cancel_btn = navBar.cancel_btn;
cancel_btn.setOnClickListener(this);
help_btn = navBar.help_btn;
help_btn.setOnClickListener(this);

}

@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.add_btn:
Toast.makeText(this, “ADD button clicked”, Toast.LENGTH_SHORT).show();
break;
case R.id.cancel_btn:
Toast.makeText(this, “CANCEL button clicked”, Toast.LENGTH_SHORT).show();
break;
case R.id.help_btn:
Toast.makeText(this, “HELP button clicked”, Toast.LENGTH_SHORT).show();
break;
default:
break;
}

}
}

Vậy là chúng ta đã hoàn thành custom NavigationBar, một UI Component chỉ cần hai thao tác kéo và thả trong iOS là đã có thể sử dụng được, tuy nhiên lại tốn khá nhiều thao tác để có được nó trong Android. Chúc mừng các bạn. Ở đây nói thế không phải mình so sánh iPhone và Android bởi vì mỗi loại có một đặc điểm riêng. Android có hard key menu, còn iPhone thì không, có lẽ đó chính là lý do mà google không muốn lãng phí Screen cho một thanh Navigation bar. Để đỡ  mất thời gian ngồi code, các bạn có thể download toàn bộ source code của bài đọc này ở đây chạy thử luôn. Rất mong các bạn đóng góp ý kiến và chia sẻ kinh nghiệm. Chúc các bạn lập trình vui vẻ! Hẹn gặp lại.

Chuyên mục:Android

Lập trình Android – Expandable List View


Chào mừng các bạn đến với bài đọc tiếp theo trong loạt bài hướng dẫn về lập trình Android. Các bạn thân mến, ở bài đọc trước, mình đã giới thiệu với các bạn cách để tạo một Custom Dialog. Hôm nay mình xin được tiếp tục giới thiệu với các bạn cách tạo một Custom UI Component khác được sử dụng khá phổ biến đó là ExpandableListView. Kết thúc bài đọc chúng ta sẽ có một UI Component như hình dưới:

Để có được một component như hình trên, chúng ta phải làm các thao tác sau:

Xây dựng layout cho component

Tạo 2 Java Beans đóng vai trò là group và child trong ExpandableListView

Tạo một CustomExpandableListAdapter

Tạo một Activity để gọi và hiển thị

1. Xây dựng layout cho component

Trước hết chúng ta tạo một layout trong đó có chứa thành phần ExpandableListView, trong thư mục layout, ta sửa lại file main.xml như sau:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:orientation=“vertical”

android:layout_width=“fill_parent”

android:layout_height=“fill_parent”>

<ExpandableListView android:id=“@+id/listView”

android:layout_width=“fill_parent”

android:layout_height=“fill_parent”

android:scrollbars=“none”/>

</LinearLayout>

Một ExpandableListView sẽ có các groups và mỗi group sẽ chứa các thành phần con là children, khi ta expand mỗi group, ta sẽ thấy các thành phần con nằm trong nó. Vì vậy để tùy biến một ExpandableListView, chúng ta sẽ phải tùy biến các groups và children của chúng. Chúng ta sẽ tạo layout cho group và child. Tạo mới 2 file trong thư mục layout:

group_layout.xml:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout

xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:orientation=“horizontal”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:background=“@drawable/header_background”>

<TextView android:id=“@+id/group_title”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:gravity=“center_vertical”

android:layout_marginLeft=“40dip”

android:paddingTop=“5dip”

android:textColor=“#000000”

android:textStyle=“bold”

android:textSize=“16sp”/>

</LinearLayout>

child_layout.xml:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout

xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:orientation=“horizontal”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:background=“#fa8400”>

<ImageView android:id=“@+id/child_icon”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginLeft=“30dip”/>

<TextView android:id=“@+id/child_title”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:gravity=“center_vertical”

android:textColor=“#ffffff”

android:textStyle=“bold”

android:textSize=“14sp”/>

</LinearLayout>

Như vậy chúng ta đã hoàn tất công việc tạo layout cho Custom ExpandableListView. Tiếp theo chúng ta sẽ tùy biến ExpandableListViewAdapter.

2. Tạo 2 Java Beans đóng vai trò là group và child trong ExpandableListView

Dưới đây là source code của 2 class Category và Item, Category đóng vai trò group và Item đóng vai trò là child trong ExpandableListView. Đối với các bạn mới đọc qua về Java chỉ để phục vụ cho mục đích lập trình Android thì có lẽ khái niệm java bean là cái gì đó có vẻ lạ lẫm. Tuy nhiên các bạn hoàn toàn không cần quan tâm tới khái niệm này. Java Bean thực chất chỉ là một java class bình thường với một constructor rỗng và các instance variable có access modifier là private cùng với các gettersetter cho chúng. Dưới đây là 2 java beans:

Category.java

public class Category {
private long id;
private String icon;

private String name;

public Category(){

}
public String getIcon() {
return icon;
}

public void setIcon(String icon) {
this.icon = icon;
}
public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

Item.java

public class Item {
private long cateId;

private long itemId;
private String name;

public Item(){
}

public long getCateId() {
return cateId;
}

public void setCateId(long cateId) {
this.cateId = cateId;
}

public long getItemId() {
return itemId;
}

public void setItemId(long itemId) {
this.itemId = itemId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

3. Tạo ExpandableListAdapter

Như chúng ta đều biết, ExpandableListAdapter chính là Datasource của ExpandableListView. Nói cách khác mọi dữ liệu mà ExpandableListView hiển thị như icon, title, content… đều được lấy từ ExpandableListAdapter. Android cung cấp sẵn cho chúng ta một class có tên là BaseExpandableListAdapter, để tùy biến ExpandableListAdapter một cách dễ dàng, nhanh chóng và hiệu quả, chúng ta sẽ tạo một adapter extends từ class này. Dưới đây là toàn bộ code cho custom adapter mà ta cần tạo:

public class MyExpandableListAdapter extends BaseExpandableListAdapter {

private Context context;
private ArrayList<Category> groups; // an array list of categories
private ArrayList<ArrayList<Item>> children; // an array list of items

public MyExpandableListAdapter(Context context){
this.context = context;
}

public MyExpandableListAdapter(Context context, ArrayList<Category> groups,
ArrayList<ArrayList<Item>> children) {
this.context = context;
this.groups = groups;
this.children = children;
}

/**
* provide a method to add an item to a category dynamically, check if the
* category is existed otherwise throws an exception
*
* @param item
* @return void
*/
public void addItem(Item item) throws ClassNotFoundException {
// check if the category is existed
if (!this.findCategory(groups, item.getCateId())) {
throw new ClassNotFoundException(“Category doesn’t exist.”);
} else {
Category category = this.getCateInListById(groups, item.getCateId());
int index = groups.indexOf(category);
/*
* check if children list already contained the list to hold this
* group or else we have to create it
*/
if (children.size() < (index + 1)) {
children.add(new ArrayList<Item>());
}
children.get(index).add(item);
}
}

@Override
public Object getChild(int groupPosition, int childPosition) {
return children.get(groupPosition).get(childPosition);
}

@Override
public long getChildId(int groupPosition, int childPosition) {
Item item = (Item) children.get(groupPosition).get(childPosition);
return item.getItemId();
}

@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
Item item = (Item) getChild(groupPosition, childPosition);
Category category = (Category) getGroup(groupPosition);

if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.child_layout, null);
}

// set title for the item
TextView tv = (TextView) convertView.findViewById(R.id.child_title);
tv.setText(” ” + item.getName());

// set icon for the item, all item in the same category have the same
// icon
ImageView imv = (ImageView) convertView.findViewById(R.id.child_icon);
if ((category.getIcon() != null)
&& (category.getIcon().trim().length() > 0)) {
int iconUri = context.getResources().getIdentifier(
category.getIcon(), null, context.getPackageName());
Drawable drawable = context.getResources().getDrawable(iconUri);
imv.setBackgroundDrawable(drawable);
}
return convertView;
}

@Override
public int getChildrenCount(int groupPosition) {
return children.get(groupPosition).size();
}

@Override
public Object getGroup(int groupPosition) {
return groups.get(groupPosition);
}

@Override
public int getGroupCount() {
return groups.size();
}

@Override
public long getGroupId(int groupPostion) {
return groups.get(groupPostion).getId();
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
Category group = (Category) getGroup(groupPosition);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.group_layout, null);
}
TextView tv = (TextView) convertView.findViewById(R.id.group_title);
tv.setText(” ” + group.getName());
return convertView;
}

@Override
public boolean hasStableIds() {
return true;
}

@Override
public boolean isChildSelectable(int arg0, int arg1) {
return true;
}

/**
* search for a category through out an array list rely on its id
*
* @param arraylist
* @param cateId
* @return true if found the category otherwise false
*/
private boolean findCategory(ArrayList<Category> arraylist, long cateId) {
for (Category category : arraylist) {
if (cateId == category.getId()) {
return true;
}
}
return false;
}
/**
* method to get a category from a group depends on its id
* @param arraylist
* @param cateId
* @return category with the specific id if it exists otherwise return null
*/

private Category getCateInListById(ArrayList<Category> arraylist, long cateId){
for (Category category : arraylist) {
if (cateId == category.getId()) {
return category;
}
}
return null;
}
}

Có lẽ không cần phải giải thích gì nhiều về source code, bản thân tên của các phương thức, tên biến và các tham số đã tự nó nói lên những điều cần nói. Có một chú ý nhỏ là ở đây mình tạo ra phương thức addItem() với mục đích cung cấp phương pháp add động trực tiếp một child vào một group nào đó của ExpandableListView.  Trong phần Activity gọi và hiển thị, mình sẽ lấy một ví dụ về nó. Đối với các phương thức getGroupId, getChildId, mình trả về id của Category và Item tương ứng do đó khi ta click lên mỗi group hoặc item chúng ta có thể lấy được ngay id của Category hay Item thay vì groupPosition hay childPosition.

4. Tạo Activity để gọi và hiển thị ExpandableListView

Cuối cùng chúng ta chỉ việc tạo một Activity, trong Activity này chúng ta tạo ra một số dữ liệu giả lập cho Custom ExpandableAdapter, bind dữ liệu đó lên ExpandableListView và hiển thị lên màn hình. Dưới đây là source code cho Activity:
public class ExpandableListViewActivity extends Activity {

// Declare MyExpandableListAdapter, datasource of expandable list view
MyExpandableListAdapter adapter;
public final Context THIS = this;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ExpandableListView listView = (ExpandableListView) findViewById(R.id.listView);

listView.setOnChildClickListener(new OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View view,
int groupPosition, int childPosition, long id) {
Toast.makeText(THIS,
“I am child with id = ” + id, Toast.LENGTH_SHORT).show();
return false;
}
});

listView.setOnGroupClickListener(new OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View view,
int groupPosition, long id) {
// Toast.makeText(THIS,
// “I am group with id = ” + id, Toast.LENGTH_SHORT).show();
return false;
}
});

// create datasource
fakeData();

// bind data to list view
listView.setAdapter(adapter);
}

/*
* Here we manually create some fake categories and fake items to bind into the
* list view. In real world we should never do it this way!?
*/
public void fakeData(){

ArrayList groups = new ArrayList();

for (int i = 1; i <= 4; i++) {
Category category = new Category();
switch (i) {
case 1:
category.setId(i);
category.setIcon(“drawable/bear”);
category.setName(“Bears”);
break;
case 2:
category.setId(i);
category.setIcon(“drawable/bee”);
category.setName(“Bees”);
break;
case 3:
category.setId(i);
category.setIcon(“drawable/dog”);
category.setName(“Dogs”);
break;
case 4:
category.setId(i);
category.setIcon(“drawable/monkey”);
category.setName(“Monkeys”);
break;
}
groups.add(category);
}

ArrayList<ArrayList> children = new ArrayList<ArrayList>();
ArrayList itemList;
for (int i = 0; i < 4; i++) {
itemList = new ArrayList();
for (int j = 1; j <= 5; j++) {
switch (i) {
case 0:
// add the items to Bears category
Item item1 = new Item();
item1.setCateId(1);
item1.setItemId(j);
item1.setName(“I am Bears No. ” + j);
itemList.add(item1);
break;
case 1:
// add items Bees category
Item item2 = new Item();
item2.setCateId(2);
item2.setItemId(j+5);
item2.setName(“I am Bees No. ” + j);
itemList.add(item2);
break;
case 2:
// add items to Dogs category
Item item3 = new Item();
item3.setCateId(3);
item3.setItemId(j+10);
item3.setName(“I am Dogs No. ” + j);
itemList.add(item3);
break;
case 3:
// add items to Monkeys category
Item item4 = new Item();
item4.setCateId(4);
item4.setItemId(j+15);
item4.setName(“I am Monkey No. ” + j);
itemList.add(item4);
break;
}
}
children.add(itemList);
}

adapter = new MyExpandableListAdapter(THIS,
groups, children);

// create new Item and add it directly to the adapter with the help of
// method addItem that we have created to test it
try {
Item item = new Item();
item.setCateId(1);// add it to Bears Category
item.setItemId(21);// hardcode the id, we should never do it in real world!?
item.setName(“I am newly added bear”);
adapter.addItem(item);
} catch (ClassNotFoundException ex) {
Log.v(“ADD NEW ITEM”, ex.toString());
}

}
}
Như vậy là chúng đã tùy biến xong một UI Component trong Android. Từ bài đọc này, các bạn có thể thay đổi ý tưởng để tự tao ra cho riêng mình những ExpandableListView thích hợp nhất cho từng ứng dụng cụ thể. Mình hy vọng chúng ta sẽ cùng nhau trao đổi những gì mà các bạn còn cảm thấy băn khoăn trong phần comment. Đây là toàn bộ phần source code cho ứng dụng trên, các bạn có thể download và chạy thử.

Chuyên mục:Android

Lập trình Android-Tạo Custom Dialog trong ứng dụng


Hiện nay một trong những đối thủ cạnh tranh mạnh mẽ nhất với iOS chính là Android, số lượng lập trình viên tham gia lập trình ứng dụng cho các thiết bị di động chạy trên hệ điều hành Android ngày càng trở nên đông đảo. Tài liệu hướng dẫn về lập trình Android được google cung cấp khá đầy đủ và chi tiết, các bạn có thể tham khảo trên website của google tại đây. Với ý định góp phần giúp các bạn mới bắt đầu lập trình Android giải quyết một số băn khoăn mà các bạn khó tránh khỏi khi bắt tay vào viết các ứng dụng ban đầu trong khi các bạn chưa thực sự quen thuộc hoặc chưa kịp tìm hiểu kĩ các API, blog này xin được cung cấp cho các bạn một số bài hướng dẫn, qua đó các bạn có thể nắm bắt, hiểu rõ hơn và tự xây dựng cho mình các kĩ năng trong lập trình Android. Bây giờ xin mời các bạn đến với bài đọc đầu tiên.
Trong bài đọc này, mình sẽ hướng dẫn các bạn tạo một Custom Dialog để thay thế cho Dialog sẵn có trong Android. Như chúng ta đã biết, Android có cung cấp một class là AlertDialog cùng với inner class của nó là Builder. Để tùy biến AlertDialog, chúng ta sẽ tùy biến AlertDialog và AlertDialog.Builder class, tạo custom theme, style và color để sử dụng cho mục đích tùy biến look and feel cho Custom Dialog của chúng ta. Sau khi hoàn thành chúng ta sẽ có một dialog như sau:

Viết các custom Layout, Color, Drawable, Style, String value và Theme

Trước hết chúng ta định nghĩa một số giá trị string để sử dụng trong ứng dụng này, mở file strings.xml và gán các giá trị cho nó:

<?xml version=“1.0” encoding=“utf-8”?>

<resources>

<string name=“app_name”>CustomDialog</string>

<string name=“default_dialog_btn”>Default Dialog</string>

<string name=“custom_dialog_btn”>Custom Dialog</string>

</resources>

Định nghĩa một số color mà ta sẽ sử dụng, trong thư mục values, tạo một file mới và đặt tên colors.xml sau đó định nghĩa các màu cơ bản mà ta sẽ dùng( ở đây mình định nghĩa 8 màu, tuy nhiên số lượng và tên các màu mà các bạn định nghĩa là hoàn toàn tùy thuộc ở các bạn):

<?xml version=“1.0” encoding=“utf-8”?>

<resources>

<color name=“red”>#ff0000</color>

<color name=“green”>#00ff00</color>

<color name=“blue”>#0000ff</color>

<color name=“black”>#000000</color>

<color name=“white”>#ffffff</color>

<color name=“grey”>#383838</color>

<color name=“yellow”>#ffff00</color>

<color name=“nocolor”>#11000000</color>

</resources>

Tiếp theo chúng ta sẽ tạo style cho titlemessage text của CustomDialog, những style này ta sẽ sử dụng cho việc định dạng text cho các component mà ta dùng trong layout. Các bạn hoàn toàn có thể bỏ qua file này và định dạng trực tiếp trong file layout, tuy nhiên mục đích của bài viết này là để các bạn làm quen với cách xây dựng và sử dụng các thành phần trong Android vì thế mình tách ra thành một file riêng. Tạo một file trong thư mục values và đặt tên là styles.xml, nội dung của file đó như sau:

<?xml version=“1.0” encoding=“utf-8”?>

<resources>

<style name=“DialogText”>

<item name=“android:textColor”>@color/white</item>

<item name=“android:textSize”>14sp</item>

</style>

<style name=“DialogText.Title”>

<item name=“android:textSize”>16sp</item>

<item name=“android:textStyle”>bold</item>

<item name=“android:textColor”>@color/yellow</item>

</style>

</resources>

Bây giờ ta sẽ tạo Theme cho CustomDialog, tạo mới một file có tên là themes.xml trong thư mục values với nội dung sau:

<?xml version=“1.0” encoding=“utf-8”?>

<resources>

<style name=“CustomDialog” parent=“android:style/Theme.Dialog”>

<item name=“android:windowBackground”>@color/nocolor</item>

<item name=“android:windowNoTitle”>true</item>

<item name=“android:windowIsFloating”>true</item>

</style>

</resources>

Các bạn chú ý rằng component mà chúng ta đang tạo là một Dialog vì thế ta sử dụng Dialog built-in theme của Android cho thuộc tính parent:

parent=“android:style/Theme.Dialog”

với thuộc tính này component của chúng ta khi hiển thị trên màn hình sẽ có dạng là một Dialog mà không phải là một Screen thông thường.

Các bạn có thể sử dụng các ảnh có có sẵn để style cho các components trong layout,  bên cạnh đó Android còn cung cấp cho chúng ta phương pháp để tạo ra các đối tượng Drawable của riêng mình, bây giờ mình sẽ tạo 3 đối tượng Drawable, mình sẽ sử dụng chúng cho phần header, content và footer của CustomDialog trong file layout. Trong thư mục drawable các bạn tạo 3 file .xml như sau:

dialog_custom_header.xml:

<?xml version=“1.0” encoding=“utf-8”?>

<shape xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:shape=“rectangle”>

<corners android:bottomRightRadius=“0dp”

android:bottomLeftRadius=“0dp”

android:topLeftRadius=“4dp”

android:topRightRadius=“4dp” />

<solid android:color=“#2A3B57” />

<stroke  android:width=“2dip”

android:top=“0dip”

android:color=“#94b66b”/>

</shape>

dialog_custom_content.xml:

<?xml version=“1.0” encoding=“utf-8”?>

<shape xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:shape=“rectangle”>

<solid android:color=“#567371” />

<stroke  android:width=“2dip”

android:bottom=“0dip”

android:color=“#94b66b”/>

<corners android:bottomRightRadius=“0dp”

android:bottomLeftRadius=“0dp”

android:topLeftRadius=“0dp”

android:topRightRadius=“0dp” />

</shape>

dialog_custom_footer.xml:

<?xml version=“1.0” encoding=“utf-8”?>

<shape xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:shape=“rectangle”>

<corners android:bottomRightRadius=“4dp”

android:bottomLeftRadius=“4dp”

android:topLeftRadius=“0dp”

android:topRightRadius=“0dp” />

<solid android:color=“#2A3B57” />

<stroke  android:width=“2dip”

android:color=“#94b66b”/>

</shape>

Kế tiếp chúng ta sẽ xây dựng layout cho CustomDialog. Trong thư mục layout, tạo một file và đặt tên là custom_dialog.xml với các thành phần sau:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:orientation=“vertical”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:minWidth=“280dip”>

<LinearLayout

android:orientation=“horizontal”

android:background=“@drawable/dialog_custom_header”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”>

<ImageView android:id=“@+id/custom_icon”

android:layout_margin=“5dip”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”/>

<TextView

android:id=“@+id/title”

android:padding=“8dip”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

style=“@style/DialogText.Title”/>

</LinearLayout>

<LinearLayout

android:id=“@+id/content”

android:orientation=“vertical”

android:background=“@drawable/dialog_custom_content”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”>

<TextView

android:id=“@+id/message”

android:padding=“5dip”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:maxWidth=“280dip”

style=“@style/DialogText”/>

</LinearLayout>

<LinearLayout

android:orientation=“horizontal”

android:background=“@drawable/dialog_custom_footer”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”>

<Button

android:id=“@+id/positive_btn”

android:layout_marginTop=“4dip”

android:layout_marginLeft=“4dip”

android:layout_marginRight=“2dip”

android:layout_width=“0dip”

android:layout_height=“wrap_content”

android:layout_weight=“1”

android:singleLine=“true”/>

<Button

android:id=“@+id/negative_btn”

android:layout_marginTop=“4dip”

android:layout_marginLeft=“2dip”

android:layout_marginRight=“4dip”

android:layout_width=“0dip”

android:layout_height=“wrap_content”

android:layout_weight=“1”

android:singleLine=“true”/>

</LinearLayout>

</LinearLayout>

Các bạn chú ý việc mình setMinWidth cho root layout, với minWidtht=280dip thì CustomDialog sẽ luôn chiếm khoảng 3/4 độ rộng màn hình của thiết bị. Khi message của CustomDialog quá dài, có thể sẽ làm cho độ rộng của nó ngang bằng với độ rộng màn hình thiết bị, vì vậy mình set maxWidth=280dip để độ rộng CustomDialog không thể vượt quá được 3/4 màn hình thiết bị.

Như vậy chúng ta đã hoàn thành phần tạo view cho CustomDialog, công việc tiếp theo mà chúng ta phải làm là tạo một lớp CustomDialog với một inner helper class có tên là Builder, dưới đây là code mà các bạn sẽ viết:

package net.danchanvit.example.android;

import android.app.Dialog;

import android.content.Context;

import android.content.DialogInterface;

import android.graphics.drawable.Drawable;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup.LayoutParams;

import android.widget.Button;

import android.widget.ImageView;

import android.widget.LinearLayout;

import android.widget.TextView;

/*

* Create custom Dialog window for your application, the custom Dialog

* rely on the custom layouts you define which allow you to create and

* use your custom look and feel

*/

public class CustomDialog extends Dialog {

public CustomDialog(Context context) {

super(context);

}

public CustomDialog(Context context, int theme) {

super(context, theme);

}

/*

* Helper class for creating custom Dialog

*/

public static class Builder {

private Context context;

private String iconUri;

private String title;

private String message;

private String positiveBtnText;

private String negativeBtnText;

private View contentView;

private DialogInterface.OnClickListener positiveBtnClickListener,

negativeBtnClickListener;

public Builder(Context context) {

this.context = context;

}

/**

* Set the Dialog icon from String

*

* @param icon

* @return

*/

public Builder setIconUri(String icon) {

this.iconUri = icon;

return this;

}

/**

* Set the Dialog icon from resource

*

* @param icon

* @return

*/

public Builder setIconUri(int icon) {

this.iconUri = (String) context.getText(icon);

return this;

}

/**

* Set the Dialog message from String

*

* @param message

* @return

*/

public Builder setMessage(String message) {

this.message = message;

return this;

}

/**

* Set the Dialog message from resource

*

* @param message

* @return

*/

public Builder setMessage(int message) {

this.message = (String) context.getText(message);

return this;

}

/**

* Set the Dialog title from resource

*

* @param title

* @return

*/

public Builder setTitle(int title) {

this.title = (String) context.getText(title);

return this;

}

/**

* Set the Dialog title from String

*

* @param title

* @return

*/

public Builder setTitle(String title) {

this.title = title;

return this;

}

/**

*Set a custom content view for the Dialog. If a  message is  set, the

* contentView is not added to the Dialog…

*

* @param v

* @return

*/

public Builder setContentView(View v) {

this.contentView = v;

return this;

}

/**

* Set the positive button resource and it’s listener

*

* @param positiveButtonText

* @param listener

* @return

*/

public Builder setPositiveButton(int positiveButtonText,

DialogInterface.OnClickListener listener) {

this.positiveBtnText = (String) context.getText(positiveButtonText);

this.positiveBtnClickListener = listener;

return this;

}

/**

* Set the positive button text and it’s listener

*

* @param positiveButtonText

* @param listener

* @return

*/

public Builder setPositiveButton(String positiveButtonText,

DialogInterface.OnClickListener listener) {

this.positiveBtnText = positiveButtonText;

this.positiveBtnClickListener = listener;

return this;

}

/**

* Set the positive button resource and it’s listener

*

* @param negativeButtonText

* @param listener

* @return

*/

public Builder setnegativeButton(int negativeButtonText,

DialogInterface.OnClickListener listener) {

this.negativeBtnText = (String) context.getText(negativeButtonText);

this.negativeBtnClickListener = listener;

return this;

}

/**

* Set the negative button text and it’s listener

*

* @param negativeButtonText

* @param listener

* @return

*/

public Builder setNegativeButton(String negativeButtonText,

DialogInterface.OnClickListener listener) {

this.negativeBtnText = negativeButtonText;

this.negativeBtnClickListener = listener;

return this;

}

/**

* Create the custom dialog

*/

public CustomDialog create() {

LayoutInflater inflater = (LayoutInflater) context

.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

// instantiate the dialog with the custom Theme

final CustomDialog dialog = new CustomDialog(context,

R.style.CustomDialog);

View layout = inflater.inflate(R.layout.custom_dialog, null);

dialog.addContentView(layout, new LayoutParams(

LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));

// set the dialog icon

if(iconUri != null && iconUri.length()>0){// Do not call this block of codes if we don’t use setIcon

int imgResource = context.getResources().getIdentifier(iconUri,

null, context.getPackageName());

Drawable drawable = context.getResources().getDrawable(imgResource);

((ImageView) layout.findViewById(R.id.custom_icon)).setBackgroundDrawable(drawable);

}

// set the dialog title

((TextView) layout.findViewById(R.id.title)).setText(title);

// set the confirm button

if (positiveBtnText != null) {

((Button) layout.findViewById(R.id.positive_btn))

.setText(positiveBtnText);

if (positiveBtnClickListener != null) {

((Button) layout.findViewById(R.id.positive_btn))

.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

positiveBtnClickListener.onClick(dialog,

DialogInterface.BUTTON_POSITIVE);

}

});

}

} else {

// if no confirm button just set the visibility to GONE

layout.findViewById(R.id.positive_btn).setVisibility(View.GONE);

}

// set the cancel button

if (negativeBtnText != null) {

((Button) layout.findViewById(R.id.negative_btn))

.setText(negativeBtnText);

if (negativeBtnClickListener != null) {

((Button) layout.findViewById(R.id.negative_btn))

.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

negativeBtnClickListener.onClick(dialog,

DialogInterface.BUTTON_NEGATIVE);

}

});

}

} else {

// if no confirm button just set the visibility to GONE

layout.findViewById(R.id.negative_btn).setVisibility(View.GONE);

}

// set the content message

if (message != null) {

((TextView) layout.findViewById(R.id.message)).setText(message);

} else if (contentView != null) {

// if no message set

// add the contentView to the dialog body

((LinearLayout) layout.findViewById(R.id.content))

.removeAllViews();

((LinearLayout) layout.findViewById(R.id.content)).addView(

contentView, new LayoutParams(

LayoutParams.WRAP_CONTENT,

LayoutParams.WRAP_CONTENT));

}

dialog.setContentView(layout);

return dialog;

}

}

}

Sử dụng CustomDialog trong Ứng dụng cụ thể

Trên đây là toàn bộ những gì các bạn phải làm để có được một custom dialog, Tuy nhiên để cho bài viết được rõ ràng hơn, tiếp theo mình sẽ trình bày cách gọi và hiển thị CustomDialog trong ứng dụng.

1. Tạo layout cho ứng dụng

Các bạn mở file layout main.xml và sửa lại như sau:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:layout_width=“fill_parent”

android:layout_height=“fill_parent”>

<RelativeLayout android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“center”

android:background=“@color/blue”>

<LinearLayout android:orientation=“vertical”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:paddingTop=“3dip”>

<Button

android:id=“@+id/default_dialog_btn”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:text=“@string/default_dialog_btn”/>

<Button

android:id=“@+id/custom_dialog_btn”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:text=“@string/custom_dialog_btn”/>

</LinearLayout>

</RelativeLayout>

</LinearLayout>

2. Tạo Activity cho ứng dụng

Để cho nhanh chóng, ta sẽ đổi tên MainActivity thành CustomDialogActivity và sửa lại code như bên dưới:

package net.danchanvit.example.android;

import android.app.Activity;

import android.app.AlertDialog;

import android.app.Dialog;

import android.content.DialogInterface;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

public class CustomDialogActivity extends Activity implements View.OnClickListener{

public final int DEFAULT_DIALOG = 0;

public final int CUSTOM_DIALOG = 1;

Button default_dialog_btn, custom_dialog_btn;

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

default_dialog_btn = (Button) findViewById(R.id.default_dialog_btn);

custom_dialog_btn = (Button) findViewById(R.id.custom_dialog_btn);

default_dialog_btn.setOnClickListener(this);

custom_dialog_btn.setOnClickListener(this);

}

@Override

public void onClick(View view) {

if(view == default_dialog_btn){

CustomDialogActivity.this.showDialog(0);

}else if(view == custom_dialog_btn){

CustomDialogActivity.this.showDialog(1);

}

}

@Override

/*

* Build desired dialog

* CUSTOM or DEFAULT

*/

public Dialog onCreateDialog(int dialogId) {

Dialog dialog = null;

String icon = “drawable/dialog_icon”;

switch (dialogId) {

case CUSTOM_DIALOG :

CustomDialog.Builder customBuilder = new

CustomDialog.Builder(CustomDialogActivity.this);

customBuilder.setIconUri(icon).setTitle(“Custom Dialog Title”)

.setMessage(“Congratulation! You have successfully create a custom dialog. I hope    you enjoy it.”)

.setNegativeButton(“Cancel”,

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

CustomDialogActivity.this

.dismissDialog(CUSTOM_DIALOG);

}

})

.setPositiveButton(“Confirm”,

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

});

dialog = customBuilder.create();

break;

case DEFAULT_DIALOG :

AlertDialog.Builder alertBuilder = new

AlertDialog.Builder(CustomDialogActivity.this);

alertBuilder.setTitle(“Default title”)

.setMessage(“Default body”)

.setNegativeButton(“Cancel”,

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

})

.setPositiveButton(“Confirm”,

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

CustomDialogActivity.this

.dismissDialog(DEFAULT_DIALOG);

}

});

dialog = alertBuilder.create();

break;

}

return dialog;

}

}

Chạy ứng dụng và bạn sẽ thấy một màn hình như sau:

Bấm vào nút Default Dialog sẽ xuất hiện built-in AlertDialog của Android:

Và đây là CustomDialog mà chúng ta vừa tạo, xuất hiện khi các bạn bấm vào nút Custom Dialog từ màn hình chính:

Khá đơn giản, tuy nhiên cũng có không ít thao tác phải làm.Vậy là các bạn đã biết cách tạo một CustomDialog, từ đây bạn có thể chỉnh sửa và style lại theo ý muốn và sử dụng trong các ứng dụng của riêng mình. Các bạn có thể download toàn bộ source code của example trên tại đây để chạy thử. Nếu các bạn có thắc mắc gì cần được giúp đỡ trong lập trình Android, hãy gửi lại trong phần comment. Mình cũng rất mong các bạn khác đã là Pro trong lập trình Android cùng chia sẻ những hiểu biết và kinh nghiệm của chính các bạn để chúng ta cùng nhau bổ sung thêm các kỹ năng. Chúc các bạn lập trình vui vẻ!

Chuyên mục:Android