在android异步加载ListView中的图片中使用异步方式加载的图片,当时要的急,写的很粗糙,是为每个图片加载一个线程来实现的。
可以用java concurrent很简明的实现类似功能,并且用到线程池。
这里加载的图片,都是从网上直接获取的。如果用android的UI线程,则需要图片全部加载后才能显示界面。
这里使用了concurrent api通过后台线程并发获取,本例中线程池中只有一个线程,可以设置为多个以加快加载速度。可参见使用java concurrent处理并发需求中的简单示例了解concurrent api的基本机制。
代码不复杂:
package com.easymorse.lazyload; import java.net.URL; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.widget.ImageView; public class LazyLoadImageActivity extends Activity { private ExecutorService executorService = Executors.newFixedThreadPool(1); /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); } @Override protected void onDestroy() { executorService.shutdown(); super.onDestroy(); } private void loadImage(final String url, final int id) { final Handler handler=new Handler(); executorService.submit(new Runnable() { @Override public void run() { try { final Drawable drawable = Drawable.createFromStream( new URL(/blog_article/url/index.html).openStream(), "image.png"); handler.post(new Runnable() { @Override public void run() { ((ImageView) LazyLoadImageActivity.this .findViewById(id)) .setImageDrawable(drawable); } }); } catch (Exception e) { throw new RuntimeException(e); } } }); } }
完整源代码见:
http://easymorse.googlecode.com/svn/tags/lazy.load.image-0.2.0/
可以对比一下不做异步处理的示例:
http://easymorse.googlecode.com/svn/tags/lazy.load.image-0.1.0/
在实现横向的类似Gallery的效果中做了实现Gallery的尝试,但是效果不好。使用的是TableLayout,出现了横向拖动图片的时候,因为有倾斜(轻微的竖向拖动),会整个列表竖向滚动。其实这个问题可以将TableRow中条目设置为clickable来解决。但是效果依然不好。
这次尝试通过GridView来解决问题,效果很好,见截图:
基本思路是:
- 每个可选的图,包括文字部分,是GridView中的一个条目;
- 一个GridView条目是相对布局(RelativeLayout),里面包含一个图片(ImageView)和一个文字(TextView);
- 关键点是GridView如何保持横向,默认的情况下会折行的,首先要用一个HorizontalScrollView提供横向滚动容器,然后内部放置一个FrameLayout,如果不放置FrameLayout布局,直接放入下面的布局或者视图,GridView将会变成单列纵向滚动,在FrameLayout布局中加入横向的LinearLayout布局,要设置它的layout_width,要足够大,这样在其中加入GridView就能横向排列了。
首先看一下GridView中条目的布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:paddingBottom="10.0dip" android:layout_width="90.0dip" android:layout_height="140.0dip"> <ImageView android:id="@+id/ItemImage" android:layout_width="80.0dip" android:layout_height="108.0dip" android:layout_marginLeft="10.0dip" android:layout_centerHorizontal="true"> </ImageView> <TextView android:layout_below="@+id/ItemImage" android:id="@+id/ItemText" android:ellipsize="end" android:layout_width="80.0dip" android:layout_height="26.0dip" android:layout_marginTop="5.0dip" android:singleLine="true" android:layout_centerHorizontal="true"> </TextView> </RelativeLayout>
这里使用了相对布局的特性,android:layout_below,表示TextView在ImageView下面。这里的图都是用的res/drawable目录下的静态图形文件,正式情况下,应该是从网络获取,可参见用Java concurrent编写异步加载图片功能的原型实现,二者结合可用于正式生产环境。
ListView的Header使用了自定义视图,更简单的示例可参见为ListView增加Header。表头(ListView Header)的布局文件:
<?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="200dp"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="最近访问人物" /> <HorizontalScrollView android:layout_width="fill_parent" android:layout_height="160dp"> <FrameLayout android:layout_width="fill_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="1100dp" android:layout_height="match_parent" android:orientation="horizontal"> <GridView android:id="@+id/grid" android:layout_width="fill_parent" android:gravity="center" android:layout_height="fill_parent" android:horizontalSpacing="1.0dip" android:verticalSpacing="1.0dip" android:stretchMode="spacingWidthUniform" android:numColumns="auto_fit" android:columnWidth="80dip"> </GridView> </LinearLayout> </FrameLayout> </HorizontalScrollView> </LinearLayout>
这是比较关键的布局文件,GridView能实现横向滚动主要靠它了。其中:
<LinearLayout android:layout_width="1100dp"
我是写死了1100dp,正式使用的时候,因为图片都可能是动态从服务器上获取的,可以根据数量以及图片的宽度,空白边动态计算这个长度。
GridView和ListView类似,都需要ViewAdapter来适配数据和视图。
见Activity的源代码:
package com.easymorse.grid.demo; import java.util.ArrayList; import java.util.HashMap; import android.app.ListActivity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.widget.ArrayAdapter; import android.widget.GridView; import android.widget.ListView; import android.widget.SimpleAdapter; public class GridDemoActivity extends ListActivity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); LayoutInflater layoutInflater = (LayoutInflater) this .getSystemService("layout_inflater"); View headerView=layoutInflater.inflate(R.layout.list_header, null); setGridView(headerView); ListView listView=(ListView) this.findViewById(android.R.id.list); listView.addHeaderView(headerView); listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,new String[]{"隋","唐","宋","元","明","清"})); } private void setGridView(View view) { GridView gridView = (GridView) view.findViewById(R.id.grid); gridView.setNumColumns(10); ArrayList<HashMap<String, Object>> items = new ArrayList<HashMap<String, Object>>(); for (int i = 0; i < 10; i++) { HashMap<String, Object> map = new HashMap<String, Object>(); map.put("ItemImage", R.drawable.k); map.put("ItemText", "清.康熙" + "(" + i + ")"); items.add(map); } SimpleAdapter adapter = new SimpleAdapter(this, items, R.layout.item, new String[] { "ItemImage", "ItemText" }, new int[] { R.id.ItemImage, R.id.ItemText }); gridView.setAdapter(adapter); } }
全部源代码:
http://easymorse.googlecode.com/svn/tags/grid.demo-0.1.0/
对用GridView实现Gallery的效果再做一个处理,假设图片加载是通过服务器端的,那么在没有加载完毕的时候界面上要有个ProgressBar显示,当加载完毕后,取消ProgressBar。
这里是示意性的代码,图片是一上来就加载了,实际情况应该是只看到ProgressBar。这里需要使用FrameLayout,它可以让多个视图重叠在一起。
看布局文件:
<?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="200dp"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="最近访问人物" /> <FrameLayout android:layout_width="fill_parent" android:layout_height="match_parent"> <HorizontalScrollView android:layout_width="fill_parent" android:layout_height="160dp"> <FrameLayout android:layout_width="fill_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="1100dp" android:layout_height="match_parent" android:orientation="horizontal"> <FrameLayout android:layout_width="fill_parent" android:layout_height="match_parent"> <GridView android:id="@+id/grid" android:layout_width="fill_parent" android:gravity="center" android:layout_height="fill_parent" android:horizontalSpacing="1.0dip" android:verticalSpacing="1.0dip" android:stretchMode="spacingWidthUniform" android:numColumns="auto_fit" android:columnWidth="80dip"> </GridView> </FrameLayout> </LinearLayout> </FrameLayout> </HorizontalScrollView> <ProgressBar android:id="@+id/p1" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout> </LinearLayout>
在代码中调用:
bar.setVisibility(ProgressBar.GONE);
即可让ProgressBar在界面中消失。
这里因为是模拟正式环境,做了个延时取消ProgressBar。使用了Android Handler和Message机制。其实自己写个Timer或者线程啥的都可以完成任务。但不是最佳实践。
这是在Activity的onCreate方法中实现的延时关闭ProgressBar的代码片段:
Message message=new Message(); message.obj=(ProgressBar) headerView.findViewById(R.id.p1);; new Handler(){ public void handleMessage(Message msg) { ProgressBar bar=(ProgressBar) msg.obj; bar.setVisibility(ProgressBar.GONE); } }.sendMessageDelayed(message,1000*10);
全部源代码:
http://easymorse.googlecode.com/svn/tags/grid.demo-0.2.0/