先来看看效果:
1、广告条效果应该是使用的比较广泛的一个效果了,使用的基本构架就是一个ViewPager组件,在低版本的Android中,我们需要手动导入v4 jar包才可以使用。
2、ViewPager的加载方式与listview的加载方式不太一样,对于listview,其中总是会使用到子view的复用,但是对于viewpager,动态滑动的时候,它只保持三个页面在内存中,也就是:当前显示页面,前一个页面和后一个页面;其他页面都被销毁释放掉。
3、对于大图片的加载,如果不做处理,可能一两张图片就会导致OOM,应用挂掉;在早期版本中,2.3以前,经常的做法是使用软引用和弱引用集合来处理在内存中加载图片,但是对于Android3.0的版本,Android系统偏向于直接回收掉软引用的对象而不是软引用的成员,这就导致了本做法不再适用。但是好在Android系统同时给出了一个比较好的工具让我们来处理图片的加载:LruCache。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
4、大图片加载的老生长提的问题就是在加载之前先使用bitmapfactory来调整图片的大小,这里涉及到BitmapFactory.Options.inJustDecodeBounds = true;通过options.outHeight;和options.outWidth;拿到图片的宽高,然后再根据屏幕的宽高来缩放图片,达到压缩的效果。
5、另外一个关于ViewPager的小问题就是在于直接调用adapter.notifydatasetchanged()方法,无法刷新viewpager的数据;这里解决的版本有两个:
a. 重写pageadapter的getItemPosition方法,让其直接返回POSITION_NONE,这时候再调用notifydatasetchanged则可以起作用
b. 在更新完数据之后,调用一个handler发送对应的message,来手动刷新局部数据,这种方式比较推荐,实际上也可以用于listview的局部刷新,比较通用。
6、这里还实现了一个两边无线循环滑动的效果,实际上的图片只有六张,但是我们可以无线往两边滑动,实际上两边的数量是integer.max_value/2,
核心思想在于getCount方法返回一个integer.max)value,然后对于postion的处理都使用position%6,这样来实现,具体细节可以参看代码
好,废话说完,上代码:
MainActivity:
package com.example.advertisebardemo; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import android.app.Activity; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.util.LruCache; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.text.format.Formatter; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; public class MainActivity extends Activity { private ViewPager viewPager; private int[] ids = { R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e, R.drawable.f }; private List<ImageView> imageViews = new ArrayList<ImageView>(); private MyAdapter adapter; private Options options; private int screenWidth; private int screenHeight; private LruCache<String, Bitmap> mMemoryCache; private WindowManager wm; private int wWidth; private int wHeight; /** * 设置一个标志位来判断是否继续自动滑动 */ private boolean isRunning = false; private int lastPointPosition; private boolean operationFlag; /** * 线程池对象 */ private ScheduledExecutorService pool; private Object[] objs; private String[] descriptions = { "图片描述一", "图片描述二", "图片描述三", "图片描述四", "图片描述五", "图片描述六" }; private LinearLayout ll_points; private TextView tv_des; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: // adapter.notifyDataSetChanged(); // 不用adapter.notifyDataSetChanged(),而使用局部刷新的方法 int tag = Integer.parseInt(String .valueOf(((Object[]) msg.obj)[0])); ImageView iv = (ImageView) viewPager.findViewWithTag(tag); iv.setImageBitmap((Bitmap) ((Object[]) msg.obj)[1]); break; case 1: viewPager.setCurrentItem(viewPager.getCurrentItem() + 1);// 往后移动一页 if (isRunning) { handler.sendEmptyMessageDelayed(1, 2000); } break; case 2: break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化imageviews集合 initImageViews(); // 初始化布局文件中的view initViews(); // 初始化窗口参数 initWindowParams(); // 初始化lrucache initLruCache(); // 三个线程的线程池 pool = Executors.newScheduledThreadPool(3); // 初始化一个bitmapfactory选项对象 options = new Options(); // 初始化小点 initPointGroup(); notifyAdapter(); // 设置初始的时候在Integer一半附近的第一张图的位置上 viewPager.setCurrentItem(Integer.MAX_VALUE / 2 + (Integer.MAX_VALUE / 2) % ids.length); // 自动滑动标志位置为true isRunning = true; // 给viewpager添加滑动监听器 setViewPagerOnageChangeListener(); // msg.what=1,延迟2秒 handler.sendEmptyMessageDelayed(1, 2000); } private void setViewPagerOnageChangeListener() { viewPager.setOnPageChangeListener(new OnPageChangeListener() { /** * 当页面被选中的时候回调的方法 */ @Override public void onPageSelected(int position) { ll_points.getChildAt(lastPointPosition % 6).setEnabled(false); ll_points.getChildAt(position % 6).setEnabled(true); lastPointPosition = position; // 设置对应的描述文字 tv_des.setText(descriptions[position % 6]); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // TODO Auto-generated method stub } @Override public void onPageScrollStateChanged(int state) { // TODO Auto-generated method stub } }); } private void initPointGroup() { LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( 15, 15); layoutParams.setMargins(15, 2, 15, 5); for (int i = 0; i < ids.length; i++) { ImageView point = new ImageView(this); point.setBackgroundResource(R.drawable.point_bg); // 设置位置参数 point.setLayoutParams(layoutParams); if (i == 0) { point.setEnabled(true); } else { point.setEnabled(false); } ll_points.addView(point); } } private void initLruCache() { // lrucache通过构造函数传入缓存值,以kb为单位,因为这里除以了1204 int maxMem = (int) (Runtime.getRuntime().maxMemory() / 1024);// 本来是1024,这里除以1024应该是为了防止int类型溢出 int totalMemory = (int) (Runtime.getRuntime().totalMemory()); int freeMemory = (int) (Runtime.getRuntime().freeMemory()); System.out.println("totalMemory=" + Formatter.formatFileSize(this, totalMemory)); System.out.println("freeMemory=" + Formatter.formatFileSize(this, freeMemory)); int cacheSize = maxMem / 2; System.out.println("分配给LruCache的内存大小:" + Formatter.formatFileSize(this, cacheSize * 1024)); mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // return super.sizeOf(key, value); // 重写此方法来衡量每张图片的大小,默认返回图片数量。 // 总像素数乘以每个像素占的字节数,除以1024 return bitmap.getWidth() * bitmap.getHeight() * getBytesPerPixel(bitmap.getConfig()) / 1024; // 下面这一句要求API 12 // return bitmap.getByteCount() / 1024;// 这里和上面都除以1024,达到一致即可 } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { super.entryRemoved(evicted, key, oldValue, newValue); } }; } /** * 获取每个像素所占用的Byte数 * * @param config * @return */ public static int getBytesPerPixel(Config config) { if (config == Config.ARGB_8888) { return 4; } else if (config == Config.RGB_565) { return 2; } else if (config == Config.ARGB_4444) { return 2; } else if (config == Config.ALPHA_8) { return 1; } return 1; } private void initWindowParams() { wm = (WindowManager) getSystemService(WINDOW_SERVICE); wWidth = wm.getDefaultDisplay().getWidth(); wHeight = wm.getDefaultDisplay().getHeight(); } private void initViews() { ll_points = (LinearLayout) findViewById(R.id.ll_points); tv_des = (TextView) findViewById(R.id.tv_des); tv_des.setText(descriptions[0]); viewPager = (ViewPager) findViewById(R.id.viewPager); // TODO 自适应屏幕的viewpager大小的参数 } private void initImageViews() { for (int i = 0; i < ids.length; i++) { imageViews.add(new ImageView(this)); } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); isRunning = false; // android.os.Debug.stopMethodTracing(); } /** * 将对应key的bitmap加入到cache中 * * @param key * @param bitmap */ public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 从cache中取出对应key的bitmap对象 * * @param key * @return */ public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } /** * 设置apdater的方法 */ private void notifyAdapter() { if (adapter == null) { adapter = new MyAdapter(); viewPager.setAdapter(adapter); } else { /* * 发现下面这一句并不能起到刷新数据的作用, 除非将 getItemPosition方法的返回值设为POSITION_NONE */ // adapter.notifyDataSetChanged(); } } public void loadBitmap(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { // imageView.setImageResource(R.drawable.ic_launcher);// 默认 // 这里是使用AsyncTask的方法 BitmapWorkerTask task = new BitmapWorkerTask(); task.execute(resId); // 下面是使用线程池的方式,两种方式都可用,线程池可能对于网络加载会好一点 // pool.submit(new MyRunnable(resId)); } } class MyRunnable implements Runnable { private int id; public MyRunnable() { super(); } public MyRunnable(int id) { super(); this.id = id; } @Override public void run() { final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), id, wWidth, wHeight); // System.out.println("wWidth=" + wWidth); // System.out.println("wHeight=" + wHeight); addBitmapToMemoryCache(String.valueOf(id), bitmap); Message msg = Message.obtain(); msg.what = 0; objs = new Object[] { id, bitmap }; msg.obj = objs; handler.sendMessage(msg); } } class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { // 在后台加载图片。 @Override protected Bitmap doInBackground(Integer... params) { final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], wWidth, wHeight); // System.out.println("wWidth=" + wWidth); // System.out.println("wHeight=" + wHeight); addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); Message msg = Message.obtain(); msg.what = 0; objs = new Object[] { params[0], bitmap }; msg.obj = objs; handler.sendMessage(msg); return bitmap; } } public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // 调用上面定义的方法计算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); System.out.println("inSampleSize=" + options.inSampleSize); // 使用获取到的inSampleSize值再次解析图片 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源图片的高度和宽度 final int height = options.outHeight; final int width = options.outWidth; // System.out.println("height=" + height); // System.out.println("width=" + width); int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 计算出实际宽高和目标宽高的比率 final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 // 一定都会大于等于目标的宽和高。 // inSampleSize = heightRatio < widthRatio ? heightRatio : // widthRatio; // 这里我们选择宽度对其的方式,所以缩放比选则宽度的比值 inSampleSize = widthRatio; } return inSampleSize; } private class MyAdapter extends PagerAdapter { @Override public int getCount() { // return ids.length; return Integer.MAX_VALUE; } @Override public boolean isViewFromObject(View view, Object object) { // TODO Auto-generated method stub return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { ImageView imageView = imageViews.get(position % 6); container.addView(imageView); loadBitmap(ids[position % 6], imageView); // 把id与imageview通过setTag方法挂钩起来 imageView.setTag(ids[position % 6]); int totalMemory = (int) (Runtime.getRuntime().totalMemory());// 本来是1024,这里除以1024应该是为了防止int类型溢出 int freeMemory = (int) (Runtime.getRuntime().freeMemory());// 本来是1024,这里除以1024应该是为了防止int类型溢出 System.out.println("totalMemory=" + Formatter.formatFileSize(MainActivity.this, totalMemory)); System.out.println("freeMemory=" + Formatter.formatFileSize(MainActivity.this, freeMemory)); return imageView; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); object = null; } // /** // * 这样返回一个position_none之后,实现了viewpager的动态刷新,但是也带来了一些问题 // */ // @Override // public int getItemPosition(Object object) { // // TODO Auto-generated method stub // // return super.getItemPosition(object); // return POSITION_NONE; // } } }
布局文件:
<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="${relativePackage}.${activityClass}" > <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="fill_parent" android:layout_height="202.5dp" android:layout_gravity="top" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignBottom="@id/viewPager" android:background="#66ffffff" android:gravity="bottom" android:orientation="vertical" > <TextView android:id="@+id/tv_des" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:textSize="15sp" /> <LinearLayout android:id="@+id/ll_points" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="horizontal" > </LinearLayout> </LinearLayout> </RelativeLayout>
point_bg.xml:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/point_normal" android:state_enabled="false"></item> <item android:drawable="@drawable/point_focused" android:state_enabled="true"></item> </selector>
point_normal.xml:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" > <size android:height="5dp" android:width="5dp" /> <solid android:color="#55eeeeee" /> </shape>
point_focused.xml:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" > <size android:height="5dp" android:width="5dp" /> <solid android:color="#ffffffff" /> </shape>