Android组件banner实现左右滑屏效果_Android

什么是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

/**
 * 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);
   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;
 } 

 /**
 *
 * @param context activity实例
 * @param width banner的宽度 单位px
 * @param height banner的高度 单位dip,-1表示根据图片比例自适应高度
 * @param bannerClickListener 单击banner的回调接口
 */
 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();
 } 

 /**
 * 通过xml方式加载banner,必须调用此方法才能显示
 */
 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;
 } 

 /**
 * 设置回调接口
 * @param callBack 单击banner的回调接口
 */
 public void setOnBannerClickListener(OnBannerClickListener bannerClickListener)
 {
 mBannerClickListener = (bannerClickListener != null ? bannerClickListener : ScrollBanner.this);
 } 

 /**
 * 设置banner的解析度
 * @param width banner的宽度
 * @param height banner的高度
 */
 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();
 } 

 /**
 * 获取banner的高度
 * @return banner的高度 单位:px
 */
 public int getHeightPixels()
 {
 return mScreenHeight;
 } 

 /**
 * 设置banner是否可以弹性滑出边界
 * @param canOverScroll true表示可以滑出边界,false不能
 */
 public void setOverScrollMode(boolean canOverScroll)
 {
 mOverScrollMode = canOverScroll;
 if(canOverScroll == false)
  mOverScrollDistance = 0;
 } 

 /**
 * 向后台获取banner的各种信息
 */
 private void fetchBannerInfo()
 {
 NetworkManager netManager = (NetworkManager) AppEngine.getInstance().getManager(
  IManager.NETWORK_ID);
 netManager.getBannerInfo( String.valueOf(mLocation), ScrollBanner.this );
 } 

 /**
 * 获取banner的滑屏图像
 */
 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 );
 }
 } 

 /**
 * 设置banner的滑屏图像
 * @param position 如果position=-1,则表示设置全部bitmap
 */
 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)));
 }
 } 

 /**
 * 是否显示banner
 * @param isShow true显示 false不显示
 */
 public void show(boolean isShow)
 {
 if(isShow)
 {
  this.setVisibility(View.VISIBLE);
  mTimerResume = true;
 }
 else
 {
  this.setVisibility(View.GONE);
  mTimerResume = false;
 }
 } 

 /**
 * 切换到指定屏幕
 * @param which 屏幕索引
 */
 public void switchToScreen(final int which)
 {
 mHorizontalScrollViewEx.switchView(which);
 } 

 /**
 * 设置屏幕的数量 (此函数暂不开放)
 * @param count 屏幕数量
 */
 protected void setScreenCount(final int count)
 {
 PAGE_COUNT = count;
 } 

 /**
 * 设置偏移的距离 如果mOverScrollMode为false,则此设置无效 (此函数暂不开放)
 * @param distance
 */
 protected void setOverScrollDistance(int distance)
 {
 if(distance < 0)
  distance = 0; 

 mOverScrollDistance = mOverScrollMode ? distance : 0;
 } 

 /**
 * 切换小圆点
 * @param position current screen index
 */
 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);
 } 

 /**
 * 初始化整个FrameLayout视图组
 */
 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);
 } 

 /**
 * 加一个banner页面 TODO此函数写的不好
 */
 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);
 } 

 /**
  * 切换到指定屏幕
  * @param whichScreen 屏幕index
  */
 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);
 } 

 /**
  * 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
  */
 @Override
 public boolean onDown(MotionEvent e)
 {
  Logger.i("MyGesture", "onDown"); 

  mScrollX = HorizontalScrollViewEx.this.getScrollX(); 

  return true;
 } 

 /**
  * 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发
  * 注意和onDown()的区别,强调的是没有松开或者拖动的状态
  */
 public void onShowPress(MotionEvent e)
 {
  Logger.i("MyGesture", "onShowPress");
 } 

 /**
  * 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
  */
 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;
 } 

 /** 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE,
  * 1个ACTION_UP触发
  */
 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;
 } 

 /**
  * 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
  */
 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;
 } 

 /**
  * 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发
  */
 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<childCount; i++)
  {
   final View childView = getChildAt(i);
   if (childView.getVisibility() != View.GONE)
   {
   final int childWidth = childView.getMeasuredWidth();
   childView.layout(childLeft, 0,
    childLeft+childWidth, childView.getMeasuredHeight());
   childLeft += childWidth;
   }
  }
  }
 } 

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
 {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec); 

  final int width = MeasureSpec.getSize(widthMeasureSpec);
  final int count = getChildCount(); 

  for (int i = 0; i < count; i++)
  {
  getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
  }
  scrollTo(mWhich * mScreenWidth, 0);
 } 

 } 

 /**
 * override此函数,防止其修改构造方法所定义的宽和高<br/>
 * 注意:在这里,设置宽和高将不起作用
 */
 @Override
 public void setLayoutParams(android.view.ViewGroup.LayoutParams params)
 {
 params.width = mScreenWidth;
 params.height = mScreenHeight; 

 super.setLayoutParams(params);
 } 

 //标志view AttachedToWindow
 @Override
 protected void onAttachedToWindow()
 {
 super.onAttachedToWindow();
 mTimerResume = true;
 } 

 //标志view已经脱离window,典型的情形是view被销毁了,此时取消timer
 @Override
 protected void onDetachedFromWindow()
 {
 super.onDetachedFromWindow();
 Logger.d(TAG, "onDetachedFromWindow"); 

 mTimerResume = false;
 int activityId = ( (BaseActivity)mContext ).activityId();
 //如果是账号管理页面 则释放内存
 if(activityId == BaseActivity.ACCOUNT_ID)
 {
  destroy();
 }
 } 

 /**
 * 销毁banner
 */
 public void destroy()
 {
 mTimerTask.cancel();
 timer.cancel();
 //去除各种bitmap对activity的引用关系
 destoryBitmaps();
 System.gc();
 } 

 /**
 * 去除各种bitmap对activity的引用关系
 */
 private void destoryBitmaps()
 {
 for (View view : mLinearLayoutScreens)
 {
  Drawable drawable = view.getBackground();
  BitmapDrawable bitmapDrawable = null;
  if(drawable instanceof BitmapDrawable)
  bitmapDrawable = (BitmapDrawable)drawable; 

  if(bitmapDrawable != null)
  {
  //解除drawable对view的引用
  bitmapDrawable.setCallback(null);
  bitmapDrawable = null;
  }
 } 

 for (ImageView imageView : mImageViewList)
 {
  Drawable drawable = imageView.getDrawable();
  if(drawable != null)
  {
  drawable.setCallback(null);
  drawable = null;
  }
 } 

 mPageIndicator.setCallback(null);
 mPageIndicator = null;
 mPageIndicatorFocused.setCallback(null);
 mPageIndicatorFocused = null; 

 mLinearLayoutScreens.clear();
 mLinearLayoutScreens = null; 

 mBannerBitmaps.clear();
 mBannerBitmaps = null; 

 mImageViewList.clear();
 mImageViewList = null; 

 mBannerItemsList.clear();
 mBannerItemsList = null;
 } 

 //单击事件
 @Override
 public void onBannerClick( BannerMotionEvent bannerMotionEvent )
 {
 final int position = bannerMotionEvent.index;
 if(mContext == null)
  return; 

 NotificationInfo notificationInfo = new NotificationInfo();
 notificationInfo.msgType = bannerMotionEvent.getAction();
 int action = bannerMotionEvent.getAction();
 if(action == NotificationInfo.NOTIFICATION_SINGLEGAME_MSG) //单个游戏消息,直接启动该游戏
 {
  try
  {
  notificationInfo.gameId = Integer.parseInt( bannerMotionEvent.getGameId() );
  notificationInfo.gameType = Integer.parseInt( bannerMotionEvent.getGameType() );
  }
  catch (NumberFormatException e)
  {
  Logger.e(TAG, e.toString());
  return;
  }
 }
 else if(action == NotificationInfo.NOTIFICATION_GAMEPAGE_MSG) //游戏主页消息,通过客户端展示游戏主页
 {
  try
  {
  notificationInfo.gameId = Integer.parseInt( bannerMotionEvent.getGameId() );
  }
  catch (NumberFormatException e)
  {
  Logger.e(TAG, e.toString());
  return;
  }
  notificationInfo.issueTitle = bannerMotionEvent.getTitle();
 }
 else if(action == NotificationInfo.NOTIFICATION_SHOW_WEBVIEW_MSG) //交叉推广消息,通过一个webview展示
 {
  notificationInfo.issueTitle = bannerMotionEvent.getTitle();
  notificationInfo.openUrl = bannerMotionEvent.getResponseUrl();
 }
 else //reserved
 {
  return;
 } 

 Intent intent = notificationInfo.generateIntent(mContext);
 if(intent != null)
  mContext.startActivity(intent);
 } 

 /**
 * ScrollBanner所关联的banner项 可以为多个 一个为一屏
 */
 public static class BannerItem extends Object
 {
 public static final String ACTION = "action";
 public static final String URL = "url";
 public static final String IMGURL = "imgurl";
 public static final String GAMEID = "gameid";
 public static final String GAMETYPE = "gametype";
 public static final String TITLE = "title"; 

 public int index = -1;
 public int action = -1;
 public String url = "";
 public String imgUrl = "";
 public String gameId = "";
 public String gameType = "";
 public String title = ""; 

 public BannerItem(){}
 } 

 /**
 * BannerMotionEvent:单击banner所产生的事件对象<br/>
 *getAction()来获取动作类别<br/>
 *getResponseUrl()来获取响应url<br/>
 *...
 */
 public static class BannerMotionEvent extends Object
 {
 /**
  * ACTION_PLAY_FLASH: 播放游戏
  */
 public static final int ACTION_PLAY = 2;
 /**
  * ACTION_HOMEPAGE:打开官网
  */
 public static final int ACTION_HOMEPAGE = 3;
 /**
  * ACTION_OPEN_URL:打开指定url
  */
 public static final int ACTION_OPEN_URL = 4; 

 //banner中屏幕的index
 private int index = -1;
 //响应url
 private String responseUrl = "";
 //动作种类
 private int action = -1;
 //gameid
 private String gameId = "";
 //gametype flash游戏(0) or h5游戏(1)
 private String gameType = "";
 //webview的标题
 private String title = ""; 

 public BannerMotionEvent(int index, int action, String responseUrl,
  String gameId, String gameType, String title)
 {
  BannerMotionEvent.this.index = index;
  BannerMotionEvent.this.action = action;
  BannerMotionEvent.this.responseUrl = responseUrl;
  BannerMotionEvent.this.gameId = gameId;
  BannerMotionEvent.this.gameType = gameType;
  BannerMotionEvent.this.title = title;
 } 

 /**
  * 获取当前BannerMotionEvent事件对象的动作种类
  * @return 动作种类:ACTION_PLAY等
  */
 public int getAction()
 {
  return action;
 } 

 /**
  * 获取当前BannerMotionEvent事件对象的title
  * @return title webview的标题
  */
 public String getTitle()
 {
  return title;
 } 

 /**
  * 获取当前BannerMotionEvent事件对象的gameId
  * @return gameId
  */
 public String getGameId()
 {
  return gameId;
 } 

 /**
  * 获取当前BannerMotionEvent事件对象的gameType
  * @return gameType 0 or 1
  */
 public String getGameType()
 {
  return gameType;
 } 

 /**
  * 获取当前BannerMotionEvent事件对象的响应url
  * @return 响应url
  */
 public String getResponseUrl()
 {
  return responseUrl;
 } 

 @SuppressLint("DefaultLocale")
 @Override
 public String toString()
 {
  return String.format("BannerMotionEvent { index=%d, action=%d, responseUrl=%s, gameId=%s, gameType=%s, title=%s }",
   index, action, responseUrl, gameId, gameType, title);
 }
 } 

 @Override
 public void onBannerInfoSuccess(List<BannerItem> 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
 {
 /**
  * banner单击事件
  * @param bannerMotionEvent 单击事件对象,包含所需的响应信息
  * 参见 {@link BannerMotionEvent}
  */
 public abstract void onBannerClick( BannerMotionEvent bannerMotionEvent );
 } 

} 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索banner左右滚动代码、手机端左右滑动banner、banner左右点击切换、css3实现banner滚动、banner 实现,以便于您获取更多的相关知识。

时间: 2024-08-15 11:18:18

Android组件banner实现左右滑屏效果_Android的相关文章

Android组件banner实现左右滑屏效果

什么是banner组件?在许多Android应用上,比如爱奇艺客户端.百度美拍.应用宝等上面,都有一个可以手动滑动的小广告条,这就是banner,实际应用中的banner,其信息(图片和点击行为)是后台可配置的,是需要通过网络从后台拉取的.网上有许多手动滑屏的例子,但是一般只是个demo,无法在实际中使用,因为其一般没有考虑如下几类问题:图片缓存.OOM问题.是否可灵活配置.是否预留外部接口以及是否封装良好.没有良好的封装,手动滑屏加在代码中,会使得代码变得很烂很脆弱. 1.原理 参见下图.整个

Android Listview多tab上滑悬浮效果_Android

样例        近期要做一个含有两个tab切换页面,两个页面有公共的描述信息区域,两个tab都是listview,可以向上或向下拉动刷新,在页面中部有一个tab切换区域,向上滑动的时候tab区域到顶部后就不在移动,向下拉又重新回到初始位置,先看一样样式图吧! 整个需求大致如上图所示,其中上拉刷新和下拉刷新没有截图,采用了开源控件PullToRefreshListView来实现这个效果. 实现方式 总体思路,为了简单不想监控很多手势问题,因此投机取巧的采用下面的方式来实现, a. 整个页面是一

Android组件Glide实现图片平滑滚动效果_Android

Glide是一款基于Android的图片加载和图片缓存组件,它可以最大性能地在Android设备上读取.解码.显示图片和视频.Glide可以将远程的图片.视频.动画图片等缓存在设备本地,便于提高用户浏览图片的流畅体验. Glide最核心的功能就是提高滚动图片列表的性能,并且Glide还能满足对远程图片的读取.改变尺寸以及展示的性能要求. Glide使用方法 最简单的示例代码如下: // For a simple view: @Override public void onCreate(Bundl

Android中SwipeBack实现右滑返回效果_Android

现在有很多App支持右滑返回,比如知乎,效果比较赞. 于是自己对Activity和Fragment进行了继承,派生出SwipeBackActivity和SwipeBackFragment,用于对这种效果的实现,也就是只要继承这两个类就可以了. 效果如下 Activity Fragment Frgament的效果实现比Activity稍微简单,因为Activity要考虑到dectorView. 支持滑动的控件SwipeLayout,核心思路就是把原有的控件添加到支持滑动的控件中,SwipeLayo

Android使用ViewFlipper和GestrueDetector共同实现滑屏效果实例

本文实例讲述了Android使用ViewFlipper和GestrueDetector共同实现滑屏效果.分享给大家供大家参考,具体如下: 关于GestureDetector的相关知识,前面已经介绍过了,不懂的大家可以去了解一下. 1.main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/

Android基于ViewPager+Fragment实现左右滑屏效果的方法

本文实例讲述了Android基于ViewPager+Fragment实现左右滑屏效果的方法.分享给大家供大家参考,具体如下: 1.xml布局模板 <android.support.v4.view.ViewPager android:id="@+id/local_software_viewpager" android:layout_width="match_parent" android:layout_height="match_parent"

android 左右页面滑动(滑屏)增加layout文件 而不是drawable(还有activity)

android 左右页面滑动(滑屏)增加layout文件 而不是drawable(还有activity)    |字号 订阅 ViewFlipper + GestureDetector 简单的实现:   这里还需实现 implements OnGestureListener . 另外需要4个动画的配置文件: 说到Animation,我们先看下如何在Android中实现自定义Animation.自定义的Animation是以XML格式定义的,定义好的XML文件存放在res/anim中.   一般的

Android仿微信图片点击全屏效果_Android

废话不多说先看下效果 先是微信的 再是模仿的 先说下实现原理再一步步分析 这里总共有2个Activity一个就是主页一个就是显示我们图片效果的页面参数通过Intent传送素材内容均来自网络(感谢聪明的蘑菇) 图片都是Glide异步下的下的下的重要的事情说三次然后就是用动画做放大操作然后显示出来了并没有做下载原图的实现反正也是一样 下载下来Set上去而且动画都不需要更简便. OK我们来看分析下 obj目录下分别创建了2个对象一个用来使用来处理显示页面的图片尺寸信息以及位置信息还有一个是用来附带UR

Android实现滑动到顶部悬停的效果_Android

先来看下要实现效果图: 查阅资料后,发现网上大部分都是用这种方法实现的: 多写一个和需要悬浮的部分一模一样的layout,先把浮动区域的可见性设置为gone.当浮动区域滑动到顶部的时候,就把浮动区域B的可见性设置为VISIBLE.这样看起来就像悬浮在顶部不动了. 这里介绍的是另外一种方式: 使用design包中的控件 <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.