原文:http://blog.csdn.net/singwhatiwanna/article/details/8875241
  1. 原理

参见下图。整个组件是一个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

[java] view plaincopy
  1. /**
  2. * ScrollBanner 支持滑屏效果的FrameLayout子类,可设置屏幕数量,尺寸。<br/>
  3. * 典型的用法:<br/>
  4. *      ScrollBanner scrollBanner = new ScrollBanner(this, mScreenWidth, 100, this);<br/>
  5. *      linearLayout.addView(scrollBanner);<br/>
  6. *注意事项:<br/>
  7. *1.如果重新设置ScrollBanner的LayoutParams,则参数中的宽和高属性将被忽略,仍然采用对象实例化的宽和高<br/>
  8. *2.点击事件的回调如果设为null,则采用默认的事件回调<br/>
  9. *3.通过setOverScrollMode来设置 banner是否能够滑出屏幕的边界<br/>
  10. *4通过xml方式加载banner,需要进行如下调用:<br/>
  11. *      setResolution(width, height);<br/>
  12. setOnBannerClickListener(bannerClickListener);<br/>
  13. showBanner()<br/>
  14. * @author singwhatiwanna
  15. * @version 2013.3.4
  16. *
  17. */
  18. public class ScrollBanner extends FrameLayout implements
  19. ComponentCallBack.OnBannerClickListener,
  20. ResponseHandler.BannerInfoHandler
  21. {
  22. private static final String TAG = "ScrollBanner";
  23. private HorizontalScrollViewEx mHorizontalScrollViewEx;
  24. //ScrollBanner的子view
  25. private LinearLayout linearLayoutScrolLayout;
  26. //linearLayoutScrolLayout的子view,用于放置若干个小圆点
  27. private LinearLayout linearLayoutForDot;
  28. private Scroller mScroller;
  29. private Context mContext;
  30. private OnBannerClickListener mBannerClickListener;
  31. //屏幕及其bitmap
  32. private List<View> mLinearLayoutScreens = new ArrayList<View>();
  33. private List<Bitmap> mBannerBitmaps = new ArrayList<Bitmap>();
  34. //banner信息
  35. private List<BannerItem> mBannerItemsList = new ArrayList<BannerItem>();
  36. //小圆点
  37. private List<ImageView> mImageViewList = new ArrayList<ImageView>();
  38. private Drawable mPageIndicator;
  39. private Drawable mPageIndicatorFocused;
  40. //banner默认图片
  41. private Bitmap mDefaultBitmap;
  42. private int mScreenWidth;
  43. private int mScreenHeight;
  44. private int mScrollX;
  45. //current screen index
  46. private int mWhich = 0;
  47. public static final int MESSAGE_AUTO_SCROLL = 1;
  48. public static final int MESSAGE_FETCH_BANNER_SUCCESS = 2;
  49. public static final int MARGIN_BOTTOM = 2;
  50. //480*150 banner的图片尺寸 150.0/480=0.3125f
  51. public static final float ratio = 0.3125f;
  52. //banner的位置
  53. private int mLocation = -1;
  54. //banner分为几屏
  55. private int PAGE_COUNT = 4;
  56. //滑动方向 是否向右滑动
  57. private boolean mScrollToRight = true;
  58. //是否自动滑屏
  59. private boolean mTimerResume = true;
  60. //标志用户是否手动滑动了屏幕
  61. private boolean mByUserAction = false;
  62. //标志banner是否可以滑出边界
  63. private boolean mOverScrollMode = false;
  64. //标志banner可以滑出边界多少像素
  65. private int mOverScrollDistance = 0;
  66. //定时器 用于banner的自动播放
  67. final Timer timer = new Timer();
  68. //定时器的时间间隔 单位:ms
  69. public static final int TIMER_DURATION = 5000;
  70. private TimerTask mTimerTask = new TimerTask()
  71. {
  72. @Override
  73. public void run()
  74. {
  75. if (mTimerResume && !mByUserAction)
  76. {
  77. mHandler.sendEmptyMessage(MESSAGE_AUTO_SCROLL);
  78. }
  79. mByUserAction = false;
  80. }
  81. };
  82. //ScrollBanner私有handler 用于处理内部逻辑
  83. private Handler mHandler = new Handler()
  84. {
  85. public void handleMessage(Message msg)
  86. {
  87. //表示已经执行了onDetachedFromWindow,banner已经被销毁了
  88. if( mBannerBitmaps == null || mLinearLayoutScreens == null ||
  89. mImageViewList == null || mBannerItemsList == null || mContext == null )
  90. return;
  91. switch (msg.what)
  92. {
  93. case MESSAGE_AUTO_SCROLL:
  94. if (mWhich == PAGE_COUNT - 1)
  95. mScrollToRight = false;
  96. else if(mWhich == 0)
  97. {
  98. mScrollToRight = true;
  99. }
  100. if (mScrollToRight)
  101. mWhich++;
  102. else
  103. {
  104. mWhich--;
  105. }
  106. mHorizontalScrollViewEx.switchView(mWhich);
  107. break;
  108. case MESSAGE_FETCH_BANNER_SUCCESS:
  109. int more = 0;
  110. if(mBannerItemsList != null)
  111. more = mBannerItemsList.size() - PAGE_COUNT;
  112. if(mBannerItemsList.size() > 0)
  113. {
  114. //如果有banner 显示它
  115. ScrollBanner.this.show(true);
  116. }
  117. //如果后台返回的banneritem的数量大于预设值4
  118. if(more > 0)
  119. {
  120. for (int i = 0; i < more; i++)
  121. addBannerItem();
  122. }
  123. fetchBannerImages();
  124. break;
  125. default:
  126. break;
  127. }
  128. };
  129. };
  130. //用于获取bitmap
  131. private Handler  mBitmapHandler = new Handler()
  132. {
  133. public void handleMessage(Message msg)
  134. {
  135. //表示已经执行了onDetachedFromWindow,banner已经被销毁了
  136. if( mBannerBitmaps == null || mLinearLayoutScreens == null ||
  137. mImageViewList == null || mBannerItemsList == null || mContext == null )
  138. return;
  139. Bitmap bitmap = (Bitmap)msg.obj;
  140. String urlString = msg.getData().getString("url");
  141. Logger.d(TAG, "url=" + urlString);
  142. if (urlString == null || bitmap == null || mBannerItemsList == null)
  143. {
  144. Logger.w(TAG, "bitmap=null imgurl=" + urlString);
  145. return;
  146. }
  147. for( int i = 0; i < mBannerItemsList.size(); i++ )
  148. {
  149. BannerItem item = mBannerItemsList.get(i);
  150. if(item != null && urlString.equals(item.imgUrl) )
  151. {
  152. Logger.d(TAG, "find " + i + urlString);
  153. if( mBannerBitmaps != null )
  154. {
  155. mBannerBitmaps.set( i, bitmap );
  156. setBannerImages(i);
  157. }
  158. break;
  159. }
  160. }
  161. };
  162. };
  163. public ScrollBanner(Context context)
  164. {
  165. this(context, null);
  166. }
  167. public ScrollBanner(Context context, AttributeSet attrs)
  168. {
  169. super(context, attrs);
  170. mContext = context;
  171. }
  172. public ScrollBanner(Context context, AttributeSet attrs, int defStyle)
  173. {
  174. super(context, attrs, defStyle);
  175. mContext = context;
  176. }
  177. /**
  178. *
  179. * @param context activity实例
  180. * @param width banner的宽度 单位px
  181. * @param height banner的高度 单位dip,-1表示根据图片比例自适应高度
  182. * @param bannerClickListener 单击banner的回调接口
  183. */
  184. public ScrollBanner(Context context, final int width, final int height, OnBannerClickListener bannerClickListener)
  185. {
  186. this(context, null);
  187. int activityId = ( (BaseActivity)context ).activityId();
  188. if(activityId == BaseActivity.ACCOUNT_ID)//位置3
  189. mLocation = 3;
  190. else if(activityId == BaseActivity.GAMEZONE_ID)//位置2
  191. {
  192. mLocation = 2;
  193. }
  194. //初始化时不显示banner
  195. this.show(false);
  196. setResolution(width, height);
  197. setOnBannerClickListener(bannerClickListener);
  198. setDefaultBannerImages();
  199. fetchBannerInfo();
  200. }
  201. /**
  202. * 通过xml方式加载banner,必须调用此方法才能显示
  203. */
  204. public void showBanner()
  205. {
  206. int activityId = ( (BaseActivity)mContext ).activityId();
  207. if(activityId == BaseActivity.ACCOUNT_ID)//位置3
  208. mLocation = 3;
  209. else if(activityId == BaseActivity.GAMEZONE_ID)//位置2
  210. {
  211. mLocation = 2;
  212. }
  213. setDefaultBannerImages();
  214. fetchBannerInfo();
  215. }
  216. /**
  217. * 暂停滚动
  218. */
  219. public void pauseScroll()
  220. {
  221. mTimerResume = false;
  222. }
  223. /**
  224. * 恢复滚动
  225. */
  226. public void resumeScroll()
  227. {
  228. mTimerResume = true;
  229. }
  230. /**
  231. * 设置回调接口
  232. * @param callBack 单击banner的回调接口
  233. */
  234. public void setOnBannerClickListener(OnBannerClickListener bannerClickListener)
  235. {
  236. mBannerClickListener = (bannerClickListener != null ? bannerClickListener : ScrollBanner.this);
  237. }
  238. /**
  239. * 设置banner的解析度
  240. * @param width banner的宽度
  241. * @param height banner的高度
  242. */
  243. public void setResolution(final int width, final int height)
  244. {
  245. int heightInPx = height;
  246. if(height == -1)
  247. heightInPx = (int)(ratio * width) ;
  248. else
  249. {
  250. Resources resources = getResources();
  251. heightInPx = Math.round( TypedValue.applyDimension(
  252. TypedValue.COMPLEX_UNIT_DIP, height, resources.getDisplayMetrics()) );
  253. }
  254. mScreenWidth = width;
  255. mScreenHeight = heightInPx;
  256. setLayoutParams(new LayoutParams(width, heightInPx));
  257. initScrollView();
  258. }
  259. /**
  260. * 获取banner的高度
  261. * @return banner的高度 单位:px
  262. */
  263. public int getHeightPixels()
  264. {
  265. return mScreenHeight;
  266. }
  267. /**
  268. * 设置banner是否可以弹性滑出边界
  269. * @param canOverScroll true表示可以滑出边界,false不能
  270. */
  271. public void setOverScrollMode(boolean canOverScroll)
  272. {
  273. mOverScrollMode = canOverScroll;
  274. if(canOverScroll == false)
  275. mOverScrollDistance = 0;
  276. }
  277. /**
  278. * 向后台获取banner的各种信息
  279. */
  280. private void fetchBannerInfo()
  281. {
  282. NetworkManager netManager = (NetworkManager) AppEngine.getInstance().getManager(
  283. IManager.NETWORK_ID);
  284. netManager.getBannerInfo( String.valueOf(mLocation), ScrollBanner.this );
  285. }
  286. /**
  287. * 获取banner的滑屏图像
  288. */
  289. private void setDefaultBannerImages()
  290. {
  291. //为banner设置默认bitmap
  292. BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
  293. bitmapFactoryOptions.inJustDecodeBounds = false;
  294. bitmapFactoryOptions.inSampleSize = 2;
  295. Resources res=mContext.getResources();
  296. mDefaultBitmap = BitmapFactory.decodeResource(res, R.drawable.banner_image_default, bitmapFactoryOptions);
  297. for(int i = 0; i < PAGE_COUNT; i++)
  298. mBannerBitmaps.add(i, mDefaultBitmap);
  299. //初始化BannerItem对象
  300. for (int i = 0; i < PAGE_COUNT; i++)
  301. mBannerItemsList.add(i, null);
  302. setBannerImages(-1);
  303. }
  304. private void fetchBannerImages()
  305. {
  306. //表示已经执行了onDetachedFromWindow,banner已经被销毁了
  307. if( mBannerItemsList == null )
  308. return;
  309. //ImageManager 根据url向其获取bitmap
  310. ImageManager imageManager = (ImageManager)AppEngine.getInstance().
  311. getManager(IManager.IMAGE_ID);
  312. BannerItem item = null;
  313. for(int i = 0; i < PAGE_COUNT; i++)
  314. {
  315. try
  316. {
  317. item = mBannerItemsList.get(i);
  318. }
  319. catch (IndexOutOfBoundsException e)
  320. {
  321. Logger.e(TAG, "fetchBannerImages error: " + e);
  322. }
  323. catch (Exception e)
  324. {
  325. Logger.e(TAG, "fetchBannerImages error: " + e);
  326. }
  327. //ImageManager为多线程,采用常见的三级cache策略(内存、文件、网络)
  328. if( item != null && item.imgUrl != null )
  329. imageManager.loadBitmap( item.imgUrl, mBitmapHandler );
  330. }
  331. }
  332. /**
  333. * 设置banner的滑屏图像
  334. * @param position 如果position=-1,则表示设置全部bitmap
  335. */
  336. private void setBannerImages(final int position)
  337. {
  338. int size = mBannerBitmaps.size();
  339. if (size < PAGE_COUNT || mLinearLayoutScreens == null)
  340. {
  341. return;
  342. }
  343. if(position >=0 && position < PAGE_COUNT )
  344. {
  345. Drawable drawable = mLinearLayoutScreens.get(position).getBackground();
  346. mLinearLayoutScreens.get(position).setBackgroundDrawable
  347. (new BitmapDrawable( mBannerBitmaps.get(position) ) );
  348. drawable.setCallback(null);
  349. drawable = null;
  350. return;
  351. }
  352. for(int i = 0; i < PAGE_COUNT; i++)
  353. {
  354. mLinearLayoutScreens.get(i).setBackgroundDrawable(new BitmapDrawable(mBannerBitmaps.get(i)));
  355. }
  356. }
  357. /**
  358. * 是否显示banner
  359. * @param isShow true显示 false不显示
  360. */
  361. public void show(boolean isShow)
  362. {
  363. if(isShow)
  364. {
  365. this.setVisibility(View.VISIBLE);
  366. mTimerResume = true;
  367. }
  368. else
  369. {
  370. this.setVisibility(View.GONE);
  371. mTimerResume = false;
  372. }
  373. }
  374. /**
  375. * 切换到指定屏幕
  376. * @param which 屏幕索引
  377. */
  378. public void switchToScreen(final int which)
  379. {
  380. mHorizontalScrollViewEx.switchView(which);
  381. }
  382. /**
  383. * 设置屏幕的数量 (此函数暂不开放)
  384. * @param count 屏幕数量
  385. */
  386. protected void setScreenCount(final int count)
  387. {
  388. PAGE_COUNT = count;
  389. }
  390. /**
  391. * 设置偏移的距离 如果mOverScrollMode为false,则此设置无效 (此函数暂不开放)
  392. * @param distance
  393. */
  394. protected void setOverScrollDistance(int distance)
  395. {
  396. if(distance < 0)
  397. distance = 0;
  398. mOverScrollDistance = mOverScrollMode ? distance : 0;
  399. }
  400. /**
  401. * 切换小圆点
  402. * @param position current screen index
  403. */
  404. private void switchScreenPosition(final int position)
  405. {
  406. if( mPageIndicator == null || mPageIndicatorFocused == null )
  407. return;
  408. int length = 0;
  409. if(mImageViewList != null)
  410. length = mImageViewList.size();
  411. if (position >= length || position < 0 || length <= 0)
  412. {
  413. return;
  414. }
  415. for(int i = 0; i < length; i++)
  416. {
  417. mImageViewList.get(i).setImageDrawable(mPageIndicator);
  418. }
  419. mImageViewList.get(position).setImageDrawable(mPageIndicatorFocused);
  420. }
  421. /**
  422. * 初始化整个FrameLayout视图组
  423. */
  424. private void initScrollView()
  425. {
  426. setLayoutParams(new LayoutParams(mScreenWidth, mScreenHeight ));
  427. linearLayoutScrolLayout = new LinearLayout(mContext);
  428. linearLayoutScrolLayout.setBackgroundColor(Color.WHITE);
  429. linearLayoutScrolLayout.setOrientation(LinearLayout.HORIZONTAL);
  430. int mVersionCode = 8;
  431. try
  432. {
  433. mVersionCode = Integer.valueOf(android.os.Build.VERSION.SDK);
  434. Logger.d(TAG, "sdk version=" + mVersionCode);
  435. }
  436. catch (Exception e)
  437. {
  438. e.printStackTrace();
  439. }
  440. //针对android1.6及以下的特殊处理 此为android的低版本bug
  441. if(mVersionCode <= 5)
  442. {
  443. linearLayoutScrolLayout.setBaselineAligned(false);
  444. }
  445. //初始化四个滑动view
  446. for(int i = 0; i < PAGE_COUNT; i++)
  447. {
  448. LinearLayout linearLayoutScreen = new LinearLayout(mContext);
  449. linearLayoutScreen.setOrientation(LinearLayout.VERTICAL);
  450. linearLayoutScrolLayout.addView(linearLayoutScreen, new LayoutParams(
  451. mScreenWidth,
  452. LayoutParams.FILL_PARENT));
  453. mLinearLayoutScreens.add(i, linearLayoutScreen);
  454. }
  455. //初始化小圆点视图
  456. RelativeLayout relativeLayout = new RelativeLayout(mContext);
  457. relativeLayout.setLayoutParams(new LayoutParams(
  458. LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
  459. //linearLayoutForDot为小圆点视图
  460. linearLayoutForDot =new LinearLayout(mContext);
  461. android.widget.RelativeLayout.LayoutParams layoutParams =
  462. new android.widget.RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
  463. LayoutParams.WRAP_CONTENT);
  464. //小圆点距底部的距离 单位:px
  465. layoutParams.bottomMargin = MARGIN_BOTTOM;
  466. layoutParams.rightMargin = MARGIN_BOTTOM;
  467. layoutParams.addRule(android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM);
  468. layoutParams.addRule(android.widget.RelativeLayout.CENTER_HORIZONTAL);
  469. linearLayoutForDot.setLayoutParams(layoutParams);
  470. linearLayoutForDot.setOrientation(LinearLayout.HORIZONTAL);
  471. linearLayoutForDot.setHorizontalGravity(Gravity.CENTER);
  472. linearLayoutForDot.setVerticalGravity(Gravity.CENTER);
  473. //下面两句实现圆角半透明效果 不采用
  474. //      linearLayoutForDot.setBackgroundResource(R.drawable.round_corner_bg);
  475. //      linearLayoutForDot.getBackground().setAlpha(100);
  476. //初始化4个小圆点
  477. mPageIndicator = getResources().getDrawable(R.drawable.page_indicator);
  478. mPageIndicatorFocused = getResources().getDrawable(R.drawable.page_indicator_focused);
  479. for(int i = 0; i < PAGE_COUNT; i++)
  480. {
  481. ImageView imageView = new ImageView(mContext);
  482. imageView.setImageDrawable(mPageIndicator);
  483. mImageViewList.add(i, imageView);
  484. LinearLayout.LayoutParams layoutParamsForDot =
  485. new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
  486. LayoutParams.WRAP_CONTENT);
  487. layoutParamsForDot.rightMargin = 5;
  488. linearLayoutForDot.addView(imageView, layoutParamsForDot);
  489. }
  490. mImageViewList.get(0).setImageDrawable(mPageIndicatorFocused);
  491. relativeLayout.addView(linearLayoutForDot);
  492. mHorizontalScrollViewEx = new HorizontalScrollViewEx(mContext, null, mBannerClickListener);
  493. mHorizontalScrollViewEx.setLayoutParams(new LayoutParams(
  494. mScreenWidth * PAGE_COUNT,
  495. LayoutParams.FILL_PARENT));
  496. mHorizontalScrollViewEx.addView(linearLayoutScrolLayout, new LayoutParams(
  497. LayoutParams.FILL_PARENT,
  498. LayoutParams.FILL_PARENT));
  499. mHorizontalScrollViewEx.setHorizontalScrollBarEnabled(false);
  500. mHorizontalScrollViewEx.setHorizontalFadingEdgeEnabled(false);
  501. addView(mHorizontalScrollViewEx);
  502. addView(relativeLayout);
  503. //自动滑屏 5秒一次
  504. timer.schedule(mTimerTask, 5000, TIMER_DURATION);
  505. }
  506. /**
  507. * 加一个banner页面 TODO此函数写的不好
  508. */
  509. private void addBannerItem()
  510. {
  511. //表示已经执行了onDetachedFromWindow,banner已经被销毁了
  512. if( mBannerBitmaps == null || mLinearLayoutScreens == null ||
  513. mImageViewList == null || mContext == null )
  514. return;
  515. //调整屏幕数量和总宽度
  516. PAGE_COUNT += 1;
  517. mHorizontalScrollViewEx.getLayoutParams().width = mScreenWidth * PAGE_COUNT;
  518. //加载默认图片资源
  519. if(mDefaultBitmap == null)
  520. {
  521. BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
  522. bitmapFactoryOptions.inJustDecodeBounds = false;
  523. bitmapFactoryOptions.inSampleSize = 2;
  524. Resources res=mContext.getResources();
  525. mDefaultBitmap = BitmapFactory.decodeResource(res, R.drawable.banner_image_default, bitmapFactoryOptions);
  526. }
  527. mBannerBitmaps.add(mDefaultBitmap);
  528. mBannerItemsList.add(null);
  529. //加一个屏幕
  530. LinearLayout linearLayoutScreen = new LinearLayout(mContext);
  531. linearLayoutScreen.setOrientation(LinearLayout.VERTICAL);
  532. linearLayoutScreen.setBackgroundDrawable(new BitmapDrawable( mBannerBitmaps.get(PAGE_COUNT - 1) ));
  533. linearLayoutScrolLayout.addView(linearLayoutScreen, new LayoutParams(
  534. mScreenWidth,
  535. LayoutParams.FILL_PARENT));
  536. mLinearLayoutScreens.add(linearLayoutScreen);
  537. //加一个小圆点
  538. ImageView imageView = new ImageView(mContext);
  539. imageView.setImageDrawable(mPageIndicator);
  540. mImageViewList.add(imageView);
  541. LinearLayout.LayoutParams layoutParamsForDot =
  542. new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
  543. LayoutParams.WRAP_CONTENT);
  544. layoutParamsForDot.rightMargin = 5;
  545. linearLayoutForDot.addView(imageView, layoutParamsForDot);
  546. }
  547. private class HorizontalScrollViewEx extends ViewGroup implements
  548. OnGestureListener
  549. {
  550. private GestureDetector mGestureDetector;
  551. private int mWhichScreen;
  552. public HorizontalScrollViewEx(Context context, AttributeSet attrs, OnBannerClickListener bannerClickListener)
  553. {
  554. super(context, attrs);
  555. mGestureDetector = new GestureDetector(this);
  556. //解决长按屏幕后无法拖动的现象
  557. mGestureDetector.setIsLongpressEnabled(false);
  558. //构造弹性滑动对象
  559. mScroller = new Scroller(context);
  560. }
  561. /**
  562. * 切换到指定屏幕
  563. * @param whichScreen 屏幕index
  564. */
  565. public void switchView(int whichScreen)
  566. {
  567. if(mLinearLayoutScreens == null)
  568. return;
  569. // 防止非法参数
  570. if (whichScreen < 0)
  571. whichScreen = 0;
  572. else if(whichScreen >= PAGE_COUNT)
  573. whichScreen = PAGE_COUNT - 1;
  574. Logger.i(TAG, "switch view to " + whichScreen);
  575. int delta = whichScreen * mScreenWidth - HorizontalScrollViewEx.this.getScrollX();
  576. //缓慢滚动到指定位置
  577. mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 3);
  578. // refresh
  579. invalidate();
  580. //delta>0 stands for user scroll view to right
  581. if (delta > 0)
  582. mScrollToRight = true;
  583. else
  584. {
  585. mScrollToRight = false;
  586. }
  587. mWhichScreen = whichScreen;
  588. mWhich = whichScreen;
  589. //切换小圆点
  590. switchScreenPosition(mWhichScreen);
  591. }
  592. /**
  593. * 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
  594. */
  595. @Override
  596. public boolean onDown(MotionEvent e)
  597. {
  598. Logger.i("MyGesture", "onDown");
  599. mScrollX = HorizontalScrollViewEx.this.getScrollX();
  600. return true;
  601. }
  602. /**
  603. * 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发
  604. * 注意和onDown()的区别,强调的是没有松开或者拖动的状态
  605. */
  606. public void onShowPress(MotionEvent e)
  607. {
  608. Logger.i("MyGesture", "onShowPress");
  609. }
  610. /**
  611. * 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
  612. */
  613. public boolean onSingleTapUp(MotionEvent e)
  614. {
  615. Logger.i("MyGesture", "onSingleTapUp");
  616. if(mBannerItemsList == null || mBannerItemsList.size() <= mWhichScreen)
  617. return false;
  618. BannerItem bannerItem = mBannerItemsList.get(mWhichScreen);
  619. if(bannerItem != null)
  620. {
  621. BannerMotionEvent bannerMotionEvent =
  622. new BannerMotionEvent(mWhichScreen, bannerItem.action, bannerItem.url,
  623. bannerItem.gameId, bannerItem.gameType, bannerItem.title);
  624. mBannerClickListener.onBannerClick(bannerMotionEvent);
  625. }
  626. return false;
  627. }
  628. /** 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE,
  629. * 1个ACTION_UP触发
  630. */
  631. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
  632. float velocityY)
  633. {
  634. Logger.i("MyGesture", "onFling velocityX=" + velocityX);
  635. mWhichScreen = velocityX > 0 ?
  636. mWhichScreen - 1
  637. : mWhichScreen + 1;
  638. switchView(mWhichScreen);
  639. return true;
  640. }
  641. /**
  642. * 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
  643. */
  644. public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
  645. float distanceY)
  646. {
  647. Logger.i("MyGesture", "onScroll");
  648. //禁止弹性滚动
  649. if (mOverScrollMode == false)
  650. {
  651. float x1 = e1.getX();
  652. float x2 = e2.getX();
  653. if(mWhichScreen == 0 && x1 < x2)
  654. return false;
  655. else if(mWhichScreen == PAGE_COUNT - 1 && x1 > x2)
  656. return false;
  657. }
  658. //          int distance = Math.abs(getScrollX() - mWhichScreen * mScreenWidth);
  659. //          if ((mWhichScreen ==0 || mWhichScreen == PAGE_COUNT -1) &&  distance > mOverScrollDistance)
  660. //              return false;
  661. this.scrollBy((int)distanceX, 0);
  662. return true;
  663. }
  664. /**
  665. *  用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发
  666. */
  667. public void onLongPress(MotionEvent e)
  668. {
  669. Logger.i("MyGesture", "onLongPress");
  670. }
  671. @Override
  672. public boolean onTouchEvent(MotionEvent event)
  673. {
  674. if(event.getAction() == MotionEvent.ACTION_DOWN)
  675. {
  676. mTimerResume = false;
  677. if ( !mScroller.isFinished() )
  678. mScroller.abortAnimation();
  679. }
  680. else if(event.getAction() == MotionEvent.ACTION_UP)
  681. {
  682. //开始自动滑屏
  683. mTimerResume = true;
  684. mByUserAction = true;
  685. }
  686. boolean consume = mGestureDetector.onTouchEvent(event);
  687. if (consume == false && event.getAction() == MotionEvent.ACTION_UP)
  688. {
  689. int curScrollX = HorizontalScrollViewEx.this.getScrollX();
  690. int mWhichScreen = (curScrollX + mScreenWidth / 2) /mScreenWidth;
  691. switchView(mWhichScreen);
  692. }
  693. return consume;
  694. }
  695. @Override
  696. public void computeScroll()
  697. {
  698. if (mScroller.computeScrollOffset())
  699. {
  700. scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  701. postInvalidate();
  702. }
  703. }
  704. @Override
  705. protected void onLayout(boolean changed, int l, int t, int r, int b)
  706. {
  707. if (changed)
  708. {
  709. int childLeft = 0;
  710. final int childCount = getChildCount();
  711. for (int i=0; i<childCount; i++)
  712. {
  713. final View childView = getChildAt(i);
  714. if (childView.getVisibility() != View.GONE)
  715. {
  716. final int childWidth = childView.getMeasuredWidth();
  717. childView.layout(childLeft, 0,
  718. childLeft+childWidth, childView.getMeasuredHeight());
  719. childLeft += childWidth;
  720. }
  721. }
  722. }
  723. }
  724. @Override
  725. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  726. {
  727. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  728. final int width = MeasureSpec.getSize(widthMeasureSpec);
  729. final int count = getChildCount();
  730. for (int i = 0; i < count; i++)
  731. {
  732. getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
  733. }
  734. scrollTo(mWhich * mScreenWidth, 0);
  735. }
  736. }
  737. /**
  738. * override此函数,防止其修改构造方法所定义的宽和高<br/>
  739. *  注意:在这里,设置宽和高将不起作用
  740. */
  741. @Override
  742. public void setLayoutParams(android.view.ViewGroup.LayoutParams params)
  743. {
  744. params.width = mScreenWidth;
  745. params.height = mScreenHeight;
  746. super.setLayoutParams(params);
  747. }
  748. //标志view AttachedToWindow
  749. @Override
  750. protected void onAttachedToWindow()
  751. {
  752. super.onAttachedToWindow();
  753. mTimerResume = true;
  754. }
  755. //标志view已经脱离window,典型的情形是view被销毁了,此时取消timer
  756. @Override
  757. protected void onDetachedFromWindow()
  758. {
  759. super.onDetachedFromWindow();
  760. Logger.d(TAG, "onDetachedFromWindow");
  761. mTimerResume = false;
  762. int activityId = ( (BaseActivity)mContext ).activityId();
  763. //如果是账号管理页面 则释放内存
  764. if(activityId == BaseActivity.ACCOUNT_ID)
  765. {
  766. destroy();
  767. }
  768. }
  769. /**
  770. * 销毁banner
  771. */
  772. public void destroy()
  773. {
  774. mTimerTask.cancel();
  775. timer.cancel();
  776. //去除各种bitmap对activity的引用关系
  777. destoryBitmaps();
  778. System.gc();
  779. }
  780. /**
  781. * 去除各种bitmap对activity的引用关系
  782. */
  783. private void destoryBitmaps()
  784. {
  785. for (View view : mLinearLayoutScreens)
  786. {
  787. Drawable drawable = view.getBackground();
  788. BitmapDrawable bitmapDrawable = null;
  789. if(drawable instanceof BitmapDrawable)
  790. bitmapDrawable = (BitmapDrawable)drawable;
  791. if(bitmapDrawable != null)
  792. {
  793. //解除drawable对view的引用
  794. bitmapDrawable.setCallback(null);
  795. bitmapDrawable = null;
  796. }
  797. }
  798. for (ImageView imageView : mImageViewList)
  799. {
  800. Drawable drawable = imageView.getDrawable();
  801. if(drawable != null)
  802. {
  803. drawable.setCallback(null);
  804. drawable = null;
  805. }
  806. }
  807. mPageIndicator.setCallback(null);
  808. mPageIndicator = null;
  809. mPageIndicatorFocused.setCallback(null);
  810. mPageIndicatorFocused = null;
  811. mLinearLayoutScreens.clear();
  812. mLinearLayoutScreens = null;
  813. mBannerBitmaps.clear();
  814. mBannerBitmaps = null;
  815. mImageViewList.clear();
  816. mImageViewList = null;
  817. mBannerItemsList.clear();
  818. mBannerItemsList = null;
  819. }
  820. //单击事件
  821. @Override
  822. public void onBannerClick( BannerMotionEvent bannerMotionEvent )
  823. {
  824. final int position = bannerMotionEvent.index;
  825. if(mContext == null)
  826. return;
  827. NotificationInfo notificationInfo = new NotificationInfo();
  828. notificationInfo.msgType = bannerMotionEvent.getAction();
  829. int action = bannerMotionEvent.getAction();
  830. if(action == NotificationInfo.NOTIFICATION_SINGLEGAME_MSG)    //单个游戏消息,直接启动该游戏
  831. {
  832. try
  833. {
  834. notificationInfo.gameId = Integer.parseInt( bannerMotionEvent.getGameId() );
  835. notificationInfo.gameType = Integer.parseInt( bannerMotionEvent.getGameType() );
  836. }
  837. catch (NumberFormatException e)
  838. {
  839. Logger.e(TAG, e.toString());
  840. return;
  841. }
  842. }
  843. else if(action == NotificationInfo.NOTIFICATION_GAMEPAGE_MSG)    //游戏主页消息,通过客户端展示游戏主页
  844. {
  845. try
  846. {
  847. notificationInfo.gameId = Integer.parseInt( bannerMotionEvent.getGameId() );
  848. }
  849. catch (NumberFormatException e)
  850. {
  851. Logger.e(TAG, e.toString());
  852. return;
  853. }
  854. notificationInfo.issueTitle = bannerMotionEvent.getTitle();
  855. }
  856. else if(action == NotificationInfo.NOTIFICATION_SHOW_WEBVIEW_MSG)    //交叉推广消息,通过一个webview展示
  857. {
  858. notificationInfo.issueTitle = bannerMotionEvent.getTitle();
  859. notificationInfo.openUrl = bannerMotionEvent.getResponseUrl();
  860. }
  861. else //reserved
  862. {
  863. return;
  864. }
  865. Intent intent = notificationInfo.generateIntent(mContext);
  866. if(intent != null)
  867. mContext.startActivity(intent);
  868. }
  869. /**
  870. * ScrollBanner所关联的banner项 可以为多个 一个为一屏
  871. */
  872. public static class BannerItem extends Object
  873. {
  874. public static final String ACTION = "action";
  875. public static final String URL = "url";
  876. public static final String IMGURL = "imgurl";
  877. public static final String GAMEID = "gameid";
  878. public static final String GAMETYPE = "gametype";
  879. public static final String TITLE = "title";
  880. public int index = -1;
  881. public int action = -1;
  882. public String url = "";
  883. public String imgUrl = "";
  884. public String gameId = "";
  885. public String gameType = "";
  886. public String title = "";
  887. public BannerItem(){}
  888. }
  889. /**
  890. * BannerMotionEvent:单击banner所产生的事件对象<br/>
  891. *getAction()来获取动作类别<br/>
  892. *getResponseUrl()来获取响应url<br/>
  893. *...
  894. */
  895. public static class BannerMotionEvent extends Object
  896. {
  897. /**
  898. * ACTION_PLAY_FLASH: 播放游戏
  899. */
  900. public static final int ACTION_PLAY = 2;
  901. /**
  902. * ACTION_HOMEPAGE:打开官网
  903. */
  904. public static final int ACTION_HOMEPAGE = 3;
  905. /**
  906. * ACTION_OPEN_URL:打开指定url
  907. */
  908. public static final int ACTION_OPEN_URL = 4;
  909. //banner中屏幕的index
  910. private int index = -1;
  911. //响应url
  912. private String responseUrl = "";
  913. //动作种类
  914. private int action = -1;
  915. //gameid
  916. private String gameId = "";
  917. //gametype flash游戏(0) or h5游戏(1)
  918. private String gameType = "";
  919. //webview的标题
  920. private String title = "";
  921. public BannerMotionEvent(int index, int action, String responseUrl,
  922. String gameId, String gameType, String title)
  923. {
  924. BannerMotionEvent.this.index = index;
  925. BannerMotionEvent.this.action = action;
  926. BannerMotionEvent.this.responseUrl = responseUrl;
  927. BannerMotionEvent.this.gameId = gameId;
  928. BannerMotionEvent.this.gameType = gameType;
  929. BannerMotionEvent.this.title = title;
  930. }
  931. /**
  932. * 获取当前BannerMotionEvent事件对象的动作种类
  933. * @return 动作种类:ACTION_PLAY等
  934. */
  935. public int getAction()
  936. {
  937. return action;
  938. }
  939. /**
  940. * 获取当前BannerMotionEvent事件对象的title
  941. * @return title webview的标题
  942. */
  943. public String getTitle()
  944. {
  945. return title;
  946. }
  947. /**
  948. * 获取当前BannerMotionEvent事件对象的gameId
  949. * @return gameId
  950. */
  951. public String getGameId()
  952. {
  953. return gameId;
  954. }
  955. /**
  956. * 获取当前BannerMotionEvent事件对象的gameType
  957. * @return gameType 0 or 1
  958. */
  959. public String getGameType()
  960. {
  961. return gameType;
  962. }
  963. /**
  964. * 获取当前BannerMotionEvent事件对象的响应url
  965. * @return 响应url
  966. */
  967. public String getResponseUrl()
  968. {
  969. return responseUrl;
  970. }
  971. @SuppressLint("DefaultLocale")
  972. @Override
  973. public String toString()
  974. {
  975. return String.format("BannerMotionEvent { index=%d, action=%d, responseUrl=%s, gameId=%s, gameType=%s, title=%s }",
  976. index, action, responseUrl, gameId, gameType, title);
  977. }
  978. }
  979. @Override
  980. public void onBannerInfoSuccess(List<BannerItem> items)
  981. {
  982. Logger.d(TAG, "onBannerInfoSuccess");
  983. mBannerItemsList = items;
  984. mHandler.sendEmptyMessage(MESSAGE_FETCH_BANNER_SUCCESS);
  985. }
  986. @Override
  987. public void onBannerInfoFailed()
  988. {
  989. Logger.e(TAG, "onBannerInfoFailed");
  990. }
  991. }

ComponentCallBack.java

[java] view plaincopy
  1. public interface ComponentCallBack
  2. {
  3. public static interface OnBannerClickListener
  4. {
  5. /**
  6. * banner单击事件
  7. * @param bannerMotionEvent 单击事件对象,包含所需的响应信息
  8. * 参见 {@link BannerMotionEvent}
  9. */
  10. public abstract void onBannerClick( BannerMotionEvent bannerMotionEvent );
  11. }
  12. }

广告位banner组件相关推荐

  1. vue商城项目开发:封装banner组件、组件参数传递

    封装banner组件 在Home组件中引入: components: 在template中使用: 组件参数传递 轮播图可以封装成组件,但是每个页面要展示的图片内容可能不一样,所以要进行参数传递,你传什 ...

  2. 自定义封装 banner 组件

    1. 效果图预览 2.基本功能 一个简单方便的轮播图组件,基于viewpager 基础上进行的封装.可设置 项目中图片,网络图片, View:支持循环自动播放,手势滑动切换,item点击事件,可设置 ...

  3. 广告位Banner设计(推广产品、游戏)

    设计思路: 思考清楚加入banner的目标,是导流,还是活动参与 导流类的,需要一句话描述清楚你的游戏.产品 可多设计几种风格,并附赠设计思路 参考其他banner海报的设计 搞清楚主要内容,思考如何 ...

  4. 【微信小程序开发】(三)首页banner组件使用swiper

    uniapp swiper文档使用地址 https://uniapp.dcloud.io/component/swiper.html 这部分直接按照文档使用方式使用就好了,贴出首页代码(没有依赖可直接 ...

  5. html中编辑广告位,广告位(banner)的可视化管理后台逻辑说明

    针对APP端的banner展示,怎样做到前后端的有机组合,方便运营人员自行操作(随时更改)广告位呢?本文简单的将可视化banner后台的逻辑做以说明. 百度百科上我们可以看到对banner的定义即&q ...

  6. banner panel 页面_广告位(banner)的可视化管理后台逻辑说明

    针对APP端的banner展示,怎样做到前后端的有机组合,方便运营人员自行操作(随时更改)广告位呢?本文简单的将可视化banner后台的逻辑做以说明. 百度百科上我们可以看到对banner的定义即&q ...

  7. 多商户商城源码_如何利用多商户B2B2C多商户商城系统后台组件玩转商城?

    打开互联网商城,尤其是购物网站,满屏的优惠活动让你忍不住心动,原本不打算入手的商品也在不知不觉中疯狂剁手了! 这就是小程序商城厉害的营销方式. 拼团.秒杀啊.团购.专题活动--这都是商城最基本的营销方 ...

  8. 广告banner:手动滑动切换,自动切换,点击跳转,异步加载网络图片

    效果图: 该banner功能有自动切换图片,点击图片可以自定义事件,手动滑动切换,异步加载图片 代码说话: 布局文件: <!-- 广告位 --><FrameLayoutandroid ...

  9. 如何利用多商户B2B2C多商户商城系统后台组件玩转商城?

    打开互联网商城,尤其是购物网站,满屏的优惠活动让你忍不住心动,原本不打算入手的商品也在不知不觉中疯狂剁手了! 这就是小程序商城厉害的营销方式. 拼团.秒杀啊.团购.专题活动--这都是商城最基本的营销方 ...

最新文章

  1. tensorflow 1
  2. Hands-On Unity 2018 x 移动游戏开发教程
  3. 李迅雷+但斌+趋势的力量+对话PPT
  4. vue 搜索框header_在Vue框架下使用Fullcalendar
  5. python安装失败0x80070570_固态硬盘装win7出现错误代码0x80070570怎么办
  6. JBPM中 使用JobExecutor执行timer定义的job
  7. 用c语言复制字符串的元音字母,急求:编写程序,将一个字符串中的元音字母复制到另一个字符串,然后输出。...
  8. Python入门很难吗? 为什么越来越多的人都学Python?
  9. 关于MySQL优化的几个问题
  10. 电脑维修之——笔记本电脑将光驱替换为硬盘
  11. php doss_ddos PHP版
  12. 电机与拖动(戚金清版)学习整理
  13. 20155305《网络对抗》信息搜集与漏洞扫描
  14. [JZOJ5424]【NOIP2017提高A组集训10.25】凤凰院凶真
  15. ip地址查询 免费 接口 linux 外网地址查询
  16. 正面管教:不凶不吼教出好孩子读后感
  17. Windows11上找BitLocker密钥
  18. 数仓知识06:什么是元数据?
  19. opencv 修改图像像素
  20. linux磁盘转gpt,Linux中磁盘如何转换GPT格式

热门文章

  1. 爱企查显示,Keep赴港上市,冲击“线上健身第一股”
  2. 计算机应用基础0006 19春在线作业2,《计算机应用基础0006》19春在线作业 参考资料...
  3. iphone11官网HTML,苹果小白入手iPhone11,以下几个基础操作要知道
  4. 高数考研归纳 - 微分学 - 一元微分学
  5. vscode在html看到图片的插件_利用花瓣插件 下载高清大图
  6. C语言-整数:short、int、long、long long(signed和unsigned)、原码、反码、补码
  7. CG学习记录(四)函数
  8. python正则表达式(2)
  9. 抽象方法和抽象类规则
  10. atoi()函数的实现