最近在做自定义控件时,发现一个极其常用的效果--广告条,即图片的自动轮播效果。现在市面上大多数的APP软件都在使用这种展示广告的效果。闲来无事,我简单翻看了一下自己的手机软件,几乎都使用了这种图片自动轮播的策略来实现展示广告的效果:
结合自己所掌握的技术,不难分析这种广告轮播策略的实现原理:
1、利用ViewPager来接收来自网络的图片
2、定时的切换ViewPager里展示的图片
想要实现上面的效果,我们需要掌握三方面的知识:ViewPager的使用原理、网络图片的读取原理、定时器的原理。OK,下面就让我们一起去剖析每一部分的原理,进而实现我们想要的效果吧。先上一个效果图看看效果:
1、ViewPager的基本使用
ViewPager是Android扩展包V4包中的类,这个类可以让用户左右切换当前的View。
1、ViewPager类直接继承了ViewGroup类,所以它是一个容器类,可以在其中添加其他的view对象。
2、ViewPager类需要一个PagerAdapter适配器来给它提供数据。
3、ViewPager经常和Fragment一起使用,并且提供了专门的FragmentPagerAdapter和FragmentStatePagerAdapter类供Fragment中的ViewPager使用。
在使用ViewPager的时候,需要现在布局文件中定义该控件,我们项目中的布局如下:
<RelativeLayout 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"
tools:context=".MainActivity" >
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="200dp" />
<LinearLayout
android:id="@+id/desc_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/viewPager"
android:background="#33000000"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_image_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/app_name"
android:textColor="@android:color/white"
android:textSize="18sp" />
<!-- 小点点动态的通过代码区添加,这里只提供一个容器,用来存储小点点 -->
<LinearLayout
android:id="@+id/point_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal" >
</LinearLayout>
</LinearLayout>
</RelativeLayout>
经过我们的分析可知,ViewPager其实就是一个容器,我们向容器中添加的是View对象,显然我们需要展示的
广告图片不会只有一条,所以我们采用一个List<View>集合来存放需要展示的广告的View对象,然后通过PagerAdapter将数据设置给ViewPager来完成展示工作。在实际开发中,我们要展示的广告图片一般都是来自互联网的,所以我采用了Volley来获取网络图片。
2、Volley方式获取网络图片
volley获取网络图片的基本方法请参考Android Volley完全解析(二),使用Volley加载网络图片。在这个Demo中,根据图片轮播的需求,我对Volley进行了一些简单的封装,集成了一个处理网络图片的工具,代码如下:
/**
* 使用Volley框架获得网络图片
*
* @author Administrator
*
*/
public class ImageUtils {
/**
* 如果宽高不指定,会根据ImageView的设置进行图片压缩
*
* @param context
* @param imageView
* 将图片设置到哪一个ImageView上
* @param iamgeUrl
* 图片地址
* @param defaultImageResId
* 默认显示的图片资源id
* @param errorImageResId
* 加载失败时显示的图片资源id
*/
public static void getImage(Context context, ImageView imageView, String iamgeUrl, int defaultImageResId, int errorImageResId) {
getImage(context, imageView, iamgeUrl, defaultImageResId, errorImageResId, 0, 0);
}
/**
*
* @param context
* @param imageView
* @param iamgeUrl
* @param defaultImageResId
* @param errorImageResId
* @param maxWidth
* @param maxHeight
*/
public static void getImage(Context context, ImageView imageView, String iamgeUrl, int defaultImageResId, int errorImageResId, int maxWidth, int maxHeight) {
try {
/* volley */
// 获得请求队列
RequestQueue mQueue = Volley.newRequestQueue(context);
// 图片的缓存暂时是一个空实现,没有做任何处理
ImageLoader imageLoader = new ImageLoader(mQueue, new com.android.volley.toolbox.ImageLoader.ImageCache() {
@Override
public void putBitmap(String arg0, Bitmap arg1) {
}
@Override
public Bitmap getBitmap(String arg0) {
return null;
}
});
// 第一个参数是将图片设置到哪一个ImageView上,第二个参数是默认的图片资源id,第三个参数是失败时显示的图片资源id
ImageListener listener = ImageLoader.getImageListener(imageView, defaultImageResId, errorImageResId);
ImageSize imageSize = getImageViewWidth(imageView);
if (maxWidth != 0 && maxHeight != 0) {
imageSize.width = maxWidth;
imageSize.height = maxHeight;
}
Log.d("imageSize", "width>>" + imageSize.width + "height>>" + imageSize.height);
imageLoader.get(iamgeUrl, listener, imageSize.width, imageSize.height);
} catch (Exception e) {
// TODO: handle exception
}
}
/**
* 根据ImageView获得适当的压缩的宽和高
*
* @param imageView
* @return
*/
private static ImageSize getImageViewWidth(ImageView imageView) {
ImageSize imageSize = new ImageSize();
final DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
final LayoutParams params = imageView.getLayoutParams();
int width = params.width == LayoutParams.WRAP_CONTENT ? 0 : imageView.getWidth(); // Get
// actual
// image
// width
if (width <= 0)
width = params.width; // Get layout width parameter
if (width <= 0)
width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check
// maxWidth
// parameter
if (width <= 0)
// width = displayMetrics.widthPixels;
width = 350;
int height = params.height == LayoutParams.WRAP_CONTENT ? 0 : imageView.getHeight(); // Get
// actual
// image
// height
if (height <= 0)
height = params.height; // Get layout height parameter
if (height <= 0)
height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check
// maxHeight
// parameter
if (height <= 0)
// height = displayMetrics.heightPixels;
height = 350;
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
private static class ImageSize {
int width;
int height;
}
/**
* 反射获得ImageView设置的最大宽度和高度
*
* @param object
* @param fieldName
* @return
*/
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = (Integer) field.get(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (Exception e) {
}
return value;
}
}
代码中的注释已经很详细了,我这里就不做过多的赘述,在后面我也会提供源码下载,需要的朋友可以直接看源码。
PS:本人写代码很烂,所有代码中有好多地方有待优化,期盼着您能及时指出我的不足之处,以便大家共同学习进步。
网络资源的地址采用JSONArray的方式加载,在主Activity的onCreate方法中初始化的时候,就加载网络资源,初始化的代码如下:
/**
* 初始化的操作
*/
private void initView() {
viewPager = (ViewPager) findViewById(R.id.viewPager);
desc_image = (TextView) findViewById(R.id.tv_image_desc);
point_group = (LinearLayout) findViewById(R.id.point_group);
// 广告的图片资源Array
advertiseArray = new JSONArray();
try {
JSONObject advertise0 = new JSONObject();
advertise0.putOpt("advertise", "http://pic1.ooopic.com/uploadfilepic/sheji/2009-09-12/OOOPIC_wenneng837_200909122b2c8368339dd52a.jpg:小泡泡");
JSONObject advertise1 = new JSONObject();
advertise1.putOpt("advertise", "http://image.zcool.com.cn/59/54/m_1303967870670.jpg:时代会远去,记忆不会,流行会过时,也会回来");
JSONObject advertise2 = new JSONObject();
advertise2.putOpt("advertise", "http://image.zcool.com.cn/47/19/1280115949992.jpg:小心闪电");
JSONObject advertise3 = new JSONObject();
advertise3.putOpt("advertise", "http://image.zcool.com.cn/59/11/m_1303967844788.jpg:闪亮的年代");
JSONObject advertise4 = new JSONObject();
advertise4.putOpt("advertise", "http://image.zcool.com.cn/56/35/1303967876491.jpg:时光会紧锁,美梦不曾停");
advertiseArray.put(advertise0);
advertiseArray.put(advertise1);
advertiseArray.put(advertise2);
advertiseArray.put(advertise3);
advertiseArray.put(advertise4);
initSource(advertiseArray, true);
} catch (JSONException e) {
e.printStackTrace();
}
}
网络图片的地址后边跟的是描述信息,在真正的项目开发中,这一部分可以使用配置文件来定义图片的地址和描述信息。
图片下边的小点点是通过代码动态添加的,需要展示几张图片,就添加几个小点点,定义ViewPager的图片滑动的监听,当图片滑动的时候,设置小点点的背景以及显示的描述信息,这一部分对应的代码如下所示:
<span style="white-space:pre"> </span>/**
* 初始化资源。将资源Array封装成List<ImageView>集合,传递给ViewPager的适配器
*
* @param advertiseArray
* @param fitXY
* 拉伸展开,适应屏幕的xy,否则水平居中
*/
private void initSource(final JSONArray advertiseArray, boolean fitXY) {
views = new ArrayList<View>();
for (int i = 0; i < advertiseArray.length(); i++) {
if (fitXY) {
views.add(View.inflate(MainActivity.this, R.layout.image_advertise_fit, null));
} else {
views.add(View.inflate(MainActivity.this, R.layout.image_advertise_center, null));
}
// 添加指示点
ImageView point = new ImageView(this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.rightMargin = 20;
params.gravity = Gravity.CENTER;
point.setLayoutParams(params);
point.setBackgroundResource(R.drawable.point_bg);
if (i == 0) {
point.setEnabled(true);// 将小点点设置成灰色
} else {
point.setEnabled(false);
}
point_group.addView(point);
}
// 准备好了views,之后设置ViewPager的适配器
AdvertiseAdapter adapter = new AdvertiseAdapter(this, views, advertiseArray);
// 设置适配器,向viewpager容器中添加图片
viewPager.setAdapter(adapter);
// 让ViewPager左右滑动的时候无限循环
viewPager.setCurrentItem(Integer.MAX_VALUE / 2 - (Integer.MAX_VALUE / 2 % views.size()));
// 设置viewpager滑动时候的监听,设置图片的描述和小点点的切换
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
/**
* 页面切换后调用
*/
@Override
public void onPageSelected(int position) {
position = position % views.size();
// 设置图片的文字描述信息
String result = advertiseArray.optJSONObject(position).optString("advertise");
String desc = result.substring(result.lastIndexOf(":") + 1, result.length());
desc_image.setText(desc);
// 改变指示点的状态
// 当前位置的点设为灰色,true
point_group.getChildAt(position).setEnabled(true);
// 上一个位置的点设为透明的 false
point_group.getChildAt(lastPosition).setEnabled(false);
lastPosition = position;
}
/**
* 页面正在滑动的时候的回调
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
/**
* 页面状态发生改变的时候回调
*/
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
ViewPager的适配器如下:
public class AdvertiseAdapter extends PagerAdapter {
private Context context;
private List<View> views;
JSONArray advertiseArray;
public AdvertiseAdapter() {
super();
}
public AdvertiseAdapter(Context context, List<View> views, JSONArray advertiseArray) {
this.context = context;
this.views = views;
this.advertiseArray = advertiseArray;
}
/**
* 获得页面的总数
*/
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
/**
* 判断View和object的对应关系
*/
@Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
/**
* 销毁对应位置的object
*/
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView(views.get(position % views.size()));
}
/**
* 获取相应位置的view
* container view的容器,其实就是ViewPager本身
* position 相应的位置
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
((ViewPager) container).addView(views.get(position % views.size()));
View view = views.get(position % views.size());
// 读取图片url的地址
String result = advertiseArray.optJSONObject(position % views.size()).optString("advertise");
// String desc = result.substring(result.lastIndexOf(":") + 1,
// result.length());
String imageUrl = result.substring(0, result.lastIndexOf(":"));
// 广告图片存放的ImageView
ImageView ivAdvertise = (ImageView) view.findViewById(R.id.ivAdvertise);
// ImageUtils工具就已经将网络图片设置到了ivAdvertise这个ImageView上了
ImageUtils.getImage(context, ivAdvertise, imageUrl, R.drawable.ic_launcher, R.drawable.ic_launcher);
return view;
}
}
在适配器中,将页面的总数设置成整数的最大值,其实就是无限大了,目的就是为了实现ViewPager左右的无限滑动,需要注意的就是position的位置需要做一些运算来获得view在集合中的位置,position % views.size(),获取view在views集合中真实的位置。
3、定时切换ViewPager,实现轮播效果
定时轮播我目前想到有三种策略:1、定时器timer
2、开启子线程,while true循环
3、使用handler方式发送延时消息,实现循环
这里我使用的是第三种,handler发送延时消息来完成轮播效果。首先定义一个Handler,在它的handleMessage方法中,发送一个延时消息,这就实现了循环发送消息的循环效果
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
// viewPager滑动到下一页
viewPager.setCurrentItem(viewPager.getCurrentItem() + 1);
// 发送一个延时消息,延时2秒钟继续执行handler,达到循环的效果
if (isRunning) {
handler.sendEmptyMessageDelayed(0, 2000);
}
};
};
OK,写到这里一个Android广告条的轮播效果也就实现了,通过这个例子,我们学习了如何使用volley加载 网络图片以及ViewPager的基本使用方式,同时对于定时器 又有了一个全新的认识。
PS:本人写代码很烂,所有代码中有好多地方有待优化,期盼着您能及时指出我的不足之处,以促进大家的共同进步。