Bài tập 19: Thực hành Gridview trong Android


– Bài tập này bạn sẽ làm quen với control Gridview, cũng tương tự như ListView. Gridview cũng dựa vào Datasource và ArrayAdapter. ListView bạn làm như thế nào thì Gridview y xì.

– Điểm khác nhau là GridView có thiết lập số cột. Dữ liệu luôn đưa vào dưới dạng hình ống (mảng, list một chiều), nhưng dựa vào số cột ta thiết lập mà nó tự động ngắt hàng, xem hình minh họa:

19_grid_0– Bạn thấy đấy, Tôi có thể hiển thị Text hoặc hình ảnh vào GridView.

– Bạn có thể thiết lập số cột cho GridView theo hình dưới đây:

19_grid_1– Nếu bạn thiết lập android:numColumns=”3″,  Tức là Gridview sẽ ngắt dòng khi đủ 3 phần tử, nó chỉ khác chỗ này, còn việc đưa dữ liệu lên như thế nào thì nó y xì như làm với ListView.

– Ví dụ 1: Hiển thị văn bản lên GridView (xem hình Tôi đánh số 1):

– Bạn tạo một Android Project tên tùy thích, ở đây Tôi đặt tên: Vidu_Gridview_Text

– Đây là activity_main.xml cho ứng dụng:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:id="@+id/LinearLayout1"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 tools:context=".MainActivity" >
 <TextView
 android:id="@+id/selection"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:background="#8A9D6F"
 android:hint="Slected here" />
 <GridView
 android:id="@+id/gridView1"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:numColumns="3" >
 </GridView>
</LinearLayout>

– Bạn xem dòng 15 là id của GridView, Tôi để mặc định gridView1

– Dòng 18 có thuộc tính: android:numColumns= “3”, tức là dữ liệu sẽ được hiển thị trong Gridview với định dạng 3 cột.

– Tiếp theo bạn xem MainActivity.java:

package tranduythanh.com;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.TextView;

public class MainActivity extends Activity {

//Dùng mảng 1 chiều hoặc ArrayList để lưu một số dữ liệu
 String arr[]={"Ipad","Iphone","New Ipad",
 "SamSung","Nokia","Sony Ericson",
 "LG","Q-Mobile","HTC","Blackberry",
 "G Phone","FPT - Phone","HK Phone"
 };
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 //Tối tượng này dùng để hiển thị phần tử được chọn trong GridView
 final TextView selection=(TextView)
 findViewById(R.id.selection);
 final GridView gv=(GridView) findViewById(R.id.gridView1);
 //Gán DataSource vào ArrayAdapter
 ArrayAdapter<String>da=new ArrayAdapter<String>
 (
 this,
 android.R.layout.simple_list_item_1,
 arr
 );
 //gán Datasource vào GridView
 gv.setAdapter(da);
 //Thiết lập sự kiện cho GridView,
 gv.setOnItemClickListener(new AdapterView
 .OnItemClickListener() {
 public void onItemClick(AdapterView<?> arg0,
 View arg1, int arg2,
 long arg3) {
 //Hiển thị phần tử được chọn trong GridView lên TextView
 selection.setText(arr[arg2]);
 }
 });
 }
}

– Bạn thấy đó, cách sử dụng GridView là y xì như ListView, nên nếu như bạn đã rành về ListView rồi thì GridView hiển nhiên bạn cũng làm tốt.

– Thực thi chương trình bạn sẽ thấy như hình bên dưới:

19_grid_3

– Xem coding đầy đủ ở đây: http://www.mediafire.com/?v3ww92ys1s5jth0

– Ví dụ 2 sẽ phức tạp hơn, hiển thị danh sách hình ảnh có sẵn lên GridView, mỗi lần chọn vào hình ảnh nào thì sẽ hiển thị chi tiết ảnh đó trên một màn hình mới:

– Có thể Demo này đã có nhiều website và Ebooks làm rồi, ở đây Tôi cũng muốn demo lại cho các bạn.

– Bạn xem giao diện của ứng dụng trước:

19_grid_5– Bên trái là màn hình chính cho phép hiển thị danh sách hình ảnh vào GridView, mỗi lần chọn vào từng hình trong GridView thì sẽ mở hình được chọn đó vào một màn hình mới (ví dụ như khi chọn hình chú Cừu thì nó sẽ hiển thị ra như màn hình bên phải ), nhấn nút Back để trở về màn hình chính.

– Ở đây có một sự khác biệt lớn đó là chúng ta chỉ sử dụng 1 MainActivity, không hề tạo thêm bất kỳ một Activity nào khác, chúng ta chỉ thay đổi Layout mà thôi. Nên nó cũng là điểm nhấn của ứng dụng.

-Hãy tạo một Android Project tên: Vidu_Gridview_DisplayImage và xem cấu trúc của chương trình:

19_grid_4-Layout sẽ có 2 cái: activity_main.xml là của màn hình chính dùng để hiển thị danh sách hình ảnh. solo_picture.xml là dùng để hiển thị từng hình riêng lẻ.

– Tạo thêm thư mục drawble và kéo thả một số hình ảnh vào.

–  Phần class có 2 class: MainActivity và MyImageAdapter kế thừa từ BaseAdapter.

– Bây giờ ta đi vào chi tiết của từng cái:

– activity_main.xml:

19_grid_6– Bạn có thể nhìn vào hình trên để làm hoặc tải coding mẫu ở bên dưới.

-solo_picture.xml:

19_grid_7– Bây giờ ta vào các class xử lý nghiệp vụ:

19_grid_8– Thứ nhất là class MyImageAdapter:

+ class này sẽ kế thừa từ BaseAdapter, và dùng để hiển thị từng hình ảnh riêng lẻ:

package tranduythanh.com;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
/**
 * Class dùng để hiển thị từng hình ảnh riêng lẻ
 * @author drthanh
 *
 */
public class MyImageAdapter extends BaseAdapter {
 private Context mContext;
 private Integer []mThumbIds;
 public MyImageAdapter(Context c){
 mContext=c;
 }
 public MyImageAdapter(Context c,Integer []arrIds){
 mContext=c;
 mThumbIds=arrIds;
 }
 public int getCount()
 {
 return mThumbIds.length;
 }
 public Object getItem(int arg0)
 {
 return null;
 }
 public long getItemId(int arg0)
 {
 return 0;
 }
 /**
 * Cần override lại hàm này để hiển thị hình ảnh
 */
 public View getView(int arg0, View convertView, ViewGroup arg2) {
 ImageView imgView;
 if(convertView==null){
 imgView=new ImageView(mContext);
 //can chỉnh lại hình cho đẹp
 imgView.setLayoutParams(new GridView.LayoutParams(85, 85));
 imgView.setScaleType(ImageView.ScaleType.CENTER_CROP);
 imgView.setPadding(8, 8, 8, 8);
 }else{
 imgView=(ImageView) convertView;
 }
 //lấy đúng vị trí hình ảnh được chọn
 //gán lại ImageResource
 imgView.setImageResource(mThumbIds[arg0]);
 return imgView;
 }
}

– Thứ hai là class MainActivity:


package tranduythanh.com;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

public class MainActivity extends Activity
 implements OnItemClickListener
{
 TextView tvMsg;
 GridView gv;
 TextView tvSoloMsg;
 //mảng lưu danh sách các id hình ảnh có sẵn
 Integer[]mThumbIds;
 //Adapter cho GridView
 MyImageAdapter adapter=null;
 //Vì không tạo thêm 1 Activity nên lấy luôn 2 Id ở bên solo_picture.xml
 ImageView ivSoloPicture;
 Button btnBack;
 //Lưu Bundle backup cho MainActivity
 Bundle myBackupBundle;
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 //Lưu savedInstanceState trước vào myBackupBundle
 myBackupBundle=savedInstanceState;
 setContentView(R.layout.activity_main);
 tvMsg=(TextView) findViewById(R.id.tvMsg);
 //gán mảng các Id hình ảnh cho mThumbIds
 mThumbIds=new Integer[]{R.drawable.image1,R.drawable.image2,
 R.drawable.image3,R.drawable.image4,
 R.drawable.image5,R.drawable.image6,
 R.drawable.ic_launcher,R.drawable.lifecycle};
 gv=(GridView) findViewById(R.id.gridView1);
 //thiết lập Datasource cho Adapter
 adapter=new MyImageAdapter(this, mThumbIds);
 //gán Adapter vào Gridview
 gv.setAdapter(adapter);
 //thiết lập sự kiện để mở từng hình ảnh chitiết
 gv.setOnItemClickListener(this);
 }
 public void onItemClick(AdapterView<?> arg0,
 View arg1, int arg2, long arg3) {
 //gọi hàm xem hình ảnh chi tiết tại ví trí thứ arg2
 showdetail(arg2);
 }
 public void showdetail(int posistion)
 {
 //Không mở Activity mới mà chỉ thiết lập lại Layout
 setContentView(R.layout.solo_picture);
 //Vừa gọi hàm trên thì màn hình sẽ thay đổi qua cái mới
 //ta lấy các control trong layout mới này
 tvSoloMsg=(TextView) findViewById(R.id.tvSoloMsg);
 tvSoloMsg.setText("Image at "+posistion);
 ivSoloPicture=(ImageView) findViewById(R.id.imgSolo);
 //thiết lập hình ảnh đang chọn lên ImageView mới
 ivSoloPicture.setImageResource(mThumbIds[posistion]);
 btnBack=(Button) findViewById(R.id.btnBack);
 //Thiết lập sự kiện click Back để phục hồi lại MainActivity
 //bằng cách gọi lại onCreate(myBackupBundle);
 btnBack.setOnClickListener(new View.OnClickListener() {
 public void onClick(View arg0) {
 onCreate(myBackupBundle);
 }
 });
 }
}

– Khởi chạy ứng dụng bạn sẽ có kết quả như mong muốn.

– Bài tập này bạn đã biết cách sử dụng GridView, biết cách đưa hình ảnh vào GridView, đặc biệt biết thêm một kỹ thuật mới đó là thay đổi Layout để đổi màn hình không cần chạy Activity.

– Ngoài ra còn biết thêm về BaseAdapter.

– Bạn có thể tải code mẫu đầy đủ ở đây: http://www.mediafire.com/?3o2jva4mzgp2kj8

– Bài tập tiếp theo bạn sẽ được thực hành về TimePickerDialog DatePickerDialog, 2 control này cũng đáng phải lưu tâm vì nó cũng dược sử dụng thường xuyên trong Android.

– Chúc các bạn thành công

14 responses

  1. Kính chào Thầy !
    Em đang làm đến phần đọc file .csv lên rùi pasre (write) sang file dạng json, em làm được nhưng theo em nghĩ làm cách của em thì chưa tối ưu lắm, code của em:

    public void readfilecsv_writeparsefilejson() throws Exception
    {
    AssetManager am = getAssets();
    InputStream fs;
    try
    {
    fs = am.open(“hoadon.csv”);
    InputStreamReader tmp = new InputStreamReader(fs);
    BufferedReader reader = new BufferedReader(tmp);
    String str;
    StringBuffer buf = new StringBuffer();

    FileWriter file = new FileWriter(“/sdcard/json.txt”);

    file.write(“{“);
    file.write(“\”Hoa Don\”:”);
    file.write(“[“);
    while ((str = reader.readLine()) != null) {
    JSONObject tempObj = createJSON(str);
    file.write(tempObj.toString()+”,”);
    file.flush();
    buf.append(str+”\n”);
    }
    file.write(“]”);
    file.write(“}”);
    file.close();
    fs.close();
    String[] b = buf.toString().split(“,”);
    setListAdapter(new ArrayAdapter(this,android.R.layout.simple_list_item_1, b));
    }
    catch (IOException e){
    // TODO Auto-generated catch block
    e.printStackTrace();
    Toast.makeText(myContext, “Không thể đọc file…?” + e.toString(), 8000).show();
    }
    }

    private JSONObject createJSON(String str)throws Exception
    {
    String[] arr = str.split(“,”);
    JSONObject result = new JSONObject();
    result.put(“Ma SP”, arr[0]);
    result.put(“Ten SP”, arr[1]);
    result.put(“So luong SP”, arr[2]);
    result.put(“Gia tien SP”, arr[3]);

    ArrayList obj = new ArrayList();
    obj.add(result);
    System.out.println(obj);
    return result;
    }

    *Khó khăn của em là ghi file dạng json nó không đúng với cú pháp của json, không biết cách ghi file của em có đúng không nữa…Mong thầy giúp đỡ ?!*
    Cảm ơn thầy vì tất cả…!

    1. bạn sử dụng gson là cách nhanh và tối ưu nhất đó

  2. Chào Thầy Thanh,
    Em cảm ơn bài viết rất hay của thầy nhưng có thắc mắc khi tìm hiểu sâu vào phần hàm getView(int, view, viewgroup), Em không tìm ra được cái gì đã tạo nên sự kiện để gọi hàm getView này. Kính mong thầy giải đáp giùm.
    Em đã tra đến tận cùng mã nguồn của các file java sdk nhưng không tìm ra được
    Gởi đến để các bạn tham khảo các file mã nguồn và có cao kiến chỉ bảo:
    http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.2.2_r1/android/widget/Adapter.java#Adapter.getView%28int%2Candroid.view.View%2Candroid.view.ViewGroup%29
    http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.2.2_r1/android/widget/GridView.java#GridView.setAdapter%28android.widget.ListAdapter%29

    Cảm ơn và chúc thầy làm việc vui vẽ

    1. Trong trường hợp custom layout thì ta đã tạo 1 Adapter kế thừa từ BaseAdapter.
      Lúc kế thừa, muốn hiển thị dữ liệu theo ý muốn thì override lại hàm getView. Nó sẽ tự động triệu gọi hàm này khi có sự thay đổi dữ
      liệu trong data source.

      Em xem lại lệnh setAdapter (truyền vào Adapter của ta kế thừa từ BaseAdapter). Đó là lúc ta gán Adapter của ta vào ListView/Gridview. Nó sẽ tự động triệu gọi hàm getView khi có sự thay đổi dữ liệu trong data source

  3. Thưa thầy em đang làm cái gridview , trên gridview có cái Edittext, khi dữ liệu thay đổi thì mình cập nhật CSDL nhưng tring sự kiện:

    editTextValue.addTextChangedListener(new TextWatcher() {

    public void beforeTextChanged(CharSequence charSequence, int i,
    int i1, int i2) {
    int k = 1;
    k = k + 2;
    }

    public void onTextChanged(CharSequence charSequence, int i,
    int i1, int i2) {
    int k = 1;
    k = k + 2;
    }

    public void afterTextChanged(Editable editable) {

    int k = 1;
    k = k + 2;

    //IDToUpdate = IDValues[12];
    if (editable.length() > 0) {
    if ((Integer.parseInt(editable.toString().trim())) > 0) {
    //dataBucket.setSomeData(editable.toString());
    TextToUpdate = editable.toString();
    if ((Integer.parseInt(TextToUpdate) 0) {
    new ConnectionToUpdate().execute();
    }
    }
    }
    // dataBucket.setSomeData(editable.toString());
    // getItem(position).setSomeData(editable.toString());
    }
    });

    em không biết cách nào để lấy được cái position., nó chỉ lấy được dòng đầu tiên, có phải gridview không phù hợp cho yêu cầu này

    Nguyên file của em là:

    package hu.android.adapter;

    import org.ksoap2.SoapEnvelope;
    import org.ksoap2.serialization.SoapObject;
    import org.ksoap2.serialization.SoapSerializationEnvelope;
    import org.ksoap2.transport.HttpTransportSE;

    import android.content.Context;
    import android.os.AsyncTask;
    import android.text.Editable;
    import android.text.TextWatcher;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.EditText;
    import android.widget.ImageView;
    import android.widget.TextView;

    import com.example.humanaandroidapp.R;

    public class GridAdapter extends BaseAdapter {
    private Context context;
    private final String[] IDValues;
    private final String[] mobileValues;
    private final String[] NumberValues;
    private final String[] ValuesNumber;
    String TextToUpdate = “”;
    String IDToUpdate = “”;

    // CShap WebServices
    private static final String SOAP_ACTION = “http://www.hotels24.vn/UpdateTest”;
    private static final String OPERATION_NAME = “UpdateTest”; //
    private static final String WSDL_TARGET_NAMESPACE = “http://www.hotels24.vn/”;
    private static final String SOAP_ADDRESS = “http://www.hotels24.vn/AndroidService.asmx”;

    public GridAdapter(Context context, String[] IDValues,
    String[] mobileValues, String[] NumberValues, String[] ValuesNumber) {
    this.context = context;
    this.IDValues = IDValues;
    this.mobileValues = mobileValues;
    this.NumberValues = NumberValues;
    this.ValuesNumber = ValuesNumber;
    }

    public View getView(int position, View convertView, ViewGroup parent) {

    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View gridView;

    // gridView.setClickable(true);

    /*
    * gridView.setOnClickListener(new OnClickListener() {
    *
    * @Override public void onClick(View v) { String s = “test”;
    * Toast.makeText(myContext, s, Toast.LENGTH_SHORT).show(); } });
    */

    if (convertView == null) {
    gridView = new View(context);
    // get layout from mobile.xml
    gridView = inflater.inflate(R.layout.grid_item, null);

    // set value into textview
    IDToUpdate = IDValues[position];
    final String ValueToEdit = NumberValues[position];
    TextView textView = (TextView) gridView
    .findViewById(R.id.grid_item_label);
    textView.setText(mobileValues[position] + ” – Còn: ” + ValueToEdit
    + ” – bán: “);

    // set image based on selected text
    ImageView imageView = (ImageView) gridView
    .findViewById(R.id.grid_item_image);
    String mobile = mobileValues[position];

    // set value into EditText
    // EditText editText = (EditText) gridView
    // .findViewById(R.id.grid_item_edit);
    // editText.setText(NumberValues[position]);

    // set value into EditText
    EditText editTextValue = (EditText) gridView
    .findViewById(R.id.grid_edit_item);

    //final DataBucket dataBucket = (DataBucket) getItem(position);

    editTextValue.addTextChangedListener(new TextWatcher() {

    public void beforeTextChanged(CharSequence charSequence, int i,
    int i1, int i2) {
    int k = 1;
    k = k + 2;
    }

    public void onTextChanged(CharSequence charSequence, int i,
    int i1, int i2) {
    int k = 1;
    k = k + 2;
    }

    public void afterTextChanged(Editable editable) {

    int k = 1;
    k = k + 2;

    //IDToUpdate = IDValues[12];
    if (editable.length() > 0) {
    if ((Integer.parseInt(editable.toString().trim())) > 0) {
    //dataBucket.setSomeData(editable.toString());
    TextToUpdate = editable.toString();
    if ((Integer.parseInt(TextToUpdate) 0) {
    new ConnectionToUpdate().execute();
    }
    }
    }
    // dataBucket.setSomeData(editable.toString());
    // getItem(position).setSomeData(editable.toString());
    }
    });

    editTextValue.setText(ValuesNumber[position]);

    if (mobile.equals(“Windows”)) {
    imageView.setImageResource(R.drawable.windows_logo);
    } else if (mobile.equals(“iOS”)) {
    imageView.setImageResource(R.drawable.ios_logo);
    } else if (mobile.equals(“Blackberry”)) {
    imageView.setImageResource(R.drawable.blackberry_logo);
    } else {
    imageView.setImageResource(R.drawable.android_logo);
    }

    } else {
    gridView = (View) convertView;
    }
    return gridView;
    }

    public static class DataBucket {
    private String someData;

    public String getSomeData() {
    return someData;
    }

    public void setSomeData(String someData) {
    this.someData = someData;
    }
    }

    @Override
    public int getCount() {
    return mobileValues.length;
    }

    @Override
    public Object getItem(int position) {
    return null;
    }

    @Override
    public long getItemId(int position) {
    return position;
    }

    public class ConnectionToUpdate extends AsyncTask {

    public void onPreExecute() {
    super.onPreExecute();
    }

    @Override
    protected String doInBackground(Void… arg0) {

    String response = “1”;

    SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE,
    OPERATION_NAME);

    request.addProperty(“Number”, TextToUpdate);

    request.addProperty(“IDStaff”, 1463);
    request.addProperty(“ID”, IDToUpdate);

    SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
    SoapEnvelope.VER11);

    envelope.setOutputSoapObject(request);
    envelope.dotNet = true;
    HttpTransportSE httpTransport = new HttpTransportSE(SOAP_ADDRESS);
    httpTransport.debug = true;

    try {
    // call SoapObject
    httpTransport.call(SOAP_ACTION, envelope);

    } catch (Exception exception) {

    exception.printStackTrace();
    }

    return response;
    }

    // @Override
    public void onPostExecute(String resd) {
    try {

    } catch (Exception exception) {
    // tvData1.setText(exception.toString() +
    // ” Or enter number is not Available!”);
    }
    }
    }

    }

  4. […] Bài tập 19: Thực hành Gridview trong AndroidIn “2. Xử lý giao diện người dùng” […]

  5. thưa thầy em có 100 cái ảnh bitmap, lưu trong drawble , em hiển thị lên girdview và khi cuộn xuống dưới thì xảy ra lỗi tràn bộ nhớ OutOfMemoryError . Vậy làm cách nào để giải quyết vấn đề trên? Mà trong bài của thầy không thấy dùng kỹ thuật ViewHolder.

    1. Em đừng load hết, mà kiểm tra vị trí scroll… khi nào tới dòng cuối rồi tiến hành load tiếp.

  6. thầy ơi! khi ta bấm vào 1 item hình để hiện chi tiết sang form solo_picture, nếu có nút back về gridview rồi thì nếu ta muốn có nút next hoặc previous để chuyển sang item khác thì code như thế nào ạ?
    nghĩa là vẫn trong solo_picture..mà từ item này sang item kia ấy!

    1. Nam bạn làm được câu hỏi trên chưa mình dựa theo câu hỏi bạn làm thử và đã có đc kết quả rồi mình không biết tối ưu chưa vì mình còn gà lắm

      //Đầu tiên bạn thêm 2 cái imageButton vào layout load ảnh nha. rồi khai báo id cho nó.
      btnTiep = (ImageButton) findViewById(R.id.btnTiep);
      btnTiep.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View arg0) {
      // TODO Auto-generated method stub
      if(arg2 0){
      showdetail(arg2-1);
      }
      }
      });

  7. thưa thầy , bây giờ em muốn khi hiện thị hình ảnh lên có kèm theo thông tin của ảnh theo kiểu text thì em phải làm như thế nào ạ ?

  8. thưa thầy, e gặp lỗi ở phần button back gọi hàm oncreate thì ứng dụng bị crash và báo lỗi ở dòng super.onCreate và dòng gọi hàm onCreate trong onClick, có cách nào sửa lỗi này không ạ

    1. Em xem Logcat nó báo lỗi om sòm là gì vậy em? dựa vào đây để sửa em nhé

  9. Thưa thầy em muốn khi click vào mỗi item thì nó sẽ chuyển sang một activity khác tương ứng với item đó thì phải làm sao ạ

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.