最近在做自定义控件时,发现一个极其常用的效果--广告条,即图片的自动轮播效果。现在市面上大多数的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="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity" ><android.support.v4.view.ViewPagerandroid:id="@+id/viewPager"android:layout_width="match_parent"android:layout_height="200dp" /><LinearLayoutandroid: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" ><TextViewandroid: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" /><!-- 小点点动态的通过代码区添加,这里只提供一个容器,用来存储小点点 --><LinearLayoutandroid: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() {@Overridepublic void putBitmap(String arg0, Bitmap arg1) {}@Overridepublic Bitmap getBitmap(String arg0) {return null;}});// 第一个参数是将图片设置到哪一个ImageView上,第二个参数是默认的图片资源id,第三个参数是失败时显示的图片资源idImageListener 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// widthif (width <= 0)width = params.width; // Get layout width parameterif (width <= 0)width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check// maxWidth// parameterif (width <= 0)// width = displayMetrics.widthPixels;width = 350;int height = params.height == LayoutParams.WRAP_CONTENT ? 0 : imageView.getHeight(); // Get// actual// image// heightif (height <= 0)height = params.height; // Get layout height parameterif (height <= 0)height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check// maxHeight// parameterif (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);// 广告的图片资源ArrayadvertiseArray = new JSONArray();try {JSONObject advertise0 = new JSONObject();advertise0.putOpt("advertise", "/uploadfilepic/sheji/-09-12/OOOPIC_wenneng837_09122b2c8368339dd52a.jpg:小泡泡");JSONObject advertise1 = new JSONObject();advertise1.putOpt("advertise", "/59/54/m_1303967870670.jpg:时代会远去,记忆不会,流行会过时,也会回来");JSONObject advertise2 = new JSONObject();advertise2.putOpt("advertise", "/47/19/1280115949992.jpg:小心闪电");JSONObject advertise3 = new JSONObject();advertise3.putOpt("advertise", "/59/11/m_1303967844788.jpg:闪亮的年代");JSONObject advertise4 = new JSONObject();advertise4.putOpt("advertise", "/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() {/*** 页面切换后调用*/@Overridepublic 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);// 改变指示点的状态// 当前位置的点设为灰色,truepoint_group.getChildAt(position).setEnabled(true);// 上一个位置的点设为透明的 falsepoint_group.getChildAt(lastPosition).setEnabled(false);lastPosition = position;}/*** 页面正在滑动的时候的回调*/@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}/*** 页面状态发生改变的时候回调*/@Overridepublic 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;}/*** 获得页面的总数*/@Overridepublic int getCount() {return Integer.MAX_VALUE;}/*** 判断View和object的对应关系*/@Overridepublic boolean isViewFromObject(View view, Object object) {return (view == object);}/*** 销毁对应位置的object*/@Overridepublic void destroyItem(ViewGroup container, int position, Object object) {((ViewPager) container).removeView(views.get(position % views.size()));}/*** 获取相应位置的view * container view的容器,其实就是ViewPager本身 * position 相应的位置*/@Overridepublic 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(":"));// 广告图片存放的ImageViewImageView 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:本人写代码很烂,所有代码中有好多地方有待优化,期盼着您能及时指出我的不足之处,以促进大家的共同进步。
点我下载源码
如果觉得《Android广告条效果--使用Volley实现网络图片的自动轮播(仿淘宝 网易广告页轮播效果)》对你有帮助,请点赞、收藏,并留下你的观点哦!