Map và Direction trong Android

02/02/2013 5 comments

Ngày bắt đầu làm quen với lập trình Android, mình có viết một ứng liên quan đến Map, hôm nay lôi ra nghịch thử thấy nó không hoạt động nữa chẳng hiểu tại sao. Tìm hiểu nguyên nhân mới biết rằng google không còn support KML direction data nữa. Đang rảnh rỗi nên ngồi thử viết lại và tìm hiểu thêm về cái Map trong Android.
Trong bài đọc này mình sẽ giới thiệu với các bạn cách hiển thị map trên thiết bị Android và tìm đường từ vị trí hiện tại tới một vị trí khác cần đến. Dưới đâylà screenshot của màn hình hiển thị map trong ứng dụng (marker màu xanh giả lập vị trí hiện tại, các markers màu đỏ giả lập các vị trí cần tới)
maptest1
Bạn chọn một vị trí trên bản đồ (marker màu đỏ) bằng cách chạm vào vị trí đó, ứng dụng sẽ tìm và vẽ đường từ vị trí hiện tại tới vị trí mà bạn vừa chọn (đường đi hiển thị bằng đường màu xanh lá cây) như hình sau:
maptest2
Sau đây là phần code cho ứng dụng vừa mô tả. Trước hết để ứng dụng hoạt động được, các bạn cần khai báo những permissions sau trong file AndroidManifest.xml:
android.permission.INTERNET”
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_NETWORK_STATE

1. Layout: my_map.xlm
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android"
android:id=”@+id/map_screen” android:orientation=”vertical”
android:layout_width=”fill_parent” android:layout_height=”fill_parent”>
<com.google.android.maps.MapView
android:id=”@+id/mapview”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:gravity=”center”
android:clickable=”true”
android:apiKey=”your_google_apimap_key” />
</LinearLayout>

Trong file layout trên, các bạn chú ý cần đăng ký với google để lấy mapapi key. Để có google mapapi key, ta cần phải tạo MD5 fingerprint, mình sẽ hướng dẫn các bạn tạo MD5 fingerprint với debugkeystore vì nó đã có sẵn nên ta bỏ qua bước tạo keystore cho đỡ loằng ngoằng mất thời gian. Debug keystore trên máy mình có đường dẫn như dưới đây, các bạn tự tìm trên máy của các bạn.
C:\Documents and Settings\Administrator\.android\debug.keystore
Bây giờ các bạn bật command line console lên và change directory tới thư mục chứa keystore và dùng jdk keytool để tạo MD5 fingerprint như hình dưới đây:

fingerprint
Sau khi đã có MD5 fingerprint các bạn vào đây để Generate API Key.

2. Các Activity và Classes trong ứng dụng:
MyMapActivity.java
public class MyMapActivity extends MapActivity {
MapView mapView;
MapController mapController;
LocationListener locListener;
Button btn_back, btn_search, btn_config;
LinearLayout mainScreen;
ProgressDialog pd;

@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.my_map);
mapView = (MapView) findViewById(R.id.mapview);
mapView.setBuiltInZoomControls(true);
mapController = mapView.getController();
mapController.setZoom(14);
doLoadMap();
}

@Override
protected boolean isRouteDisplayed() {
return false;
}

private void doLoadMap() {
Context context = getApplicationContext();
int latDegree = 0;
int longDegree = 0;
int numberOfPlaces = 10;
try {
Drawable redMarker = getResources().getDrawable(
R.drawable.redmarker);
MyItemOverlay mainMapOverlay = new MyItemOverlay(
redMarker, context);
//hardcode the curent latitude and longitude, in real world should get it from where we saved them
double currentLatitude = 21.0325;
double currentLongitude = 105.8492;
try {
latDegree = (int) (currentLatitude * 1E6);
longDegree = (int) (currentLongitude * 1E6);
} catch (NumberFormatException ex) {
Log.v(“EXCEPTION”, ex.toString());
}
// Provide some default locations and these to the map for testing purpose
for (int i = 1; i <= numberOfPlaces; i++) {
double itemLat = 0.0;
double itemLong = 0.0;
if(i%2 == 0){
itemLat = currentLatitude + (i * 0.001);
itemLong = currentLongitude – (i * 0.002);
}else{
itemLat = currentLatitude;
itemLong = currentLongitude – (i * 0.003);
}
GeoPoint geoPoint = new GeoPoint((int) (itemLat * 1E6),
(int) (itemLong * 1E6));
OverlayItem overlayItem = new OverlayItem(geoPoint, “”, “”);
mainMapOverlay.addOverlay(overlayItem);

}
Drawable greenMarker = getResources().getDrawable(
R.drawable.greenandroidmarker);
MyItemOverlay mainMapOverlayCurrent = new MyItemOverlay(
greenMarker, context);
GeoPoint myGeoPoint = new GeoPoint(latDegree, longDegree);
OverlayItem overlayItem = new OverlayItem(myGeoPoint, “Hello”,
“You are here”);
mainMapOverlayCurrent.addOverlay(overlayItem);
List mapOverlayList = mapView.getOverlays();
mapOverlayList.clear();
mapOverlayList.add(mainMapOverlay);
mapOverlayList.add(mainMapOverlayCurrent);
mapController.animateTo(myGeoPoint);
} catch (Exception e) {
e.printStackTrace();
return;
}
}
}
Class Segment.java
public class Segment {
/** Points in this segment. **/
private GeoPoint start;
/** Turn instruction to reach next segment. **/
private String instruction;
/** Length of segment. **/
private int length;
/** Distance covered. **/
private double distance;

public Segment() {
}
public void setInstruction(final String turn) {
this.instruction = turn;
}
public String getInstruction() {
return instruction;
}
public void setPoint(final GeoPoint point) {
start = point;
}
public GeoPoint startPoint() {
return start;
}

/** Creates a segment which is a copy of this one.
* @return a Segment that is a copy of this one.
*/
public Segment copy() {
final Segment copy = new Segment();
copy.start = start;
copy.instruction = instruction;
copy.length = length;
copy.distance = distance;
return copy;
}
public void setLength(final int length) {
this.length = length;
}
public int getLength() {
return length;
}
public void setDistance(double distance) {
this.distance = distance;
}
public double getDistance() {
return distance;
}
}
Class Route.java
public class Route {
private String name;
private final List points;
private List segments;
private String copyright;
private String warning;
private String country;
private int length;
private String polyline;

public Route() {
points = new ArrayList();
segments = new ArrayList();
}
public void addPoint(final GeoPoint p) {
points.add(p);
}
public void addPoints(final List points) {
this.points.addAll(points);
}
public List getPoints() {
return points;
}
public void addSegment(final Segment s) {
segments.add(s);
}
public List getSegments() {
return segments;
}
public void setName(final String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setCopyright(String copyright) {
this.copyright = copyright;
}
public String getCopyright() {
return copyright;
}
public void setWarning(String warning) {
this.warning = warning;
}
public String getWarning() {
return warning;
}
public void setCountry(String country) {
this.country = country;
}
public String getCountry() {
return country;
}
public void setLength(int length) {
this.length = length;
}
public int getLength() {
return length;
}
public void setPolyline(String polyline) {
this.polyline = polyline;
}
public String getPolyline() {
return polyline;
}
}
Class XMLParser.java
public class XMLParser {
protected static final String MARKERS = "markers";
protected static final String MARKER = "marker";
protected URL url;
protected XMLParser(final String url) {
try {
this.url = new URL(url);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
protected InputStream getInputStream() {
try {
return url.openConnection().getInputStream();
} catch (IOException e) {
return null;
}
}
}

Class GoogleParser.java
Google không support KML data nữa vì thế class dùng để parse direction JSON data và decode polyline mà google trả về
public class GoogleParser extends XMLParser{
/** Distance covered. **/
private int distance;

public GoogleParser(String url) {
super(url);
}

/**
* Parses a url pointing to a Google JSON object to a Route object.
*
* @return a Route object based on the JSON object.
*/

public Route parse() {
final String result = convertStreamToString(this.getInputStream());
// Create an empty route
final Route route = new Route();
// Create an empty segment
final Segment segment = new Segment();
try {
// Tranform the string into a json object
final JSONObject json = new JSONObject(result);
// Get the route object
final JSONObject jsonRoute = json.getJSONArray(“routes”)
.getJSONObject(0);
// Get the leg, only one leg as we don’t support waypoints
final JSONObject leg = jsonRoute.getJSONArray(“legs”)
.getJSONObject(0);
// Get the steps for this leg
final JSONArray steps = leg.getJSONArray(“steps”);
// Number of steps for use in for loop
final int numSteps = steps.length();
// Set the name of this route using the start & end addresses
route.setName(leg.getString(“start_address”) + ” to ”
+ leg.getString(“end_address”));
// Get google’s copyright notice (tos requirement)
route.setCopyright(jsonRoute.getString(“copyrights”));
// Get the total length of the route.
route.setLength(leg.getJSONObject(“distance”).getInt(“value”));
// Get any warnings provided (tos requirement)
if (!jsonRoute.getJSONArray(“warnings”).isNull(0)) {
route.setWarning(jsonRoute.getJSONArray(“warnings”)
.getString(0));
}
/*
* Loop through the steps, creating a segment for each one and
* decoding any polylines found as we go to add to the route
* object’s map array. Using an explicit for loop because it is
* faster!
*/
for (int i = 0; i < numSteps; i++) {
// Get the individual step
final JSONObject step = steps.getJSONObject(i);
// Get the start position for this step and set it on the
// segment
final JSONObject start = step.getJSONObject(“start_location”);
final GeoPoint position = new GeoPoint(
(int) (start.getDouble(“lat”) * 1E6),
(int) (start.getDouble(“lng”) * 1E6));
segment.setPoint(position);
// Set the length of this segment in metres
final int length = step.getJSONObject(“distance”).getInt(
“value”);
distance += length;
segment.setLength(length);
segment.setDistance(distance / 1000);
// Strip html from google directions and set as turn instruction
segment.setInstruction(step.getString(“html_instructions”)
.replaceAll(“<(.*?)*>”, “”));
// Retrieve & decode this segment’s polyline and add it to the
// route.
route.addPoints(decodePolyLine(step.getJSONObject(“polyline”)
.getString(“points”)));
// Push a copy of the segment to the route
route.addSegment(segment.copy());
}
} catch (JSONException e) {
Log.e(e.getMessage(), “Google JSON Parser – ” + url);
}
return route;
}

/**
* Convert an inputstream to a string.
*
* @param input
* inputstream to convert.
* @return a String of the inputstream.
*/

private static String convertStreamToString(final InputStream input) {
final BufferedReader reader = new BufferedReader(new InputStreamReader(
input));
final StringBuilder sBuf = new StringBuilder();

String line = null;
try {
while ((line = reader.readLine()) != null) {
sBuf.append(line);
}
} catch (IOException e) {
Log.e(e.getMessage(), “Google parser, stream2string”);
} finally {
try {
input.close();
} catch (IOException e) {
Log.e(e.getMessage(), “Google parser, stream2string”);
}
}
return sBuf.toString();
}

/**
* Decode a polyline string into a list of GeoPoints.
*
* @param poly
* polyline encoded string to decode.
* @return the list of GeoPoints represented by this polystring.
*/

private List decodePolyLine(final String poly) {
int len = poly.length();
int index = 0;
List decoded = new ArrayList();
int lat = 0;
int lng = 0;

while (index < len) {
int b;
int shift = 0;
int result = 0;
do {
b = poly.charAt(index++) – 63;
result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;

shift = 0;
result = 0;
do {
b = poly.charAt(index++) – 63;
result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;

decoded.add(new GeoPoint((int) (lat * 1E6 / 1E5),
(int) (lng * 1E6 / 1E5)));
}
return decoded;
}
}
Class MapRouteOverlay.java
public class MapRouteOverlay extends Overlay {
Route mRoute;
ArrayList mPoints;

public MapRouteOverlay(Route route, MapView mv) {
mRoute = route;
if (route.getPoints().size() > 0) {
mPoints = new ArrayList();
mPoints = (ArrayList)route.getPoints();
int moveToLat = (mPoints.get(0).getLatitudeE6() + (mPoints.get(
mPoints.size() – 1).getLatitudeE6() – mPoints.get(0)
.getLatitudeE6()) / 2);
int moveToLong = (mPoints.get(0).getLongitudeE6() + (mPoints.get(
mPoints.size() – 1).getLongitudeE6() – mPoints.get(0)
.getLongitudeE6()) / 2);
GeoPoint moveTo = new GeoPoint(moveToLat, moveToLong);
MapController mapController = mv.getController();
mapController.animateTo(moveTo);
//mapController.setZoom(14);
}
}

@Override
public boolean draw(Canvas canvas, MapView mv, boolean shadow, long when) {
super.draw(canvas, mv, shadow);
drawPath(mv, canvas);
return true;
}

public void drawPath(MapView mv, Canvas canvas) {
int x1 = -1, y1 = -1, x2 = -1, y2 = -1;
Paint paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
for (int i = 0; i < mPoints.size(); i++) { Point point = new Point(); mv.getProjection().toPixels(mPoints.get(i), point); x2 = point.x; y2 = point.y; if (i > 0) {
canvas.drawLine(x1, y1, x2, y2, paint);
}
x1 = x2;
y1 = y2;
}
}
}
Class MyItemOverlay.java
public class MyItemOverlay extends ItemizedOverlay {
private ArrayList mOverlay = new ArrayList();
Context context;
private Drawable marker = null;
MapView mapView;
private Route mRoute;
double fromLat, fromLong;

public MyItemOverlay(Drawable defaultMarker) {
super(boundCenterBottom(defaultMarker));
this.marker = defaultMarker;
}

public MyItemOverlay(Drawable defaultMarker, Context context) {
super(boundCenterBottom(defaultMarker));
this.marker = defaultMarker;
this.context = context;
}

public void addOverlay(OverlayItem item) {
mOverlay.add(item);
populate();
}

@Override
protected OverlayItem createItem(int i) {
return mOverlay.get(i);
}

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

@Override
protected boolean onTap(int index) {
return true;
}

@Override
public boolean onTouchEvent(MotionEvent event, MapView mapView) {
final int action = event.getAction();
final int x = (int) event.getX();
final int y = (int) event.getY();
this.mapView = mapView;

if (action == MotionEvent.ACTION_DOWN) {
for (OverlayItem item : mOverlay) {
Point p = new Point(0, 0);
GeoPoint toPoint = mapView.getProjection().fromPixels(x, y);
mapView.getProjection().toPixels(item.getPoint(), p);
if (hitTest(item, marker, x – p.x, y – p.y)) {
//hardcode the curent latitude and longitude, in real world should get it from where we saved them
fromLat = 21.0325;
fromLong = 105.8492;
mRoute = getRoute(new GeoPoint((int)(fromLat*1E6),(int)(fromLong*1E6)), toPoint);
MapRouteOverlay mapOverlay = new MapRouteOverlay(mRoute,
mapView);
List listOfOverlays = mapView.getOverlays();
int size = listOfOverlays.size();
for (int i = 0; i < size; i++) {
if (listOfOverlays.get(i) instanceof MapRouteOverlay) {
listOfOverlays.remove(i);
}
}
listOfOverlays.add(mapOverlay);
mapView.invalidate();
}
}
}
return super.onTouchEvent(event, mapView);
}

private Route getRoute(final GeoPoint start, final GeoPoint dest) {
String jsonURL = “http://maps.googleapis.com/maps/api/directions/json?";
final StringBuffer sBuf = new StringBuffer(jsonURL);
sBuf.append(“origin=”);
sBuf.append(start.getLatitudeE6()/1E6);
sBuf.append(‘,’);
sBuf.append(start.getLongitudeE6()/1E6);
sBuf.append(“&destination=”);
sBuf.append(dest.getLatitudeE6()/1E6);
sBuf.append(‘,’);
sBuf.append(dest.getLongitudeE6()/1E6);
sBuf.append(“&sensor=true&mode=driving”);
GoogleParser parser = new GoogleParser(sBuf.toString());
Route r = parser.parse();
return r;
}
}
Mình sẽ không up source code lên vì hay nghịch bậy nên tài khoản và các file up lên thường bị xóa dẫn đến tình trạng link không down được🙂, do đó các bạn tự copy các đoạn code trên vào project và chạy thử nhé. Chúc các bạn vui vẻ!

Chuyên mục:Android

Serialize và Deserialize một object trong Android


Đôi lúc ta muốn serialize một object cùng trạng thái các thuộc tính của nó tại một thời điểm nhất định xuống một file và deserialize object đó để lấy được các thuộc tính của object tại thời điểm mà nó được lưu xuống file. Trong Android ta làm như sau:

public void writeObjectToFile(Context context, MyObject myObj){
File file = new File(context.getDir("data", Context.MODE_PRIVATE),"fileName");
ObjectOutputStream outputStream;
try {
outputStream = new ObjectOutputStream(new FileOutputStream(file));
outputStream.writeObject(myObj);
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public MyObject readObjectFromFile(Context context, String fileName){
MyObject myObj = null;
File file = new File(context.getDir("data", Context.MODE_PRIVATE), fileName);
InputStream instream = null;
try {
instream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
ObjectInputStream ois = new ObjectInputStream(instream);
try {
myObj = (MyObject) ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (StreamCorruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return myObj;
}

Chuyên mục:Android

Kiểm tra một thiết bị có phải là Android Tablet và lấy chiều rộng màn hình của một thiết bị Android


1. Code kiểm tra một thiết bị android có phải là Tablet không:

public boolean isTablet(Context context) {
boolean ret = false;
int screenLayout = context.getResources().getConfiguration().screenLayout;
ret = ((screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 4);
return ret;
}

2. Code lấy chiều rộng màn hình một thiết bị Android:

public int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int screenWidth = display.getWidth();
return screenWidth;
}

Chuyên mục:Android

Code kiểm tra trạng thái GPS trong Android


Dưới đây là đoạn code để kiểm tra trạng thái GPS trên một thiết bị Android:

LocationManager manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
boolean statusOfGPS = manager.isProviderEnabled(LocationManager.GPS_PROVIDER);

Lưu ý để kiểm tra được trạng thái GPS trên Android, ta cần khai báo android.permission.ACCESS_FINE_LOCATION trong file AndroidManifest.xml

Chuyên mục:Android

Kiểm tra tình trạng kết nối mạng trong Android


Đây là đoạn code dùng để kiểm tra tình trạng kết nối mạng trong Android:

public boolean isNetworkAvail(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isAvailable()
&& cm.getActiveNetworkInfo().isConnected()) {
return true;
}
return false;
}

Để đoạn code trên hoạt động, ta cần khai báo các permissions sau trong file AndroidManifest.xml:
android.permission.INTERNET
android.permission.ACCESS_NETWORK_STATE

Chuyên mục:Android

Cách lấy Universally unique identifier và User agent


1. Để lấy universally unique identifier (UUID) của một thiết bị chạy hệ điều hành Android, ta làm như sau:

public String getDeviceID() {
TelephonyManager tManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
String uuid = tManager.getSubscriberId();
return uuid;
}

2. Bạn cần tạo một http-based connection để gửi request tới một resource online nào đó và bạn muốn gửi kèm user-agent trong phần headers của request để giúp resource online kia nhận dạng được thiết bị đang gửi request tới nó và trả về cho bạn một response thích hợp nhất? Quá đen cho Sunderland. Bản thân mình đã từng phải loay hoay tìm cách lấy user-agent bằng code trong android, rất mất thời gian. Tuy nhiên bắt đầu từ Android 2.1 một thuộc tính trong đối tượng System đã được thêm vào để giúp cho việc lấy user-agent trong android trở nên rất đơn giản:

String userAgent = System.getProperty("http.agent");

Chuyên mục:Android

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ó😀. 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