Android AsyncTask update ListView notifyDataSetChanged when Items Load finish |
Android AsyncTask update ListView notifyDataSetChanged when Items Load finish ในการแสดงผลข้อมูลจาก Server บน ListView บน Android นั้นปกติถ้าใช้การดึงแบบปกติด้วยการใช้ BaseAdapter หรือ Adapter อื่น ๆ ตัว ListView จะต้องรอให้ข้อมูลทุกรายการโหลดให้เรียบร้อยก่อนที่จะแสดงผล ซึ่งถ้าอัตราการโหลดข้อมูลช้า โปรแกรมก็จะค้างไปซะพัก และรอจนกว่าโปรแกรมทำงานเสร็จสิ้นถึงจะแสดงผลออกมา แต่ในทางปฏิบัติจริง ๆ การที่จะให้ Application เรานั้นค้างไปแบบดื้อ ๆ ก็จะทำให้ผู้ใช้เกิดความเข้าใจผิดคิดว่าโปรแกรมค้างหรือแฮ้งก์ มองถึงขั้นการออกแบบและเขียนโปรแกรมไม่ดี เพราะฉะนั้นในระหว่างที่โปรแกรมทำงานอยู่ เราจึงควรใช้พวก ProgressBar เข้ามาแสดงผลหรือสถานะว่ากำลังทำงานหรือโหลดข้อมูล ซึ่งใน Android สามารถทำได้หลายวิธี เช่นการใช้ AsyncTask ทำงานเป็น Background โหลดข้อมูลอยู่เบื้องหลัง และรายการไหนที่โหลดเรียบร้อยแล้วก็จะแสดงผลออกทางหน้าจอทันที และวิธีนี้หลาย ๆ Application ที่เราใช้อยู่ก็ใช้หลักการเช่นนี้เดียวกัน
รูปอธิบายการทำงานและการแสดงผลข้อมูลของแต่ล่ะ Item ทันทีที่โหลดเรียบร้อยแล้ว
Android AsyncTask and ProgressBar
หลักการนั้นไม่ยาก คือในส่วนของ AsyncTask ซึ่งจะ Loop ข้อมูลจาก Server และอ่านภาพจาก Thumbnail นั้น เมื่อ Loop นั้น ๆ ทำงานเรียบร้อยแล้ว สามารถใช้คำสั่ง publishProgress(i); ซึ่งจะทำงานภายใต้ Method ของ
@Override
public void onProgressUpdate(Integer... progress) {
imageAdapter.notifyDataSetChanged();
}
จะเห็นว่ามีการใช้ notifyDataSetChanged() คือทำการ Update ข้อมูลไปยัง ListView ส่วน Code ทั้งหมดลองทำความเข้าใจได้จากในส่วน Code ของ Java ด้านล่าง
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
ในการเขียน Android เพื่อติดต่อกับ Internet จะต้องกำหนด Permission ในส่วนนี้ด้วยทุกครั้ง
Web Server
images
CREATE TABLE IF NOT EXISTS `images` (
`ImageID` int(11) NOT NULL auto_increment,
`ImageName` varchar(50) NOT NULL,
`ImagePath_Thumbnail` varchar(150) NOT NULL,
`ImagePath_FullPhoto` varchar(150) NOT NULL,
PRIMARY KEY (`ImageID`)
) ENGINE=MyISAM AUTO_INCREMENT=7 ;
--
-- Dumping data for table `images`
--
INSERT INTO `images` (`ImageID`, `ImageName`, `ImagePath_Thumbnail`, `ImagePath_FullPhoto`) VALUES
(1, 'Image 1', 'https://www.thaicreate.com/android/img1_thum.jpg', 'https://www.thaicreate.com/android/img1_full.jpg'),
(2, 'Image 2', 'https://www.thaicreate.com/android/img2_thum.jpg', 'https://www.thaicreate.com/android/img2_full.jpg'),
(3, 'Image 3', 'https://www.thaicreate.com/android/img3_thum.jpg', 'https://www.thaicreate.com/android/img3_full.jpg'),
(4, 'Image 4', 'https://www.thaicreate.com/android/img4_thum.jpg', 'https://www.thaicreate.com/android/img4_full.jpg'),
(5, 'Image 5', 'https://www.thaicreate.com/android/img5_thum.jpg', 'https://www.thaicreate.com/android/img5_full.jpg'),
(6, 'Image 6', 'https://www.thaicreate.com/android/img6_thum.jpg', 'https://www.thaicreate.com/android/img6_full.jpg');
โครงสร้างของ MySQL Database ที่อยู่บน Web Server โดยใน Table จะเห็นว่ามี Path ไฟล์ที่จัดเก็บรูปภาพและอยู่บน Server
getJSON.php
<?
$objConnect = mysql_connect("localhost","root","root");
$objDB = mysql_select_db("mydatabase");
$strSQL = "SELECT * FROM images WHERE 1 ";
$objQuery = mysql_query($strSQL);
$intNumField = mysql_num_fields($objQuery);
$resultArray = array();
while($obResult = mysql_fetch_array($objQuery))
{
$arrCol = array();
for($i=0;$i<$intNumField;$i++)
{
$arrCol[mysql_field_name($objQuery,$i)] = $obResult[$i];
}
array_push($resultArray,$arrCol);
}
mysql_close($objConnect);
echo json_encode($resultArray);
?>
ไฟล์ php ที่จะส่งข้อมูลให้กับ Android ขั้นตอนนั้น Android จะทำการ Request ไปยัง Web Server เพื่อดึงรายการข้อมูลทั้งหมด โดย Server จะส่งค่ากลับไปในรูปแบบของ JSON และส่งเฉพาะข้อความเท่านั้น ส่วนรูปภาพ เมื่อแสดงจะทำการ Request หลังจากที่ได้แปลง JSON เรียบร้อยแล้ว โดยจะอ่านรูปภาพทีล่ะ Item และแสดงผลออกทาง ListView ทันที
Android Project
โครงสร้างของไฟล์ประกอบด้วย 3 ไฟล์คือ MainActivity.java, activity_main.xml และ activity_column.xml
activity_main.xml
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tableLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow
android:id="@+id/tableRow1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Download and ProgressBar : "
android:layout_span="1"
android:textAppearance="?android:attr/textAppearanceMedium" />
</TableRow>
<View
android:layout_height="1dip"
android:background="#CCCCCC" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.1">
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
<View
android:layout_height="1dip"
android:background="#CCCCCC" />
<LinearLayout
android:id="@+id/LinearLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dip" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="By.. ThaiCreate.Com" />
</LinearLayout>
</TableLayout>
activity_column.xml
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tableLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/ColImgPath"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/ColImgID"
android:text="Column 1" />
<TextView
android:id="@+id/ColImgName"
android:text="Column 2" />
</TableRow>
</TableLayout>
MainActivity.java
package com.myapp;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
private ListView lstView;
private ImageAdapter imageAdapter;
ArrayList<HashMap<String, Object>> MyArrList = new ArrayList<HashMap<String, Object>>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ProgressBar
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main);
// ListView and imageAdapter
lstView = (ListView) findViewById(R.id.listView1);
lstView.setClipToPadding(false);
imageAdapter = new ImageAdapter(getApplicationContext());
lstView.setAdapter(imageAdapter);
setProgressBarIndeterminateVisibility(true);
new LoadContentFromServer().execute();
}
class LoadContentFromServer extends AsyncTask<Object, Integer, Object> {
@Override
protected Object doInBackground(Object... params) {
String url = "https://www.thaicreate.com/android/getJSON.php";
JSONArray data;
try {
data = new JSONArray(getJSONUrl(url));
MyArrList = new ArrayList<HashMap<String, Object>>();
HashMap<String, Object> map;
for(int i = 0; i < data.length(); i++){
JSONObject c = data.getJSONObject(i);
map = new HashMap<String, Object>();
map.put("ImageID", (String)c.getString("ImageID"));
map.put("ImageName", (String)c.getString("ImageName"));
// Thumbnail Get ImageBitmap To Object
map.put("ImagePathThum", (String)c.getString("ImagePath_Thumbnail"));
Bitmap newBitmap = loadBitmap(c.getString("ImagePath_Thumbnail"));
map.put("ImageThumBitmap", newBitmap);
// Full (for View Popup)
//map.put("ImagePathFull", (String)c.getString("ImagePath_FullPhoto"));
MyArrList.add(map);
publishProgress(i);
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
@Override
public void onProgressUpdate(Integer... progress) {
imageAdapter.notifyDataSetChanged();
}
@Override
protected void onPostExecute(Object result) {
setProgressBarIndeterminateVisibility(false); // When Finish
}
}
class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context context) {
mContext = context;
}
public int getCount() {
return MyArrList.size();
}
public Object getItem(int position) {
return MyArrList.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(R.layout.activity_column, null);
}
// ColImage
ImageView imageView = (ImageView) convertView.findViewById(R.id.ColImgPath);
imageView.getLayoutParams().height = 80;
imageView.getLayoutParams().width = 80;
imageView.setPadding(5, 5, 5, 5);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
try
{
imageView.setImageBitmap((Bitmap)MyArrList.get(position).get("ImageThumBitmap"));
} catch (Exception e) {
// When Error
imageView.setImageResource(android.R.drawable.ic_menu_report_image);
}
// ColImgID
TextView txtImgID = (TextView) convertView.findViewById(R.id.ColImgID);
txtImgID.setPadding(10, 0, 0, 0);
txtImgID.setText("ID : " + MyArrList.get(position).get("ImageID").toString());
// ColImgName
TextView txtPicName = (TextView) convertView.findViewById(R.id.ColImgName);
txtPicName.setPadding(50, 0, 0, 0);
txtPicName.setText("Name : " + MyArrList.get(position).get("ImageName").toString());
return convertView;
}
}
/*** Get JSON Code from URL ***/
public String getJSONUrl(String url) {
StringBuilder str = new StringBuilder();
HttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse response = client.execute(httpGet);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == 200) { // Download OK
HttpEntity entity = response.getEntity();
InputStream content = entity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(content));
String line;
while ((line = reader.readLine()) != null) {
str.append(line);
}
} else {
Log.e("Log", "Failed to download file..");
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return str.toString();
}
/***** Get Image Resource from URL (Start) *****/
private static final String TAG = "Image";
private static final int IO_BUFFER_SIZE = 4 * 1024;
public static Bitmap loadBitmap(String url) {
Bitmap bitmap = null;
InputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(new URL(url).openStream(), IO_BUFFER_SIZE);
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
out = new BufferedOutputStream(dataStream, IO_BUFFER_SIZE);
copy(in, out);
out.flush();
final byte[] data = dataStream.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
//options.inSampleSize = 1;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length,options);
} catch (IOException e) {
Log.e(TAG, "Could not load Bitmap from: " + url);
} finally {
closeStream(in);
closeStream(out);
}
return bitmap;
}
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
android.util.Log.e(TAG, "Could not close stream", e);
}
}
}
private static void copy(InputStream in, OutputStream out) throws IOException {
byte[] b = new byte[IO_BUFFER_SIZE];
int read;
while ((read = in.read(b)) != -1) {
out.write(b, 0, read);
}
}
/***** Get Image Resource from URL (End) *****/
}
Screenshot
แสดง ProgressBar ในส่วนของ Title Bar ในขณะที่กำลังโหลดข้อมูล
จะเห็นว่า Item ไหนที่โหลดเรียบร้อยแล้วก็จะแสดงผลทันที
แสดง Item ที่ได้โหลดข้อมูลเรียบร้อยแล้ว
เมื่อโหลดข้อมูลครบทุกตัว ProgressBar ก็จะหายไป
.
|