你還在用notifyDataSetChanged?

  想到發這篇帖子是源於個人上一篇帖子 #Testin杯#多線程斷點續傳後臺下載  。
帖子中講述的項目使用了listView這個控件,並且自定義了adapter。在更新item的進度條時發現每次使用notifyDataSetChanged(),都會去調用自定義adapter中的getView方法。這時問題就出現了,用notifyDataSetChanged方法去更新listView中的item,是更新須要更新的Item呢?仍是更新全部的item呢?若是是更新全部的item那麼效率不就會很低嗎?有什麼辦法能夠解決這個問題呢?
  懷着心中的疑惑,我開始了此次的實驗。。。
  個人想法很簡單現實模擬遠程下載文件,建立一個Activity作主界面,主界面採用listView。而後自定義一個adapter實現BaseAdapter,再建立一個線程類,線程類當中採用循環的方式不斷的往adapter發送消息.而後使用notifyDataSetChanged方法更新界面,在調用getView方法時在控制檯輸出語句,這樣我就能夠知道notifyDatatSetChanged方法執行時是更新一個item仍是更新全部的item了。

  有了思路就好辦了,咱們先創建一個類,叫FileState。 html

public class FileState 
{
String fileName;//文件名字
int completeSize;//完成的長度
boolean state;//文件狀態,true爲已經完成,false爲未完成
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public int getCompleteSize() {
return completeSize;
}
public void setCompleteSize(int completeSize) {
this.completeSize = completeSize;
}
public boolean isState() {
return state;
}
public void setState(boolean state) {
this.state = state;
}

}
這 個類中有3個屬性,分別是文件名字,文件已經下載的長度,還有文件當前的狀態。而後就是get與set方法。這個類的做用我想你們應該一眼就明白了,沒 錯,既然使用了listView,我在主界面就想着要定義一個List,這個list當中的內容固然就是咱們FileState啦。
  接着咱們去實現主界面。建立主界面MainActivity

package edu.notify.viking.activity;

import java.util.ArrayList;
import java.util.List;

import edu.notify.viking.adapter.MyAdapter;
import edu.notify.viking.entity.FileState;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

public class MainActivity extends Activity 
{
private List<FileState> list=new ArrayList<FileState>();
private ListView listView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initFileState();//先對FileState進行初始化
initUI();//對界面進行初始化
}

/**
* 把數據放進list中,由於是測試因此我手工添加數據
* **/
private void initFileState()
{
//給FileState賦值
for(int i =1;i<8;i++)
{
FileState fileState=new FileState();
fileState.setFileName(i+".mp3");//名字
fileState.setCompleteSize(100);//初始化下載程度
fileState.setState(true);
list.add(fileState);
}
FileState f=new FileState();
f.setFileName("8.mp3");
f.setCompleteSize(0);
f.setState(false);
list.add(f);
}

private void initUI()
{
listView = (ListView)this.findViewById(R.id.listview);
MyAdapter adapter = new MyAdapter(list,this);
listView.setAdapter(adapter);
adapter.setListView(listView);
}
}
因 爲是模擬,因此主界面很簡單,只有2個屬性,一個List<FileState>,這裏面的內容用來顯示到listview上,還有一個就是 我們的ListView啦。我們先對FileState進行初始化,不然就沒內容顯示。我使用了一個循環,把文件的名字取爲1.mp3---7.mp3, 這7個文件的狀態都是true,也就是已經下載完成。而後單獨的初始化了8.mp3這個文件,這個文件完成度爲0,狀態也是false,我這麼作的目的相 信聰明的你,已經明白了。按照正常的邏輯,咱們去更新界面,這些已經下載完成的文件,咱們就沒有必要再讓他們去更新,只用更新正在下載中的文件就能夠了。 可是使用notifyDatatSetChanged方法真的能知足咱們的需求嗎?
  接着我建立了自定義的adapter,並將他與listView綁定在一塊兒。而後將listView傳進了adapter中。
  那咱們來看看adapter中是如何實現的吧。新建一個adapter,取名叫MyAdatper繼承BaseAdapter.
package edu.notify.viking.adapter;

import java.util.List;

import edu.notify.viking.activity.R;
import edu.notify.viking.down.Downloader;
import edu.notify.viking.entity.FileState;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MyAdapter extends BaseAdapter 
{
private List<FileState> list;
private Context context;
private LayoutInflater inflater=null;
private ListView listView;
private Handler mHandler = new Handler()
{

@Override
public void handleMessage(Message msg) 
{
if(msg.what==1)
{
String name=(String)msg.obj;
int length=msg.arg1;
for(int i=0;i<list.size();i++)
{
FileState fileState=list.get(i);
if(fileState.getFileName().equals(name))
{
fileState.setCompleteSize(length);
list.set(i, fileState);

break;
}
}
notifyDataSetChanged();
}
}

};
public MyAdapter(List<FileState> list,Context context)
{
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.list=list;
}


class ViewHolder
{
public TextView fileName;//文件名稱
public ProgressBar progressBar;//進度條
public TextView percent;//百分比
public ImageView down;//下載
}



public int getCount() 
{
// TODO Auto-generated method stub
return list.size();
}

public Object getItem(int position) 
{
// TODO Auto-generated method stub
return list.get(position);
}

public long getItemId(int position) 
{
// TODO Auto-generated method stub
return position;
}

public View getView(int position, View convertView, ViewGroup parent) 
{
ViewHolder holder;
if(convertView==null)
{
convertView=inflater.inflate(R.layout.main_item, null);
holder=new ViewHolder();
holder.fileName=(TextView)convertView.findViewById(R.id.fileName);
holder.progressBar=(ProgressBar)convertView.findViewById(R.id.down_progressBar);
holder.percent = (TextView) convertView.findViewById(R.id.percent_text);
holder.down = (ImageView) convertView.findViewById(R.id.down_view);
convertView.setTag(holder);
}
else
{
holder=(ViewHolder)convertView.getTag();
}
FileState fileState=list.get(position);
final String name = fileState.getFileName();
System.out.println(name+"---run getView");
//若是文件狀態爲已經下載
if(fileState.isState()==true)
{
holder.fileName.setText(fileState.getFileName());
//下載完成的文件,進度條被隱藏
holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
//設置爲已下載
holder.percent.setText("已下載");
//下載完成的文件,下載按鈕被隱藏,防止重複下載
holder.down.setVisibility(ImageView.INVISIBLE);
}
else
{
holder.fileName.setText(fileState.getFileName());
holder.progressBar.setVisibility(ProgressBar.VISIBLE);
holder.progressBar.setProgress(fileState.getCompleteSize());
holder.percent.setText(fileState.getCompleteSize()+"%");
holder.down.setOnClickListener(new View.OnClickListener()
{

public void onClick(View v) 
{
Downloader down= new Downloader(name,mHandler);
down.download();
}

});
if(fileState.getCompleteSize()==100)
{
holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
holder.percent.setText("已下載");
holder.down.setVisibility(ProgressBar.INVISIBLE);
fileState.setState(true);
list.set(position, fileState);
}

}
return convertView;
}

public void setListView(ListView listView) {
this.listView = listView;
}

}
爲 了運行的效率,我在adapter中定義了一個內部類,ViewHolder,其中的屬性都是咱們要繪製出來的控件。主要的繪製工做在於getView這 個方法,在getView中咱們對變量初始化之後,就將list當中對應的FileState拿了出來,根據文件的狀態進行了分別的處理,下載完成的怎麼 怎麼顯示。。。下載爲完成的怎麼怎麼顯示。。。這些都不難,很容易就看明白了。在這裏面咱們實現了下載按鈕這個點擊事件,這個事件中的代碼也很簡單,就是 建立一個Downloader對象,而後調用其中的download方法進行下載。在這個類中咱們看到,其中建立了一個Handler對象,並在裏面實現 它的消息處理方法handleMessage。當咱們點擊下載圖標時會把這個Handler對象傳進Downloader這個類中。當文件開始下載時,下 載完成的數據長度將會傳到handlerMessage這個方法中被處理,咱們在這個方法中對FileState與list作了更新,而後調用了 notifyDatatSetChanged方法.
  接下來咱們來看最後一個類。建立Downloader類。
package edu.notify.viking.down;
import java.util.Map;

import android.os.Handler;
import android.os.Message;


public class Downloader 
{
private String fileName;
private Handler mHandler;
public Downloader(String fileName, Handler handler)
{
super();
this.fileName = fileName;
mHandler = handler;
}

public void download()
{
new MyThread().start();
}

class MyThread extends Thread
{

@Override
public void run()
{
for(int i=0;i<=100;i++)
{
System.out.println("i:"+i);
try {
this.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Message msg=Message.obtain();
msg.what=1;
msg.obj=fileName;
msg.arg1=i;
mHandler.sendMessage(msg);
}
}

}
}



這 個類用於開啓一個單獨的線程來模擬下載的環節。其中的內容很簡單,無非是接收從adapter中傳遞過來的數據,當用戶點擊下載圖標時,執行 download方法。在線程類的run方法中,咱們作了一個想0-100的循環,固然爲了讓你們看到進度條的更新,咱們讓線程每次休眠1秒鐘。而後用 adapter對象中傳遞過來的handler對象發送message。這時候咱們的adapter類中的handleMessage方法就能夠收到消 息,並進行處理,最後調用notifyDataSetChanged方法了。好的,咱們把xml文件也先給你們。
  先是main.xml
<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <ListView
    android:id="@+id/listview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:fastScrollEnabled="true"
    />
    </LinearLayout>
而後是main_item.xml
<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
    android:id="@+id/fileName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    />
    <ProgressBar
    android:id="@+id/down_progressBar"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    style="@android:style/Widget.ProgressBar.Horizontal"
    />
    <TextView
    android:id="@+id/percent_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />
    <ImageView 
    android:id="@+id/down_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/down"
    />
    </LinearLayout>
這兩個佈局文件很簡單,你們一看就明白了,如今咱們來測試一下,看看notifyDataSetChanged方法後,getView一共會執行幾回?


1.jpg 
  從圖中咱們能夠看見,儘管咱們只是想更新8.mp3這個item的進度條,可是全部的進度條都被更新了,每次使用notifyDataSetChanged方法,對會調用8次getView方法。天哪。這個效率。。。
  這不是咱們想要的,咱們想要的是什麼?咱們想要的就是若是隻須要更新8.mp3這個item,那麼其餘的item將保持不變,不須要更新他們。那麼咱們該怎麼解決這個問題呢?
  其實這個問題的解決也不麻煩,所謂難者不會,會者不難啊。咱們只須要修改adapter這個類,在其中添加一個方法便可。如今咱們來看更新後的adapter類。 java

package edu.notify.viking.adapter;

    import java.util.List;

    import edu.notify.viking.activity.R;
    import edu.notify.viking.down.Downloader;
    import edu.notify.viking.entity.FileState;

    import android.content.Context;
    import android.os.Handler;
    import android.os.Message;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.ImageView;
    import android.widget.ListView;
    import android.widget.ProgressBar;
    import android.widget.TextView;

    public class MyAdapter extends BaseAdapter 
    {
    private List<FileState> list;
    private Context context;
    private LayoutInflater inflater=null;
    private ListView listView;
    private Handler mHandler = new Handler()
    {

    @Override
    public void handleMessage(Message msg) 
    {
    if(msg.what==1)
    {
    String name=(String)msg.obj;
    int length=msg.arg1;
    for(int i=0;i<list.size();i++)
    {
    FileState fileState=list.get(i);
    if(fileState.getFileName().equals(name))
    {
    fileState.setCompleteSize(length);
    list.set(i, fileState);
    updateView(i);//用咱們本身寫的方法 
    break;
    }
    }
    //notifyDataSetChanged();不用了
    }
    }

    };
    public MyAdapter(List<FileState> list,Context context)
    {
    inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    this.list=list;
    }


    class ViewHolder
    {
    public TextView fileName;//文件名稱
    public ProgressBar progressBar;//進度條
    public TextView percent;//百分比
    public ImageView down;//下載
    }

    /**
    * 用於更新咱們想要更新的item
    * @param itemIndex 想更新item的下標
    * **/
    private void updateView(int itemIndex)
    { 
    //獲得第1個可顯示控件的位置,記住是第1個可顯示控件噢。而不是第1個控件
    int visiblePosition = listView.getFirstVisiblePosition(); 
    //獲得你須要更新item的View
    View view = listView.getChildAt(itemIndex - visiblePosition);
    FileState fileState=list.get(itemIndex);
    final String name=fileState.getFileName();
    System.out.println(name+"---run updateView");
    if(fileState.isState()==false)
    {
    ViewHolder holderOne=new ViewHolder();
    //start:初始化
    holderOne.fileName=(TextView)view.findViewById(R.id.fileName);
    holderOne.progressBar=(ProgressBar)view.findViewById(R.id.down_progressBar);
    holderOne.percent = (TextView) view.findViewById(R.id.percent_text);
    holderOne.down = (ImageView) view.findViewById(R.id.down_view);
    //end
    holderOne.fileName.setText(fileState.getFileName());
    holderOne.progressBar.setVisibility(ProgressBar.VISIBLE);
    holderOne.progressBar.setProgress(fileState.getCompleteSize());
    holderOne.percent.setText(fileState.getCompleteSize()+"%");
    holderOne.down.setOnClickListener(new View.OnClickListener()
    {

    public void onClick(View v) 
    {
    Downloader down= new Downloader(name,mHandler);
    down.download();
    }

    });
    if(fileState.getCompleteSize()==100)
    {
    holderOne.progressBar.setVisibility(ProgressBar.INVISIBLE);
    holderOne.percent.setText("已下載");
    holderOne.down.setVisibility(ProgressBar.INVISIBLE);
    fileState.setState(true);
    list.set(itemIndex, fileState);
    }
    }
    } 

    public int getCount() 
    {
    // TODO Auto-generated method stub
    return list.size();
    }

    public Object getItem(int position) 
    {
    // TODO Auto-generated method stub
    return list.get(position);
    }

    public long getItemId(int position) 
    {
    // TODO Auto-generated method stub
    return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) 
    {
    ViewHolder holder;
    if(convertView==null)
    {
    convertView=inflater.inflate(R.layout.main_item, null);
    holder=new ViewHolder();
    holder.fileName=(TextView)convertView.findViewById(R.id.fileName);
    holder.progressBar=(ProgressBar)convertView.findViewById(R.id.down_progressBar);
    holder.percent = (TextView) convertView.findViewById(R.id.percent_text);
    holder.down = (ImageView) convertView.findViewById(R.id.down_view);
    convertView.setTag(holder);
    }
    else
    {
    holder=(ViewHolder)convertView.getTag();
    }
    FileState fileState=list.get(position);
    final String name = fileState.getFileName();
    System.out.println(name+"---run getView");
    //若是文件狀態爲已經下載
    if(fileState.isState()==true)
    {
    holder.fileName.setText(fileState.getFileName());
    //下載完成的文件,進度條被隱藏
    holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
    //設置爲已下載
    holder.percent.setText("已下載");
    //下載完成的文件,下載按鈕被隱藏,防止重複下載
    holder.down.setVisibility(ImageView.INVISIBLE);
    }
    else
    {
    holder.fileName.setText(fileState.getFileName());
    holder.progressBar.setVisibility(ProgressBar.VISIBLE);
    holder.progressBar.setProgress(fileState.getCompleteSize());
    holder.percent.setText(fileState.getCompleteSize()+"%");
    holder.down.setOnClickListener(new View.OnClickListener()
    {

    public void onClick(View v) 
    {
    Downloader down= new Downloader(name,mHandler);
    down.download();
    }

    });
    if(fileState.getCompleteSize()==100)
    {
    holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
    holder.percent.setText("已下載");
    holder.down.setVisibility(ProgressBar.INVISIBLE);
    fileState.setState(true);
    list.set(position, fileState);
    }

    }
    return convertView;
    }

    public void setListView(ListView listView) {
    this.listView = listView;
    }

    }
在類中咱們對handleMessage作了一點修改。


  3.jpg 
  看到紅色箭頭的地方就是修改過的。而後多添加了一個updateView方法。這個方法須要傳入你想更新的item下標。
  最核心的地方是這2句代碼。
   4.jpg 
  主要的意思就是根據你想要更新的item下標去獲得這個item的View對象,這樣你就能夠隨心所欲啦。哈哈哈哈哈。。。
  我們來看看運行效果。
  5.jpg 
  這裏我改成同時更新2個item,看看這樣會不會出錯,能夠看到,運行的很正常。控制檯的打印結果也只是更新了7-8.mp3這2個item。

android

相關文章
相關標籤/搜索