想实现一个类似于微信联系人列表的功能。网上查看了很多实现方案,主要有以下2种方案:
1.在服务器端将图片的文件流通过base64编码,再经过json/xml数据格式传送给Android客户端,客户端对图片流进行解码,使用ImagView的setImageBitmap()方法渲染ImageView。
- 优点:图片数据可以跟随对象(javaBean)传送,解析操作比较简单。
- 缺点:此方案只适传送较小的图片,而且传送数据时要注意base64编码产生的空格问题(可以再进行URL编码)。
2.使用异步加载方式来 加载头像,并将头像图片存入缓存文件中。
- 优点:避免了上一种只能传输较小图片的问题。使用异步方式避免了UI无响应,带来更好的用户体验。
- 缺点:实现方式较上一种复杂。
第一种方式这里就不进行说明了。下面主要对第二种实现方案来说明。考虑到用户一次加载较多数据会影响性能,这里加入了批量加载。
先上传效果图:
当ListView滚动到最后一行时会加载新的数据,并提示加载等待信息。还有一种常见的做法是在ListView底部放置【加载更多】按钮来触发继续加载操作。
具体是实现步骤如下:
- 创建加载等待环形进度条:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal"> <ProgressBar android:layout_width="40dp" android:layout_height="40dp" style="@android:style/Widget.ProgressBar.Small"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="数据加载中。。。。。。"/> </LinearLayout>
- 创建ListView布局,这里的ListView布局放置在一个FrameLayout里,代码如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" tools:context="com.example.xinxin.pandachild.NearFragment"> <ListView android:id="@+id/nearListView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" ></ListView> </FrameLayout>
- 创建ListView条目布局,代码如下:
<LinearLayout android:layout_width="match_parent" android:layout_height="100dp" android:orientation="horizontal" android:gravity="center_vertical" android:layout_margin="10dp" xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:id="@+id/logoImage" android:layout_width="80dp" android:layout_height="80dp" android:layout_margin="10dp"/> <TextView android:id="@+id/userName" android:textSize="28sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp"/> </LinearLayout>
- 实现自定的Adapter,详细可以参考自定义Adapter的知识,主要代码如下:
/** * Created by xinxin on 2015/8/24. */ public class NearAdapter extends BaseAdapter { private List<PUser> list; private Context context; private LayoutInflater layoutInflater; private File catchFile; private String catchPath; public NearAdapter(Context context, List<PUser> list) { this.context = context; this.list = list; layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); initCatchFile(); } /** * 初始化缓存目录 */ private void initCatchFile(){ catchPath = this.context.getCacheDir().getAbsolutePath() + "/png"; catchFile = new File(catchPath); if (!catchFile.exists()) catchFile.mkdirs(); } //该方法在批量加载ListView数据时使用 public void addData(List<PUser> list) { if (this.list == null) this.list = new ArrayList<>(); this.list.addAll(list); } @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override /** * 重写父类方法 * 使用缓存将ListView条目布局进行缓存 */ public View getView(int position, View convertView, ViewGroup parent) { ImageView logImage = null; TextView userName = null; if (convertView == null) { convertView = layoutInflater.inflate(R.layout.near_list_item, null); logImage = (ImageView) convertView.findViewById(R.id.logoImage); userName = (TextView) convertView.findViewById(R.id.userName); ViewCatch viewCatch = new ViewCatch(); viewCatch.logoImage = logImage; viewCatch.userNaem = userName; convertView.setTag(viewCatch); } else { ViewCatch viewCatch = (ViewCatch) convertView.getTag(); logImage = viewCatch.logoImage; userName = viewCatch.userNaem; } PUser pUser = list.get(position); userName.setText(pUser.getUserName()); //异步加载图片 asyncImageLoad(logImage, pUser.getLogoUrl()); return convertView; } /** * 缓存ListView条目类 */ class ViewCatch { public ImageView logoImage; public TextView userNaem; } /** * 异步加载图片 * @param imageView * @param url */ public void asyncImageLoad(final ImageView imageView, final String url) { AsyncTask<Integer, Integer, Uri> asyncTask = new AsyncTask<Integer, Integer, Uri>() { @Override protected Uri doInBackground(Integer... params) {//执行加载图片URI操作,并返回图片URI return getImageUri(url); } @Override protected void onPostExecute(Uri uri) {//跟新主线程UI if (uri != null) if (imageView != null) imageView.setImageURI(uri); } }; asyncTask.execute(); } /** * 获得图片URI * 加载过的文件会存放在缓存目录 * @param url * @return */ private Uri getImageUri(String url) { Uri imageUri = null; //将图片地址进行MD5, String catchFileName = catchPath + "/" + MD5.GetMD5Code(url) + ".png"; if (!FileUtil.exists(catchFileName)) {//判断缓存目录是否存在该图片资源 FileUtil.writeFile(catchFileName, HttpUtil.getInputStream(url, null)); } //将文件类型转成URI格式 imageUri = Uri.fromFile(new File(catchFileName)); return imageUri; } }
- 主界面的实现,这里在fragment中实现与activity大致相似,主要是通过ListView的OnScrollListener监听事件来控制LlistView的加载,主要代码如下:
说明:这里只给出了主要的实现方式,及部分代码,PUser 及 业务处理类就不贴了,当然也不足与待优化的地方。继续学习!public class NearFragment extends Fragment { private Context context; private ListView listView; private NearAdapter adapter; private boolean loadFinished; private boolean loadAll; private LinearLayout footer; private PUserService pUserService = new PUserService(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); context = this.getActivity(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_near, container, false); footer = (LinearLayout) inflater.inflate(R.layout.footer, null); listView = (ListView) view.findViewById(R.id.nearListView); setOnScrollListener(); new Thread(new Runnable() {//启动子线程初始化ListView,防止主线程响应超时 @Override public void run() { try { List<PUser> list = pUserService.getUserList();//获得数据 handler.sendMessage(handler.obtainMessage(100, list));//发送消息 } catch (Exception e) { e.printStackTrace(); } } }).start(); return view; } /** * 处理ListView初始化 */ Handler handler = new Handler() { @Override public void handleMessage(Message msg) { List<PUser> list = (List<PUser>) msg.obj; if (list != null) { listView.addFooterView(footer);//在实例化前添加footer,此处必须 adapter = new NearAdapter(context, list); listView.setAdapter(adapter); listView.removeFooterView(footer);//去除footer } } }; /** * 处理批量加载 */ Handler scrollHandler = new Handler() { @Override public void handleMessage(Message msg) { List<PUser> list = (List<PUser>) msg.obj; if (list != null && list.size() > 0) { adapter.addData(list); adapter.notifyDataSetChanged(); loadFinished = true; } listView.removeFooterView(footer); } }; /** * 实现LilstView批量加载数据 */ private void setOnScrollListener() { loadFinished = true; loadAll = false; listView.setOnScrollListener(new AbsListView.OnScrollListener() { private boolean finished;//可以控制ListView滚动是否完全停止 private int pageSize = 20; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { finished = true; } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (!loadAll && totalItemCount > 0 && (firstVisibleItem + visibleItemCount) == totalItemCount && loadFinished) {//为了测试方便,没有判断finished; listView.addFooterView(footer);//添加等待进度条 finished = false; loadFinished = false; final int startNo = totalItemCount; new Thread(new Runnable() {//启动子线程,加载数据 @Override public void run() { //获得分页数据 List<PUser> list = pUserService.getUserList(startNo + 1, pageSize + startNo); if (list != null && list.size() > 0) { //不处理 } else { loadAll = true; } //发送消息给主线程 scrollHandler.sendMessage(scrollHandler.obtainMessage(100, list)); } }).start(); } } }); } }