最近在做自定义控件时,发现一个极其常用的效果--广告条,即图片的自动轮播效果。现在市面上大多数的APP软件都在使用这种展示广告的效果。闲来无事,我简单翻看了一下自己的手机软件,几乎都使用了这种图片自动轮播的策略来实现展示广告的效果:
结合自己所掌握的技术,不难分析这种广告轮播策略的实现原理:
1、利用ViewPager来接收来自网络的图片
2、定时的切换ViewPager里展示的图片
想要实现上面的效果,我们需要掌握三方面的知识:ViewPager的使用原理、网络图片的读取原理、定时器的原理。OK,下面就让我们一起去剖析每一部分的原理,进而实现我们想要的效果吧。先上一个效果图看看效果:
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来获取网络图片。
在这个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集合中真实的位置。
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); } }; };
点我下载源码