什么是banner组件?在许多Android应用上,比如爱奇艺客户端、百度美拍、应用宝等上面,都有一个可以手动滑动的小广告条,这就是banner,实际应用中的banner,其信息(图片和点击行为)是后台可配置的,是需要通过网络从后台拉取的。网上有许多手动滑屏的例子,但是一般只是个demo,无法在实际中使用,因为其一般没有考虑如下几类问题:图片缓存、OOM问题、是否可灵活配置、是否预留外部接口以及是否封装良好。没有良好的封装,手动滑屏加在代码中,会使得代码变得很烂很脆弱。
1.原理
参见下图。整个组件是一个frameLayout,里面有两个view,第一个是LinearLayout,承载了4个(或多个)可以滑动的view,见图中绿色背景的部分;第二个是一个RelativeLayout,在其底部放置了一个LinearLayout,在LinearLayout的内部放置了若干个小圆点,用来指示当前屏幕的索引。手势检测用了GestureDetector,并实现了OnGestureListener接口。为了能控制滑动速度,采用了Scroller弹性滑动对象。
为什么组件继承frameLayout,是因为用于指示的小圆点是出现在view上面的,一个view叠在另一个view上面,这就是frameLayout的特性
2.功能、效果
banner属性可动态设置,默认数量为4,可以调整默认的数量
banner信息从后台获取,banner的条数就是屏幕的数量
可自动滑动也能手动滑动
图片下载为多线程,并采用常见的三级cache策略(内存、文件、网络),节省流量,并处理了OOM异常
内部处理点击事件,同时预留出了接口函数
banner封装成一个ViewGroup类,使用起来简单,最少只需要两行代码
3.代码
代码注释写的比较详细,应该很好理解。分为2个文件,一个是banner的类,另一个是接口声明。
ScrollBanner.java
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 mLinearLayoutScreens = new ArrayList();
private List mBannerBitmaps = new ArrayList();
//banner信息
private List mBannerItemsList = new ArrayList();
//小圆点
private List mImageViewList = new ArrayList();
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);
if( mBannerBitmaps != null )
{
mBannerBitmaps.set( i, bitmap );
setBannerImages(i);
}
break;
}
}
};
};
public ScrollBanner(Context context)
{
this(context, null);
}
public ScrollBanner(Context context, AttributeSet attrs)
{
super(context, attrs);
mContext = context;
}
public ScrollBanner(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mContext = context;
}
public ScrollBanner(Context context, final int width, final int height, onBannerClickListener bannerClickListener)
{
this(context, null);
int activityId = ( (baseActivity)context ).activityId();
if(activityId == baseActivity.ACCOUNT_ID)//位置3
mLocation = 3;
else if(activityId == baseActivity.GAMEZONE_ID)//位置2
{
mLocation = 2;
}
//初始化时不显示banner
this.show(false);
setResolution(width, height);
setonBannerClickListener(bannerClickListener);
setDefaultBannerImages();
fetchBannerInfo();
}
public void showBanner()
{
int activityId = ( (baseActivity)mContext ).activityId();
if(activityId == baseActivity.ACCOUNT_ID)//位置3
mLocation = 3;
else if(activityId == baseActivity.GAMEZONE_ID)//位置2
{
mLocation = 2;
}
setDefaultBannerImages();
fetchBannerInfo();
}
public void pauseScroll()
{
mTimerResume = false;
}
public void resumeScroll()
{
mTimerResume = true;
}
public void setonBannerClickListener(onBannerClickListener bannerClickListener)
{
mBannerClickListener = (bannerClickListener != null ? bannerClickListener : ScrollBanner.this);
}
public void setResolution(final int width, final int height)
{
int heightInPx = height;
if(height == -1)
heightInPx = (int)(ratio * width) ;
else
{
Resources resources = getResources();
heightInPx = Math.round( TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, height, resources.getDisplayMetrics()) );
}
mScreenWidth = width;
mScreenHeight = heightInPx;
setLayoutParams(new LayoutParams(width, heightInPx));
initScrollView();
}
public int getHeightPixels()
{
return mScreenHeight;
}
public void setOverScrollMode(boolean canOverScroll)
{
mOverScrollMode = canOverScroll;
if(canOverScroll == false)
mOverScrollDistance = 0;
}
private void fetchBannerInfo()
{
NetworkManager netManager = (NetworkManager) AppEngine.getInstance().getManager(
IManager.NETWORK_ID);
netManager.getBannerInfo( String.valueOf(mLocation), ScrollBanner.this );
}
private void setDefaultBannerImages()
{
//为banner设置默认bitmap
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
bitmapFactoryOptions.inJustDecodeBounds = false;
bitmapFactoryOptions.inSampleSize = 2;
Resources res=mContext.getResources();
mDefaultBitmap = BitmapFactory.decodeResource(res, R.drawable.banner_image_default, bitmapFactoryOptions);
for(int i = 0; i < PAGE_COUNT; i++)
mBannerBitmaps.add(i, mDefaultBitmap);
//初始化BannerItem对象
for (int i = 0; i < PAGE_COUNT; i++)
mBannerItemsList.add(i, null);
setBannerImages(-1);
}
private void fetchBannerImages()
{
//表示已经执行了onDetachedFromWindow,banner已经被销毁了
if( mBannerItemsList == null )
return;
//ImageManager 根据url向其获取bitmap
ImageManager imageManager = (ImageManager)AppEngine.getInstance().
getManager(IManager.IMAGE_ID);
BannerItem item = null;
for(int i = 0; i < PAGE_COUNT; i++)
{
try
{
item = mBannerItemsList.get(i);
}
catch (IndexOutOfBoundsException e)
{
Logger.e(TAG, "fetchBannerImages error: " + e);
}
catch (Exception e)
{
Logger.e(TAG, "fetchBannerImages error: " + e);
}
//ImageManager为多线程,采用常见的三级cache策略(内存、文件、网络)
if( item != null && item.imgUrl != null )
imageManager.loadBitmap( item.imgUrl, mBitmapHandler );
}
}
private void setBannerImages(final int position)
{
int size = mBannerBitmaps.size();
if (size < PAGE_COUNT || mLinearLayoutScreens == null)
{
return;
}
if(position >=0 && position < PAGE_COUNT )
{
Drawable drawable = mLinearLayoutScreens.get(position).getBackground();
mLinearLayoutScreens.get(position).setBackgroundDrawable
(new BitmapDrawable( mBannerBitmaps.get(position) ) );
drawable.setCallback(null);
drawable = null;
return;
}
for(int i = 0; i < PAGE_COUNT; i++)
{
mLinearLayoutScreens.get(i).setBackgroundDrawable(new BitmapDrawable(mBannerBitmaps.get(i)));
}
}
public void show(boolean isShow)
{
if(isShow)
{
this.setVisibility(View.VISIBLE);
mTimerResume = true;
}
else
{
this.setVisibility(View.GONE);
mTimerResume = false;
}
}
public void switchToScreen(final int which)
{
mHorizontalScrollViewEx.switchView(which);
}
protected void setScreenCount(final int count)
{
PAGE_COUNT = count;
}
protected void setOverScrollDistance(int distance)
{
if(distance < 0)
distance = 0;
mOverScrollDistance = mOverScrollMode ? distance : 0;
}
private void switchScreenPosition(final int position)
{
if( mPageIndicator == null || mPageIndicatorFocused == null )
return;
int length = 0;
if(mImageViewList != null)
length = mImageViewList.size();
if (position >= length || position < 0 || length <= 0)
{
return;
}
for(int i = 0; i < length; i++)
{
mImageViewList.get(i).setImageDrawable(mPageIndicator);
}
mImageViewList.get(position).setImageDrawable(mPageIndicatorFocused);
}
private void initScrollView()
{
setLayoutParams(new LayoutParams(mScreenWidth, mScreenHeight ));
linearLayoutScrolLayout = new LinearLayout(mContext);
linearLayoutScrolLayout.setBackgroundColor(Color.WHITE);
linearLayoutScrolLayout.setOrientation(LinearLayout.HORIZONTAL);
int mVersionCode = 8;
try
{
mVersionCode = Integer.valueOf(android.os.Build.VERSION.SDK);
Logger.d(TAG, "sdk version=" + mVersionCode);
}
catch (Exception e)
{
e.printStackTrace();
}
//针对android1.6及以下的特殊处理 此为android的低版本bug
if(mVersionCode <= 5)
{
linearLayoutScrolLayout.setbaselineAligned(false);
}
//初始化四个滑动view
for(int i = 0; i < PAGE_COUNT; i++)
{
LinearLayout linearLayoutScreen = new LinearLayout(mContext);
linearLayoutScreen.setOrientation(LinearLayout.VERTICAL);
linearLayoutScrolLayout.addView(linearLayoutScreen, new LayoutParams(
mScreenWidth,
LayoutParams.FILL_PARENT));
mLinearLayoutScreens.add(i, linearLayoutScreen);
}
//初始化小圆点视图
RelativeLayout relativeLayout = new RelativeLayout(mContext);
relativeLayout.setLayoutParams(new LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
//linearLayoutForDot为小圆点视图
linearLayoutForDot =new LinearLayout(mContext);
android.widget.RelativeLayout.LayoutParams layoutParams =
new android.widget.RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
//小圆点距底部的距离 单位:px
layoutParams.bottomMargin = MARGIN_BOTTOM;
layoutParams.rightMargin = MARGIN_BOTTOM;
layoutParams.addRule(android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams.addRule(android.widget.RelativeLayout.CENTER_HORIZONTAL);
linearLayoutForDot.setLayoutParams(layoutParams);
linearLayoutForDot.setOrientation(LinearLayout.HORIZONTAL);
linearLayoutForDot.setHorizontalGravity(Gravity.CENTER);
linearLayoutForDot.setVerticalGravity(Gravity.CENTER);
//下面两句实现圆角半透明效果 不采用
// linearLayoutForDot.setBackgroundResource(R.drawable.round_corner_bg);
// linearLayoutForDot.getBackground().setAlpha(100);
//初始化4个小圆点
mPageIndicator = getResources().getDrawable(R.drawable.page_indicator);
mPageIndicatorFocused = getResources().getDrawable(R.drawable.page_indicator_focused);
for(int i = 0; i < PAGE_COUNT; i++)
{
ImageView imageView = new ImageView(mContext);
imageView.setImageDrawable(mPageIndicator);
mImageViewList.add(i, imageView);
LinearLayout.LayoutParams layoutParamsForDot =
new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
layoutParamsForDot.rightMargin = 5;
linearLayoutForDot.addView(imageView, layoutParamsForDot);
}
mImageViewList.get(0).setImageDrawable(mPageIndicatorFocused);
relativeLayout.addView(linearLayoutForDot);
mHorizontalScrollViewEx = new HorizontalScrollViewEx(mContext, null, mBannerClickListener);
mHorizontalScrollViewEx.setLayoutParams(new LayoutParams(
mScreenWidth * PAGE_COUNT,
LayoutParams.FILL_PARENT));
mHorizontalScrollViewEx.addView(linearLayoutScrolLayout, new LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
mHorizontalScrollViewEx.setHorizontalScrollBarEnabled(false);
mHorizontalScrollViewEx.setHorizontalFadingEdgeEnabled(false);
addView(mHorizontalScrollViewEx);
addView(relativeLayout);
//自动滑屏 5秒一次
timer.schedule(mTimerTask, 5000, TIMER_DURATION);
}
private void addBannerItem()
{
//表示已经执行了onDetachedFromWindow,banner已经被销毁了
if( mBannerBitmaps == null || mLinearLayoutScreens == null ||
mImageViewList == null || mContext == null )
return;
//调整屏幕数量和总宽度
PAGE_COUNT += 1;
mHorizontalScrollViewEx.getLayoutParams().width = mScreenWidth * PAGE_COUNT;
//加载默认图片资源
if(mDefaultBitmap == null)
{
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
bitmapFactoryOptions.inJustDecodeBounds = false;
bitmapFactoryOptions.inSampleSize = 2;
Resources res=mContext.getResources();
mDefaultBitmap = BitmapFactory.decodeResource(res, R.drawable.banner_image_default, bitmapFactoryOptions);
}
mBannerBitmaps.add(mDefaultBitmap);
mBannerItemsList.add(null);
//加一个屏幕
LinearLayout linearLayoutScreen = new LinearLayout(mContext);
linearLayoutScreen.setOrientation(LinearLayout.VERTICAL);
linearLayoutScreen.setBackgroundDrawable(new BitmapDrawable( mBannerBitmaps.get(PAGE_COUNT - 1) ));
linearLayoutScrolLayout.addView(linearLayoutScreen, new LayoutParams(
mScreenWidth,
LayoutParams.FILL_PARENT));
mLinearLayoutScreens.add(linearLayoutScreen);
//加一个小圆点
ImageView imageView = new ImageView(mContext);
imageView.setImageDrawable(mPageIndicator);
mImageViewList.add(imageView);
LinearLayout.LayoutParams layoutParamsForDot =
new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
layoutParamsForDot.rightMargin = 5;
linearLayoutForDot.addView(imageView, layoutParamsForDot);
}
private class HorizontalScrollViewEx extends ViewGroup implements
onGestureListener
{
private GestureDetector mGestureDetector;
private int mWhichScreen;
public HorizontalScrollViewEx(Context context, AttributeSet attrs, onBannerClickListener bannerClickListener)
{
super(context, attrs);
mGestureDetector = new GestureDetector(this);
//解决长按屏幕后无法拖动的现象
mGestureDetector.setIsLongpressEnabled(false);
//构造弹性滑动对象
mScroller = new Scroller(context);
}
public void switchView(int whichScreen)
{
if(mLinearLayoutScreens == null)
return;
// 防止非法参数
if (whichScreen < 0)
whichScreen = 0;
else if(whichScreen >= PAGE_COUNT)
whichScreen = PAGE_COUNT - 1;
Logger.i(TAG, "switch view to " + whichScreen);
int delta = whichScreen * mScreenWidth - HorizontalScrollViewEx.this.getScrollX();
//缓慢滚动到指定位置
mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 3);
// refresh
invalidate();
//delta>0 stands for user scroll view to right
if (delta > 0)
mScrollToRight = true;
else
{
mScrollToRight = false;
}
mWhichScreen = whichScreen;
mWhich = whichScreen;
//切换小圆点
switchScreenPosition(mWhichScreen);
}
@Override
public boolean onDown(MotionEvent e)
{
Logger.i("MyGesture", "onDown");
mScrollX = HorizontalScrollViewEx.this.getScrollX();
return true;
}
public void onShowPress(MotionEvent e)
{
Logger.i("MyGesture", "onShowPress");
}
public boolean onSingleTapUp(MotionEvent e)
{
Logger.i("MyGesture", "onSingleTapUp");
if(mBannerItemsList == null || mBannerItemsList.size() <= mWhichScreen)
return false;
BannerItem bannerItem = mBannerItemsList.get(mWhichScreen);
if(bannerItem != null)
{
BannerMotionEvent bannerMotionEvent =
new BannerMotionEvent(mWhichScreen, bannerItem.action, bannerItem.url,
bannerItem.gameId, bannerItem.gameType, bannerItem.title);
mBannerClickListener.onBannerClick(bannerMotionEvent);
}
return false;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY)
{
Logger.i("MyGesture", "onFling velocityX=" + velocityX);
mWhichScreen = velocityX > 0 ?
mWhichScreen - 1
: mWhichScreen + 1;
switchView(mWhichScreen);
return true;
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY)
{
Logger.i("MyGesture", "onScroll");
//禁止弹性滚动
if (mOverScrollMode == false)
{
float x1 = e1.getX();
float x2 = e2.getX();
if(mWhichScreen == 0 && x1 < x2)
return false;
else if(mWhichScreen == PAGE_COUNT - 1 && x1 > x2)
return false;
}
// int distance = Math.abs(getScrollX() - mWhichScreen * mScreenWidth);
// if ((mWhichScreen ==0 || mWhichScreen == PAGE_COUNT -1) && distance > mOverScrollDistance)
// return false;
this.scrollBy((int)distanceX, 0);
return true;
}
public void onLongPress(MotionEvent e)
{
Logger.i("MyGesture", "onLongPress");
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
mTimerResume = false;
if ( !mScroller.isFinished() )
mScroller.abortAnimation();
}
else if(event.getAction() == MotionEvent.ACTION_UP)
{
//开始自动滑屏
mTimerResume = true;
mByUserAction = true;
}
boolean consume = mGestureDetector.onTouchEvent(event);
if (consume == false && event.getAction() == MotionEvent.ACTION_UP)
{
int curScrollX = HorizontalScrollViewEx.this.getScrollX();
int mWhichScreen = (curScrollX + mScreenWidth / 2) /mScreenWidth;
switchView(mWhichScreen);
}
return consume;
}
@Override
public void computeScroll()
{
if (mScroller.computeScrollOffset())
{
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
if (changed)
{
int childLeft = 0;
final int childCount = getChildCount();
for (int i=0; i items)
{
Logger.d(TAG, "onBannerInfoSuccess");
mBannerItemsList = items;
mHandler.sendEmptyMessage(MESSAGE_FETCH_BANNER_SUCCESS);
}
@Override
public void onBannerInfoFailed()
{
Logger.e(TAG, "onBannerInfoFailed");
}
}
ComponentCallBack.java
public interface ComponentCallBack
{
public static interface onBannerClickListener
{
public abstract void onBannerClick( BannerMotionEvent bannerMotionEvent );
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。



