- 原理
参见下图。整个组件是一个FrameLayout,里面有两个view,第一个是LinearLayout,承载了4个(或多个)可以滑动的view,见图中绿色背景的部分;第二个是一个RelativeLayout,在其底部放置了一个LinearLayout,在LinearLayout的内部放置了若干个小圆点,用来指示当前屏幕的索引。手势检测用了GestureDetector,并实现了OnGestureListener接口。为了能控制滑动速度,采用了Scroller弹性滑动对象。
为什么组件继承FrameLayout,是因为用于指示的小圆点是出现在view上面的,一个view叠在另一个view上面,这就是FrameLayout的特性
什么是banner组件?在许多android应用上,比如爱奇艺客户端、百度美拍、应用宝等上面,都有一个可以手动滑动的小广告条,这就是banner,实际应用中的banner,其信息(图片和点击行为)是后台可配置的,是需要通过网络从后台拉取的。网上有许多手动滑屏的例子,但是一般只是个demo,无法在实际中使用,因为其一般没有考虑如下几类问题:图片缓存、OOM问题、是否可灵活配置、是否预留外部接口以及是否封装良好。没有良好的封装,手动滑屏加在代码中,会使得代码变得很烂很脆弱。
2. 功能、效果
- banner属性可动态设置,默认数量为4,可以调整默认的数量
- banner信息从后台获取,banner的条数就是屏幕的数量
- 可自动滑动也能手动滑动
- 图片下载为多线程,并采用常见的三级cache策略(内存、文件、网络),节省流量,并处理了OOM异常
- 内部处理点击事件,同时预留出了接口函数
- banner封装成一个ViewGroup类,使用起来简单,最少只需要两行代码
3. 代码
代码注释写的比较详细,应该很好理解。分为2个文件,一个是banner的类,另一个是接口声明。
ScrollBanner.java
/** * ScrollBanner 支持滑屏效果的FrameLayout子类,可设置屏幕数量,尺寸。<br/> * 典型的用法:<br/> * ScrollBanner scrollBanner = new ScrollBanner(this, mScreenWidth, 100, this);<br/> * linearLayout.addView(scrollBanner);<br/> *注意事项:<br/> *1.如果重新设置ScrollBanner的LayoutParams,则参数中的宽和高属性将被忽略,仍然采用对象实例化的宽和高<br/> *2.点击事件的回调如果设为null,则采用默认的事件回调<br/> *3.通过setOverScrollMode来设置 banner是否能够滑出屏幕的边界<br/> *4通过xml方式加载banner,需要进行如下调用:<br/> * setResolution(width, height);<br/> setOnBannerClickListener(bannerClickListener);<br/> showBanner()<br/> * @author singwhatiwanna * @version 2013.3.4 * */public class ScrollBanner extends FrameLayout implements ComponentCallBack.OnBannerClickListener, ResponseHandler.BannerInfoHandler{ private static final String TAG = "ScrollBanner"; private HorizontalScrollViewEx mHorizontalScrollViewEx; //ScrollBanner的子view private LinearLayout linearLayoutScrolLayout; //linearLayoutScrolLayout的子view,用于放置若干个小圆点 private LinearLayout linearLayoutForDot; private Scroller mScroller; private Context mContext; private OnBannerClickListener mBannerClickListener; //屏幕及其bitmap private List<View> mLinearLayoutScreens = new ArrayList<View>(); private List<Bitmap> mBannerBitmaps = new ArrayList<Bitmap>(); //banner信息 private List<BannerItem> mBannerItemsList = new ArrayList<BannerItem>(); //小圆点 private List<ImageView> mImageViewList = new ArrayList<ImageView>(); private Drawable mPageIndicator; private Drawable mPageIndicatorFocused; //banner默认图片 private Bitmap mDefaultBitmap; private int mScreenWidth; private int mScreenHeight; private int mScrollX; //current screen index private int mWhich = 0; public static final int MESSAGE_AUTO_SCROLL = 1; public static final int MESSAGE_FETCH_BANNER_SUCCESS = 2; public static final int MARGIN_BOTTOM = 2; //480*150 banner的图片尺寸 150.0/480=0.3125f public static final float ratio = 0.3125f; //banner的位置 private int mLocation = -1; //banner分为几屏 private int PAGE_COUNT = 4; //滑动方向 是否向右滑动 private boolean mScrollToRight = true; //是否自动滑屏 private boolean mTimerResume = true; //标志用户是否手动滑动了屏幕 private boolean mByUserAction = false; //标志banner是否可以滑出边界 private boolean mOverScrollMode = false; //标志banner可以滑出边界多少像素 private int mOverScrollDistance = 0; //定时器 用于banner的自动播放 final Timer timer = new Timer(); //定时器的时间间隔 单位:ms public static final int TIMER_DURATION = 5000; private TimerTask mTimerTask = new TimerTask() { @Override public void run() { if (mTimerResume && !mByUserAction) { mHandler.sendEmptyMessage(MESSAGE_AUTO_SCROLL); } mByUserAction = false; } }; //ScrollBanner私有handler 用于处理内部逻辑 private Handler mHandler = new Handler() { public void handleMessage(Message msg) { //表示已经执行了onDetachedFromWindow,banner已经被销毁了 if( mBannerBitmaps == null || mLinearLayoutScreens == null || mImageViewList == null || mBannerItemsList == null || mContext == null ) return; switch (msg.what) { case MESSAGE_AUTO_SCROLL: if (mWhich == PAGE_COUNT - 1) mScrollToRight = false; else if(mWhich == 0) { mScrollToRight = true; } if (mScrollToRight) mWhich++; else { mWhich--; } mHorizontalScrollViewEx.switchView(mWhich); break; case MESSAGE_FETCH_BANNER_SUCCESS: int more = 0; if(mBannerItemsList != null) more = mBannerItemsList.size() - PAGE_COUNT; if(mBannerItemsList.size() > 0) { //如果有banner 显示它 ScrollBanner.this.show(true); } //如果后台返回的banneritem的数量大于预设值4 if(more > 0) { for (int i = 0; i < more; i++) addBannerItem(); } fetchBannerImages(); break; default: break; } }; }; //用于获取bitmap private Handler mBitmapHandler = new Handler() { public void handleMessage(Message msg) { //表示已经执行了onDetachedFromWindow,banner已经被销毁了 if( mBannerBitmaps == null || mLinearLayoutScreens == null || mImageViewList == null || mBannerItemsList == null || mContext == null ) return; Bitmap bitmap = (Bitmap)msg.obj; String urlString = msg.getData().getString("url"); Logger.d(TAG, "url=" + urlString); if (urlString == null || bitmap == null || mBannerItemsList == null) { Logger.w(TAG, "bitmap=null imgurl=" + urlString); return; } for( int i = 0; i < mBannerItemsList.size(); i++ ) { BannerItem item = mBannerItemsList.get(i); if(item != null && urlString.equals(item.imgUrl) ) { Logger.d(TAG, "find " + i + urlString);